refactoring

This commit is contained in:
Stefan Allius
2024-09-24 21:12:51 +02:00
parent 89c2c11ed9
commit d5c369e5fe
23 changed files with 1257 additions and 942 deletions

View File

@@ -4,408 +4,455 @@
<!-- 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="927pt" height="1386pt"
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 927.00 1386.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 1382)">
<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,-1382 923,-1382 923,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="565.6964,-1354 457.3036,-1354 457.3036,-1318 571.6964,-1318 571.6964,-1348 565.6964,-1354"/>
<polyline fill="none" stroke="#000000" points="153.6964,-1250 153.6964,-1244 "/> <polyline fill="none" stroke="#000000" points="565.6964,-1354 565.6964,-1348 "/>
<polyline fill="none" stroke="#000000" points="159.6964,-1244 153.6964,-1244 "/> <polyline fill="none" stroke="#000000" points="571.6964,-1348 565.6964,-1348 "/>
<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="514.5" y="-1339" 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="514.5" y="-1327" 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="180.5,-820 180.5,-864 302.5,-864 302.5,-820 180.5,-820"/>
<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="231.777" y="-845" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Mqtt</text>
<text text-anchor="start" x="208.9815" y="-833" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;Singleton&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="180.5,-764 180.5,-820 302.5,-820 302.5,-764 180.5,-764"/>
<text text-anchor="start" x="198.9875" y="-801" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;ha_restarts</text>
<text text-anchor="start" x="206.7665" y="-789" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__client</text>
<text text-anchor="start" x="190.3735" y="-777" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;static&gt;__cb_MqttIsUp</text>
<polygon fill="none" stroke="#000000" points="180.5,-720 180.5,-764 302.5,-764 302.5,-720 180.5,-720"/>
<text text-anchor="start" x="203.436" y="-745" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;async&gt;publish()</text>
<text text-anchor="start" x="207.6045" y="-733" 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="187.5,-482 187.5,-514 295.5,-514 295.5,-482 187.5,-482"/>
<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="224.5535" y="-495" 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="187.5,-390 187.5,-482 295.5,-482 295.5,-390 187.5,-390"/>
<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="217.604" y="-463" 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="210.9405" y="-451" 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="201.7755" y="-439" 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="201.2115" y="-427" 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="197.3225" y="-415" 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="213.1655" y="-403" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">cls.mqtt:Mqtt</text>
<polygon fill="none" stroke="#000000" points="187.5,-370 187.5,-390 295.5,-390 295.5,-370 187.5,-370"/>
</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="M241.5,-709.8039C241.5,-654.5754 241.5,-581.9953 241.5,-526.3277"/>
<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="241.5,-719.9042 237.0001,-709.9041 241.5,-714.9042 241.5001,-709.9042 241.5001,-709.9042 241.5001,-709.9042 241.5,-714.9042 246.0001,-709.9042 241.5,-719.9042 241.5,-719.9042"/>
</g> <polygon fill="#000000" stroke="#000000" points="241.5001,-526.0023 237.5,-520.0023 241.5,-514.0023 245.5,-520.0023 241.5001,-526.0023"/>
<!-- 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="142.5,-186 142.5,-218 264.5,-218 264.5,-186 142.5,-186"/>
<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="179.8845" y="-199" 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="142.5,-154 142.5,-186 264.5,-186 264.5,-154 142.5,-154"/>
<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="172.9355" y="-167" 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="142.5,-86 142.5,-154 264.5,-154 264.5,-86 142.5,-86"/>
<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="152.1035" y="-135" 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="162.382" y="-123" 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="188.5025" y="-99" 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="M230.7486,-359.9499C224.8735,-315.1138 217.6928,-260.3131 212.1779,-218.2258"/>
<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="227.2883,-360.4815 232.0579,-369.942 234.2289,-359.572 227.2883,-360.4815"/>
<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="528.5,-180 528.5,-212 647.5,-212 647.5,-180 528.5,-180"/>
<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="561.05" y="-193" 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="528.5,-148 528.5,-180 647.5,-180 647.5,-148 528.5,-148"/>
<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="557.4355" y="-161" 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="528.5,-92 528.5,-148 647.5,-148 647.5,-92 528.5,-92"/>
<text text-anchor="start" x="538.268" y="-129" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">async_create_remote(</text>
<text text-anchor="start" x="545.2175" y="-117" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">)async_publ_mqtt()</text>
<text text-anchor="start" x="573.0025" y="-105" 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="M253.8698,-359.8546C262.7907,-327.0676 278.1312,-291.5818 304.5,-268 375.3712,-204.6196 428.113,-261.8022 512.5,-218 517.7341,-215.2832 522.9008,-212.1238 527.9257,-208.6874"/>
<polygon fill="none" stroke="#000000" points="250.3809,-359.3724 251.3023,-369.9271 257.164,-361.1015 250.3809,-359.3724"/>
</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="590.5,-1346 590.5,-1378 661.5,-1378 661.5,-1346 590.5,-1346"/>
<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="600.445" y="-1359" 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="590.5,-1326 590.5,-1346 661.5,-1346 661.5,-1326 590.5,-1326"/>
<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="590.5,-1294 590.5,-1326 661.5,-1326 661.5,-1294 590.5,-1294"/>
<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="607.939" y="-1307" 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="558.5,-1212 558.5,-1244 692.5,-1244 692.5,-1212 558.5,-1212"/>
<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="605.2175" y="-1225" 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="558.5,-1036 558.5,-1212 692.5,-1212 692.5,-1036 558.5,-1036"/>
<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="588.8265" y="-1193" 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="586.043" y="-1181" 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="578.814" y="-1169" 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="584.648" y="-1157" 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="603.8245" y="-1145" 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="607.7135" y="-1133" 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="604.6585" y="-1121" 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="575.489" y="-1109" 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="574.0945" y="-1097" 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="568.2665" y="-1085" 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="607.7135" y="-1073" 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="596.326" y="-1061" 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="614.662" y="-1049" 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="558.5,-968 558.5,-1036 692.5,-1036 692.5,-968 558.5,-968"/>
<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="575.2095" y="-1017" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_read():void&lt;abstract&gt;</text>
<text text-anchor="start" x="599.9445" y="-1005" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close():void</text>
<text text-anchor="start" x="585.7725" y="-993" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter():void</text>
<text text-anchor="start" x="584.1025" y="-981" 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">
<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"/>
<polygon fill="none" stroke="#000000" points="400.8317,-775.2382 400.269,-785.8181 407.3066,-777.8983 400.8317,-775.2382"/>
</g>
<!-- A7 -->
<g id="node8" class="node">
<title>A7</title>
<polygon fill="none" stroke="#000000" points="172.5,-668 172.5,-700 263.5,-700 263.5,-668 172.5,-668"/>
<text text-anchor="start" x="190.495" y="-681" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">SolarmanV5</text>
<polygon fill="none" stroke="#000000" points="172.5,-576 172.5,-668 263.5,-668 263.5,-576 172.5,-576"/>
<text text-anchor="start" x="202.998" y="-649" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">control</text>
<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="211.056" y="-625" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snr</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="191.884" y="-601" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="204.112" y="-589" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</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="182.4405" y="-557" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="203.0025" y="-533" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A5&#45;&gt;A7 -->
<g id="edge4" class="edge"> <g id="edge4" class="edge">
<title>A5&#45;&gt;A7</title> <title>A5&#45;&gt;A6</title>
<path fill="none" stroke="#000000" d="M284.228,-776.2903C273.8281,-750.3733 263.2923,-724.1174 253.7595,-700.3611"/> <path fill="none" stroke="#000000" d="M625.5,-1283.5582C625.5,-1271.4749 625.5,-1258.067 625.5,-1244.189"/>
<polygon fill="none" stroke="#000000" points="281.0788,-777.8409 288.0512,-785.8181 287.5753,-775.2339 281.0788,-777.8409"/> <polygon fill="none" stroke="#000000" points="622.0001,-1283.8144 625.5,-1293.8145 629.0001,-1283.8145 622.0001,-1283.8144"/>
</g>
<!-- A6&#45;&gt;A3 -->
<g id="edge6" class="edge">
<title>A6&#45;&gt;A3</title>
<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"/>
<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="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>
<!-- A8 -->
<g id="node9" class="node">
<title>A8</title>
<polygon fill="none" stroke="#000000" points="410.5,-330 410.5,-362 560.5,-362 560.5,-330 410.5,-330"/>
<text text-anchor="start" x="453.5455" y="-343" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3</text>
<polygon fill="none" stroke="#000000" points="410.5,-298 410.5,-330 560.5,-330 560.5,-298 410.5,-298"/>
<text text-anchor="start" x="420.487" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote_stream:ConnectionG3</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="466.054" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="470.5025" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A6&#45;&gt;A8 -->
<g id="edge5" class="edge">
<title>A6&#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"/>
<polygon fill="none" stroke="#000000" points="474.8712,-473.5333 477.9378,-483.6747 481.8648,-473.8345 474.8712,-473.5333"/>
</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>
<!-- A9 -->
<g id="node10" class="node">
<title>A9</title>
<polygon fill="none" stroke="#000000" points="125.5,-330 125.5,-362 281.5,-362 281.5,-330 125.5,-330"/>
<text text-anchor="start" x="168.211" y="-343" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3P</text>
<polygon fill="none" stroke="#000000" points="125.5,-298 125.5,-330 281.5,-330 281.5,-298 125.5,-298"/>
<text text-anchor="start" x="135.1525" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote_stream:ConnectionG3P</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="184.054" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="188.5025" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A7&#45;&gt;A9 -->
<g id="edge7" class="edge">
<title>A7&#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"/>
<polygon fill="none" stroke="#000000" points="209.3591,-509.972 213.3185,-519.7991 216.3516,-509.6477 209.3591,-509.972"/>
</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>
<!-- A10 -->
<g id="node11" class="node">
<title>A10</title>
<polygon fill="none" stroke="#000000" points="281.5,-698 281.5,-730 397.5,-730 397.5,-698 281.5,-698"/>
<text text-anchor="start" x="309.774" y="-711" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStream</text>
<polygon fill="none" stroke="#000000" points="281.5,-618 281.5,-698 397.5,-698 397.5,-618 281.5,-618"/>
<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="327.283" y="-667" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">writer</text>
<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="325.053" y="-643" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">r_addr</text>
<text text-anchor="start" x="325.608" y="-631" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">l_addr</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="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>
<!-- A10&#45;&gt;A8 -->
<g id="edge9" class="edge">
<title>A10&#45;&gt;A8</title>
<path fill="none" stroke="#000000" d="M402.0319,-480.6532C422.1536,-439.0316 443.3588,-395.1687 459.3606,-362.0691"/>
<polygon fill="none" stroke="#000000" points="398.824,-479.2474 397.6226,-489.7739 405.1262,-482.2941 398.824,-479.2474"/>
</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>
<!-- A14 -->
<g id="node15" class="node">
<title>A14</title>
<polygon fill="none" stroke="#000000" points="178.5,-1320 178.5,-1352 281.5,-1352 281.5,-1320 178.5,-1320"/>
<text text-anchor="start" x="219.162" y="-1333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Infos</text>
<polygon fill="none" stroke="#000000" points="178.5,-1264 178.5,-1320 281.5,-1320 281.5,-1264 178.5,-1264"/>
<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="197.486" y="-1289" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_stat_data</text>
<text text-anchor="start" x="211.1035" y="-1277" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">info_dev</text>
<polygon fill="none" stroke="#000000" points="178.5,-1112 178.5,-1264 281.5,-1264 281.5,-1112 178.5,-1112"/>
<text text-anchor="start" x="205.8355" y="-1245" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">static_init()</text>
<text text-anchor="start" x="203.8845" y="-1233" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dev_value()</text>
<text text-anchor="start" x="200.8305" y="-1221" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
<text text-anchor="start" x="199.1605" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
<text text-anchor="start" x="197.21" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_proxy_conf</text>
<text text-anchor="start" x="212.213" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_conf</text>
<text text-anchor="start" x="204.994" y="-1173" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_remove</text>
<text text-anchor="start" x="206.3745" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">update_db</text>
<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>
<text text-anchor="start" x="199.9855" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_db_value</text>
<text text-anchor="start" x="188.3225" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ignore_this_device</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="356.5,-886 356.5,-918 470.5,-918 470.5,-886 356.5,-886"/>
<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="399.608" y="-899" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Talent</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="356.5,-782 356.5,-886 470.5,-886 470.5,-782 356.5,-782"/>
<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="366.263" y="-867" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">await_conn_resp_cnt</text>
<text text-anchor="start" x="441.384" y="-901" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text> <text text-anchor="start" x="401.2775" y="-855" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">id_str</text>
<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="382.1" y="-843" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_name</text>
<text text-anchor="start" x="385.44" y="-831" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_mail</text>
<text text-anchor="start" x="389.0445" y="-819" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3</text>
<text text-anchor="start" x="387.384" y="-807" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="399.612" y="-795" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="356.5,-666 356.5,-782 470.5,-782 470.5,-666 356.5,-666"/>
<text text-anchor="start" x="370.9925" y="-763" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_contact_info()</text>
<text text-anchor="start" x="372.9325" y="-751" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_ota_update()</text>
<text text-anchor="start" x="378.7765" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_get_time()</text>
<text text-anchor="start" x="366.8285" y="-727" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_collector_data()</text>
<text text-anchor="start" x="368.7735" y="-715" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_inverter_data()</text>
<text text-anchor="start" x="377.9405" y="-703" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="398.5025" y="-679" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g> </g>
<!-- A14&#45;&gt;A15 --> <!-- A6&#45;&gt;A15 -->
<g id="edge18" class="edge"> <g id="edge21" class="edge">
<title>A14&#45;&gt;A15</title> <title>A6&#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"/> <path fill="none" stroke="#000000" d="M552.2302,-1011.4277C540.9417,-996.8789 529.4136,-982.0336 518.5,-968 501.1988,-945.7528 494.7669,-941.6895 479.5,-918 476.4901,-913.3296 473.4995,-908.5069 470.548,-903.5947"/>
<polygon fill="none" stroke="#000000" points="284.8741,-1121.5366 281.0238,-1131.4071 290.1891,-1126.0921 284.8741,-1121.5366"/> <polygon fill="none" stroke="#000000" points="549.5765,-1013.7171 558.4715,-1019.4729 555.1073,-1009.4264 549.5765,-1013.7171"/>
</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="610.5,-850 610.5,-882 701.5,-882 701.5,-850 610.5,-850"/>
<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="628.495" y="-863" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">SolarmanV5</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="610.5,-758 610.5,-850 701.5,-850 701.5,-758 610.5,-758"/>
<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="640.998" y="-831" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">control</text>
<text text-anchor="start" x="197.384" y="-901" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text> <text text-anchor="start" x="644.0575" y="-819" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">serial</text>
<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="649.056" y="-807" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snr</text>
<text text-anchor="start" x="628.21" y="-795" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3P</text>
<text text-anchor="start" x="629.884" y="-783" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
<text text-anchor="start" x="642.112" y="-771" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
<polygon fill="none" stroke="#000000" points="610.5,-702 610.5,-758 701.5,-758 701.5,-702 610.5,-702"/>
<text text-anchor="start" x="620.4405" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
<text text-anchor="start" x="641.0025" y="-715" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g> </g>
<!-- A14&#45;&gt;A16 --> <!-- A6&#45;&gt;A16 -->
<g id="edge19" class="edge"> <g id="edge22" class="edge">
<title>A14&#45;&gt;A16</title> <title>A6&#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="M639.6776,-957.6082C642.1325,-931.9128 644.617,-905.9091 646.8668,-882.3611"/>
<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="636.1691,-957.5305 638.7021,-967.8181 643.1374,-958.1963 636.1691,-957.5305"/>
</g> </g>
<!-- A15&#45;&gt;A6 --> <!-- A7 -->
<g id="edge21" class="edge"> <g id="node8" class="node">
<title>A15&#45;&gt;A6</title> <title>A7</title>
<path fill="none" stroke="#000000" d="M465.7237,-875.9684C466.6086,-841.2366 467.8512,-792.4655 469.031,-746.1572"/> <polygon fill="none" stroke="#000000" points="399.5,-584 399.5,-616 516.5,-616 516.5,-584 399.5,-584"/>
<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"/> <text text-anchor="start" x="427.429" y="-597" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">&lt;&lt;AsyncIfc&gt;&gt;</text>
<polygon fill="none" stroke="#000000" points="399.5,-564 399.5,-584 516.5,-584 516.5,-564 399.5,-564"/>
<polygon fill="none" stroke="#000000" points="399.5,-268 399.5,-564 516.5,-564 516.5,-268 399.5,-268"/>
<text text-anchor="start" x="427.436" y="-545" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_node_id()</text>
<text text-anchor="start" x="425.766" y="-533" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_conn_no()</text>
<text text-anchor="start" x="439.6635" y="-509" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_add()</text>
<text text-anchor="start" x="437.444" y="-497" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_flush()</text>
<text text-anchor="start" x="441.0535" y="-485" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_get()</text>
<text text-anchor="start" x="437.1635" y="-473" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_peek()</text>
<text text-anchor="start" x="441.3335" y="-461" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_log()</text>
<text text-anchor="start" x="437.169" y="-449" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_clear()</text>
<text text-anchor="start" x="441.3335" y="-437" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_len()</text>
<text text-anchor="start" x="435.7745" y="-413" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_add()</text>
<text text-anchor="start" x="433.555" y="-401" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_flush()</text>
<text text-anchor="start" x="437.4445" y="-389" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_log()</text>
<text text-anchor="start" x="433.28" y="-377" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_clear()</text>
<text text-anchor="start" x="440.7785" y="-365" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_get()</text>
<text text-anchor="start" x="436.8885" y="-353" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_peek()</text>
<text text-anchor="start" x="441.0585" y="-341" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_log()</text>
<text text-anchor="start" x="436.894" y="-329" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_clear()</text>
<text text-anchor="start" x="441.0585" y="-317" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_len()</text>
<text text-anchor="start" x="432.999" y="-305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_set_cb()</text>
<text text-anchor="start" x="409.3795" y="-281" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_set_timeout_cb()</text>
</g>
<!-- A8 -->
<g id="node9" class="node">
<title>A8</title>
<polygon fill="none" stroke="#000000" points="411.5,-182 411.5,-214 504.5,-214 504.5,-182 411.5,-182"/>
<text text-anchor="start" x="429.664" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncIfcImpl</text>
<polygon fill="none" stroke="#000000" points="411.5,-90 411.5,-182 504.5,-182 504.5,-90 411.5,-90"/>
<text text-anchor="start" x="421.048" y="-163" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_fifo:ByteFifo</text>
<text text-anchor="start" x="424.937" y="-151" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_fifo:ByteFifo</text>
<text text-anchor="start" x="424.662" y="-139" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_fifo:ByteFifo</text>
<text text-anchor="start" x="424.096" y="-127" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no:Count</text>
<text text-anchor="start" x="440.2135" y="-115" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
<text text-anchor="start" x="433.5495" y="-103" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout_cb</text>
</g>
<!-- A7&#45;&gt;A8 -->
<g id="edge5" class="edge">
<title>A7&#45;&gt;A8</title>
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M457.5,-257.632C457.5,-242.1873 457.5,-227.3874 457.5,-214.0086"/>
<polygon fill="none" stroke="#000000" points="454.0001,-257.8659 457.5,-267.8659 461.0001,-257.866 454.0001,-257.8659"/>
</g>
<!-- A9 -->
<g id="node10" class="node">
<title>A9</title>
<polygon fill="none" stroke="#000000" points="501.4532,-36 413.5468,-36 413.5468,0 501.4532,0 501.4532,-36"/>
<text text-anchor="middle" x="457.5" y="-15" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStream</text>
</g>
<!-- A8&#45;&gt;A9 -->
<g id="edge6" class="edge">
<title>A8&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M457.5,-79.615C457.5,-63.4386 457.5,-47.6836 457.5,-36.1546"/>
<polygon fill="#000000" stroke="#000000" points="457.5,-89.7075 453.0001,-79.7074 457.5,-84.7075 457.5001,-79.7075 457.5001,-79.7075 457.5001,-79.7075 457.5,-84.7075 462.0001,-79.7075 457.5,-89.7075 457.5,-89.7075"/>
</g>
<!-- A10 -->
<g id="node11" class="node">
<title>A10</title>
<polygon fill="none" stroke="#000000" points=".5,-464 .5,-496 151.5,-496 151.5,-464 .5,-464"/>
<text text-anchor="start" x="44.0455" y="-477" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3</text>
<polygon fill="none" stroke="#000000" points=".5,-432 .5,-464 151.5,-464 151.5,-432 .5,-432"/>
<text text-anchor="start" x="10.432" y="-445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3</text>
<polygon fill="none" stroke="#000000" points=".5,-388 .5,-432 151.5,-432 151.5,-388 .5,-388"/>
<text text-anchor="start" x="56.554" y="-413" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="61.0025" y="-401" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A10&#45;&gt;A3 -->
<g id="edge7" class="edge">
<title>A10&#45;&gt;A3</title>
<path fill="none" stroke="#000000" d="M103.5115,-378.5364C124.4716,-331.0488 153.0618,-266.2741 174.3025,-218.1506"/>
<polygon fill="none" stroke="#000000" points="100.2617,-377.2316 99.4257,-387.7934 106.6657,-380.0582 100.2617,-377.2316"/>
</g>
<!-- A10&#45;&gt;A9 -->
<g id="edge9" class="edge">
<title>A10&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M65.9191,-375.3961C58.4986,-295.6629 59.6829,-163.2447 133.5,-86 170.0095,-47.7953 321.2395,-29.3905 403.5448,-22.053"/>
<polygon fill="#000000" stroke="#000000" points="65.9586,-375.7857 70.5458,-381.3497 67.1741,-387.724 62.5869,-382.16 65.9586,-375.7857"/>
<polygon fill="#000000" stroke="#000000" points="413.7128,-21.1716 404.1388,-26.5185 408.7314,-21.6034 403.7501,-22.0353 403.7501,-22.0353 403.7501,-22.0353 408.7314,-21.6034 403.3615,-17.5521 413.7128,-21.1716 413.7128,-21.1716"/>
<text text-anchor="middle" x="396.3843" y="-28.1578" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A10&#45;&gt;A10 -->
<g id="edge8" class="edge">
<title>A10&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M129.3063,-496.0431C150.8867,-499.0964 169,-481.082 169,-442 169,-409.3299 156.3426,-391.3816 139.5832,-388.1553"/>
<polygon fill="#000000" stroke="#000000" points="129.3063,-387.9569 139.3914,-383.6508 134.3054,-388.0534 139.3044,-388.15 139.3044,-388.15 139.3044,-388.15 134.3054,-388.0534 139.2175,-392.6492 129.3063,-387.9569 129.3063,-387.9569"/>
<text text-anchor="middle" x="147.5922" y="-376.856" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
<text text-anchor="middle" x="147.7917" y="-485.4082" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
</g>
<!-- A11 -->
<g id="node12" class="node">
<title>A11</title>
<polygon fill="none" stroke="#000000" points="744.5,-464 744.5,-496 901.5,-496 901.5,-464 744.5,-464"/>
<text text-anchor="start" x="787.711" y="-477" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ConnectionG3P</text>
<polygon fill="none" stroke="#000000" points="744.5,-432 744.5,-464 901.5,-464 901.5,-432 744.5,-432"/>
<text text-anchor="start" x="754.0975" y="-445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote.stream:ConnectionG3P</text>
<polygon fill="none" stroke="#000000" points="744.5,-388 744.5,-432 901.5,-432 901.5,-388 744.5,-388"/>
<text text-anchor="start" x="803.554" y="-413" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
<text text-anchor="start" x="808.0025" y="-401" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g>
<!-- A11&#45;&gt;A4 -->
<g id="edge10" class="edge">
<title>A11&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M798.6926,-378.1971C783.8063,-343.412 762.3505,-300.8772 735.5,-268 710.6165,-237.5312 676.3674,-210.0448 647.0827,-189.4838"/>
<polygon fill="none" stroke="#000000" points="795.6201,-379.9208 802.7161,-387.7883 802.0752,-377.2128 795.6201,-379.9208"/>
</g>
<!-- A11&#45;&gt;A9 -->
<g id="edge12" class="edge">
<title>A11&#45;&gt;A9</title>
<path fill="none" stroke="#000000" d="M808.4328,-376.0571C788.1602,-296.5483 743.2373,-163.9158 656.5,-86 615.9314,-49.5574 554.9588,-32.4915 511.2023,-24.5972"/>
<polygon fill="#000000" stroke="#000000" points="808.4732,-376.2202 813.8001,-381.0807 811.3623,-387.8673 806.0354,-383.0068 808.4732,-376.2202"/>
<polygon fill="#000000" stroke="#000000" points="501.3083,-22.9145 511.9213,-20.1549 506.2375,-23.7529 511.1668,-24.5912 511.1668,-24.5912 511.1668,-24.5912 506.2375,-23.7529 510.4123,-29.0275 501.3083,-22.9145 501.3083,-22.9145"/>
<text text-anchor="middle" x="520.595" y="-14.6208" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A11&#45;&gt;A11 -->
<g id="edge11" class="edge">
<title>A11&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M878.0327,-496.0431C900.3055,-499.0964 919,-481.082 919,-442 919,-409.0245 905.6911,-391.0474 888.1533,-388.0687"/>
<polygon fill="#000000" stroke="#000000" points="878.0327,-387.9569 888.0819,-383.5677 883.0324,-388.0122 888.0321,-388.0675 888.0321,-388.0675 888.0321,-388.0675 883.0324,-388.0122 887.9823,-392.5672 878.0327,-387.9569 878.0327,-387.9569"/>
<text text-anchor="middle" x="896.2511" y="-376.7053" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
<text text-anchor="middle" x="896.5075" y="-485.3826" 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="489.5,-880 489.5,-912 592.5,-912 592.5,-880 489.5,-880"/>
<text text-anchor="start" x="530.162" y="-893" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Infos</text>
<polygon fill="none" stroke="#000000" points="489.5,-824 489.5,-880 592.5,-880 592.5,-824 489.5,-824"/>
<text text-anchor="start" x="532.9415" y="-861" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stat</text>
<text text-anchor="start" x="508.486" y="-849" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_stat_data</text>
<text text-anchor="start" x="522.1035" y="-837" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">info_dev</text>
<polygon fill="none" stroke="#000000" points="489.5,-672 489.5,-824 592.5,-824 592.5,-672 489.5,-672"/>
<text text-anchor="start" x="516.8355" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">static_init()</text>
<text text-anchor="start" x="514.8845" y="-793" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dev_value()</text>
<text text-anchor="start" x="511.8305" y="-781" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
<text text-anchor="start" x="510.1605" y="-769" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
<text text-anchor="start" x="508.21" y="-757" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_proxy_conf</text>
<text text-anchor="start" x="523.213" y="-745" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_conf</text>
<text text-anchor="start" x="515.994" y="-733" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_remove</text>
<text text-anchor="start" x="517.3745" y="-721" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">update_db</text>
<text text-anchor="start" x="501.537" y="-709" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_db_def_value</text>
<text text-anchor="start" x="510.9855" y="-697" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_db_value</text>
<text text-anchor="start" x="499.3225" y="-685" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ignore_this_device</text>
</g>
<!-- A13 -->
<g id="node14" class="node">
<title>A13</title>
<polygon fill="none" stroke="#000000" points="314.5,-458 314.5,-490 381.5,-490 381.5,-458 314.5,-458"/>
<text text-anchor="start" x="330.493" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3</text>
<polygon fill="none" stroke="#000000" points="314.5,-438 314.5,-458 381.5,-458 381.5,-438 314.5,-438"/>
<polygon fill="none" stroke="#000000" points="314.5,-394 314.5,-438 381.5,-438 381.5,-394 314.5,-394"/>
<text text-anchor="start" x="324.384" y="-419" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="332.168" y="-407" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A12&#45;&gt;A13 -->
<g id="edge13" class="edge">
<title>A12&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M482.2133,-668.9537C481.32,-667.9536 480.4156,-666.9686 479.5,-666 448.0663,-632.7472 417.2023,-652.4199 389.5,-616 361.9383,-579.7649 352.1673,-528.0191 348.8512,-490.3816"/>
<polygon fill="none" stroke="#000000" points="479.7494,-671.4639 488.8434,-676.8998 485.1241,-666.9792 479.7494,-671.4639"/>
</g>
<!-- A14 -->
<g id="node15" class="node">
<title>A14</title>
<polygon fill="none" stroke="#000000" points="659.5,-458 659.5,-490 726.5,-490 726.5,-458 659.5,-458"/>
<text text-anchor="start" x="672.1585" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3P</text>
<polygon fill="none" stroke="#000000" points="659.5,-438 659.5,-458 726.5,-458 726.5,-438 659.5,-438"/>
<polygon fill="none" stroke="#000000" points="659.5,-394 659.5,-438 726.5,-438 726.5,-394 659.5,-394"/>
<text text-anchor="start" x="669.384" y="-419" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
<text text-anchor="start" x="677.168" y="-407" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
</g>
<!-- A12&#45;&gt;A14 -->
<g id="edge14" class="edge">
<title>A12&#45;&gt;A14</title>
<path fill="none" stroke="#000000" d="M597.6498,-671.6826C598.9193,-669.7598 600.2029,-667.864 601.5,-666 619.0948,-640.7144 634.6333,-642.98 649.5,-616 671.1251,-576.755 682.0274,-526.606 687.4278,-490.2373"/>
<polygon fill="none" stroke="#000000" points="594.5993,-669.9569 592.1768,-680.2711 600.5026,-673.7188 594.5993,-669.9569"/>
</g>
<!-- A15&#45;&gt;A7 -->
<g id="edge16" class="edge">
<title>A15&#45;&gt;A7</title>
<path fill="none" stroke="#000000" d="M429.385,-665.642C430.9935,-652.8472 432.6494,-639.6756 434.3143,-626.4317"/>
<polygon fill="#000000" stroke="#000000" points="435.5886,-616.295 438.8061,-626.7783 434.9649,-621.256 434.3412,-626.217 434.3412,-626.217 434.3412,-626.217 434.9649,-621.256 429.8764,-625.6556 435.5886,-616.295 435.5886,-616.295"/>
<text text-anchor="middle" x="423.2596" y="-643.6031" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A15&#45;&gt;A10 -->
<g id="edge15" class="edge">
<title>A15&#45;&gt;A10</title>
<path fill="none" stroke="#000000" d="M349.6312,-700.7451C337.9746,-687.9557 325.1424,-675.8414 311.5,-666 260.2851,-629.0543 226.8831,-656.5836 178.5,-616 140.443,-584.0779 112.9887,-534.285 96.0432,-496.1296"/>
<polygon fill="none" stroke="#000000" points="347.1067,-703.1737 356.3558,-708.3413 352.348,-698.5337 347.1067,-703.1737"/>
</g>
<!-- A15&#45;&gt;A13 -->
<g id="edge17" class="edge">
<title>A15&#45;&gt;A13</title>
<path fill="none" stroke="#000000" d="M389.6725,-665.642C378.9966,-609.0277 366.9296,-545.0355 358.447,-500.0525"/>
<polygon fill="#000000" stroke="#000000" points="356.5861,-490.1839 362.8613,-499.1768 357.5127,-495.0973 358.4392,-500.0107 358.4392,-500.0107 358.4392,-500.0107 357.5127,-495.0973 354.0172,-500.8446 356.5861,-490.1839 356.5861,-490.1839"/>
</g> </g>
<!-- A16&#45;&gt;A7 --> <!-- A16&#45;&gt;A7 -->
<g id="edge20" class="edge"> <g id="edge19" class="edge">
<title>A16&#45;&gt;A7</title> <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"/> <path fill="none" stroke="#000000" d="M624.6836,-701.7474C618.1571,-689.0117 610.4679,-676.6496 601.5,-666 575.2168,-634.7882 550.1961,-647.697 524.5,-616 523.7865,-615.1199 523.08,-614.2314 522.3805,-613.3347"/>
<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"/> <polygon fill="#000000" stroke="#000000" points="516.2699,-605.0582 525.8298,-610.4303 519.2397,-609.0807 522.2095,-613.1032 522.2095,-613.1032 522.2095,-613.1032 519.2397,-609.0807 518.5893,-615.776 516.2699,-605.0582 516.2699,-605.0582"/>
<text text-anchor="middle" x="608.7901" y="-686.6067" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
</g>
<!-- A16&#45;&gt;A11 -->
<g id="edge18" class="edge">
<title>A16&#45;&gt;A11</title>
<path fill="none" stroke="#000000" d="M702.9385,-692.5781C733.3829,-628.7724 771.744,-548.3749 796.6816,-496.1104"/>
<polygon fill="none" stroke="#000000" points="699.6542,-691.3338 698.5066,-701.8663 705.9719,-694.3483 699.6542,-691.3338"/>
</g>
<!-- A16&#45;&gt;A14 -->
<g id="edge20" class="edge">
<title>A16&#45;&gt;A14</title>
<path fill="none" stroke="#000000" d="M665.0284,-701.8663C671.7268,-638.5029 680.4957,-555.5538 686.3042,-500.609"/>
<polygon fill="#000000" stroke="#000000" points="687.389,-490.3473 690.8127,-500.765 686.8633,-495.3196 686.3376,-500.2919 686.3376,-500.2919 686.3376,-500.2919 686.8633,-495.3196 681.8626,-499.8187 687.389,-490.3473 687.389,-490.3473"/>
</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="435.5,-1200 435.5,-1232 510.5,-1232 510.5,-1200 435.5,-1200"/>
<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="455.2175" y="-1213" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</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="435.5,-1048 435.5,-1200 510.5,-1200 510.5,-1048 435.5,-1048"/>
<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="464.6615" y="-1181" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
<text text-anchor="start" x="45.387" y="-305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</text> <text text-anchor="start" x="445.49" y="-1157" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
<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="446.605" y="-1145" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
<text text-anchor="start" x="10.383" y="-281" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text> <text text-anchor="start" x="456.6085" y="-1133" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
<polygon fill="none" stroke="#000000" points=".5,-248 .5,-268 107.5,-268 107.5,-248 .5,-248"/> <text text-anchor="start" x="446.8895" y="-1121" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
<text text-anchor="start" x="454.942" y="-1109" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
<text text-anchor="start" x="466.8915" y="-1097" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
<text text-anchor="start" x="453.5535" y="-1085" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
<text text-anchor="start" x="451.879" y="-1073" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
<text text-anchor="start" x="466.3365" y="-1061" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
<polygon fill="none" stroke="#000000" points="435.5,-980 435.5,-1048 510.5,-1048 510.5,-980 435.5,-980"/>
<text text-anchor="start" x="446.89" y="-1029" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
<text text-anchor="start" x="450.224" y="-1017" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
<text text-anchor="start" x="447.724" y="-1005" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
<text text-anchor="start" x="458.0025" y="-993" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
</g> </g>
<!-- A17&#45;&gt;A13 --> <!-- A17&#45;&gt;A15 -->
<g id="edge22" class="edge"> <g id="edge24" class="edge">
<title>A17&#45;&gt;A13</title> <title>A17&#45;&gt;A15</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="M446.8724,-969.6092C443.6566,-952.4944 440.3783,-935.047 437.1939,-918.0999"/>
<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="#000000" stroke="#000000" points="448.7575,-979.6415 442.4881,-970.6445 447.8341,-974.7275 446.9107,-969.8135 446.9107,-969.8135 446.9107,-969.8135 447.8341,-974.7275 451.3333,-968.9824 448.7575,-979.6415 448.7575,-979.6415"/>
<text text-anchor="middle" x="151.047" y="-142.8423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text> <text text-anchor="middle" x="448.8482" y="-931.3534" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="81.2636" y="-224.8385" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text> <text text-anchor="middle" x="437.1032" y="-960.3879" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
</g>
<!-- A17&#45;&gt;A16 -->
<g id="edge23" class="edge">
<title>A17&#45;&gt;A16</title>
<path fill="none" stroke="#000000" d="M514.9768,-1020.5466C525.3303,-1002.5556 537.0447,-984.1492 549.5,-968 569.0808,-942.6121 583.2659,-944.3717 601.5,-918 609.1473,-906.9397 616.0494,-894.6241 622.1627,-882.146"/>
<polygon fill="#000000" stroke="#000000" points="510.0062,-1029.3335 511.0131,-1018.414 512.4681,-1024.9816 514.9299,-1020.6296 514.9299,-1020.6296 514.9299,-1020.6296 512.4681,-1024.9816 518.8467,-1022.8453 510.0062,-1029.3335 510.0062,-1029.3335"/>
<text text-anchor="middle" x="621.6642" y="-899.1398" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
<text text-anchor="middle" x="511.574" y="-1006.3951" 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="534.5,-470 534.5,-502 641.5,-502 641.5,-470 534.5,-470"/>
<text text-anchor="start" x="558.2695" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusConn</text>
<polygon fill="none" stroke="#000000" points="534.5,-402 534.5,-470 641.5,-470 641.5,-402 534.5,-402"/>
<text text-anchor="start" x="578.5515" y="-451" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">host</text>
<text text-anchor="start" x="579.387" y="-439" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</text>
<text text-anchor="start" x="577.997" y="-427" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
<text text-anchor="start" x="544.383" y="-415" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text>
<polygon fill="none" stroke="#000000" points="534.5,-382 534.5,-402 641.5,-402 641.5,-382 534.5,-382"/>
</g>
<!-- A18&#45;&gt;A4 -->
<g id="edge25" class="edge">
<title>A18&#45;&gt;A4</title>
<path fill="none" stroke="#000000" d="M587.5,-381.9118C587.5,-335.4458 587.5,-271.0175 587.5,-222.1832"/>
<polygon fill="#000000" stroke="#000000" points="587.5,-212.0112 592.0001,-222.0112 587.5,-217.0112 587.5001,-222.0112 587.5001,-222.0112 587.5001,-222.0112 587.5,-217.0112 583.0001,-222.0113 587.5,-212.0112 587.5,-212.0112"/>
<text text-anchor="middle" x="595.9524" y="-227.1374" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
<text text-anchor="middle" x="579.0476" y="-360.7857" 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: 39 KiB

View File

@@ -3,28 +3,49 @@
// {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()] [Mqtt;<<Singleton>>|<static>ha_restarts;<static>__client;<static>__cb_MqttIsUp|<async>publish();<async>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] [Inverter|cls.db_stat;cls.entity_prfx;cls.discovery_prfx;cls.proxy_node_id;cls.proxy_unique_id;cls.mqtt:Mqtt|]
[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()] [Inverter]^[InverterG3|__ha_restarts|async_create_remote();async_publ_mqtt();;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()] [Inverter]^[InverterG3P|__ha_restarts|async_create_remote(;)async_publ_mqtt();close()]
[Mqtt]-[Inverter] [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]
[<<AsyncIfc>>]^-.-[AsyncIfcImpl]
[AsyncIfcImpl]<-[AsyncStream]
[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|remote.stream:ConnectionG3|healthy();close()]
[ConnectionG3]^[InverterG3] [ConnectionG3]^[InverterG3]
[ConnectionG3]has-0..1>[ConnectionG3] [ConnectionG3]has-0..1>[ConnectionG3]
[ConnectionG3]++-1>[AsyncStream]
[ConnectionG3P|remote.stream:ConnectionG3P|healthy();close()]
[ConnectionG3P]^[InverterG3P] [ConnectionG3P]^[InverterG3P]
[ConnectionG3P]has-0..1>[ConnectionG3P] [ConnectionG3P]has-0..1>[ConnectionG3P]
[ConnectionG3P]++-1>[AsyncStream]
[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()] [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,111 @@
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

View File

@@ -7,19 +7,114 @@ 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.byte_fifo import ByteFifo
from app.src.async_ifc import AsyncIfc from app.src.async_ifc import AsyncIfc
from app.src.messages import State from app.src.infos import Infos
else: # pragma: no cover else: # pragma: no cover
from byte_fifo import ByteFifo
from async_ifc import AsyncIfc from async_ifc import AsyncIfc
from messages import 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
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
class StreamPtr():
def __init__(self, stream):
self.stream = stream
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,31 +125,31 @@ 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, async_publ_mqtt, async_create_remote,
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.async_create_remote = async_create_remote
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 = async_publ_mqtt
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): async def publish_outstanding_mqtt(self):
'''Publish all outstanding MQTT topics''' '''Publish all outstanding MQTT topics'''
@@ -69,25 +164,25 @@ class AsyncStream():
'''Loop for receiving messages from the inverter (server-side)''' '''Loop for receiving messages from the inverter (server-side)'''
logger.info(f'[{self.node_id}:{self.conn_no}] ' logger.info(f'[{self.node_id}:{self.conn_no}] '
f'Accept connection from {addr}') f'Accept connection from {addr}')
self.inc_counter('Inverter_Cnt') Infos.inc_counter('Inverter_Cnt')
await self.publish_outstanding_mqtt() await self.publish_outstanding_mqtt()
await self.loop() await self.loop()
self.dec_counter('Inverter_Cnt') Infos.dec_counter('Inverter_Cnt')
await self.publish_outstanding_mqtt() await self.publish_outstanding_mqtt()
logger.info(f'[{self.node_id}:{self.conn_no}] Server loop stopped for' logger.info(f'[{self.node_id}:{self.conn_no}] Server loop stopped for'
f' r{self.r_addr}') f' r{self.r_addr}')
# if the server connection closes, we also have to disconnect # if the server connection closes, we also have to disconnect
# the connection to te TSUN cloud # the connection to te TSUN cloud
if self.remote_stream: if self.remote.stream:
logger.info(f'[{self.node_id}:{self.conn_no}] disc client ' logger.info(f'[{self.node_id}:{self.conn_no}] disc client '
f'connection: [{self.remote_stream.node_id}:' f'connection: [{self.remote.stream.node_id}:'
f'{self.remote_stream.conn_no}]') f'{self.remote.stream.conn_no}]')
await self.remote_stream.disc() await self.remote.stream._ifc.disc()
async def client_loop(self, _: str) -> None: async def client_loop(self, _: str) -> None:
'''Loop for receiving messages from the TSUN cloud (client-side)''' '''Loop for receiving messages from the TSUN cloud (client-side)'''
client_stream = await self.remote_stream.loop() client_stream = await self.remote.stream._ifc.loop()
logger.info(f'[{client_stream.node_id}:{client_stream.conn_no}] ' logger.info(f'[{client_stream.node_id}:{client_stream.conn_no}] '
'Client loop stopped for' 'Client loop stopped for'
f' l{client_stream.l_addr}') f' l{client_stream.l_addr}')
@@ -98,13 +193,13 @@ class AsyncStream():
# establish a new connection to the TSUN cloud # establish a new connection to the TSUN cloud
# erase backlink to inverter # erase backlink to inverter
client_stream.remote_stream = None client_stream.remote.stream = None
if self.remote_stream == client_stream: if self.remote.stream == client_stream:
# logging.debug(f'Client l{client_stream.l_addr} refs:' # logging.debug(f'Client l{client_stream.l_addr} refs:'
# f' {gc.get_referrers(client_stream)}') # f' {gc.get_referrers(client_stream)}')
# than erase client connection # than erase client connection
self.remote_stream = None 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,10 +216,10 @@ 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: # if self.unique_id:
await self.__async_write() await self.__async_write()
await self.__async_forward() await self.__async_forward()
await self.async_publ_mqtt() await self.async_publ_mqtt()
except asyncio.TimeoutError: except asyncio.TimeoutError:
logger.warning(f'[{self.node_id}:{self.conn_no}] Dead ' logger.warning(f'[{self.node_id}:{self.conn_no}] Dead '
@@ -150,7 +245,7 @@ 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()}")
@@ -158,9 +253,9 @@ class AsyncStream():
async def __async_write(self, headline: str = 'Transmit to ') -> None: async def __async_write(self, headline: str = 'Transmit to ') -> None:
"""Async write handler to transmit the send_buffer""" """Async write handler to transmit the send_buffer"""
if len(self.ifc.write) > 0: if len(self.tx_fifo) > 0:
self.ifc.write.logging(logging.INFO, f'{headline}{self.addr}:') self.tx_fifo.logging(logging.INFO, f'{headline}{self.addr}:')
self._writer.write(self.ifc.write.get()) self._writer.write(self.tx_fifo.get())
await self._writer.drain() await self._writer.drain()
async def disc(self) -> None: async def disc(self) -> None:
@@ -176,18 +271,19 @@ class AsyncStream():
hint: must be called before releasing the connection instance hint: must be called before releasing the connection instance
""" """
self.tx_fifo.reg_trigger(None)
self.async_create_remote = None
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,8 +299,8 @@ 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:
@@ -212,42 +308,42 @@ class AsyncStream():
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 len(self.ifc.forward) == 0: if len(self.fwd_fifo) == 0:
return return
try: try:
if not self.remote_stream: if not self.remote.stream:
await self.async_create_remote() await self.async_create_remote()
if self.remote_stream: if self.remote.stream:
if self.remote_stream._init_new_client_conn(): if self.remote.stream._init_new_client_conn():
await self.remote_stream.__async_write() await self.remote.stream._ifc.__async_write()
if self.remote_stream: if self.remote.stream:
self.remote_stream._update_header(self.ifc.forward.peek()) self.remote.stream._update_header(self.fwd_fifo.peek())
self.ifc.forward.logging(logging.INFO, 'Forward to ' self.fwd_fifo.logging(logging.INFO, 'Forward to '
f'{self.remote_stream.addr}:') f'{self.remote.stream.addr}:')
self.remote_stream._writer.write(self.ifc.forward.get()) self.remote.stream._ifc._writer.write(self.fwd_fifo.get())
await self.remote_stream._writer.drain() await self.remote.stream._ifc._writer.drain()
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()}")

View File

@@ -2,33 +2,35 @@ 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 AsyncStream, StreamPtr
from app.src.async_stream import AsyncStream
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 AsyncStream, StreamPtr
from async_stream import AsyncStream
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, def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, remote_stream: 'ConnectionG3', server_side: bool, addr, rstream: 'ConnectionG3', server_side: bool,
id_str=b'') -> None: id_str=b'') -> None:
self._ifc = AsyncIfc() self.remote = StreamPtr(rstream)
AsyncStream.__init__(self, reader, writer, addr, self._ifc) self._ifc = AsyncStream(reader, writer, addr,
self.async_publ_mqtt,
self.async_create_remote,
self.remote)
Talent.__init__(self, server_side, self._ifc, id_str) Talent.__init__(self, server_side, self._ifc, id_str)
self.remote_stream: 'ConnectionG3' = remote_stream self.conn_no = self._ifc.get_conn_no()
self.addr = addr
''' '''
Our puplic methods Our puplic methods
''' '''
def close(self): def close(self):
AsyncStream.close(self) self._ifc.close()
Talent.close(self) Talent.close(self)
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}') # logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
@@ -40,10 +42,4 @@ 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()
'''
Our private methods
'''
def __del__(self):
super().__del__()

View File

@@ -53,6 +53,7 @@ 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, True)
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 +66,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 = ConnectionG3(reader, writer, addr, self,
False, self.id_str) False, 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._ifc.client_loop(addr))
except (ConnectionRefusedError, TimeoutError) as error: except (ConnectionRefusedError, TimeoutError) as error:
logging.info(f'{error}') logging.info(f'{error}')
@@ -128,10 +129,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

@@ -48,7 +48,8 @@ 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)
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 +108,8 @@ 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)
super().close() super().close()
def __set_serial_no(self, serial_no: str): def __set_serial_no(self, serial_no: str):
@@ -143,10 +145,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,10 +156,10 @@ 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}')
self.__set_serial_no(self.id_str.decode("utf-8")) self.__set_serial_no(self.id_str.decode("utf-8"))
self.__dispatch_msg() self.__dispatch_msg()
@@ -170,9 +172,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.ifc.forward += buffer self.ifc.fwd_add(buffer)
self.ifc.forward.logging(logging.DEBUG, 'Store for forwarding:') self.ifc.fwd_log(logging.DEBUG, 'Store for forwarding:')
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') +
@@ -181,18 +183,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)
self.ifc.forward += buffer self.ifc.fwd_add(buffer)
self.ifc.forward.logging(logging.INFO, 'Store for forwarding:') self.ifc.fwd_log(logging.INFO, 'Store for forwarding:')
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:
@@ -201,13 +203,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:
@@ -235,9 +237,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
@@ -321,7 +323,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
@@ -342,16 +344,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:
@@ -366,7 +368,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
''' '''
@@ -376,7 +378,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
@@ -390,7 +392,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
@@ -417,16 +419,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}')
@@ -446,10 +448,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
@@ -457,11 +459,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}')
@@ -473,7 +475,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
@@ -482,7 +484,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]
@@ -495,7 +497,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()
@@ -510,7 +512,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
@@ -530,7 +532,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))
@@ -550,7 +552,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
@@ -559,7 +561,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
@@ -580,12 +582,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,34 +2,37 @@ 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 AsyncStream, StreamPtr
from app.src.async_stream import AsyncStream
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 AsyncStream, StreamPtr
from async_stream import AsyncStream
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, def __init__(self, reader: StreamReader, writer: StreamWriter,
addr, remote_stream: 'ConnectionG3P', addr, rstream: 'ConnectionG3P',
server_side: bool, server_side: bool,
client_mode: bool) -> None: client_mode: bool) -> None:
self._ifc = AsyncIfc()
AsyncStream.__init__(self, reader, writer, addr, self._ifc) self.remote = StreamPtr(rstream)
self._ifc = AsyncStream(reader, writer, addr,
self.async_publ_mqtt,
self.async_create_remote,
self.remote)
SolarmanV5.__init__(self, server_side, client_mode, self._ifc) SolarmanV5.__init__(self, server_side, client_mode, self._ifc)
self.remote_stream: 'ConnectionG3P' = remote_stream self.conn_no = self._ifc.get_conn_no()
self.addr = addr
''' '''
Our puplic methods Our puplic methods
''' '''
def close(self): def close(self):
AsyncStream.close(self) self._ifc.close()
SolarmanV5.close(self) SolarmanV5.close(self)
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}') # logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
@@ -41,10 +44,4 @@ 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()
'''
Our private methods
'''
def __del__(self):
super().__del__()

View File

@@ -55,6 +55,7 @@ class InverterG3P(Inverter, ConnectionG3P):
super().__init__(reader, writer, addr, None, super().__init__(reader, writer, addr, None,
server_side=True, client_mode=client_mode) server_side=True, 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 +68,13 @@ 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 = ConnectionG3P(reader, writer, addr, self,
server_side=False, server_side=False,
client_mode=False) 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._ifc.client_loop(addr))
except (ConnectionRefusedError, TimeoutError) as error: except (ConnectionRefusedError, TimeoutError) as error:
logging.info(f'{error}') logging.info(f'{error}')
@@ -131,10 +132,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,8 @@ 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)
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
@@ -163,7 +164,8 @@ 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)
super().close() super().close()
async def send_start_cmd(self, snr: int, host: str, async def send_start_cmd(self, snr: int, host: str,
@@ -234,10 +236,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()
@@ -248,10 +250,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
@@ -264,8 +266,8 @@ class SolarmanV5(Message):
return return
tsun = Config.get('solarman') tsun = Config.get('solarman')
if tsun['enabled']: if tsun['enabled']:
self.ifc.forward += buffer[:buflen] self.ifc.fwd_add(buffer[:buflen])
self.ifc.forward.logging(logging.DEBUG, 'Store for forwarding:') self.ifc.fwd_log(logging.DEBUG, 'Store for forwarding:')
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') +
@@ -321,7 +323,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
@@ -333,11 +335,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
@@ -353,22 +355,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,
@@ -399,14 +401,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):
@@ -415,12 +417,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, 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:
@@ -462,18 +464,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.write.logging(logging.INFO, 'Send AT Command:') self.ifc.tx_log(logging.INFO, 'Send AT Command:')
try: try:
self.ifc.write() 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
@@ -494,7 +496,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':
@@ -513,7 +515,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]
@@ -534,7 +536,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]
@@ -562,7 +564,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]
@@ -575,8 +577,8 @@ 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]
if ftype == self.AT_CMD: if ftype == self.AT_CMD:
@@ -588,8 +590,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:
@@ -604,7 +606,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
@@ -616,8 +618,8 @@ 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:
if not self.forward_at_cmd_resp: if not self.forward_at_cmd_resp:
@@ -653,7 +655,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]
@@ -662,7 +664,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]
@@ -675,7 +677,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,12 +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.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
@@ -138,6 +153,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

@@ -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,8 +2,9 @@
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 ConnectionG3
from app.src.gen3.talent import Talent from app.src.gen3.talent import Talent
@@ -60,19 +61,19 @@ 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
spy5 = patch_talent_close spy5 = patch_talent_close
reader = FakeReader() reader = FakeReader()
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 = ConnectionG3(reader, writer, addr,
remote_stream= None, server_side=True, id_str=id_str) rstream= None, server_side=True, 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 ConnectionG3P
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,18 +67,18 @@ 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
spy5 = patch_solarman_close spy5 = patch_solarman_close
reader = FakeReader() reader = FakeReader()
writer = FakeWriter() writer = FakeWriter()
addr = ('proxy.local', 10000) addr = ('proxy.local', 10000)
conn = ConnectionG3P(reader, writer, addr, conn = ConnectionG3P(reader, writer, addr,
remote_stream= None, server_side=True, client_mode=False) rstream= None, server_side=True, 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

@@ -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()

View File

@@ -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()

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)
@@ -63,7 +64,7 @@ class MemoryStream(SolarmanV5):
def write_cb(self): def write_cb(self):
if self.test_exception_async_write: if self.test_exception_async_write:
raise RuntimeError("Peer closed.") raise RuntimeError("Peer closed.")
self.sent_pdu = self.ifc.write.get() self.sent_pdu = self.ifc.tx_fifo.get()
def _timestamp(self): def _timestamp(self):
return timestamp return timestamp
@@ -87,11 +88,11 @@ 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:
@@ -100,8 +101,8 @@ class MemoryStream(SolarmanV5):
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:
@@ -687,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.ifc.forward.get()==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()
@@ -708,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.ifc.forward.get()==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()
@@ -728,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.ifc.forward.get()==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()
@@ -753,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.ifc.forward.get()==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()
@@ -775,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.ifc.forward.get()==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()
@@ -799,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.ifc.forward.get()==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()
@@ -821,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.ifc.forward.get()==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()
@@ -893,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.ifc.forward.get()==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):
@@ -918,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.ifc.forward.get()==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):
@@ -947,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.ifc.forward.get()==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):
@@ -966,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.ifc.forward.get()==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()
@@ -984,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.ifc.forward.get()==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()
@@ -1002,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.ifc.forward.get()==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()
@@ -1020,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.ifc.forward.get()==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()
@@ -1037,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.ifc.forward.get()==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()
@@ -1055,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.ifc.forward.get()==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()
@@ -1073,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.ifc.forward.get()==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()
@@ -1090,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.ifc.forward.peek()==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.ifc.forward.peek()) 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.ifc.forward.get()==sync_start_fwd_msg assert m.ifc.fwd_fifo.get()==sync_start_fwd_msg
m.close() m.close()
@@ -1114,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.ifc.forward.get()==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()
@@ -1131,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.ifc.forward.get()==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()
@@ -1149,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.ifc.forward.get()==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()
@@ -1169,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):
@@ -1235,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
@@ -1255,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
@@ -1279,14 +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.ifc.forward.get()==device_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg
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.ifc.forward.get() == 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()
@@ -1296,15 +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.ifc.forward.get()==inverter_ind_msg assert m.ifc.fwd_fifo.get()==inverter_ind_msg
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.ifc.forward.get() == 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.close() m.close()
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -1314,12 +1315,12 @@ 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.ifc.forward.get()==device_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg
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.ifc.forward.get()==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.sent_pdu == 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 == ''
@@ -1329,12 +1330,12 @@ 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.ifc.forward.get()==inverter_ind_msg assert m.ifc.fwd_fifo.get()==inverter_ind_msg
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.forward.get() == 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 == at_command_ind_msg assert m.sent_pdu == at_command_ind_msg
m.sent_pdu = bytearray() m.sent_pdu = bytearray()
@@ -1346,9 +1347,9 @@ 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.ifc.forward.get()==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"
@@ -1356,9 +1357,9 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
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.sent_pdu == b'' assert m.sent_pdu == b''
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.ifc.forward.get()==b'' assert m.ifc.fwd_fifo.get()==b''
assert m.sent_pdu == 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
@@ -1373,12 +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.ifc.forward.get()==device_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg
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.ifc.forward.get()==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 == ""
@@ -1387,14 +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.ifc.forward.get()==inverter_ind_msg assert m.ifc.fwd_fifo.get()==inverter_ind_msg
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.ifc.forward.get()==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'
@@ -1416,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.ifc.forward.get()==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
@@ -1440,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.ifc.forward.get()==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
@@ -1462,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.ifc.forward.get()==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()
@@ -1481,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.ifc.forward.get()==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()
@@ -1506,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.ifc.forward.get()==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
@@ -1533,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.ifc.forward.get()==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
@@ -1556,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.ifc.forward.get()==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
@@ -1577,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.ifc.forward.get()==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()
@@ -1601,8 +1602,8 @@ 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.ifc.forward.get()==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
@@ -1613,8 +1614,8 @@ 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 == 2 assert m.msg_count == 2
assert m.ifc.forward.get()==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
@@ -1638,8 +1639,8 @@ 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.ifc.forward.get()==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
@@ -1649,8 +1650,8 @@ 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 == 5 assert m.mb.err == 5
assert m.msg_count == 2 assert m.msg_count == 2
assert m.ifc.forward.get()==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
@@ -1668,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.ifc.forward.get()==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()
@@ -1682,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.ifc.forward.get()==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()
@@ -1705,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.ifc.forward.get()==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
@@ -1729,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.ifc.forward.get()==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
@@ -1740,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()
@@ -1771,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
@@ -35,10 +37,10 @@ class MemoryStream(Talent):
self.addr = 'Test: SrvSide' self.addr = 'Test: SrvSide'
self.send_msg_ofs = 0 self.send_msg_ofs = 0
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):
@@ -52,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:
@@ -71,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:
@@ -741,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.ifc.forward.get()==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):
@@ -761,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.ifc.forward.get()==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):
@@ -846,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.ifc.forward.get()==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):
@@ -871,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.ifc.forward.get()==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):
@@ -889,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.ifc.forward.get()==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):
@@ -907,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.ifc.forward.get()==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):
@@ -925,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.ifc.forward.get()==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):
@@ -944,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.ifc.forward.get()==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()
@@ -964,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.ifc.forward.get()==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()
@@ -984,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.ifc.forward.get()==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()
@@ -1002,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.ifc.forward.get()==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()
@@ -1023,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.ifc.forward.get()==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()
@@ -1044,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.ifc.forward.get()==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()
@@ -1055,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
@@ -1068,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.ifc.forward.get()==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()
@@ -1089,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.ifc.forward.get()==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()
@@ -1108,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.ifc.forward.get()==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()
@@ -1127,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.ifc.forward.get()==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()
@@ -1146,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.ifc.forward.get()==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()
@@ -1170,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.ifc.forward.get()==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()
@@ -1194,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.ifc.forward.get()==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()
@@ -1215,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.ifc.forward.get()==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()
@@ -1235,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.ifc.forward.get()==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()
@@ -1255,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.ifc.forward.get()==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()
@@ -1273,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.ifc.forward.get()==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()
@@ -1291,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.ifc.forward.get()==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()
@@ -1309,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.ifc.forward.get()==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()
@@ -1328,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.peek()==msg_controller_ind assert m.ifc.fwd_fifo.peek()==msg_controller_ind
m.ts_offset = -4096 m.ts_offset = -4096
m._update_header(m.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1350,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.ifc.forward.get()==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()
@@ -1369,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.peek()==msg_controller_invalid assert m.ifc.fwd_fifo.peek()==msg_controller_invalid
m.ts_offset = -4096 m.ts_offset = -4096
m._update_header(m.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1393,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.peek()==msg_inverter_ind assert m.ifc.fwd_fifo.peek()==msg_inverter_ind
m.ts_offset = +256 m.ts_offset = +256
m._update_header(m.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1420,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1444,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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}
@@ -1472,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1496,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.ifc.forward.get()==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()
@@ -1515,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.peek()==msg_inverter_invalid assert m.ifc.fwd_fifo.peek()==msg_inverter_invalid
m.ts_offset = 256 m.ts_offset = 256
m._update_header(m.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1539,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.peek()==msg_ota_req assert m.ifc.fwd_fifo.peek()==msg_ota_req
m.ts_offset = 4096 m.ts_offset = 4096
m._update_header(m.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1566,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.peek()==msg_ota_ack assert m.ifc.fwd_fifo.peek()==msg_ota_ack
m.ts_offset = 256 m.ts_offset = 256
m._update_header(m.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.get()==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()
@@ -1591,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.ifc.forward.peek()) m._update_header(m.ifc.fwd_fifo.peek())
assert m.ifc.forward.peek()==msg_ota_invalid assert m.ifc.fwd_fifo.peek()==msg_ota_invalid
m.ts_offset = 4096 m.ts_offset = 4096
assert m.ifc.forward.get()==msg_ota_invalid assert m.ifc.fwd_fifo.get()==msg_ota_invalid
m._update_header(m.ifc.forward.peek()) 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()
@@ -1614,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.ifc.forward.get()==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()
@@ -1635,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
@@ -1739,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.ifc.forward.get()==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.ifc.forward.get()==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
@@ -1769,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.ifc.forward.get()==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.ifc.forward.get()==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
@@ -1798,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.ifc.forward.get()==b'' assert c.ifc.fwd_fifo.get()==b''
assert c.ifc.write.get()==b'' assert c.ifc.tx_fifo.get()==b''
assert m.ifc.forward.get()==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
@@ -1823,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.ifc.forward.get()==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()
@@ -1845,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.ifc.forward.get()==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
@@ -1873,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.ifc.forward.get()==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()
@@ -1903,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.ifc.forward.get()==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()
@@ -1932,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.ifc.forward.get()==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()
@@ -1957,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.ifc.forward.get()==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()
@@ -1977,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.ifc.forward.get()==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()
@@ -2006,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.ifc.forward.get()==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
@@ -2021,15 +2025,15 @@ 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.ifc.forward.get() == 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.ifc.forward.get() == 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.close() m.close()
@@ -2048,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.ifc.forward.get()==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()
@@ -2072,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.ifc.forward.get()==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()
@@ -2133,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()