Dev 0.11 (#200)
* Code Cleanup (#158) * print coverage report * create sonar-project property file * install all py dependencies in one step * code cleanup * reduce cognitive complexity * do not build on *.yml changes * optimise versionstring handling (#159) - Reading the version string from the image updates it even if the image is re-pulled without re-deployment * fix linter warning * exclude *.pyi filese * ignore some rules for tests * cleanup (#160) * Sonar qube 3 (#163) fix SonarQube warnings in modbus.py * Sonar qube 3 (#164) * fix SonarQube warnings * Sonar qube 3 (#165) * cleanup * Add support for TSUN Titan inverter Fixes #161 * fix SonarQube warnings * fix error * rename field "config" * SonarQube reads flake8 output * don't stop on flake8 errors * flake8 scan only app/src for SonarQube * update flake8 run * ignore flake8 C901 * cleanup * fix linter warnings * ignore changed *.yml files * read sensor list solarman data packets * catch 'No route to' error and log only in debug mode * fix unit tests * add sensor_list configuration * adapt unit tests * fix SonarQube warnings * Sonar qube 3 (#166) * add unittests for mqtt.py * add mock * move test requirements into a file * fix unit tests * fix formating * initial version * fix SonarQube warning * Sonar qube 4 (#169) * add unit test for inverter.py * fix SonarQube warning * Sonar qube 5 (#170) * fix SonarLints warnings * use random IP adresses for unit tests * Docker: The description ist missing (#171) Fixes #167 * S allius/issue167 (#172) * cleanup * Sonar qube 6 (#174) * test class ModbusConn * Sonar qube 3 (#178) * add more unit tests * GEN3: don't crash on overwritten msg in the receive buffer * improve test coverage und reduce test delays * reduce cognitive complexity * fix merge * fix merge conflikt * fix merge conflict * S allius/issue182 (#183) * GEN3: After inverter firmware update the 'Unknown Msg Type' increases continuously Fixes #182 * add support for Controller serial no and MAC * test hardening * GEN3: add support for new messages of version 3 firmwares * bump libraries to latest versions - bump aiomqtt to version 2.3.0 - bump aiohttp to version 3.10.5 * improve test coverage * reduce cognective complexity * fix target preview * remove dubbled fixtures * increase test coverage * Update README.md (#185) update badges * S allius/issue186 (#187) * Parse more values in Server Mode Fixes #186 * read OUTPUT_COEFFICIENT and MAC_ADDR in SrvMode * fix unit test * increase test coverage * S allius/issue186 (#188) * increase test coverage * update changelog * add dokumentation * change default config * Update README.md (#189) Config file is now foldable * GEN3: Invalid Contact Info Msg (#192) Fixes #191 * Refactoring async stream (#194) * GEN3: Invalid Contact Info Msg Fixes #191 * introduce ifc with FIFOs * add object factory * use AsyncIfc class with FIFO * declare more methods as classmethods * - refactoring - remove _forward_buffer - make async_write private * remove _forward_buffer * refactoring * avoid mqtt handling for invalid serial numbers * add two more callbacks * FIX update_header_cb handling * split AsyncStream in two classes * split ConnectionG3(P) in server and client class * update class diagramm * refactor server creation * remove duplicated imports * reduce code duplication * move StremPtr instances into Inverter class * resolution of connection classes - remove ConnectionG3Client - remove ConnectionG3Server - remove ConnectionG3PClient - remove ConnectionG3PServer * fix server connections * fix client loop closing * don't overwrite self.remote in constructor * update class diagramm * fixes - fixes null pointer accesses - initalize AsyncStreamClient with proper StreamPtr instance * add close callback * refactor close handling * remove connection classes * move more code into InverterBase class * remove test_inverter_base.py * add abstract inverter interface class * initial commit * fix sonar qube warnings * rename class Inverter into Proxy * fix typo * move class InverterIfc into a separate file * add more testcases * use ProtocolIfc class * add unit tests for AsyncStream class * icrease test coverage * reduce cognitive complexity * increase test coverage * increase tes coverage * simplify heartbeat handler * remove obsolete tx_get method * add more unittests * update changelog * remove __del__ method for proper gc runs * check releasing of ModbusConn instances * call garbage collector to release unreachable objs * decrease ref counter after the with block * S allius/issue196 (#198) * fix healthcheck - on infrastructure with IPv6 support localhost might be resolved to an IPv6 adress. Since the proxy only support IPv4 for now, we replace localhost by 127.0.0.1, to fix this * merge from main
This commit is contained in:
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [unreleased]
|
||||
|
||||
- fix healthcheck on infrastructure with IPv6 support [#196](https://github.com/s-allius/tsun-gen3-proxy/issues/196)
|
||||
- refactoring: cleaner architecture, increase test coverage
|
||||
- Parse more values in Server Mode [#186](https://github.com/s-allius/tsun-gen3-proxy/issues/186)
|
||||
- GEN3: add support for new messages of version 3 firmwares [#182](https://github.com/s-allius/tsun-gen3-proxy/issues/182)
|
||||
- add support for controller MAC and serial number
|
||||
|
||||
535
app/proxy.svg
535
app/proxy.svg
@@ -4,408 +4,257 @@
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
<!-- Title: G Pages: 1 -->
|
||||
<svg width="720pt" height="1360pt"
|
||||
viewBox="0.00 0.00 719.50 1360.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)">
|
||||
<svg width="626pt" height="966pt"
|
||||
viewBox="0.00 0.00 625.50 966.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 962)">
|
||||
<title>G</title>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1356 715.5,-1356 715.5,4 -4,4"/>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-962 621.5,-962 621.5,4 -4,4"/>
|
||||
<!-- A0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>A0</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="153.6964,-1250 45.3036,-1250 45.3036,-1214 159.6964,-1214 159.6964,-1244 153.6964,-1250"/>
|
||||
<polyline fill="none" stroke="#000000" points="153.6964,-1250 153.6964,-1244 "/>
|
||||
<polyline fill="none" stroke="#000000" points="159.6964,-1244 153.6964,-1244 "/>
|
||||
<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="102.5" y="-1223" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="191.6964,-934 83.3036,-934 83.3036,-898 197.6964,-898 197.6964,-928 191.6964,-934"/>
|
||||
<polyline fill="none" stroke="#000000" points="191.6964,-934 191.6964,-928 "/>
|
||||
<polyline fill="none" stroke="#000000" points="197.6964,-928 191.6964,-928 "/>
|
||||
<text text-anchor="middle" x="140.5" y="-919" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">You can stick notes</text>
|
||||
<text text-anchor="middle" x="140.5" y="-907" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text>
|
||||
</g>
|
||||
<!-- A1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>A1</title>
|
||||
<polygon fill="none" stroke="#000000" points="685.1817,-942 615.8183,-942 615.8183,-906 685.1817,-906 685.1817,-942"/>
|
||||
<text text-anchor="middle" x="650.5" y="-921" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Singleton</text>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>A2</title>
|
||||
<polygon fill="none" stroke="#000000" points="589.5,-644 589.5,-676 711.5,-676 711.5,-644 589.5,-644"/>
|
||||
<text text-anchor="start" x="640.777" y="-657" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Mqtt</text>
|
||||
<polygon fill="none" stroke="#000000" points="589.5,-588 589.5,-644 711.5,-644 711.5,-588 589.5,-588"/>
|
||||
<text text-anchor="start" x="607.9875" y="-625" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><static>ha_restarts</text>
|
||||
<text text-anchor="start" x="615.7665" y="-613" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><static>__client</text>
|
||||
<text text-anchor="start" x="599.3735" y="-601" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><static>__cb_MqttIsUp</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="612.436" y="-569" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>publish()</text>
|
||||
<text text-anchor="start" x="616.6045" y="-557" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>close()</text>
|
||||
</g>
|
||||
<!-- A1->A2 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>A1->A2</title>
|
||||
<path fill="none" stroke="#000000" d="M650.5,-895.5395C650.5,-846.311 650.5,-744.0351 650.5,-676.2069"/>
|
||||
<polygon fill="none" stroke="#000000" points="647.0001,-895.7608 650.5,-905.7608 654.0001,-895.7608 647.0001,-895.7608"/>
|
||||
</g>
|
||||
<!-- 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->A11 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A2->A11</title>
|
||||
<path fill="none" stroke="#000000" d="M650.5,-543.7248C650.5,-495.3688 650.5,-429.8734 650.5,-380.1918"/>
|
||||
</g>
|
||||
<!-- A3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>A3</title>
|
||||
<polygon fill="none" stroke="#000000" points="318.5,-402 318.5,-434 393.5,-434 393.5,-402 318.5,-402"/>
|
||||
<text text-anchor="start" x="338.2175" y="-415" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
|
||||
<polygon fill="none" stroke="#000000" points="318.5,-250 318.5,-402 393.5,-402 393.5,-250 318.5,-250"/>
|
||||
<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="328.49" y="-359" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
|
||||
<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="339.6085" y="-335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</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="337.942" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
|
||||
<text text-anchor="start" x="349.8915" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
|
||||
<text text-anchor="start" x="336.5535" y="-287" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
|
||||
<text text-anchor="start" x="334.879" y="-275" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
|
||||
<text text-anchor="start" x="349.3365" y="-263" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
|
||||
<polygon fill="none" stroke="#000000" points="318.5,-182 318.5,-250 393.5,-250 393.5,-182 318.5,-182"/>
|
||||
<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>
|
||||
<polygon fill="none" stroke="#000000" points="215.5,-926 215.5,-958 331.5,-958 331.5,-926 215.5,-926"/>
|
||||
<text text-anchor="start" x="225.149" y="-939" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AbstractIterMeta>></text>
|
||||
<polygon fill="none" stroke="#000000" points="215.5,-906 215.5,-926 331.5,-926 331.5,-906 215.5,-906"/>
|
||||
<polygon fill="none" stroke="#000000" points="215.5,-874 215.5,-906 331.5,-906 331.5,-874 215.5,-874"/>
|
||||
<text text-anchor="start" x="252.11" y="-887" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__()</text>
|
||||
</g>
|
||||
<!-- A4 -->
|
||||
<g id="node5" class="node">
|
||||
<title>A4</title>
|
||||
<polygon fill="none" stroke="#000000" points="308.5,-1242 308.5,-1274 379.5,-1274 379.5,-1242 308.5,-1242"/>
|
||||
<text text-anchor="start" x="318.445" y="-1255" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">IterRegistry</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="308.5,-1190 308.5,-1222 379.5,-1222 379.5,-1190 308.5,-1190"/>
|
||||
<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="178.5,-726 178.5,-758 369.5,-758 369.5,-726 178.5,-726"/>
|
||||
<text text-anchor="start" x="240.0965" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<InverterIfc>></text>
|
||||
<polygon fill="none" stroke="#000000" points="178.5,-706 178.5,-726 369.5,-726 369.5,-706 178.5,-706"/>
|
||||
<polygon fill="none" stroke="#000000" points="178.5,-650 178.5,-706 369.5,-706 369.5,-650 178.5,-650"/>
|
||||
<text text-anchor="start" x="240.522" y="-687" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()->bool</text>
|
||||
<text text-anchor="start" x="188.2835" y="-675" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>disc(shutdown_started=False)</text>
|
||||
<text text-anchor="start" x="219.544" y="-663" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>create_remote()</text>
|
||||
</g>
|
||||
<!-- A1->A4 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>A1->A4</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M273.5,-863.7744C273.5,-831.6663 273.5,-790.6041 273.5,-758.1476"/>
|
||||
<polygon fill="none" stroke="#000000" points="270.0001,-863.8621 273.5,-873.8622 277.0001,-863.8622 270.0001,-863.8621"/>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>A2</title>
|
||||
<polygon fill="none" stroke="#000000" points="441.5,-454 441.5,-498 563.5,-498 563.5,-454 441.5,-454"/>
|
||||
<text text-anchor="start" x="492.777" y="-479" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Mqtt</text>
|
||||
<text text-anchor="start" x="469.9815" y="-467" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<Singleton>></text>
|
||||
<polygon fill="none" stroke="#000000" points="441.5,-398 441.5,-454 563.5,-454 563.5,-398 441.5,-398"/>
|
||||
<text text-anchor="start" x="459.9875" y="-435" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><static>ha_restarts</text>
|
||||
<text text-anchor="start" x="467.7665" y="-423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><static>__client</text>
|
||||
<text text-anchor="start" x="451.3735" y="-411" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><static>__cb_MqttIsUp</text>
|
||||
<polygon fill="none" stroke="#000000" points="441.5,-354 441.5,-398 563.5,-398 563.5,-354 441.5,-354"/>
|
||||
<text text-anchor="start" x="464.436" y="-379" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>publish()</text>
|
||||
<text text-anchor="start" x="468.6045" y="-367" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>close()</text>
|
||||
</g>
|
||||
<!-- A3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>A3</title>
|
||||
<polygon fill="none" stroke="#000000" points="387.5,-792 387.5,-824 617.5,-824 617.5,-792 387.5,-792"/>
|
||||
<text text-anchor="start" x="489.7215" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Proxy</text>
|
||||
<polygon fill="none" stroke="#000000" points="387.5,-676 387.5,-792 617.5,-792 617.5,-676 387.5,-676"/>
|
||||
<text text-anchor="start" x="474.1545" y="-773" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><cls>db_stat</text>
|
||||
<text text-anchor="start" x="467.491" y="-761" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><cls>entity_prfx</text>
|
||||
<text text-anchor="start" x="458.326" y="-749" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><cls>discovery_prfx</text>
|
||||
<text text-anchor="start" x="457.762" y="-737" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><cls>proxy_node_id</text>
|
||||
<text text-anchor="start" x="453.873" y="-725" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><cls>proxy_unique_id</text>
|
||||
<text text-anchor="start" x="469.716" y="-713" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><cls>mqtt:Mqtt</text>
|
||||
<text text-anchor="start" x="471.9355" y="-689" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
|
||||
<polygon fill="none" stroke="#000000" points="387.5,-584 387.5,-676 617.5,-676 617.5,-584 387.5,-584"/>
|
||||
<text text-anchor="start" x="478.6145" y="-657" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">class_init()</text>
|
||||
<text text-anchor="start" x="473.334" y="-645" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">class_close()</text>
|
||||
<text text-anchor="start" x="444.984" y="-621" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>_cb_mqtt_is_up()</text>
|
||||
<text text-anchor="start" x="397.197" y="-609" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>_register_proxy_stat_home_assistant()</text>
|
||||
<text text-anchor="start" x="406.084" y="-597" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>_async_publ_mqtt_proxy_stat(key)</text>
|
||||
</g>
|
||||
<!-- A3->A2 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>A3->A2</title>
|
||||
<path fill="none" stroke="#000000" d="M502.5,-571.373C502.5,-549.9571 502.5,-528.339 502.5,-508.5579"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="502.5001,-571.682 506.5,-577.6821 502.5,-583.682 498.5,-577.682 502.5001,-571.682"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="502.5,-498.392 507.0001,-508.3919 502.5,-503.392 502.5001,-508.392 502.5001,-508.392 502.5001,-508.392 502.5,-503.392 498.0001,-508.392 502.5,-498.392 502.5,-498.392"/>
|
||||
</g>
|
||||
<!-- A5 -->
|
||||
<g id="node6" class="node">
|
||||
<title>A5</title>
|
||||
<polygon fill="none" stroke="#000000" points="276.5,-1030 276.5,-1062 410.5,-1062 410.5,-1030 276.5,-1030"/>
|
||||
<text text-anchor="start" x="323.2175" y="-1043" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
|
||||
<polygon fill="none" stroke="#000000" points="276.5,-854 276.5,-1030 410.5,-1030 410.5,-854 276.5,-854"/>
|
||||
<text text-anchor="start" x="306.8265" y="-1011" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text>
|
||||
<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="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<abstract></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>
|
||||
<polygon fill="none" stroke="#000000" points="205.5,-502 205.5,-534 396.5,-534 396.5,-502 205.5,-502"/>
|
||||
<text text-anchor="start" x="272.66" y="-515" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterBase</text>
|
||||
<polygon fill="none" stroke="#000000" points="205.5,-386 205.5,-502 396.5,-502 396.5,-386 205.5,-386"/>
|
||||
<text text-anchor="start" x="281.8335" y="-483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_registry</text>
|
||||
<text text-anchor="start" x="270.4355" y="-471" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__ha_restarts</text>
|
||||
<text text-anchor="start" x="290.997" y="-447" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
|
||||
<text text-anchor="start" x="274.0505" y="-435" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">config_id:str</text>
|
||||
<text text-anchor="start" x="247.3785" y="-423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_class:MessageProt</text>
|
||||
<text text-anchor="start" x="261.553" y="-411" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
|
||||
<text text-anchor="start" x="266.832" y="-399" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
|
||||
<polygon fill="none" stroke="#000000" points="205.5,-318 205.5,-386 396.5,-386 396.5,-318 205.5,-318"/>
|
||||
<text text-anchor="start" x="267.522" y="-367" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()->bool</text>
|
||||
<text text-anchor="start" x="215.2835" y="-355" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>disc(shutdown_started=False)</text>
|
||||
<text text-anchor="start" x="246.544" y="-343" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>create_remote()</text>
|
||||
<text text-anchor="start" x="240.984" y="-331" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>async_publ_mqtt()</text>
|
||||
</g>
|
||||
<!-- A3->A5 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>A3->A5</title>
|
||||
<path fill="none" stroke="#000000" d="M409.1791,-575.5683C399.1409,-561.7533 389.0008,-547.7982 379.1588,-534.2532"/>
|
||||
<polygon fill="none" stroke="#000000" points="406.3649,-577.6495 415.0747,-583.682 412.0279,-573.5347 406.3649,-577.6495"/>
|
||||
</g>
|
||||
<!-- A4->A5 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>A4->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"/>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M279.7719,-639.4228C282.8086,-608.1559 286.5373,-569.7639 289.991,-534.2034"/>
|
||||
<polygon fill="none" stroke="#000000" points="276.2531,-639.4473 278.77,-649.7389 283.2203,-640.1241 276.2531,-639.4473"/>
|
||||
</g>
|
||||
<!-- A6 -->
|
||||
<g id="node7" class="node">
|
||||
<title>A6</title>
|
||||
<polygon fill="none" stroke="#000000" points="415.5,-704 415.5,-736 529.5,-736 529.5,-704 415.5,-704"/>
|
||||
<text text-anchor="start" x="458.608" y="-717" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Talent</text>
|
||||
<polygon fill="none" stroke="#000000" points="415.5,-600 415.5,-704 529.5,-704 529.5,-600 415.5,-600"/>
|
||||
<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="460.2775" y="-673" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">id_str</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="444.44" y="-649" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_mail</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="446.384" y="-625" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
|
||||
<text text-anchor="start" x="458.612" y="-613" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</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="429.9925" y="-581" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_contact_info()</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="437.7765" y="-557" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_get_time()</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="427.7735" y="-533" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_inverter_data()</text>
|
||||
<text text-anchor="start" x="436.9405" y="-521" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
|
||||
<text text-anchor="start" x="457.5025" y="-497" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<polygon fill="none" stroke="#000000" points="356.5,-236 356.5,-268 456.5,-268 456.5,-236 356.5,-236"/>
|
||||
<text text-anchor="start" x="383.9995" y="-249" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">StreamPtr</text>
|
||||
<polygon fill="none" stroke="#000000" points="356.5,-216 356.5,-236 456.5,-236 456.5,-216 356.5,-216"/>
|
||||
<polygon fill="none" stroke="#000000" points="356.5,-172 356.5,-216 456.5,-216 456.5,-172 356.5,-172"/>
|
||||
<text text-anchor="start" x="366.2175" y="-197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:ProtocolIfc</text>
|
||||
<text text-anchor="start" x="381.2185" y="-185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
|
||||
</g>
|
||||
<!-- A5->A6 -->
|
||||
<g id="edge3" class="edge">
|
||||
<g id="edge8" class="edge">
|
||||
<title>A5->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"/>
|
||||
<path fill="none" stroke="#000000" d="M356.1387,-317.872C363.3786,-303.802 370.5526,-289.86 377.1187,-277.0995"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="381.7846,-268.0318 381.2105,-278.9826 379.4969,-272.4777 377.2091,-276.9237 377.2091,-276.9237 377.2091,-276.9237 379.4969,-272.4777 373.2078,-274.8647 381.7846,-268.0318 381.7846,-268.0318"/>
|
||||
<text text-anchor="middle" x="381.0069" y="-285.0166" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">2</text>
|
||||
</g>
|
||||
<!-- A7 -->
|
||||
<g id="node8" class="node">
|
||||
<title>A7</title>
|
||||
<polygon fill="none" stroke="#000000" points="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>
|
||||
<polygon fill="none" stroke="#000000" points="338.2314,-238 262.7686,-238 262.7686,-202 338.2314,-202 338.2314,-238"/>
|
||||
<text text-anchor="middle" x="300.5" y="-217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3</text>
|
||||
</g>
|
||||
<!-- A5->A7 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>A5->A7</title>
|
||||
<path fill="none" stroke="#000000" d="M284.228,-776.2903C273.8281,-750.3733 263.2923,-724.1174 253.7595,-700.3611"/>
|
||||
<polygon fill="none" stroke="#000000" points="281.0788,-777.8409 288.0512,-785.8181 287.5753,-775.2339 281.0788,-777.8409"/>
|
||||
</g>
|
||||
<!-- A6->A3 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>A6->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->A8 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>A6->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->A3 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>A7->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>
|
||||
<title>A5->A7</title>
|
||||
<path fill="none" stroke="#000000" d="M300.5,-307.7729C300.5,-280.5002 300.5,-254.684 300.5,-238.2013"/>
|
||||
<polygon fill="none" stroke="#000000" points="297.0001,-307.872 300.5,-317.872 304.0001,-307.872 297.0001,-307.872"/>
|
||||
</g>
|
||||
<!-- A9 -->
|
||||
<g id="node10" class="node">
|
||||
<title>A9</title>
|
||||
<polygon fill="none" stroke="#000000" points="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>
|
||||
<polygon fill="none" stroke="#000000" points="94.4001,-238 12.5999,-238 12.5999,-202 94.4001,-202 94.4001,-238"/>
|
||||
<text text-anchor="middle" x="53.5" y="-217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3P</text>
|
||||
</g>
|
||||
<!-- A7->A9 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>A7->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"/>
|
||||
<!-- A5->A9 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>A5->A9</title>
|
||||
<path fill="none" stroke="#000000" d="M196.7667,-346.4637C165.8973,-321.9347 132.3582,-294.4156 102.5,-268 91.7971,-258.5312 80.3616,-247.3925 71.232,-238.23"/>
|
||||
<polygon fill="none" stroke="#000000" points="194.962,-349.4991 204.9739,-352.965 199.3086,-344.0121 194.962,-349.4991"/>
|
||||
</g>
|
||||
<!-- A8->A8 -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>A8->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>
|
||||
<!-- A11 -->
|
||||
<g id="node12" class="node">
|
||||
<title>A11</title>
|
||||
<polygon fill="none" stroke="#000000" points="450.1421,-36 360.8579,-36 360.8579,0 450.1421,0 450.1421,-36"/>
|
||||
<text text-anchor="middle" x="405.5" y="-15" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AsyncIfc>></text>
|
||||
</g>
|
||||
<!-- A6->A11 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>A6->A11</title>
|
||||
<path fill="none" stroke="#000000" d="M392.6633,-171.974C386.9982,-146.4565 382.5868,-114.547 386.5,-86 388.3468,-72.5276 392.161,-57.9618 395.8907,-45.7804"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="398.9587,-36.1851 400.1994,-47.0805 397.4359,-40.9476 395.9131,-45.71 395.9131,-45.71 395.9131,-45.71 397.4359,-40.9476 391.6269,-44.3395 398.9587,-36.1851 398.9587,-36.1851"/>
|
||||
<text text-anchor="middle" x="401.4892" y="-53.0243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
|
||||
</g>
|
||||
<!-- A12 -->
|
||||
<g id="node13" class="node">
|
||||
<title>A12</title>
|
||||
<polygon fill="none" stroke="#000000" points="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>
|
||||
<polygon fill="none" stroke="#000000" points="493.5879,-122 395.4121,-122 395.4121,-86 493.5879,-86 493.5879,-122"/>
|
||||
<text text-anchor="middle" x="444.5" y="-101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<ProtocolIfc>></text>
|
||||
</g>
|
||||
<!-- A8->A12 -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>A8->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"/>
|
||||
<!-- A6->A12 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>A6->A12</title>
|
||||
<path fill="none" stroke="#000000" d="M422.2853,-171.8133C426.7329,-158.2365 431.4225,-143.9208 435.3408,-131.9595"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="438.5602,-122.132 439.7235,-133.036 437.0036,-126.8835 435.4471,-131.6351 435.4471,-131.6351 435.4471,-131.6351 437.0036,-126.8835 431.1707,-130.2341 438.5602,-122.132 438.5602,-122.132"/>
|
||||
<text text-anchor="middle" x="440.9498" y="-138.9887" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
|
||||
</g>
|
||||
<!-- A9->A9 -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>A9->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>
|
||||
<!-- A8 -->
|
||||
<g id="node9" class="node">
|
||||
<title>A8</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="574.906,-248 474.094,-248 474.094,-192 580.906,-192 580.906,-242 574.906,-248"/>
|
||||
<polyline fill="none" stroke="#000000" points="574.906,-248 574.906,-242 "/>
|
||||
<polyline fill="none" stroke="#000000" points="580.906,-242 574.906,-242 "/>
|
||||
<text text-anchor="middle" x="527.5" y="-235" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Creates an GEN3</text>
|
||||
<text text-anchor="middle" x="527.5" y="-223" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inverter instance</text>
|
||||
<text text-anchor="middle" x="527.5" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">with</text>
|
||||
<text text-anchor="middle" x="527.5" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_class:Talent</text>
|
||||
</g>
|
||||
<!-- 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->A13 -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>A9->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"/>
|
||||
<!-- A7->A8 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>A7->A8</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M308.5491,-238.3283C317.4345,-256.0056 333.5793,-281.6949 356.5,-293 396.3598,-312.6598 415.5578,-310.2929 456.5,-293 478.1607,-283.8511 496.4784,-264.5049 509.0802,-248.0264"/>
|
||||
</g>
|
||||
<!-- A10 -->
|
||||
<g id="node11" class="node">
|
||||
<title>A10</title>
|
||||
<polygon fill="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"><async>server_loop()</text>
|
||||
<text text-anchor="start" x="293.378" y="-587" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>client_loop()</text>
|
||||
<text text-anchor="start" x="311.154" y="-575" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>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>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="239.022,-248 111.978,-248 111.978,-192 245.022,-192 245.022,-242 239.022,-248"/>
|
||||
<polyline fill="none" stroke="#000000" points="239.022,-248 239.022,-242 "/>
|
||||
<polyline fill="none" stroke="#000000" points="245.022,-242 239.022,-242 "/>
|
||||
<text text-anchor="middle" x="178.5" y="-235" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Creates an GEN3PLUS</text>
|
||||
<text text-anchor="middle" x="178.5" y="-223" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inverter instance</text>
|
||||
<text text-anchor="middle" x="178.5" y="-211" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">with</text>
|
||||
<text text-anchor="middle" x="178.5" y="-199" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_class:SolarmanV5</text>
|
||||
</g>
|
||||
<!-- A10->A8 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>A10->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"/>
|
||||
<!-- A9->A10 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>A9->A10</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M94.5156,-220C100.3114,-220 106.1072,-220 111.903,-220"/>
|
||||
</g>
|
||||
<!-- A10->A9 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>A10->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->A12 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>A11->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->A13 -->
|
||||
<!-- A12->A11 -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>A11->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"/>
|
||||
<title>A12->A11</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M436.2291,-85.7616C430.9033,-74.0176 423.8824,-58.5355 417.896,-45.3349"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="413.759,-36.2121 421.9874,-43.4608 415.824,-40.7657 417.8891,-45.3194 417.8891,-45.3194 417.8891,-45.3194 415.824,-40.7657 413.7908,-47.1779 413.759,-36.2121 413.759,-36.2121"/>
|
||||
<text text-anchor="middle" x="421.0451" y="-69.7445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
|
||||
</g>
|
||||
<!-- A13 -->
|
||||
<g id="node14" class="node">
|
||||
<title>A13</title>
|
||||
<polygon fill="none" stroke="#000000" points=".5,-454 .5,-486 107.5,-486 107.5,-454 .5,-454"/>
|
||||
<text text-anchor="start" x="24.2695" y="-467" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusConn</text>
|
||||
<polygon fill="none" stroke="#000000" points=".5,-386 .5,-454 107.5,-454 107.5,-386 .5,-386"/>
|
||||
<text text-anchor="start" x="44.5515" y="-435" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">host</text>
|
||||
<text text-anchor="start" x="45.387" y="-423" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</text>
|
||||
<text text-anchor="start" x="43.997" y="-411" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
|
||||
<text text-anchor="start" x="10.383" y="-399" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text>
|
||||
<polygon fill="none" stroke="#000000" points=".5,-366 .5,-386 107.5,-386 107.5,-366 .5,-366"/>
|
||||
</g>
|
||||
<!-- A13->A9 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A13->A9</title>
|
||||
<path fill="none" stroke="#000000" d="M53.5,-365.8625C53.5,-327.1513 53.5,-278.6088 53.5,-248.4442"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="53.5,-238.2147 58.0001,-248.2147 53.5,-243.2147 53.5001,-248.2147 53.5001,-248.2147 53.5001,-248.2147 53.5,-243.2147 49.0001,-248.2148 53.5,-238.2147 53.5,-238.2147"/>
|
||||
<text text-anchor="middle" x="61.9524" y="-253.3409" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
|
||||
<text text-anchor="middle" x="45.0476" y="-344.7363" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
</g>
|
||||
<!-- A14 -->
|
||||
<g id="node15" class="node">
|
||||
<title>A14</title>
|
||||
<polygon fill="none" stroke="#000000" points="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>
|
||||
<polygon fill="none" stroke="#000000" points="93.7333,-722 13.2667,-722 13.2667,-686 93.7333,-686 93.7333,-722"/>
|
||||
<text text-anchor="middle" x="53.5" y="-701" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusTcp</text>
|
||||
</g>
|
||||
<!-- A15 -->
|
||||
<g id="node16" class="node">
|
||||
<title>A15</title>
|
||||
<polygon fill="none" stroke="#000000" points="431.5,-940 431.5,-972 498.5,-972 498.5,-940 431.5,-940"/>
|
||||
<text text-anchor="start" x="447.493" y="-953" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3</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="431.5,-876 431.5,-920 498.5,-920 498.5,-876 431.5,-876"/>
|
||||
<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="449.168" y="-889" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
|
||||
</g>
|
||||
<!-- A14->A15 -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>A14->A15</title>
|
||||
<path fill="none" stroke="#000000" d="M287.6238,-1123.7067C291.4001,-1119.5529 295.359,-1115.6229 299.5,-1112 342.985,-1073.9563 380.3024,-1104.4478 419.5,-1062 442.1524,-1037.4693 453.4109,-1001.3633 459.0018,-972.2357"/>
|
||||
<polygon fill="none" stroke="#000000" points="284.8741,-1121.5366 281.0238,-1131.4071 290.1891,-1126.0921 284.8741,-1121.5366"/>
|
||||
</g>
|
||||
<!-- A16 -->
|
||||
<g id="node17" class="node">
|
||||
<title>A16</title>
|
||||
<polygon fill="none" stroke="#000000" points="187.5,-940 187.5,-972 254.5,-972 254.5,-940 187.5,-940"/>
|
||||
<text text-anchor="start" x="200.1585" y="-953" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3P</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="187.5,-876 187.5,-920 254.5,-920 254.5,-876 187.5,-876"/>
|
||||
<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="205.168" y="-889" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
|
||||
</g>
|
||||
<!-- A14->A16 -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>A14->A16</title>
|
||||
<path fill="none" stroke="#000000" d="M225.6878,-1101.5366C224.3454,-1055.5988 222.9195,-1006.7991 221.9029,-972.012"/>
|
||||
<polygon fill="none" stroke="#000000" points="222.191,-1101.7024 225.9817,-1111.5959 229.188,-1101.4979 222.191,-1101.7024"/>
|
||||
</g>
|
||||
<!-- A15->A6 -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>A15->A6</title>
|
||||
<path fill="none" stroke="#000000" d="M465.7237,-875.9684C466.6086,-841.2366 467.8512,-792.4655 469.031,-746.1572"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="469.2896,-736.0098 473.5333,-746.1212 469.1622,-741.0082 469.0348,-746.0066 469.0348,-746.0066 469.0348,-746.0066 469.1622,-741.0082 464.5362,-745.8919 469.2896,-736.0098 469.2896,-736.0098"/>
|
||||
</g>
|
||||
<!-- A16->A7 -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>A16->A7</title>
|
||||
<path fill="none" stroke="#000000" d="M220.0411,-875.9684C219.6216,-832.0581 218.9877,-765.7079 218.4579,-710.2644"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="218.3603,-700.0467 222.9557,-710.0032 218.4081,-705.0465 218.456,-710.0463 218.456,-710.0463 218.456,-710.0463 218.4081,-705.0465 213.9562,-710.0893 218.3603,-700.0467 218.3603,-700.0467"/>
|
||||
</g>
|
||||
<!-- A17 -->
|
||||
<g id="node18" class="node">
|
||||
<title>A17</title>
|
||||
<polygon fill="none" stroke="#000000" points=".5,-336 .5,-368 107.5,-368 107.5,-336 .5,-336"/>
|
||||
<text text-anchor="start" x="24.2695" y="-349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ModbusConn</text>
|
||||
<polygon fill="none" stroke="#000000" points=".5,-268 .5,-336 107.5,-336 107.5,-268 .5,-268"/>
|
||||
<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="45.387" y="-305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">port</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="10.383" y="-281" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stream:InverterG3P</text>
|
||||
<polygon fill="none" stroke="#000000" points=".5,-248 .5,-268 107.5,-268 107.5,-248 .5,-248"/>
|
||||
</g>
|
||||
<!-- A17->A13 -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>A17->A13</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"/>
|
||||
<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"/>
|
||||
<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="81.2636" y="-224.8385" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
<!-- A14->A13 -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>A14->A13</title>
|
||||
<path fill="none" stroke="#000000" d="M53.5,-685.7596C53.5,-647.9991 53.5,-559.5189 53.5,-496.3277"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="53.5,-486.0223 58.0001,-496.0223 53.5,-491.0223 53.5001,-496.0223 53.5001,-496.0223 53.5001,-496.0223 53.5,-491.0223 49.0001,-496.0224 53.5,-486.0223 53.5,-486.0223"/>
|
||||
<text text-anchor="middle" x="61.9524" y="-501.1485" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">*</text>
|
||||
<text text-anchor="middle" x="45.0476" y="-664.6335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">creates</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 20 KiB |
@@ -3,28 +3,34 @@
|
||||
// {generate:true}
|
||||
|
||||
[note: You can stick notes on diagrams too!{bg:cornsilk}]
|
||||
[Singleton]^[Mqtt|<static>ha_restarts;<static>__client;<static>__cb_MqttIsUp|<async>publish();<async>close()]
|
||||
[Modbus|que;;snd_handler;rsp_handler;timeout;max_retires;last_xxx;err;retry_cnt;req_pend;tim|build_msg();recv_req();recv_resp();close()]
|
||||
[IterRegistry||__iter__]^[Message|server_side:bool;header_valid:bool;header_len:unsigned;data_len:unsigned;unique_id;node_id;sug_area;_recv_buffer:bytearray;_send_buffer:bytearray;_forward_buffer:bytearray;db:Infos;new_data:list;state|_read():void<abstract>;close():void;inc_counter():void;dec_counter():void]
|
||||
[Message]^[Talent|await_conn_resp_cnt;id_str;contact_name;contact_mail;db:InfosG3;mb:Modbus;switch|msg_contact_info();msg_ota_update();msg_get_time();msg_collector_data();msg_inverter_data();msg_unknown();;close()]
|
||||
[Message]^[SolarmanV5|control;serial;snr;db:InfosG3P;mb:Modbus;switch|msg_unknown();;close()]
|
||||
[Talent]^[ConnectionG3|remote_stream:ConnectionG3|healthy();close()]
|
||||
[Talent]has-1>[Modbus]
|
||||
[SolarmanV5]^[ConnectionG3P|remote_stream:ConnectionG3P|healthy();close()]
|
||||
[SolarmanV5]has-1>[Modbus]
|
||||
[AsyncStream|reader;writer;addr;r_addr;l_addr|<async>server_loop();<async>client_loop();<async>loop;disc();close();;__async_read();async_write();__async_forward()]^[ConnectionG3]
|
||||
[AsyncStream]^[ConnectionG3P]
|
||||
[Inverter|cls.db_stat;cls.entity_prfx;cls.discovery_prfx;cls.proxy_node_id;cls.proxy_unique_id;cls.mqtt:Mqtt|]^[InverterG3|__ha_restarts|async_create_remote();async_publ_mqtt();;close()]
|
||||
[Inverter]^[InverterG3P|__ha_restarts|async_create_remote(;)async_publ_mqtt();close()]
|
||||
[Mqtt]-[Inverter]
|
||||
[ConnectionG3]^[InverterG3]
|
||||
[ConnectionG3]has-0..1>[ConnectionG3]
|
||||
[ConnectionG3P]^[InverterG3P]
|
||||
[ConnectionG3P]has-0..1>[ConnectionG3P]
|
||||
[<<AbstractIterMeta>>||__iter__()]
|
||||
|
||||
[Mqtt;<<Singleton>>|<static>ha_restarts;<static>__client;<static>__cb_MqttIsUp|<async>publish();<async>close()]
|
||||
[Proxy|<cls>db_stat;<cls>entity_prfx;<cls>discovery_prfx;<cls>proxy_node_id;<cls>proxy_unique_id;<cls>mqtt:Mqtt;;__ha_restarts|class_init();class_close();;<async>_cb_mqtt_is_up();<async>_register_proxy_stat_home_assistant();<async>_async_publ_mqtt_proxy_stat(key)]
|
||||
|
||||
[<<InverterIfc>>||healthy()->bool;<async>disc(shutdown_started=False);<async>create_remote();]
|
||||
[<<AbstractIterMeta>>]^-.-[<<InverterIfc>>]
|
||||
[InverterBase|_registry;__ha_restarts;;addr;config_id:str;prot_class:MessageProt;remote:StreamPtr;local:StreamPtr;|healthy()->bool;<async>disc(shutdown_started=False);<async>create_remote();<async>async_publ_mqtt()]
|
||||
[StreamPtr||stream:ProtocolIfc;ifc:AsyncIfc]
|
||||
[<<InverterIfc>>]^-.-[InverterBase]
|
||||
[InverterG3]-[note: Creates an GEN3 inverter instance with prot_class:Talent{bg:cornsilk}]
|
||||
[InverterG3P]-[note: Creates an GEN3PLUS inverter instance with prot_class:SolarmanV5{bg:cornsilk}]
|
||||
[InverterBase]^[InverterG3]
|
||||
[InverterBase]^[InverterG3P]
|
||||
[Proxy]^[InverterBase]
|
||||
[InverterBase]-2>[StreamPtr]
|
||||
[Proxy]++->[Mqtt;<<Singleton>>]
|
||||
|
||||
[<<AsyncIfc>>]
|
||||
|
||||
|
||||
[StreamPtr]-1>[<<ProtocolIfc>>]
|
||||
[StreamPtr]-1>[<<AsyncIfc>>]
|
||||
|
||||
|
||||
[<<ProtocolIfc>>]use-.->[<<AsyncIfc>>]
|
||||
|
||||
|
||||
[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]^[InfosG3P||ha_confs();parse()]
|
||||
[InfosG3P]->[SolarmanV5]
|
||||
[InfosG3]->[Talent]
|
||||
[ModbusConn|host;port;addr;stream:InverterG3P;|]has-1>[InverterG3P]
|
||||
[ModbusTcp]creates-*>[ModbusConn]
|
||||
|
||||
|
||||
432
app/proxy_2.svg
Normal file
432
app/proxy_2.svg
Normal file
@@ -0,0 +1,432 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
<!-- Title: G Pages: 1 -->
|
||||
<svg width="468pt" height="1942pt"
|
||||
viewBox="0.00 0.00 468.35 1942.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 1938)">
|
||||
<title>G</title>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1938 464.348,-1938 464.348,4 -4,4"/>
|
||||
<!-- A0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>A0</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="108.5444,-1910 .1516,-1910 .1516,-1874 114.5444,-1874 114.5444,-1904 108.5444,-1910"/>
|
||||
<polyline fill="none" stroke="#000000" points="108.5444,-1910 108.5444,-1904 "/>
|
||||
<polyline fill="none" stroke="#000000" points="114.5444,-1904 108.5444,-1904 "/>
|
||||
<text text-anchor="middle" x="57.348" y="-1895" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">You can stick notes</text>
|
||||
<text text-anchor="middle" x="57.348" y="-1883" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">on diagrams too!</text>
|
||||
</g>
|
||||
<!-- A1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>A1</title>
|
||||
<polygon fill="none" stroke="#000000" points="132.348,-1902 132.348,-1934 248.348,-1934 248.348,-1902 132.348,-1902"/>
|
||||
<text text-anchor="start" x="141.997" y="-1915" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AbstractIterMeta>></text>
|
||||
<polygon fill="none" stroke="#000000" points="132.348,-1882 132.348,-1902 248.348,-1902 248.348,-1882 132.348,-1882"/>
|
||||
<polygon fill="none" stroke="#000000" points="132.348,-1850 132.348,-1882 248.348,-1882 248.348,-1850 132.348,-1850"/>
|
||||
<text text-anchor="start" x="168.958" y="-1863" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__()</text>
|
||||
</g>
|
||||
<!-- A14 -->
|
||||
<g id="node15" class="node">
|
||||
<title>A14</title>
|
||||
<polygon fill="none" stroke="#000000" points="145.348,-1768 145.348,-1800 235.348,-1800 235.348,-1768 145.348,-1768"/>
|
||||
<text text-anchor="start" x="155.0545" y="-1781" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<ProtocolIfc>></text>
|
||||
<polygon fill="none" stroke="#000000" points="145.348,-1736 145.348,-1768 235.348,-1768 235.348,-1736 145.348,-1736"/>
|
||||
<text text-anchor="start" x="171.1815" y="-1749" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_registry</text>
|
||||
<polygon fill="none" stroke="#000000" points="145.348,-1704 145.348,-1736 235.348,-1736 235.348,-1704 145.348,-1704"/>
|
||||
<text text-anchor="start" x="175.3505" y="-1717" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A1->A14 -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>A1->A14</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M190.348,-1839.847C190.348,-1826.914 190.348,-1813.1101 190.348,-1800.3669"/>
|
||||
<polygon fill="none" stroke="#000000" points="186.8481,-1839.9953 190.348,-1849.9954 193.8481,-1839.9954 186.8481,-1839.9953"/>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>A2</title>
|
||||
<polygon fill="none" stroke="#000000" points="362.348,-584 362.348,-616 460.348,-616 460.348,-584 362.348,-584"/>
|
||||
<text text-anchor="start" x="387.7325" y="-597" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3</text>
|
||||
<polygon fill="none" stroke="#000000" points="362.348,-528 362.348,-584 460.348,-584 460.348,-528 362.348,-528"/>
|
||||
<text text-anchor="start" x="401.345" y="-565" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
|
||||
<text text-anchor="start" x="371.901" y="-553" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
|
||||
<text text-anchor="start" x="377.18" y="-541" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
|
||||
<polygon fill="none" stroke="#000000" points="362.348,-472 362.348,-528 460.348,-528 460.348,-472 362.348,-472"/>
|
||||
<text text-anchor="start" x="375.79" y="-509" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create_remote()</text>
|
||||
<text text-anchor="start" x="396.3505" y="-485" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A7 -->
|
||||
<g id="node8" class="node">
|
||||
<title>A7</title>
|
||||
<polygon fill="none" stroke="#000000" points="29.348,-100 29.348,-132 207.348,-132 207.348,-100 29.348,-100"/>
|
||||
<text text-anchor="start" x="73.8995" y="-113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamServer</text>
|
||||
<polygon fill="none" stroke="#000000" points="29.348,-68 29.348,-100 207.348,-100 207.348,-68 29.348,-68"/>
|
||||
<text text-anchor="start" x="86.119" y="-81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create_remote</text>
|
||||
<polygon fill="none" stroke="#000000" points="29.348,0 29.348,-68 207.348,-68 207.348,0 29.348,0"/>
|
||||
<text text-anchor="start" x="70.0055" y="-49" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>server_loop()</text>
|
||||
<text text-anchor="start" x="60.8365" y="-37" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>_async_forward()</text>
|
||||
<text text-anchor="start" x="39.157" y="-25" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>publish_outstanding_mqtt()</text>
|
||||
<text text-anchor="start" x="103.3505" y="-13" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A2->A7 -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>A2->A7</title>
|
||||
<path fill="none" stroke="#000000" d="M377.1669,-460.7015C372.2935,-447.8271 367.5247,-434.6178 363.348,-422 328.6206,-317.0888 364.6855,-270.3837 298.348,-182 272.7246,-147.8611 252.2817,-155.039 216.348,-132 216.2563,-131.9412 216.1645,-131.8823 216.0727,-131.8234"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="377.1669,-460.7018 383.0503,-464.8714 381.4643,-471.9059 375.5809,-467.7363 377.1669,-460.7018"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="207.433,-126.2441 218.2748,-127.8888 211.6333,-128.9566 215.8336,-131.6691 215.8336,-131.6691 215.8336,-131.6691 211.6333,-128.9566 213.3923,-135.4493 207.433,-126.2441 207.433,-126.2441"/>
|
||||
<text text-anchor="middle" x="367.0813" y="-455.0087" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
|
||||
</g>
|
||||
<!-- A8 -->
|
||||
<g id="node9" class="node">
|
||||
<title>A8</title>
|
||||
<polygon fill="none" stroke="#000000" points="225.348,-82 225.348,-114 363.348,-114 363.348,-82 225.348,-82"/>
|
||||
<text text-anchor="start" x="251.845" y="-95" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStreamClient</text>
|
||||
<polygon fill="none" stroke="#000000" points="225.348,-62 225.348,-82 363.348,-82 363.348,-62 225.348,-62"/>
|
||||
<polygon fill="none" stroke="#000000" points="225.348,-18 225.348,-62 363.348,-62 363.348,-18 225.348,-18"/>
|
||||
<text text-anchor="start" x="248.226" y="-43" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>client_loop()</text>
|
||||
<text text-anchor="start" x="235.172" y="-31" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>_async_forward())</text>
|
||||
</g>
|
||||
<!-- A2->A8 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>A2->A8</title>
|
||||
<path fill="none" stroke="#000000" d="M410.2845,-471.9734C407.2471,-397.4776 396.9339,-278.7213 363.348,-182 356.28,-161.6455 345.3872,-140.9471 334.3293,-122.7781"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="328.9036,-114.0745 338.0125,-120.18 331.5487,-118.3175 334.1938,-122.5606 334.1938,-122.5606 334.1938,-122.5606 331.5487,-118.3175 330.375,-124.9412 328.9036,-114.0745 328.9036,-114.0745"/>
|
||||
<text text-anchor="middle" x="345.6654" y="-121.9851" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
|
||||
</g>
|
||||
<!-- A3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>A3</title>
|
||||
<polygon fill="none" stroke="#000000" points="40.348,-342 40.348,-374 138.348,-374 138.348,-342 40.348,-342"/>
|
||||
<text text-anchor="start" x="62.398" y="-355" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InverterG3P</text>
|
||||
<polygon fill="none" stroke="#000000" points="40.348,-286 40.348,-342 138.348,-342 138.348,-286 40.348,-286"/>
|
||||
<text text-anchor="start" x="79.345" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
|
||||
<text text-anchor="start" x="49.901" y="-311" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote:StreamPtr</text>
|
||||
<text text-anchor="start" x="55.18" y="-299" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local:StreamPtr</text>
|
||||
<polygon fill="none" stroke="#000000" points="40.348,-230 40.348,-286 138.348,-286 138.348,-230 40.348,-230"/>
|
||||
<text text-anchor="start" x="53.79" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">create_remote()</text>
|
||||
<text text-anchor="start" x="74.3505" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A3->A7 -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>A3->A7</title>
|
||||
<path fill="none" stroke="#000000" d="M99.7163,-217.6237C102.7418,-193.0021 106.0292,-166.2494 108.9885,-142.1671"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="99.6702,-217.9995 102.9085,-224.4425 98.2065,-229.9099 94.9682,-223.4668 99.6702,-217.9995"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="110.2349,-132.0235 113.4816,-142.4978 109.6251,-136.9862 109.0152,-141.9489 109.0152,-141.9489 109.0152,-141.9489 109.6251,-136.9862 104.5488,-141.4 110.2349,-132.0235 110.2349,-132.0235"/>
|
||||
<text text-anchor="middle" x="92.028" y="-207.8882" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
|
||||
</g>
|
||||
<!-- A3->A8 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>A3->A8</title>
|
||||
<path fill="none" stroke="#000000" d="M138.4873,-232.1115C151.0204,-215.3231 164.7944,-197.7122 178.348,-182 196.1632,-161.3474 216.8826,-139.9135 235.8403,-121.173"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="243.01,-114.1296 239.0299,-124.3477 239.4432,-117.6336 235.8763,-121.1375 235.8763,-121.1375 235.8763,-121.1375 239.4432,-117.6336 232.7227,-117.9274 243.01,-114.1296 243.01,-114.1296"/>
|
||||
<text text-anchor="middle" x="236.0028" y="-129.8619" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
|
||||
</g>
|
||||
<!-- A4 -->
|
||||
<g id="node5" class="node">
|
||||
<title>A4</title>
|
||||
<polygon fill="none" stroke="#000000" points="180.348,-958 180.348,-990 297.348,-990 297.348,-958 180.348,-958"/>
|
||||
<text text-anchor="start" x="208.277" y="-971" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AsyncIfc>></text>
|
||||
<polygon fill="none" stroke="#000000" points="180.348,-938 180.348,-958 297.348,-958 297.348,-938 180.348,-938"/>
|
||||
<polygon fill="none" stroke="#000000" points="180.348,-666 180.348,-938 297.348,-938 297.348,-666 180.348,-666"/>
|
||||
<text text-anchor="start" x="208.284" y="-919" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_node_id()</text>
|
||||
<text text-anchor="start" x="206.614" y="-907" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_conn_no()</text>
|
||||
<text text-anchor="start" x="220.5115" y="-883" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_add()</text>
|
||||
<text text-anchor="start" x="218.292" y="-871" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_flush()</text>
|
||||
<text text-anchor="start" x="221.9015" y="-859" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_get()</text>
|
||||
<text text-anchor="start" x="218.0115" y="-847" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_peek()</text>
|
||||
<text text-anchor="start" x="222.1815" y="-835" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_log()</text>
|
||||
<text text-anchor="start" x="218.017" y="-823" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_clear()</text>
|
||||
<text text-anchor="start" x="222.1815" y="-811" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_len()</text>
|
||||
<text text-anchor="start" x="216.6225" y="-787" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_add()</text>
|
||||
<text text-anchor="start" x="218.2925" y="-775" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_log()</text>
|
||||
<text text-anchor="start" x="221.6265" y="-763" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_get()</text>
|
||||
<text text-anchor="start" x="217.7365" y="-751" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_peek()</text>
|
||||
<text text-anchor="start" x="221.9065" y="-739" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_log()</text>
|
||||
<text text-anchor="start" x="217.742" y="-727" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_clear()</text>
|
||||
<text text-anchor="start" x="221.9065" y="-715" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_len()</text>
|
||||
<text text-anchor="start" x="213.847" y="-703" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_set_cb()</text>
|
||||
<text text-anchor="start" x="190.2275" y="-679" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">prot_set_timeout_cb()</text>
|
||||
</g>
|
||||
<!-- A5 -->
|
||||
<g id="node6" class="node">
|
||||
<title>A5</title>
|
||||
<polygon fill="none" stroke="#000000" points="192.348,-574 192.348,-606 285.348,-606 285.348,-574 192.348,-574"/>
|
||||
<text text-anchor="start" x="210.512" y="-587" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncIfcImpl</text>
|
||||
<polygon fill="none" stroke="#000000" points="192.348,-482 192.348,-574 285.348,-574 285.348,-482 192.348,-482"/>
|
||||
<text text-anchor="start" x="201.896" y="-555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">fwd_fifo:ByteFifo</text>
|
||||
<text text-anchor="start" x="205.785" y="-543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tx_fifo:ByteFifo</text>
|
||||
<text text-anchor="start" x="205.51" y="-531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rx_fifo:ByteFifo</text>
|
||||
<text text-anchor="start" x="204.944" y="-519" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no:Count</text>
|
||||
<text text-anchor="start" x="221.0615" y="-507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
|
||||
<text text-anchor="start" x="214.3975" y="-495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout_cb</text>
|
||||
</g>
|
||||
<!-- A4->A5 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>A4->A5</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M238.348,-655.339C238.348,-637.8929 238.348,-621.0952 238.348,-606.0686"/>
|
||||
<polygon fill="none" stroke="#000000" points="234.8481,-655.6774 238.348,-665.6775 241.8481,-655.6775 234.8481,-655.6774"/>
|
||||
</g>
|
||||
<!-- A6 -->
|
||||
<g id="node7" class="node">
|
||||
<title>A6</title>
|
||||
<polygon fill="none" stroke="#000000" points="187.348,-390 187.348,-422 289.348,-422 289.348,-390 187.348,-390"/>
|
||||
<text text-anchor="start" x="208.622" y="-403" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">AsyncStream</text>
|
||||
<polygon fill="none" stroke="#000000" points="187.348,-310 187.348,-390 289.348,-390 289.348,-310 187.348,-310"/>
|
||||
<text text-anchor="start" x="223.901" y="-371" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">reader</text>
|
||||
<text text-anchor="start" x="226.131" y="-359" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">writer</text>
|
||||
<text text-anchor="start" x="228.345" y="-347" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
|
||||
<text text-anchor="start" x="223.901" y="-335" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">r_addr</text>
|
||||
<text text-anchor="start" x="224.456" y="-323" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">l_addr</text>
|
||||
<polygon fill="none" stroke="#000000" points="187.348,-182 187.348,-310 289.348,-310 289.348,-182 187.348,-182"/>
|
||||
<text text-anchor="start" x="210.002" y="-279" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>loop</text>
|
||||
<text text-anchor="start" x="226.13" y="-267" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">disc()</text>
|
||||
<text text-anchor="start" x="223.3505" y="-255" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<text text-anchor="start" x="218.902" y="-243" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
|
||||
<text text-anchor="start" x="203.6185" y="-219" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_read()</text>
|
||||
<text text-anchor="start" x="203.069" y="-207" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_write()</text>
|
||||
<text text-anchor="start" x="196.955" y="-195" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__async_forward()</text>
|
||||
</g>
|
||||
<!-- A5->A6 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>A5->A6</title>
|
||||
<path fill="none" stroke="#000000" d="M238.348,-471.886C238.348,-456.1951 238.348,-439.1858 238.348,-422.1976"/>
|
||||
<polygon fill="none" stroke="#000000" points="234.8481,-471.9932 238.348,-481.9932 241.8481,-471.9933 234.8481,-471.9932"/>
|
||||
</g>
|
||||
<!-- A6->A7 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>A6->A7</title>
|
||||
<path fill="none" stroke="#000000" d="M182.6502,-192.461C172.1789,-171.8674 161.5319,-150.9284 151.9938,-132.1701"/>
|
||||
<polygon fill="none" stroke="#000000" points="179.6308,-194.2451 187.2832,-201.5725 185.8705,-191.0723 179.6308,-194.2451"/>
|
||||
</g>
|
||||
<!-- A6->A8 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>A6->A8</title>
|
||||
<path fill="none" stroke="#000000" d="M269.1746,-172.088C274.0746,-151.438 278.8579,-131.2796 282.9161,-114.1772"/>
|
||||
<polygon fill="none" stroke="#000000" points="265.7436,-171.3879 266.8402,-181.9259 272.5545,-173.0041 265.7436,-171.3879"/>
|
||||
</g>
|
||||
<!-- A9 -->
|
||||
<g id="node10" class="node">
|
||||
<title>A9</title>
|
||||
<polygon fill="none" stroke="#000000" points="302.348,-1320 302.348,-1352 416.348,-1352 416.348,-1320 302.348,-1320"/>
|
||||
<text text-anchor="start" x="345.456" y="-1333" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Talent</text>
|
||||
<polygon fill="none" stroke="#000000" points="302.348,-1168 302.348,-1320 416.348,-1320 416.348,-1168 302.348,-1168"/>
|
||||
<text text-anchor="start" x="334.0665" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
|
||||
<text text-anchor="start" x="340.171" y="-1289" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no</text>
|
||||
<text text-anchor="start" x="349.345" y="-1277" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
|
||||
<text text-anchor="start" x="312.111" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">await_conn_resp_cnt</text>
|
||||
<text text-anchor="start" x="347.1255" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">id_str</text>
|
||||
<text text-anchor="start" x="327.948" y="-1229" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_name</text>
|
||||
<text text-anchor="start" x="331.288" y="-1217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">contact_mail</text>
|
||||
<text text-anchor="start" x="334.8925" y="-1205" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3</text>
|
||||
<text text-anchor="start" x="333.232" y="-1193" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
|
||||
<text text-anchor="start" x="345.46" y="-1181" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
|
||||
<polygon fill="none" stroke="#000000" points="302.348,-1040 302.348,-1168 416.348,-1168 416.348,-1040 302.348,-1040"/>
|
||||
<text text-anchor="start" x="316.8405" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_contact_info()</text>
|
||||
<text text-anchor="start" x="318.7805" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_ota_update()</text>
|
||||
<text text-anchor="start" x="324.6245" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_get_time()</text>
|
||||
<text text-anchor="start" x="312.6765" y="-1113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_collector_data()</text>
|
||||
<text text-anchor="start" x="314.6215" y="-1101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_inverter_data()</text>
|
||||
<text text-anchor="start" x="323.7885" y="-1089" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
|
||||
<text text-anchor="start" x="339.902" y="-1065" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
|
||||
<text text-anchor="start" x="344.3505" y="-1053" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A9->A2 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>A9->A2</title>
|
||||
<path fill="none" stroke="#000000" d="M377.8061,-1029.6429C379.5037,-1016.2527 381.0593,-1002.9144 382.348,-990 395.4702,-858.5013 399.9848,-704.3277 404.1955,-616.0127"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="376.4823,-1039.8864 373.3012,-1029.3921 377.1232,-1034.9276 377.764,-1029.9689 377.764,-1029.9689 377.764,-1029.9689 377.1232,-1034.9276 382.2269,-1030.5457 376.4823,-1039.8864 376.4823,-1039.8864"/>
|
||||
<text text-anchor="middle" x="370.4228" y="-1017.8264" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
|
||||
</g>
|
||||
<!-- A9->A2 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>A9->A2</title>
|
||||
<path fill="none" stroke="#000000" d="M395.7566,-1029.6429C397.5037,-1016.2527 399.0593,-1002.9144 400.348,-990 412.8936,-864.2801 417.5714,-717.8344 416.9909,-628.0298"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="394.3845,-1039.8864 391.2521,-1029.3774 395.0484,-1034.9307 395.7122,-1029.9749 395.7122,-1029.9749 395.7122,-1029.9749 395.0484,-1034.9307 400.1724,-1030.5724 394.3845,-1039.8864 394.3845,-1039.8864"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="416.9908,-628.0122 412.9345,-622.0501 416.8778,-616.0127 420.9341,-621.9747 416.9908,-628.0122"/>
|
||||
<text text-anchor="middle" x="425.5004" y="-631.0585" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
|
||||
</g>
|
||||
<!-- A9->A4 -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>A9->A4</title>
|
||||
<path fill="none" stroke="#000000" d="M308.0331,-1039.9349C303.6793,-1026.6936 299.2605,-1013.2547 294.8698,-999.9011"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="291.679,-990.1968 299.0774,-998.2908 293.2408,-994.9466 294.8026,-999.6964 294.8026,-999.6964 294.8026,-999.6964 293.2408,-994.9466 290.5277,-1001.102 291.679,-990.1968 291.679,-990.1968"/>
|
||||
<text text-anchor="middle" x="294.3419" y="-1022.3558" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
|
||||
</g>
|
||||
<!-- A12 -->
|
||||
<g id="node13" class="node">
|
||||
<title>A12</title>
|
||||
<polygon fill="none" stroke="#000000" points="315.348,-844 315.348,-876 382.348,-876 382.348,-844 315.348,-844"/>
|
||||
<text text-anchor="start" x="331.341" y="-857" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3</text>
|
||||
<polygon fill="none" stroke="#000000" points="315.348,-824 315.348,-844 382.348,-844 382.348,-824 315.348,-824"/>
|
||||
<polygon fill="none" stroke="#000000" points="315.348,-780 315.348,-824 382.348,-824 382.348,-780 315.348,-780"/>
|
||||
<text text-anchor="start" x="325.232" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
|
||||
<text text-anchor="start" x="333.016" y="-793" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
|
||||
</g>
|
||||
<!-- A9->A12 -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>A9->A12</title>
|
||||
<path fill="none" stroke="#000000" d="M354.683,-1039.9349C353.0582,-985.577 351.3338,-927.8888 350.095,-886.4463"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="349.7909,-876.272 354.5878,-886.1331 349.9404,-881.2698 350.0898,-886.2676 350.0898,-886.2676 350.0898,-886.2676 349.9404,-881.2698 345.5918,-886.4021 349.7909,-876.272 349.7909,-876.272"/>
|
||||
</g>
|
||||
<!-- A10 -->
|
||||
<g id="node11" class="node">
|
||||
<title>A10</title>
|
||||
<polygon fill="none" stroke="#000000" points="72.348,-1284 72.348,-1316 163.348,-1316 163.348,-1284 72.348,-1284"/>
|
||||
<text text-anchor="start" x="90.343" y="-1297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">SolarmanV5</text>
|
||||
<polygon fill="none" stroke="#000000" points="72.348,-1144 72.348,-1284 163.348,-1284 163.348,-1144 72.348,-1144"/>
|
||||
<text text-anchor="start" x="92.5665" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
|
||||
<text text-anchor="start" x="98.671" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">conn_no</text>
|
||||
<text text-anchor="start" x="107.845" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">addr</text>
|
||||
<text text-anchor="start" x="102.846" y="-1217" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">control</text>
|
||||
<text text-anchor="start" x="105.9055" y="-1205" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">serial</text>
|
||||
<text text-anchor="start" x="110.904" y="-1193" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snr</text>
|
||||
<text text-anchor="start" x="90.058" y="-1181" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">db:InfosG3P</text>
|
||||
<text text-anchor="start" x="91.732" y="-1169" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
|
||||
<text text-anchor="start" x="103.96" y="-1157" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">switch</text>
|
||||
<polygon fill="none" stroke="#000000" points="72.348,-1076 72.348,-1144 163.348,-1144 163.348,-1076 72.348,-1076"/>
|
||||
<text text-anchor="start" x="82.2885" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">msg_unknown()</text>
|
||||
<text text-anchor="start" x="98.402" y="-1101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">healthy()</text>
|
||||
<text text-anchor="start" x="102.8505" y="-1089" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A10->A3 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>A10->A3</title>
|
||||
<path fill="none" stroke="#000000" d="M85.2254,-1066.0442C81.4271,-1040.8951 78.2542,-1014.6889 76.348,-990 59.0619,-766.107 69.322,-500.0139 79.5568,-374.449"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="86.7695,-1075.9973 80.7895,-1066.8055 86.0029,-1071.0565 85.2364,-1066.1156 85.2364,-1066.1156 85.2364,-1066.1156 86.0029,-1071.0565 89.6832,-1065.4256 86.7695,-1075.9973 86.7695,-1075.9973"/>
|
||||
<text text-anchor="middle" x="75.6382" y="-1056.3813" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">remote</text>
|
||||
</g>
|
||||
<!-- A10->A3 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>A10->A3</title>
|
||||
<path fill="none" stroke="#000000" d="M102.8817,-1066.0442C99.4271,-1040.8951 96.2542,-1014.6889 94.348,-990 77.6021,-773.1036 86.7076,-516.6035 90.1168,-386.6269"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="104.2701,-1075.9973 98.4317,-1066.7149 103.5793,-1071.0453 102.8885,-1066.0932 102.8885,-1066.0932 102.8885,-1066.0932 103.5793,-1071.0453 107.3454,-1065.4715 104.2701,-1075.9973 104.2701,-1075.9973"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="90.1213,-386.4451 86.2759,-380.3449 90.4278,-374.449 94.2733,-380.5493 90.1213,-386.4451"/>
|
||||
<text text-anchor="middle" x="98.4146" y="-389.7851" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">local</text>
|
||||
</g>
|
||||
<!-- A10->A4 -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>A10->A4</title>
|
||||
<path fill="none" stroke="#000000" d="M156.8842,-1075.7577C164.8622,-1051.494 173.3952,-1025.5424 181.8248,-999.9053"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="185.0491,-990.0992 186.2003,-1001.0045 183.4873,-994.849 181.9255,-999.5989 181.9255,-999.5989 181.9255,-999.5989 183.4873,-994.849 177.6506,-998.1932 185.0491,-990.0992 185.0491,-990.0992"/>
|
||||
<text text-anchor="middle" x="154.5165" y="-1052.8983" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
|
||||
</g>
|
||||
<!-- A13 -->
|
||||
<g id="node14" class="node">
|
||||
<title>A13</title>
|
||||
<polygon fill="none" stroke="#000000" points="95.348,-844 95.348,-876 162.348,-876 162.348,-844 95.348,-844"/>
|
||||
<text text-anchor="start" x="108.0065" y="-857" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">InfosG3P</text>
|
||||
<polygon fill="none" stroke="#000000" points="95.348,-824 95.348,-844 162.348,-844 162.348,-824 95.348,-824"/>
|
||||
<polygon fill="none" stroke="#000000" points="95.348,-780 95.348,-824 162.348,-824 162.348,-780 95.348,-780"/>
|
||||
<text text-anchor="start" x="105.232" y="-805" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_confs()</text>
|
||||
<text text-anchor="start" x="113.016" y="-793" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">parse()</text>
|
||||
</g>
|
||||
<!-- A10->A13 -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>A10->A13</title>
|
||||
<path fill="none" stroke="#000000" d="M120.9422,-1075.7577C122.842,-1012.1995 125.0881,-937.0598 126.6045,-886.3285"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="126.9072,-876.2026 131.1063,-886.3326 126.7577,-881.2004 126.6083,-886.1981 126.6083,-886.1981 126.6083,-886.1981 126.7577,-881.2004 122.1103,-886.0636 126.9072,-876.2026 126.9072,-876.2026"/>
|
||||
</g>
|
||||
<!-- A11 -->
|
||||
<g id="node12" class="node">
|
||||
<title>A11</title>
|
||||
<polygon fill="none" stroke="#000000" points="181.348,-1284 181.348,-1316 284.348,-1316 284.348,-1284 181.348,-1284"/>
|
||||
<text text-anchor="start" x="222.01" y="-1297" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Infos</text>
|
||||
<polygon fill="none" stroke="#000000" points="181.348,-1228 181.348,-1284 284.348,-1284 284.348,-1228 181.348,-1228"/>
|
||||
<text text-anchor="start" x="224.7895" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">stat</text>
|
||||
<text text-anchor="start" x="200.334" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_stat_data</text>
|
||||
<text text-anchor="start" x="213.9515" y="-1241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">info_dev</text>
|
||||
<polygon fill="none" stroke="#000000" points="181.348,-1076 181.348,-1228 284.348,-1228 284.348,-1076 181.348,-1076"/>
|
||||
<text text-anchor="start" x="208.6835" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">static_init()</text>
|
||||
<text text-anchor="start" x="206.7325" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dev_value()</text>
|
||||
<text text-anchor="start" x="203.6785" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
|
||||
<text text-anchor="start" x="202.0085" y="-1173" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
|
||||
<text text-anchor="start" x="200.058" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_proxy_conf</text>
|
||||
<text text-anchor="start" x="215.061" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_conf</text>
|
||||
<text text-anchor="start" x="207.842" y="-1137" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ha_remove</text>
|
||||
<text text-anchor="start" x="209.2225" y="-1125" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">update_db</text>
|
||||
<text text-anchor="start" x="193.385" y="-1113" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">set_db_def_value</text>
|
||||
<text text-anchor="start" x="202.8335" y="-1101" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">get_db_value</text>
|
||||
<text text-anchor="start" x="191.1705" y="-1089" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ignore_this_device</text>
|
||||
</g>
|
||||
<!-- A11->A12 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A11->A12</title>
|
||||
<path fill="none" stroke="#000000" d="M280.9382,-1066.3836C289.808,-1041.1849 298.6736,-1014.8736 306.348,-990 318.0407,-952.103 329.205,-908.562 337.0802,-876.1703"/>
|
||||
<polygon fill="none" stroke="#000000" points="277.5783,-1065.3868 277.541,-1075.9815 284.1771,-1067.7225 277.5783,-1065.3868"/>
|
||||
</g>
|
||||
<!-- A11->A13 -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>A11->A13</title>
|
||||
<path fill="none" stroke="#000000" d="M192.4636,-1066.2454C184.9411,-1041.0063 177.2684,-1014.7156 170.348,-990 159.6936,-951.9489 148.506,-908.6064 140.3587,-876.3336"/>
|
||||
<polygon fill="none" stroke="#000000" points="189.1205,-1067.2825 195.3374,-1075.8616 195.8274,-1065.2781 189.1205,-1067.2825"/>
|
||||
</g>
|
||||
<!-- A15 -->
|
||||
<g id="node16" class="node">
|
||||
<title>A15</title>
|
||||
<polygon fill="none" stroke="#000000" points="150.348,-1550 150.348,-1582 231.348,-1582 231.348,-1550 150.348,-1550"/>
|
||||
<text text-anchor="start" x="170.5655" y="-1563" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
|
||||
<polygon fill="none" stroke="#000000" points="150.348,-1518 150.348,-1550 231.348,-1550 231.348,-1518 150.348,-1518"/>
|
||||
<text text-anchor="start" x="173.0615" y="-1531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
|
||||
<polygon fill="none" stroke="#000000" points="150.348,-1474 150.348,-1518 231.348,-1518 231.348,-1474 150.348,-1474"/>
|
||||
<text text-anchor="start" x="161.6785" y="-1499" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
|
||||
<text text-anchor="start" x="160.0085" y="-1487" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
|
||||
</g>
|
||||
<!-- A14->A15 -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>A14->A15</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M190.348,-1693.5712C190.348,-1659.1613 190.348,-1615.9264 190.348,-1582.2666"/>
|
||||
<polygon fill="none" stroke="#000000" points="186.8481,-1693.9463 190.348,-1703.9464 193.8481,-1693.9464 186.8481,-1693.9463"/>
|
||||
</g>
|
||||
<!-- A15->A9 -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>A15->A9</title>
|
||||
<path fill="none" stroke="#000000" d="M229.7336,-1465.2507C249.8317,-1432.1375 274.0435,-1390.492 293.348,-1352 296.3143,-1346.0855 299.269,-1340.0055 302.1912,-1333.8349"/>
|
||||
<polygon fill="none" stroke="#000000" points="226.6683,-1463.555 224.4506,-1473.9151 232.6449,-1467.1992 226.6683,-1463.555"/>
|
||||
</g>
|
||||
<!-- A15->A10 -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>A15->A10</title>
|
||||
<path fill="none" stroke="#000000" d="M176.3052,-1464.1339C167.0994,-1422.2665 154.7762,-1366.2214 143.7769,-1316.197"/>
|
||||
<polygon fill="none" stroke="#000000" points="172.8909,-1464.9045 178.4568,-1473.9196 179.7276,-1463.4012 172.8909,-1464.9045"/>
|
||||
</g>
|
||||
<!-- A16 -->
|
||||
<g id="node17" class="node">
|
||||
<title>A16</title>
|
||||
<polygon fill="none" stroke="#000000" points="285.348,-1622 285.348,-1654 360.348,-1654 360.348,-1622 285.348,-1622"/>
|
||||
<text text-anchor="start" x="305.0655" y="-1635" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
|
||||
<polygon fill="none" stroke="#000000" points="285.348,-1470 285.348,-1622 360.348,-1622 360.348,-1470 285.348,-1470"/>
|
||||
<text text-anchor="start" x="314.5095" y="-1603" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
|
||||
<text text-anchor="start" x="295.338" y="-1579" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
|
||||
<text text-anchor="start" x="296.453" y="-1567" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
|
||||
<text text-anchor="start" x="306.4565" y="-1555" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
|
||||
<text text-anchor="start" x="296.7375" y="-1543" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
|
||||
<text text-anchor="start" x="304.79" y="-1531" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
|
||||
<text text-anchor="start" x="316.7395" y="-1519" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
|
||||
<text text-anchor="start" x="303.4015" y="-1507" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
|
||||
<text text-anchor="start" x="301.727" y="-1495" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
|
||||
<text text-anchor="start" x="316.1845" y="-1483" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
|
||||
<polygon fill="none" stroke="#000000" points="285.348,-1402 285.348,-1470 360.348,-1470 360.348,-1402 285.348,-1402"/>
|
||||
<text text-anchor="start" x="296.738" y="-1451" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
|
||||
<text text-anchor="start" x="300.072" y="-1439" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
|
||||
<text text-anchor="start" x="297.572" y="-1427" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
|
||||
<text text-anchor="start" x="307.8505" y="-1415" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A16->A9 -->
|
||||
<g id="edge24" class="edge">
|
||||
<title>A16->A9</title>
|
||||
<path fill="none" stroke="#000000" d="M337.5861,-1391.2689C339.0212,-1378.3918 340.4833,-1365.272 341.9365,-1352.2329"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="336.4417,-1401.5379 333.077,-1391.101 336.9955,-1396.5687 337.5493,-1391.5995 337.5493,-1391.5995 337.5493,-1391.5995 336.9955,-1396.5687 342.0217,-1392.0979 336.4417,-1401.5379 336.4417,-1401.5379"/>
|
||||
<text text-anchor="middle" x="348.3292" y="-1368.1837" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
<text text-anchor="middle" x="330.049" y="-1379.5871" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
|
||||
</g>
|
||||
<!-- A16->A10 -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>A16->A10</title>
|
||||
<path fill="none" stroke="#000000" d="M279.1725,-1451.7757C267.6828,-1434.4941 254.4915,-1416.871 240.348,-1402 214.2483,-1374.5578 193.9188,-1382.411 171.348,-1352 163.2346,-1341.0684 156.273,-1328.8227 150.315,-1316.1269"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="284.6729,-1460.2184 275.4437,-1454.2962 281.9435,-1456.0291 279.2141,-1451.8397 279.2141,-1451.8397 279.2141,-1451.8397 281.9435,-1456.0291 282.9845,-1449.3833 284.6729,-1460.2184 284.6729,-1460.2184"/>
|
||||
<text text-anchor="middle" x="165.7688" y="-1325.8225" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
<text text-anchor="middle" x="267.6964" y="-1446.645" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">1</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 38 KiB |
51
app/proxy_2.yuml
Normal file
51
app/proxy_2.yuml
Normal file
@@ -0,0 +1,51 @@
|
||||
// {type:class}
|
||||
// {direction:topDown}
|
||||
// {generate:true}
|
||||
|
||||
[note: You can stick notes on diagrams too!{bg:cornsilk}]
|
||||
[<<AbstractIterMeta>>||__iter__()]
|
||||
|
||||
[InverterG3|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()]
|
||||
[InverterG3P|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()]
|
||||
|
||||
[<<AsyncIfc>>||set_node_id();get_conn_no();;tx_add();tx_flush();tx_get();tx_peek();tx_log();tx_clear();tx_len();;fwd_add();fwd_log();rx_get();rx_peek();rx_log();rx_clear();rx_len();rx_set_cb();;prot_set_timeout_cb()]
|
||||
[AsyncIfcImpl|fwd_fifo:ByteFifo;tx_fifo:ByteFifo;rx_fifo:ByteFifo;conn_no:Count;node_id;timeout_cb]
|
||||
[AsyncStream|reader;writer;addr;r_addr;l_addr|;<async>loop;disc();close();healthy();;__async_read();__async_write();__async_forward()]
|
||||
[AsyncStreamServer|create_remote|<async>server_loop();<async>_async_forward();<async>publish_outstanding_mqtt();close()]
|
||||
[AsyncStreamClient||<async>client_loop();<async>_async_forward())]
|
||||
[<<AsyncIfc>>]^-.-[AsyncIfcImpl]
|
||||
[AsyncIfcImpl]^[AsyncStream]
|
||||
[AsyncStream]^[AsyncStreamServer]
|
||||
[AsyncStream]^[AsyncStreamClient]
|
||||
|
||||
|
||||
[Talent|ifc:AsyncIfc;conn_no;addr;;await_conn_resp_cnt;id_str;contact_name;contact_mail;db:InfosG3;mb:Modbus;switch|msg_contact_info();msg_ota_update();msg_get_time();msg_collector_data();msg_inverter_data();msg_unknown();;healthy();close()]
|
||||
[Talent]<remote-[InverterG3]
|
||||
[InverterG3]-remote>[AsyncStreamClient]
|
||||
[Talent]<-local++[InverterG3]
|
||||
[InverterG3]++local->[AsyncStreamServer]
|
||||
|
||||
[SolarmanV5|ifc:AsyncIfc;conn_no;addr;;control;serial;snr;db:InfosG3P;mb:Modbus;switch|msg_unknown();;healthy();close()]
|
||||
[SolarmanV5]<remote-[InverterG3P]
|
||||
[InverterG3P]-remote>[AsyncStreamClient]
|
||||
[SolarmanV5]<-local++[InverterG3P]
|
||||
[InverterG3P]++local->[AsyncStreamServer]
|
||||
|
||||
[Infos|stat;new_stat_data;info_dev|static_init();dev_value();inc_counter();dec_counter();ha_proxy_conf;ha_conf;ha_remove;update_db;set_db_def_value;get_db_value;ignore_this_device]
|
||||
[Infos]^[InfosG3||ha_confs();parse()]
|
||||
[Infos]^[InfosG3P||ha_confs();parse()]
|
||||
|
||||
[Talent]use->[<<AsyncIfc>>]
|
||||
[Talent]->[InfosG3]
|
||||
[SolarmanV5]use->[<<AsyncIfc>>]
|
||||
[SolarmanV5]->[InfosG3P]
|
||||
|
||||
[<<ProtocolIfc>>|_registry|close()]
|
||||
[<<AbstractIterMeta>>]^-.-[<<ProtocolIfc>>]
|
||||
[<<ProtocolIfc>>]^-.-[Message|node_id|inc_counter();dec_counter()]
|
||||
[Message]^[Talent]
|
||||
[Message]^[SolarmanV5]
|
||||
|
||||
[Modbus|que;;snd_handler;rsp_handler;timeout;max_retires;last_xxx;err;retry_cnt;req_pend;tim|build_msg();recv_req();recv_resp();close()]
|
||||
[Modbus]<1-has[SolarmanV5]
|
||||
[Modbus]<1-has[Talent]
|
||||
104
app/src/async_ifc.py
Normal file
104
app/src/async_ifc.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class AsyncIfc(ABC):
|
||||
@abstractmethod
|
||||
def get_conn_no(self):
|
||||
pass # pragma: no cover
|
||||
|
||||
@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_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_log(self, level, info):
|
||||
''' log the forward queue'''
|
||||
pass # pragma: no cover
|
||||
|
||||
#
|
||||
# RX - QUEUE
|
||||
#
|
||||
@abstractmethod
|
||||
def rx_get(self, size: int = None) -> bytearray:
|
||||
'''removes size numbers of bytes and return them'''
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def rx_peek(self, size: int = None) -> bytearray:
|
||||
'''returns size numbers of byte without removing them'''
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def rx_log(self, level, info):
|
||||
''' logs the receive queue'''
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def rx_clear(self):
|
||||
''' clear receive queue'''
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def rx_len(self):
|
||||
''' get numner of bytes in the receive queue'''
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def rx_set_cb(self, callback):
|
||||
pass # pragma: no cover
|
||||
|
||||
#
|
||||
# Protocol Callbacks
|
||||
#
|
||||
@abstractmethod
|
||||
def prot_set_timeout_cb(self, callback):
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def prot_set_init_new_client_conn_cb(self, callback):
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def prot_set_update_header_cb(self, callback):
|
||||
pass # pragma: no cover
|
||||
@@ -7,17 +7,136 @@ from typing import Self
|
||||
from itertools import count
|
||||
|
||||
if __name__ == "app.src.async_stream":
|
||||
from app.src.messages import hex_dump_memory, State
|
||||
from app.src.proxy import Proxy
|
||||
from app.src.byte_fifo import ByteFifo
|
||||
from app.src.async_ifc import AsyncIfc
|
||||
from app.src.infos import Infos
|
||||
else: # pragma: no cover
|
||||
from messages import hex_dump_memory, State
|
||||
from proxy import Proxy
|
||||
from byte_fifo import ByteFifo
|
||||
from async_ifc import AsyncIfc
|
||||
from infos import Infos
|
||||
|
||||
|
||||
import gc
|
||||
logger = logging.getLogger('conn')
|
||||
|
||||
|
||||
class AsyncStream():
|
||||
class AsyncIfcImpl(AsyncIfc):
|
||||
_ids = count(0)
|
||||
|
||||
def __init__(self) -> None:
|
||||
logger.debug('AsyncIfcImpl.__init__')
|
||||
self.fwd_fifo = ByteFifo()
|
||||
self.tx_fifo = ByteFifo()
|
||||
self.rx_fifo = ByteFifo()
|
||||
self.conn_no = next(self._ids)
|
||||
self.node_id = ''
|
||||
self.timeout_cb = None
|
||||
self.init_new_client_conn_cb = None
|
||||
self.update_header_cb = None
|
||||
|
||||
def close(self):
|
||||
self.timeout_cb = None
|
||||
self.fwd_fifo.reg_trigger(None)
|
||||
self.tx_fifo.reg_trigger(None)
|
||||
self.rx_fifo.reg_trigger(None)
|
||||
|
||||
def set_node_id(self, value: str):
|
||||
self.node_id = value
|
||||
|
||||
def get_conn_no(self):
|
||||
return self.conn_no
|
||||
|
||||
def tx_add(self, data: bytearray):
|
||||
''' add data to transmit queue'''
|
||||
self.tx_fifo += data
|
||||
|
||||
def tx_flush(self):
|
||||
''' send transmit queue and clears it'''
|
||||
self.tx_fifo()
|
||||
|
||||
def tx_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_log(self, level, info):
|
||||
''' log the forward queue'''
|
||||
self.fwd_fifo.logging(level, info)
|
||||
|
||||
def rx_get(self, size: int = None) -> bytearray:
|
||||
'''removes size numbers of bytes and return them'''
|
||||
return self.rx_fifo.get(size)
|
||||
|
||||
def rx_peek(self, size: int = None) -> bytearray:
|
||||
'''returns size numbers of byte without removing them'''
|
||||
return self.rx_fifo.peek(size)
|
||||
|
||||
def rx_log(self, level, info):
|
||||
''' logs the receive queue'''
|
||||
self.rx_fifo.logging(level, info)
|
||||
|
||||
def rx_clear(self):
|
||||
''' clear receive queue'''
|
||||
self.rx_fifo.clear()
|
||||
|
||||
def rx_len(self):
|
||||
''' get numner of bytes in the receive queue'''
|
||||
return len(self.rx_fifo)
|
||||
|
||||
def rx_set_cb(self, callback):
|
||||
self.rx_fifo.reg_trigger(callback)
|
||||
|
||||
def prot_set_timeout_cb(self, callback):
|
||||
self.timeout_cb = callback
|
||||
|
||||
def prot_set_init_new_client_conn_cb(self, callback):
|
||||
self.init_new_client_conn_cb = callback
|
||||
|
||||
def prot_set_update_header_cb(self, callback):
|
||||
self.update_header_cb = callback
|
||||
|
||||
|
||||
class StreamPtr():
|
||||
'''Descr StreamPtr'''
|
||||
def __init__(self, _stream, _ifc=None):
|
||||
self.stream = _stream
|
||||
self.ifc = _ifc
|
||||
|
||||
@property
|
||||
def ifc(self):
|
||||
return self._ifc
|
||||
|
||||
@ifc.setter
|
||||
def ifc(self, value):
|
||||
self._ifc = value
|
||||
|
||||
@property
|
||||
def stream(self):
|
||||
return self._stream
|
||||
|
||||
@stream.setter
|
||||
def stream(self, value):
|
||||
self._stream = value
|
||||
|
||||
|
||||
class AsyncStream(AsyncIfcImpl):
|
||||
MAX_PROC_TIME = 2
|
||||
'''maximum processing time for a received msg in sec'''
|
||||
MAX_START_TIME = 400
|
||||
@@ -28,95 +147,42 @@ class AsyncStream():
|
||||
'''maximum default time without a received msg in sec'''
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
addr) -> None:
|
||||
rstream: "StreamPtr") -> None:
|
||||
AsyncIfcImpl.__init__(self)
|
||||
|
||||
logger.debug('AsyncStream.__init__')
|
||||
self.reader = reader
|
||||
self.writer = writer
|
||||
self.addr = addr
|
||||
self.r_addr = ''
|
||||
self.l_addr = ''
|
||||
self.conn_no = next(self._ids)
|
||||
|
||||
self.remote = rstream
|
||||
self.tx_fifo.reg_trigger(self.__write_cb)
|
||||
self._reader = reader
|
||||
self._writer = writer
|
||||
self.r_addr = writer.get_extra_info('peername')
|
||||
self.l_addr = writer.get_extra_info('sockname')
|
||||
self.proc_start = None # start processing start timestamp
|
||||
self.proc_max = 0
|
||||
self.async_publ_mqtt = None # will be set AsyncStreamServer only
|
||||
|
||||
def __write_cb(self):
|
||||
self._writer.write(self.tx_fifo.get())
|
||||
|
||||
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
|
||||
|
||||
async def publish_outstanding_mqtt(self):
|
||||
'''Publish all outstanding MQTT topics'''
|
||||
try:
|
||||
if self.unique_id:
|
||||
await self.async_publ_mqtt()
|
||||
await self._async_publ_mqtt_proxy_stat('proxy')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def server_loop(self, addr: str) -> None:
|
||||
'''Loop for receiving messages from the inverter (server-side)'''
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] '
|
||||
f'Accept connection from {addr}')
|
||||
self.inc_counter('Inverter_Cnt')
|
||||
await self.publish_outstanding_mqtt()
|
||||
await self.loop()
|
||||
self.dec_counter('Inverter_Cnt')
|
||||
await self.publish_outstanding_mqtt()
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] Server loop stopped for'
|
||||
f' r{self.r_addr}')
|
||||
|
||||
# if the server connection closes, we also have to disconnect
|
||||
# the connection to te TSUN cloud
|
||||
if self.remote_stream:
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] disc client '
|
||||
f'connection: [{self.remote_stream.node_id}:'
|
||||
f'{self.remote_stream.conn_no}]')
|
||||
await self.remote_stream.disc()
|
||||
|
||||
async def client_loop(self, _: str) -> None:
|
||||
'''Loop for receiving messages from the TSUN cloud (client-side)'''
|
||||
client_stream = await self.remote_stream.loop()
|
||||
logger.info(f'[{client_stream.node_id}:{client_stream.conn_no}] '
|
||||
'Client loop stopped for'
|
||||
f' l{client_stream.l_addr}')
|
||||
|
||||
# if the client connection closes, we don't touch the server
|
||||
# connection. Instead we erase the client connection stream,
|
||||
# thus on the next received packet from the inverter, we can
|
||||
# establish a new connection to the TSUN cloud
|
||||
|
||||
# erase backlink to inverter
|
||||
client_stream.remote_stream = None
|
||||
|
||||
if self.remote_stream == client_stream:
|
||||
# logging.debug(f'Client l{client_stream.l_addr} refs:'
|
||||
# f' {gc.get_referrers(client_stream)}')
|
||||
# than erase client connection
|
||||
self.remote_stream = None
|
||||
if self.timeout_cb:
|
||||
return self.timeout_cb()
|
||||
return 360
|
||||
|
||||
async def loop(self) -> Self:
|
||||
"""Async loop handler for precessing all received messages"""
|
||||
self.r_addr = self.writer.get_extra_info('peername')
|
||||
self.l_addr = self.writer.get_extra_info('sockname')
|
||||
self.proc_start = time.time()
|
||||
while True:
|
||||
try:
|
||||
proc = time.time() - self.proc_start
|
||||
if proc > self.proc_max:
|
||||
self.proc_max = proc
|
||||
self.proc_start = None
|
||||
self.__calc_proc_time()
|
||||
dead_conn_to = self.__timeout()
|
||||
await asyncio.wait_for(self.__async_read(),
|
||||
dead_conn_to)
|
||||
|
||||
if self.unique_id:
|
||||
await self.async_write()
|
||||
await self.__async_forward()
|
||||
await self.__async_write()
|
||||
await self.__async_forward()
|
||||
if self.async_publ_mqtt:
|
||||
await self.async_publ_mqtt()
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
@@ -124,7 +190,6 @@ class AsyncStream():
|
||||
f'connection timeout ({dead_conn_to}s) '
|
||||
f'for {self.l_addr}')
|
||||
await self.disc()
|
||||
self.close()
|
||||
return self
|
||||
|
||||
except OSError as error:
|
||||
@@ -132,56 +197,54 @@ class AsyncStream():
|
||||
f'{error} for l{self.l_addr} | '
|
||||
f'r{self.r_addr}')
|
||||
await self.disc()
|
||||
self.close()
|
||||
return self
|
||||
|
||||
except RuntimeError as error:
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] '
|
||||
f'{error} for {self.l_addr}')
|
||||
await self.disc()
|
||||
self.close()
|
||||
return self
|
||||
|
||||
except Exception:
|
||||
self.inc_counter('SW_Exception')
|
||||
Infos.inc_counter('SW_Exception')
|
||||
logger.error(
|
||||
f"Exception for {self.addr}:\n"
|
||||
f"Exception for {self.r_addr}:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
await asyncio.sleep(0) # be cooperative to other task
|
||||
|
||||
async def async_write(self, headline: str = 'Transmit to ') -> None:
|
||||
"""Async write handler to transmit the send_buffer"""
|
||||
if self._send_buffer:
|
||||
hex_dump_memory(logging.INFO, f'{headline}{self.addr}:',
|
||||
self._send_buffer, len(self._send_buffer))
|
||||
self.writer.write(self._send_buffer)
|
||||
await self.writer.drain()
|
||||
self._send_buffer = bytearray(0) # self._send_buffer[sent:]
|
||||
def __calc_proc_time(self):
|
||||
if self.proc_start:
|
||||
proc = time.time() - self.proc_start
|
||||
if proc > self.proc_max:
|
||||
self.proc_max = proc
|
||||
self.proc_start = None
|
||||
|
||||
async def disc(self) -> None:
|
||||
"""Async disc handler for graceful disconnect"""
|
||||
if self.writer.is_closing():
|
||||
self.remote = None
|
||||
if self._writer.is_closing():
|
||||
return
|
||||
logger.debug(f'AsyncStream.disc() l{self.l_addr} | r{self.r_addr}')
|
||||
self.writer.close()
|
||||
await self.writer.wait_closed()
|
||||
self._writer.close()
|
||||
await self._writer.wait_closed()
|
||||
|
||||
def close(self) -> None:
|
||||
logging.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}')
|
||||
"""close handler for a no waiting disconnect
|
||||
|
||||
hint: must be called before releasing the connection instance
|
||||
"""
|
||||
self.reader.feed_eof() # abort awaited read
|
||||
if self.writer.is_closing():
|
||||
super().close()
|
||||
self._reader.feed_eof() # abort awaited read
|
||||
if self._writer.is_closing():
|
||||
return
|
||||
logger.debug(f'AsyncStream.close() l{self.l_addr} | r{self.r_addr}')
|
||||
self.writer.close()
|
||||
self._writer.close()
|
||||
|
||||
def healthy(self) -> bool:
|
||||
elapsed = 0
|
||||
if self.proc_start is not None:
|
||||
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}:'
|
||||
f'{type(self).__name__}]'
|
||||
f' act:{round(1000*elapsed)}ms'
|
||||
@@ -194,61 +257,139 @@ class AsyncStream():
|
||||
'''
|
||||
async def __async_read(self) -> None:
|
||||
"""Async read handler to read received data from TCP stream"""
|
||||
data = await self.reader.read(4096)
|
||||
data = await self._reader.read(4096)
|
||||
if data:
|
||||
self.proc_start = time.time()
|
||||
self._recv_buffer += data
|
||||
wait = self.read() # call read in parent class
|
||||
if wait > 0:
|
||||
self.rx_fifo += data
|
||||
wait = self.rx_fifo() # call read in parent class
|
||||
if wait and wait > 0:
|
||||
await asyncio.sleep(wait)
|
||||
else:
|
||||
raise RuntimeError("Peer closed.")
|
||||
|
||||
async def __async_write(self, headline: str = 'Transmit to ') -> None:
|
||||
"""Async write handler to transmit the send_buffer"""
|
||||
if len(self.tx_fifo) > 0:
|
||||
self.tx_fifo.logging(logging.INFO, f'{headline}{self.r_addr}:')
|
||||
self._writer.write(self.tx_fifo.get())
|
||||
await self._writer.drain()
|
||||
|
||||
async def __async_forward(self) -> None:
|
||||
"""forward handler transmits data over the remote connection"""
|
||||
if not self._forward_buffer:
|
||||
if len(self.fwd_fifo) == 0:
|
||||
return
|
||||
try:
|
||||
if not self.remote_stream:
|
||||
await self.async_create_remote()
|
||||
if self.remote_stream:
|
||||
if self.remote_stream._init_new_client_conn():
|
||||
await self.remote_stream.async_write()
|
||||
|
||||
if self.remote_stream:
|
||||
self.remote_stream._update_header(self._forward_buffer)
|
||||
hex_dump_memory(logging.INFO,
|
||||
f'Forward to {self.remote_stream.addr}:',
|
||||
self._forward_buffer,
|
||||
len(self._forward_buffer))
|
||||
self.remote_stream.writer.write(self._forward_buffer)
|
||||
await self.remote_stream.writer.drain()
|
||||
self._forward_buffer = bytearray(0)
|
||||
await self._async_forward()
|
||||
|
||||
except OSError as error:
|
||||
if self.remote_stream:
|
||||
rmt = self.remote_stream
|
||||
self.remote_stream = None
|
||||
logger.error(f'[{rmt.node_id}:{rmt.conn_no}] Fwd: {error} for '
|
||||
f'l{rmt.l_addr} | r{rmt.r_addr}')
|
||||
await rmt.disc()
|
||||
rmt.close()
|
||||
if self.remote.stream:
|
||||
rmt = self.remote
|
||||
logger.error(f'[{rmt.stream.node_id}:{rmt.stream.conn_no}] '
|
||||
f'Fwd: {error} for '
|
||||
f'l{rmt.ifc.l_addr} | r{rmt.ifc.r_addr}')
|
||||
await rmt.ifc.disc()
|
||||
if rmt.ifc.close_cb:
|
||||
rmt.ifc.close_cb()
|
||||
|
||||
except RuntimeError as error:
|
||||
if self.remote_stream:
|
||||
rmt = self.remote_stream
|
||||
self.remote_stream = None
|
||||
logger.info(f'[{rmt.node_id}:{rmt.conn_no}] '
|
||||
f'Fwd: {error} for {rmt.l_addr}')
|
||||
await rmt.disc()
|
||||
rmt.close()
|
||||
if self.remote.stream:
|
||||
rmt = self.remote
|
||||
logger.info(f'[{rmt.stream.node_id}:{rmt.stream.conn_no}] '
|
||||
f'Fwd: {error} for {rmt.ifc.l_addr}')
|
||||
await rmt.ifc.disc()
|
||||
if rmt.ifc.close_cb:
|
||||
rmt.ifc.close_cb()
|
||||
|
||||
except Exception:
|
||||
self.inc_counter('SW_Exception')
|
||||
Infos.inc_counter('SW_Exception')
|
||||
logger.error(
|
||||
f"Fwd Exception for {self.addr}:\n"
|
||||
f"Fwd Exception for {self.r_addr}:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
|
||||
def __del__(self):
|
||||
logger.debug(
|
||||
f"AsyncStream.__del__ l{self.l_addr} | r{self.r_addr}")
|
||||
|
||||
class AsyncStreamServer(AsyncStream):
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
async_publ_mqtt, create_remote,
|
||||
rstream: "StreamPtr") -> None:
|
||||
AsyncStream.__init__(self, reader, writer, rstream)
|
||||
self.create_remote = create_remote
|
||||
self.async_publ_mqtt = async_publ_mqtt
|
||||
|
||||
def close(self) -> None:
|
||||
logging.debug('AsyncStreamServer.close()')
|
||||
self.create_remote = None
|
||||
self.async_publ_mqtt = None
|
||||
super().close()
|
||||
|
||||
async def server_loop(self) -> None:
|
||||
'''Loop for receiving messages from the inverter (server-side)'''
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] '
|
||||
f'Accept connection from {self.r_addr}')
|
||||
Infos.inc_counter('Inverter_Cnt')
|
||||
await self.publish_outstanding_mqtt()
|
||||
await self.loop()
|
||||
Infos.dec_counter('Inverter_Cnt')
|
||||
await self.publish_outstanding_mqtt()
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] Server loop stopped for'
|
||||
f' r{self.r_addr}')
|
||||
|
||||
# if the server connection closes, we also have to disconnect
|
||||
# the connection to te TSUN cloud
|
||||
if self.remote and self.remote.stream:
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] disc client '
|
||||
f'connection: [{self.remote.ifc.node_id}:'
|
||||
f'{self.remote.ifc.conn_no}]')
|
||||
await self.remote.ifc.disc()
|
||||
|
||||
async def _async_forward(self) -> None:
|
||||
"""forward handler transmits data over the remote connection"""
|
||||
if not self.remote.stream:
|
||||
await self.create_remote()
|
||||
if self.remote.stream and \
|
||||
self.remote.ifc.init_new_client_conn_cb():
|
||||
await self.remote.ifc._AsyncStream__async_write()
|
||||
if self.remote.stream:
|
||||
self.remote.ifc.update_header_cb(self.fwd_fifo.peek())
|
||||
self.fwd_fifo.logging(logging.INFO, 'Forward to '
|
||||
f'{self.remote.ifc.r_addr}:')
|
||||
self.remote.ifc._writer.write(self.fwd_fifo.get())
|
||||
await self.remote.ifc._writer.drain()
|
||||
|
||||
async def publish_outstanding_mqtt(self):
|
||||
'''Publish all outstanding MQTT topics'''
|
||||
try:
|
||||
await self.async_publ_mqtt()
|
||||
await Proxy._async_publ_mqtt_proxy_stat('proxy')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class AsyncStreamClient(AsyncStream):
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
rstream: "StreamPtr", close_cb) -> None:
|
||||
AsyncStream.__init__(self, reader, writer, rstream)
|
||||
self.close_cb = close_cb
|
||||
|
||||
def close(self) -> None:
|
||||
logging.debug('AsyncStreamClient.close()')
|
||||
self.close_cb = None
|
||||
super().close()
|
||||
|
||||
async def client_loop(self, _: str) -> None:
|
||||
'''Loop for receiving messages from the TSUN cloud (client-side)'''
|
||||
await self.loop()
|
||||
logger.info(f'[{self.node_id}:{self.conn_no}] '
|
||||
'Client loop stopped for'
|
||||
f' l{self.l_addr}')
|
||||
|
||||
if self.close_cb:
|
||||
self.close_cb()
|
||||
|
||||
async def _async_forward(self) -> None:
|
||||
"""forward handler transmits data over the remote connection"""
|
||||
if self.remote.stream:
|
||||
self.remote.ifc.update_header_cb(self.fwd_fifo.peek())
|
||||
self.fwd_fifo.logging(logging.INFO, 'Forward to '
|
||||
f'{self.remote.ifc.r_addr}:')
|
||||
self.remote.ifc._writer.write(self.fwd_fifo.get())
|
||||
await self.remote.ifc._writer.drain()
|
||||
|
||||
54
app/src/byte_fifo.py
Normal file
54
app/src/byte_fifo.py
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
if __name__ == "app.src.byte_fifo":
|
||||
from app.src.messages import hex_dump_str, hex_dump_memory
|
||||
else: # pragma: no cover
|
||||
from messages import hex_dump_str, hex_dump_memory
|
||||
|
||||
|
||||
class ByteFifo:
|
||||
""" a byte FIFO buffer with trigger callback """
|
||||
def __init__(self):
|
||||
self.__buf = bytearray()
|
||||
self.__trigger_cb = None
|
||||
|
||||
def reg_trigger(self, cb) -> None:
|
||||
self.__trigger_cb = cb
|
||||
|
||||
def __iadd__(self, data):
|
||||
self.__buf.extend(data)
|
||||
return self
|
||||
|
||||
def __call__(self):
|
||||
'''triggers the observer'''
|
||||
if callable(self.__trigger_cb):
|
||||
return self.__trigger_cb()
|
||||
return None
|
||||
|
||||
def get(self, size: int = None) -> bytearray:
|
||||
'''removes size numbers of byte and return them'''
|
||||
if not size:
|
||||
data = self.__buf
|
||||
self.clear()
|
||||
else:
|
||||
data = self.__buf[:size]
|
||||
# The fast delete syntax
|
||||
self.__buf[:size] = b''
|
||||
return data
|
||||
|
||||
def peek(self, size: int = None) -> bytearray:
|
||||
'''returns size numbers of byte without removing them'''
|
||||
if not size:
|
||||
return self.__buf
|
||||
return self.__buf[:size]
|
||||
|
||||
def clear(self):
|
||||
self.__buf = bytearray()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__buf)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return hex_dump_str(self.__buf, self.__len__())
|
||||
|
||||
def logging(self, level, info):
|
||||
hex_dump_memory(level, info, self.__buf, self.__len__())
|
||||
@@ -1,46 +0,0 @@
|
||||
import logging
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
|
||||
if __name__ == "app.src.gen3.connection_g3":
|
||||
from app.src.async_stream import AsyncStream
|
||||
from app.src.gen3.talent import Talent
|
||||
else: # pragma: no cover
|
||||
from async_stream import AsyncStream
|
||||
from gen3.talent import Talent
|
||||
|
||||
logger = logging.getLogger('conn')
|
||||
|
||||
|
||||
class ConnectionG3(AsyncStream, Talent):
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
addr, remote_stream: 'ConnectionG3', server_side: bool,
|
||||
id_str=b'') -> None:
|
||||
AsyncStream.__init__(self, reader, writer, addr)
|
||||
Talent.__init__(self, server_side, id_str)
|
||||
|
||||
self.remote_stream: 'ConnectionG3' = remote_stream
|
||||
|
||||
'''
|
||||
Our puplic methods
|
||||
'''
|
||||
def close(self):
|
||||
AsyncStream.close(self)
|
||||
Talent.close(self)
|
||||
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
|
||||
|
||||
async def async_create_remote(self) -> None:
|
||||
pass # virtual interface # pragma: no cover
|
||||
|
||||
async def async_publ_mqtt(self) -> None:
|
||||
pass # virtual interface # pragma: no cover
|
||||
|
||||
def healthy(self) -> bool:
|
||||
logger.debug('ConnectionG3 healthy()')
|
||||
return AsyncStream.healthy(self)
|
||||
|
||||
'''
|
||||
Our private methods
|
||||
'''
|
||||
def __del__(self):
|
||||
super().__del__()
|
||||
@@ -1,137 +1,12 @@
|
||||
import logging
|
||||
import traceback
|
||||
import json
|
||||
import asyncio
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
from aiomqtt import MqttCodeError
|
||||
|
||||
if __name__ == "app.src.gen3.inverter_g3":
|
||||
from app.src.config import Config
|
||||
from app.src.inverter import Inverter
|
||||
from app.src.gen3.connection_g3 import ConnectionG3
|
||||
from app.src.infos import Infos
|
||||
from app.src.inverter_base import InverterBase
|
||||
from app.src.gen3.talent import Talent
|
||||
else: # pragma: no cover
|
||||
from config import Config
|
||||
from inverter import Inverter
|
||||
from gen3.connection_g3 import ConnectionG3
|
||||
from infos import Infos
|
||||
from inverter_base import InverterBase
|
||||
from gen3.talent import Talent
|
||||
|
||||
|
||||
logger_mqtt = logging.getLogger('mqtt')
|
||||
|
||||
|
||||
class InverterG3(Inverter, ConnectionG3):
|
||||
'''class Inverter is a derivation of an Async_Stream
|
||||
|
||||
The class has some class method for managing common resources like a
|
||||
connection to the MQTT broker or proxy error counter which are common
|
||||
for all inverter connection
|
||||
|
||||
Instances of the class are connections to an inverter and can have an
|
||||
optional link to an remote connection to the TSUN cloud. A remote
|
||||
connection dies with the inverter connection.
|
||||
|
||||
class methods:
|
||||
class_init(): initialize the common resources of the proxy (MQTT
|
||||
broker, Proxy DB, etc). Must be called before the
|
||||
first inverter instance can be created
|
||||
class_close(): release the common resources of the proxy. Should not
|
||||
be called before any instances of the class are
|
||||
destroyed
|
||||
|
||||
methods:
|
||||
server_loop(addr): Async loop method for receiving messages from the
|
||||
inverter (server-side)
|
||||
client_loop(addr): Async loop method for receiving messages from the
|
||||
TSUN cloud (client-side)
|
||||
async_create_remote(): Establish a client connection to the TSUN cloud
|
||||
async_publ_mqtt(): Publish data to MQTT broker
|
||||
close(): Release method which must be called before a instance can be
|
||||
destroyed
|
||||
'''
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter, addr):
|
||||
super().__init__(reader, writer, addr, None, True)
|
||||
self.__ha_restarts = -1
|
||||
|
||||
async def async_create_remote(self) -> None:
|
||||
'''Establish a client connection to the TSUN cloud'''
|
||||
tsun = Config.get('tsun')
|
||||
host = tsun['host']
|
||||
port = tsun['port']
|
||||
addr = (host, port)
|
||||
|
||||
try:
|
||||
logging.info(f'[{self.node_id}] Connect to {addr}')
|
||||
connect = asyncio.open_connection(host, port)
|
||||
reader, writer = await connect
|
||||
self.remote_stream = ConnectionG3(reader, writer, addr, self,
|
||||
False, self.id_str)
|
||||
logging.info(f'[{self.remote_stream.node_id}:'
|
||||
f'{self.remote_stream.conn_no}] '
|
||||
f'Connected to {addr}')
|
||||
asyncio.create_task(self.client_loop(addr))
|
||||
|
||||
except (ConnectionRefusedError, TimeoutError) as error:
|
||||
logging.info(f'{error}')
|
||||
except Exception:
|
||||
self.inc_counter('SW_Exception')
|
||||
logging.error(
|
||||
f"Inverter: Exception for {addr}:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
|
||||
async def async_publ_mqtt(self) -> None:
|
||||
'''publish data to MQTT broker'''
|
||||
# check if new inverter or collector infos are available or when the
|
||||
# home assistant has changed the status back to online
|
||||
try:
|
||||
if (('inverter' in self.new_data and self.new_data['inverter'])
|
||||
or ('collector' in self.new_data and
|
||||
self.new_data['collector'])
|
||||
or self.mqtt.ha_restarts != self.__ha_restarts):
|
||||
await self._register_proxy_stat_home_assistant()
|
||||
await self.__register_home_assistant()
|
||||
self.__ha_restarts = self.mqtt.ha_restarts
|
||||
|
||||
for key in self.new_data:
|
||||
await self.__async_publ_mqtt_packet(key)
|
||||
for key in Infos.new_stat_data:
|
||||
await self._async_publ_mqtt_proxy_stat(key)
|
||||
|
||||
except MqttCodeError as error:
|
||||
logging.error(f'Mqtt except: {error}')
|
||||
except Exception:
|
||||
self.inc_counter('SW_Exception')
|
||||
logging.error(
|
||||
f"Inverter: Exception:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
|
||||
async def __async_publ_mqtt_packet(self, key):
|
||||
db = self.db.db
|
||||
if key in db and self.new_data[key]:
|
||||
data_json = json.dumps(db[key])
|
||||
node_id = self.node_id
|
||||
logger_mqtt.debug(f'{key}: {data_json}')
|
||||
await self.mqtt.publish(f'{self.entity_prfx}{node_id}{key}', data_json) # noqa: E501
|
||||
self.new_data[key] = False
|
||||
|
||||
async def __register_home_assistant(self) -> None:
|
||||
'''register all our topics at home assistant'''
|
||||
for data_json, component, node_id, id in self.db.ha_confs(
|
||||
self.entity_prfx, self.node_id, self.unique_id,
|
||||
self.sug_area):
|
||||
logger_mqtt.debug(f"MQTT Register: cmp:'{component}'"
|
||||
f" node_id:'{node_id}' {data_json}")
|
||||
await self.mqtt.publish(f"{self.discovery_prfx}{component}"
|
||||
f"/{node_id}{id}/config", data_json)
|
||||
|
||||
self.db.reg_clr_at_midnight(f'{self.entity_prfx}{self.node_id}')
|
||||
|
||||
def close(self) -> None:
|
||||
logging.debug(f'InverterG3.close() l{self.l_addr} | r{self.r_addr}')
|
||||
super().close() # call close handler in the parent class
|
||||
# logging.info(f'Inverter refs: {gc.get_referrers(self)}')
|
||||
|
||||
def __del__(self):
|
||||
logging.debug("InverterG3.__del__")
|
||||
super().__del__()
|
||||
class InverterG3(InverterBase):
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter):
|
||||
super().__init__(reader, writer, 'tsun', Talent)
|
||||
|
||||
@@ -5,14 +5,16 @@ from datetime import datetime
|
||||
from tzlocal import get_localzone
|
||||
|
||||
if __name__ == "app.src.gen3.talent":
|
||||
from app.src.messages import hex_dump_memory, Message, State
|
||||
from app.src.async_ifc import AsyncIfc
|
||||
from app.src.messages import Message, State
|
||||
from app.src.modbus import Modbus
|
||||
from app.src.my_timer import Timer
|
||||
from app.src.config import Config
|
||||
from app.src.gen3.infos_g3 import InfosG3
|
||||
from app.src.infos import Register
|
||||
else: # pragma: no cover
|
||||
from messages import hex_dump_memory, Message, State
|
||||
from async_ifc import AsyncIfc
|
||||
from messages import Message, State
|
||||
from modbus import Modbus
|
||||
from my_timer import Timer
|
||||
from config import Config
|
||||
@@ -44,8 +46,16 @@ class Talent(Message):
|
||||
MB_REGULAR_TIMEOUT = 60
|
||||
TXT_UNKNOWN_CTRL = 'Unknown Ctrl'
|
||||
|
||||
def __init__(self, server_side: bool, id_str=b''):
|
||||
def __init__(self, addr, ifc: "AsyncIfc", server_side: bool,
|
||||
client_mode: bool = False, id_str=b''):
|
||||
super().__init__(server_side, self.send_modbus_cb, mb_timeout=15)
|
||||
ifc.rx_set_cb(self.read)
|
||||
ifc.prot_set_timeout_cb(self._timeout)
|
||||
ifc.prot_set_init_new_client_conn_cb(self._init_new_client_conn)
|
||||
ifc.prot_set_update_header_cb(self._update_header)
|
||||
self.addr = addr
|
||||
self.ifc = ifc
|
||||
self.conn_no = ifc.get_conn_no()
|
||||
self.await_conn_resp_cnt = 0
|
||||
self.id_str = id_str
|
||||
self.contact_name = b''
|
||||
@@ -56,7 +66,7 @@ class Talent(Message):
|
||||
0x00: self.msg_contact_info,
|
||||
0x13: self.msg_ota_update,
|
||||
0x22: self.msg_get_time,
|
||||
0x99: self.msg_act_time,
|
||||
0x99: self.msg_heartbeat,
|
||||
0x71: self.msg_collector_data,
|
||||
# 0x76:
|
||||
0x77: self.msg_modbus,
|
||||
@@ -103,6 +113,11 @@ class Talent(Message):
|
||||
self.log_lvl.clear()
|
||||
self.state = State.closed
|
||||
self.mb_timer.close()
|
||||
self.ifc.rx_set_cb(None)
|
||||
self.ifc.prot_set_timeout_cb(None)
|
||||
self.ifc.prot_set_init_new_client_conn_cb(None)
|
||||
self.ifc.prot_set_update_header_cb(None)
|
||||
self.ifc = None
|
||||
super().close()
|
||||
|
||||
def __set_serial_no(self, serial_no: str):
|
||||
@@ -138,10 +153,10 @@ class Talent(Message):
|
||||
self._read()
|
||||
while True:
|
||||
if not self.header_valid:
|
||||
self.__parse_header(self._recv_buffer, len(self._recv_buffer))
|
||||
self.__parse_header(self.ifc.rx_peek(), self.ifc.rx_len())
|
||||
|
||||
if self.header_valid and \
|
||||
len(self._recv_buffer) >= (self.header_len + self.data_len):
|
||||
self.ifc.rx_len() >= (self.header_len + self.data_len):
|
||||
if self.state == State.init:
|
||||
self.state = State.received # received 1st package
|
||||
|
||||
@@ -149,11 +164,10 @@ class Talent(Message):
|
||||
if callable(log_lvl):
|
||||
log_lvl = log_lvl()
|
||||
|
||||
hex_dump_memory(log_lvl, f'Received from {self.addr}:'
|
||||
f' BufLen: {len(self._recv_buffer)}'
|
||||
self.ifc.rx_log(log_lvl, f'Received from {self.addr}:'
|
||||
f' BufLen: {self.ifc.rx_len()}'
|
||||
f' HdrLen: {self.header_len}'
|
||||
f' DtaLen: {self.data_len}',
|
||||
self._recv_buffer, len(self._recv_buffer))
|
||||
f' DtaLen: {self.data_len}')
|
||||
|
||||
self.__set_serial_no(self.id_str.decode("utf-8"))
|
||||
self.__dispatch_msg()
|
||||
@@ -165,35 +179,15 @@ class Talent(Message):
|
||||
'''add the actual receive msg to the forwarding queue'''
|
||||
tsun = Config.get('tsun')
|
||||
if tsun['enabled']:
|
||||
buffer = self._recv_buffer
|
||||
buflen = self.header_len+self.data_len
|
||||
self._forward_buffer += buffer[:buflen]
|
||||
hex_dump_memory(logging.DEBUG, 'Store for forwarding:',
|
||||
buffer, buflen)
|
||||
buffer = self.ifc.rx_peek(buflen)
|
||||
self.ifc.fwd_add(buffer)
|
||||
self.ifc.fwd_log(logging.DEBUG, 'Store for forwarding:')
|
||||
|
||||
fnc = self.switch.get(self.msg_id, self.msg_unknown)
|
||||
logger.info(self.__flow_str(self.server_side, 'forwrd') +
|
||||
f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}')
|
||||
|
||||
def forward_snd(self) -> None:
|
||||
'''add the actual receive msg to the forwarding queue'''
|
||||
tsun = Config.get('tsun')
|
||||
if tsun['enabled']:
|
||||
_len = len(self._send_buffer) - self.send_msg_ofs
|
||||
struct.pack_into('!l', self._send_buffer, self.send_msg_ofs,
|
||||
_len-4)
|
||||
|
||||
buffer = self._send_buffer[self.send_msg_ofs:]
|
||||
buflen = _len
|
||||
self._forward_buffer += buffer[:buflen]
|
||||
hex_dump_memory(logging.INFO, 'Store for forwarding:',
|
||||
buffer, buflen)
|
||||
|
||||
fnc = self.switch.get(self.msg_id, self.msg_unknown)
|
||||
logger.info(self.__flow_str(self.server_side, 'forwrd') +
|
||||
f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}')
|
||||
self._send_buffer = self._send_buffer[:self.send_msg_ofs]
|
||||
|
||||
def send_modbus_cb(self, modbus_pdu: bytearray, log_lvl: int, state: str):
|
||||
if self.state != State.up:
|
||||
logger.warning(f'[{self.node_id}] ignore MODBUS cmd,'
|
||||
@@ -201,15 +195,13 @@ class Talent(Message):
|
||||
return
|
||||
|
||||
self.__build_header(0x70, 0x77)
|
||||
self._send_buffer += b'\x00\x01\xa3\x28' # magic ?
|
||||
self._send_buffer += struct.pack('!B', len(modbus_pdu))
|
||||
self._send_buffer += modbus_pdu
|
||||
self.ifc.tx_add(b'\x00\x01\xa3\x28') # magic ?
|
||||
self.ifc.tx_add(struct.pack('!B', len(modbus_pdu)))
|
||||
self.ifc.tx_add(modbus_pdu)
|
||||
self.__finish_send_msg()
|
||||
|
||||
hex_dump_memory(log_lvl, f'Send Modbus {state}:{self.addr}:',
|
||||
self._send_buffer, len(self._send_buffer))
|
||||
self.writer.write(self._send_buffer)
|
||||
self._send_buffer = bytearray(0) # self._send_buffer[sent:]
|
||||
self.ifc.tx_log(log_lvl, f'Send Modbus {state}:{self.addr}:')
|
||||
self.ifc.tx_flush()
|
||||
|
||||
def _send_modbus_cmd(self, func, addr, val, log_lvl) -> None:
|
||||
if self.state != State.up:
|
||||
@@ -237,9 +229,9 @@ class Talent(Message):
|
||||
self.msg_id = 0
|
||||
self.await_conn_resp_cnt += 1
|
||||
self.__build_header(0x91)
|
||||
self._send_buffer += struct.pack(f'!{len(contact_name)+1}p'
|
||||
f'{len(contact_mail)+1}p',
|
||||
contact_name, contact_mail)
|
||||
self.ifc.tx_add(struct.pack(f'!{len(contact_name)+1}p'
|
||||
f'{len(contact_mail)+1}p',
|
||||
contact_name, contact_mail))
|
||||
|
||||
self.__finish_send_msg()
|
||||
return True
|
||||
@@ -323,7 +315,7 @@ class Talent(Message):
|
||||
self.inc_counter('Invalid_Msg_Format')
|
||||
|
||||
# erase broken recv buffer
|
||||
self._recv_buffer = bytearray()
|
||||
self.ifc.rx_clear()
|
||||
return
|
||||
|
||||
hdr_len = 5+id_len+2
|
||||
@@ -344,16 +336,17 @@ class Talent(Message):
|
||||
def __build_header(self, ctrl, msg_id=None) -> None:
|
||||
if not msg_id:
|
||||
msg_id = self.msg_id
|
||||
self.send_msg_ofs = len(self._send_buffer)
|
||||
self._send_buffer += struct.pack(f'!l{len(self.id_str)+1}pBB',
|
||||
0, self.id_str, ctrl, msg_id)
|
||||
self.send_msg_ofs = self.ifc.tx_len()
|
||||
self.ifc.tx_add(struct.pack(f'!l{len(self.id_str)+1}pBB',
|
||||
0, self.id_str, ctrl, msg_id))
|
||||
fnc = self.switch.get(msg_id, self.msg_unknown)
|
||||
logger.info(self.__flow_str(self.server_side, 'tx') +
|
||||
f' Ctl: {int(ctrl):#02x} Msg: {fnc.__name__!r}')
|
||||
|
||||
def __finish_send_msg(self) -> None:
|
||||
_len = len(self._send_buffer) - self.send_msg_ofs
|
||||
struct.pack_into('!l', self._send_buffer, self.send_msg_ofs, _len-4)
|
||||
_len = self.ifc.tx_len() - self.send_msg_ofs
|
||||
struct.pack_into('!l', self.ifc.tx_peek(), self.send_msg_ofs,
|
||||
_len-4)
|
||||
|
||||
def __dispatch_msg(self) -> None:
|
||||
fnc = self.switch.get(self.msg_id, self.msg_unknown)
|
||||
@@ -367,7 +360,7 @@ class Talent(Message):
|
||||
f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}')
|
||||
|
||||
def __flush_recv_msg(self) -> None:
|
||||
self._recv_buffer = self._recv_buffer[(self.header_len+self.data_len):]
|
||||
self.ifc.rx_get(self.header_len+self.data_len)
|
||||
self.header_valid = False
|
||||
|
||||
'''
|
||||
@@ -377,7 +370,7 @@ class Talent(Message):
|
||||
if self.ctrl.is_ind():
|
||||
if self.server_side and self.__process_contact_info():
|
||||
self.__build_header(0x91)
|
||||
self._send_buffer += b'\x01'
|
||||
self.ifc.tx_add(b'\x01')
|
||||
self.__finish_send_msg()
|
||||
# don't forward this contact info here, we will build one
|
||||
# when the remote connection is established
|
||||
@@ -391,19 +384,21 @@ class Talent(Message):
|
||||
self.forward()
|
||||
|
||||
def __process_contact_info(self) -> bool:
|
||||
result = struct.unpack_from('!B', self._recv_buffer, self.header_len)
|
||||
buf = self.ifc.rx_peek()
|
||||
result = struct.unpack_from('!B', buf, self.header_len)
|
||||
name_len = result[0]
|
||||
if self.data_len < name_len+2:
|
||||
if self.data_len == 1: # this is a response withone status byte
|
||||
return False
|
||||
result = struct.unpack_from(f'!{name_len+1}pB', self._recv_buffer,
|
||||
self.header_len)
|
||||
self.contact_name = result[0]
|
||||
mail_len = result[1]
|
||||
logger.info(f'name: {self.contact_name}')
|
||||
if self.data_len >= name_len+2:
|
||||
result = struct.unpack_from(f'!{name_len+1}pB', buf,
|
||||
self.header_len)
|
||||
self.contact_name = result[0]
|
||||
mail_len = result[1]
|
||||
logger.info(f'name: {self.contact_name}')
|
||||
|
||||
result = struct.unpack_from(f'!{mail_len+1}p', self._recv_buffer,
|
||||
self.header_len+name_len+1)
|
||||
self.contact_mail = result[0]
|
||||
result = struct.unpack_from(f'!{mail_len+1}p', buf,
|
||||
self.header_len+name_len+1)
|
||||
self.contact_mail = result[0]
|
||||
logger.info(f'mail: {self.contact_mail}')
|
||||
return True
|
||||
|
||||
@@ -416,16 +411,16 @@ class Talent(Message):
|
||||
ts = self._timestamp()
|
||||
logger.debug(f'time: {ts:08x}')
|
||||
self.__build_header(0x91)
|
||||
self._send_buffer += struct.pack('!q', ts)
|
||||
self.ifc.tx_add(struct.pack('!q', ts))
|
||||
self.__finish_send_msg()
|
||||
|
||||
elif self.data_len >= 8:
|
||||
ts = self._timestamp()
|
||||
result = struct.unpack_from('!q', self._recv_buffer,
|
||||
result = struct.unpack_from('!q', self.ifc.rx_peek(),
|
||||
self.header_len)
|
||||
self.ts_offset = result[0]-ts
|
||||
if self.remote_stream:
|
||||
self.remote_stream.ts_offset = self.ts_offset
|
||||
if self.ifc.remote.stream:
|
||||
self.ifc.remote.stream.ts_offset = self.ts_offset
|
||||
logger.debug(f'tsun-time: {int(result[0]):08x}'
|
||||
f' proxy-time: {ts:08x}'
|
||||
f' offset: {self.ts_offset}')
|
||||
@@ -436,7 +431,7 @@ class Talent(Message):
|
||||
|
||||
self.forward()
|
||||
|
||||
def msg_act_time(self):
|
||||
def msg_heartbeat(self):
|
||||
if self.ctrl.is_ind():
|
||||
if self.data_len == 9:
|
||||
self.state = State.up # allow MODBUS cmds
|
||||
@@ -445,25 +440,23 @@ class Talent(Message):
|
||||
self.db.set_db_def_value(Register.POLLING_INTERVAL,
|
||||
self.mb_timeout)
|
||||
self.__build_header(0x99)
|
||||
self._send_buffer += b'\x02'
|
||||
self.ifc.tx_add(b'\x02')
|
||||
self.__finish_send_msg()
|
||||
|
||||
result = struct.unpack_from('!Bq', self._recv_buffer,
|
||||
result = struct.unpack_from('!Bq', self.ifc.rx_peek(),
|
||||
self.header_len)
|
||||
resp_code = result[0]
|
||||
ts = result[1]+self.ts_offset
|
||||
logger.debug(f'inv-time: {int(result[1]):08x}'
|
||||
f' tsun-time: {ts:08x}'
|
||||
f' offset: {self.ts_offset}')
|
||||
self.__build_header(0x91)
|
||||
self._send_buffer += struct.pack('!Bq', resp_code, ts)
|
||||
self.forward_snd()
|
||||
return
|
||||
struct.pack_into('!Bq', self.ifc.rx_peek(),
|
||||
self.header_len, resp_code, ts)
|
||||
elif self.ctrl.is_resp():
|
||||
result = struct.unpack_from('!B', self._recv_buffer,
|
||||
result = struct.unpack_from('!B', self.ifc.rx_peek(),
|
||||
self.header_len)
|
||||
resp_code = result[0]
|
||||
logging.debug(f'TimeActRespCode: {resp_code}')
|
||||
logging.debug(f'Heartbeat-RespCode: {resp_code}')
|
||||
return
|
||||
else:
|
||||
logger.warning(self.TXT_UNKNOWN_CTRL)
|
||||
@@ -472,7 +465,8 @@ class Talent(Message):
|
||||
self.forward()
|
||||
|
||||
def parse_msg_header(self):
|
||||
result = struct.unpack_from('!lB', self._recv_buffer, self.header_len)
|
||||
result = struct.unpack_from('!lB', self.ifc.rx_peek(),
|
||||
self.header_len)
|
||||
|
||||
data_id = result[0] # len of complete message
|
||||
id_len = result[1] # len of variable id string
|
||||
@@ -480,7 +474,7 @@ class Talent(Message):
|
||||
|
||||
msg_hdr_len = 5+id_len+9
|
||||
|
||||
result = struct.unpack_from(f'!{id_len+1}pBq', self._recv_buffer,
|
||||
result = struct.unpack_from(f'!{id_len+1}pBq', self.ifc.rx_peek(),
|
||||
self.header_len + 4)
|
||||
|
||||
timestamp = result[2]
|
||||
@@ -493,7 +487,7 @@ class Talent(Message):
|
||||
def msg_collector_data(self):
|
||||
if self.ctrl.is_ind():
|
||||
self.__build_header(0x99)
|
||||
self._send_buffer += b'\x01'
|
||||
self.ifc.tx_add(b'\x01')
|
||||
self.__finish_send_msg()
|
||||
self.__process_data()
|
||||
|
||||
@@ -508,7 +502,7 @@ class Talent(Message):
|
||||
def msg_inverter_data(self):
|
||||
if self.ctrl.is_ind():
|
||||
self.__build_header(0x99)
|
||||
self._send_buffer += b'\x01'
|
||||
self.ifc.tx_add(b'\x01')
|
||||
self.__finish_send_msg()
|
||||
self.__process_data()
|
||||
self.state = State.up # allow MODBUS cmds
|
||||
@@ -528,7 +522,7 @@ class Talent(Message):
|
||||
def __process_data(self):
|
||||
msg_hdr_len, ts = self.parse_msg_header()
|
||||
|
||||
for key, update in self.db.parse(self._recv_buffer, self.header_len
|
||||
for key, update in self.db.parse(self.ifc.rx_peek(), self.header_len
|
||||
+ msg_hdr_len, self.node_id):
|
||||
if update:
|
||||
self._set_mqtt_timestamp(key, self._utcfromts(ts))
|
||||
@@ -548,7 +542,7 @@ class Talent(Message):
|
||||
|
||||
msg_hdr_len = 5
|
||||
|
||||
result = struct.unpack_from('!lBB', self._recv_buffer,
|
||||
result = struct.unpack_from('!lBB', self.ifc.rx_peek(),
|
||||
self.header_len)
|
||||
modbus_len = result[1]
|
||||
return msg_hdr_len, modbus_len
|
||||
@@ -557,7 +551,7 @@ class Talent(Message):
|
||||
|
||||
msg_hdr_len = 6
|
||||
|
||||
result = struct.unpack_from('!lBBB', self._recv_buffer,
|
||||
result = struct.unpack_from('!lBBB', self.ifc.rx_peek(),
|
||||
self.header_len)
|
||||
modbus_len = result[2]
|
||||
return msg_hdr_len, modbus_len
|
||||
@@ -578,13 +572,12 @@ class Talent(Message):
|
||||
self.__msg_modbus(hdr_len)
|
||||
|
||||
def __msg_modbus(self, hdr_len):
|
||||
data = self._recv_buffer[self.header_len:
|
||||
self.header_len+self.data_len]
|
||||
data = self.ifc.rx_peek()[self.header_len:
|
||||
self.header_len+self.data_len]
|
||||
|
||||
if self.ctrl.is_req():
|
||||
if self.remote_stream.mb.recv_req(data[hdr_len:],
|
||||
self.remote_stream.
|
||||
msg_forward):
|
||||
rstream = self.ifc.remote.stream
|
||||
if rstream.mb.recv_req(data[hdr_len:], rstream.msg_forward):
|
||||
self.inc_counter('Modbus_Command')
|
||||
else:
|
||||
self.inc_counter('Invalid_Msg_Format')
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import logging
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
|
||||
if __name__ == "app.src.gen3plus.connection_g3p":
|
||||
from app.src.async_stream import AsyncStream
|
||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||
else: # pragma: no cover
|
||||
from async_stream import AsyncStream
|
||||
from gen3plus.solarman_v5 import SolarmanV5
|
||||
|
||||
logger = logging.getLogger('conn')
|
||||
|
||||
|
||||
class ConnectionG3P(AsyncStream, SolarmanV5):
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
addr, remote_stream: 'ConnectionG3P',
|
||||
server_side: bool,
|
||||
client_mode: bool) -> None:
|
||||
AsyncStream.__init__(self, reader, writer, addr)
|
||||
SolarmanV5.__init__(self, server_side, client_mode)
|
||||
|
||||
self.remote_stream: 'ConnectionG3P' = remote_stream
|
||||
|
||||
'''
|
||||
Our puplic methods
|
||||
'''
|
||||
def close(self):
|
||||
AsyncStream.close(self)
|
||||
SolarmanV5.close(self)
|
||||
# logger.info(f'AsyncStream refs: {gc.get_referrers(self)}')
|
||||
|
||||
async def async_create_remote(self) -> None:
|
||||
pass # virtual interface # pragma: no cover
|
||||
|
||||
async def async_publ_mqtt(self) -> None:
|
||||
pass # virtual interface # pragma: no cover
|
||||
|
||||
def healthy(self) -> bool:
|
||||
logger.debug('ConnectionG3P healthy()')
|
||||
return AsyncStream.healthy(self)
|
||||
|
||||
'''
|
||||
Our private methods
|
||||
'''
|
||||
def __del__(self):
|
||||
super().__del__()
|
||||
@@ -1,140 +1,15 @@
|
||||
import logging
|
||||
import traceback
|
||||
import json
|
||||
import asyncio
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
from aiomqtt import MqttCodeError
|
||||
|
||||
if __name__ == "app.src.gen3plus.inverter_g3p":
|
||||
from app.src.config import Config
|
||||
from app.src.inverter import Inverter
|
||||
from app.src.gen3plus.connection_g3p import ConnectionG3P
|
||||
from app.src.infos import Infos
|
||||
from app.src.inverter_base import InverterBase
|
||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||
else: # pragma: no cover
|
||||
from config import Config
|
||||
from inverter import Inverter
|
||||
from gen3plus.connection_g3p import ConnectionG3P
|
||||
from infos import Infos
|
||||
from inverter_base import InverterBase
|
||||
from gen3plus.solarman_v5 import SolarmanV5
|
||||
|
||||
|
||||
logger_mqtt = logging.getLogger('mqtt')
|
||||
|
||||
|
||||
class InverterG3P(Inverter, ConnectionG3P):
|
||||
'''class Inverter is a derivation of an Async_Stream
|
||||
|
||||
The class has some class method for managing common resources like a
|
||||
connection to the MQTT broker or proxy error counter which are common
|
||||
for all inverter connection
|
||||
|
||||
Instances of the class are connections to an inverter and can have an
|
||||
optional link to an remote connection to the TSUN cloud. A remote
|
||||
connection dies with the inverter connection.
|
||||
|
||||
class methods:
|
||||
class_init(): initialize the common resources of the proxy (MQTT
|
||||
broker, Proxy DB, etc). Must be called before the
|
||||
first inverter instance can be created
|
||||
class_close(): release the common resources of the proxy. Should not
|
||||
be called before any instances of the class are
|
||||
destroyed
|
||||
|
||||
methods:
|
||||
server_loop(addr): Async loop method for receiving messages from the
|
||||
inverter (server-side)
|
||||
client_loop(addr): Async loop method for receiving messages from the
|
||||
TSUN cloud (client-side)
|
||||
async_create_remote(): Establish a client connection to the TSUN cloud
|
||||
async_publ_mqtt(): Publish data to MQTT broker
|
||||
close(): Release method which must be called before a instance can be
|
||||
destroyed
|
||||
'''
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter, addr,
|
||||
class InverterG3P(InverterBase):
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
client_mode: bool = False):
|
||||
super().__init__(reader, writer, addr, None,
|
||||
server_side=True, client_mode=client_mode)
|
||||
self.__ha_restarts = -1
|
||||
|
||||
async def async_create_remote(self) -> None:
|
||||
'''Establish a client connection to the TSUN cloud'''
|
||||
tsun = Config.get('solarman')
|
||||
host = tsun['host']
|
||||
port = tsun['port']
|
||||
addr = (host, port)
|
||||
|
||||
try:
|
||||
logging.info(f'[{self.node_id}] Connect to {addr}')
|
||||
connect = asyncio.open_connection(host, port)
|
||||
reader, writer = await connect
|
||||
self.remote_stream = ConnectionG3P(reader, writer, addr, self,
|
||||
server_side=False,
|
||||
client_mode=False)
|
||||
logging.info(f'[{self.remote_stream.node_id}:'
|
||||
f'{self.remote_stream.conn_no}] '
|
||||
f'Connected to {addr}')
|
||||
asyncio.create_task(self.client_loop(addr))
|
||||
|
||||
except (ConnectionRefusedError, TimeoutError) as error:
|
||||
logging.info(f'{error}')
|
||||
except Exception:
|
||||
self.inc_counter('SW_Exception')
|
||||
logging.error(
|
||||
f"Inverter: Exception for {addr}:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
|
||||
async def async_publ_mqtt(self) -> None:
|
||||
'''publish data to MQTT broker'''
|
||||
# check if new inverter or collector infos are available or when the
|
||||
# home assistant has changed the status back to online
|
||||
try:
|
||||
if (('inverter' in self.new_data and self.new_data['inverter'])
|
||||
or ('collector' in self.new_data and
|
||||
self.new_data['collector'])
|
||||
or self.mqtt.ha_restarts != self.__ha_restarts):
|
||||
await self._register_proxy_stat_home_assistant()
|
||||
await self.__register_home_assistant()
|
||||
self.__ha_restarts = self.mqtt.ha_restarts
|
||||
|
||||
for key in self.new_data:
|
||||
await self.__async_publ_mqtt_packet(key)
|
||||
for key in Infos.new_stat_data:
|
||||
await self._async_publ_mqtt_proxy_stat(key)
|
||||
|
||||
except MqttCodeError as error:
|
||||
logging.error(f'Mqtt except: {error}')
|
||||
except Exception:
|
||||
self.inc_counter('SW_Exception')
|
||||
logging.error(
|
||||
f"Inverter: Exception:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
|
||||
async def __async_publ_mqtt_packet(self, key):
|
||||
db = self.db.db
|
||||
if key in db and self.new_data[key]:
|
||||
data_json = json.dumps(db[key])
|
||||
node_id = self.node_id
|
||||
logger_mqtt.debug(f'{key}: {data_json}')
|
||||
await self.mqtt.publish(f'{self.entity_prfx}{node_id}{key}', data_json) # noqa: E501
|
||||
self.new_data[key] = False
|
||||
|
||||
async def __register_home_assistant(self) -> None:
|
||||
'''register all our topics at home assistant'''
|
||||
for data_json, component, node_id, id in self.db.ha_confs(
|
||||
self.entity_prfx, self.node_id, self.unique_id,
|
||||
self.sug_area):
|
||||
logger_mqtt.debug(f"MQTT Register: cmp:'{component}'"
|
||||
f" node_id:'{node_id}' {data_json}")
|
||||
await self.mqtt.publish(f"{self.discovery_prfx}{component}"
|
||||
f"/{node_id}{id}/config", data_json)
|
||||
|
||||
self.db.reg_clr_at_midnight(f'{self.entity_prfx}{self.node_id}')
|
||||
|
||||
def close(self) -> None:
|
||||
logging.debug(f'InverterG3P.close() l{self.l_addr} | r{self.r_addr}')
|
||||
super().close() # call close handler in the parent class
|
||||
# logger.debug (f'Inverter refs: {gc.get_referrers(self)}')
|
||||
|
||||
def __del__(self):
|
||||
logging.debug("InverterG3P.__del__")
|
||||
super().__del__()
|
||||
super().__init__(reader, writer, 'solarman',
|
||||
SolarmanV5, client_mode)
|
||||
|
||||
@@ -5,6 +5,7 @@ import asyncio
|
||||
from datetime import datetime
|
||||
|
||||
if __name__ == "app.src.gen3plus.solarman_v5":
|
||||
from app.src.async_ifc import AsyncIfc
|
||||
from app.src.messages import hex_dump_memory, Message, State
|
||||
from app.src.modbus import Modbus
|
||||
from app.src.my_timer import Timer
|
||||
@@ -12,6 +13,7 @@ if __name__ == "app.src.gen3plus.solarman_v5":
|
||||
from app.src.gen3plus.infos_g3p import InfosG3P
|
||||
from app.src.infos import Register
|
||||
else: # pragma: no cover
|
||||
from async_ifc import AsyncIfc
|
||||
from messages import hex_dump_memory, Message, State
|
||||
from config import Config
|
||||
from modbus import Modbus
|
||||
@@ -60,9 +62,17 @@ class SolarmanV5(Message):
|
||||
HDR_FMT = '<BLLL'
|
||||
'''format string for packing of the header'''
|
||||
|
||||
def __init__(self, server_side: bool, client_mode: bool):
|
||||
def __init__(self, addr, ifc: "AsyncIfc",
|
||||
server_side: bool, client_mode: bool):
|
||||
super().__init__(server_side, self.send_modbus_cb, mb_timeout=8)
|
||||
ifc.rx_set_cb(self.read)
|
||||
ifc.prot_set_timeout_cb(self._timeout)
|
||||
ifc.prot_set_init_new_client_conn_cb(self._init_new_client_conn)
|
||||
ifc.prot_set_update_header_cb(self._update_header)
|
||||
|
||||
self.addr = addr
|
||||
self.ifc = ifc
|
||||
self.conn_no = ifc.get_conn_no()
|
||||
self.header_len = 11 # overwrite construcor in class Message
|
||||
self.control = 0
|
||||
self.seq = Sequence(server_side)
|
||||
@@ -160,6 +170,11 @@ class SolarmanV5(Message):
|
||||
self.log_lvl.clear()
|
||||
self.state = State.closed
|
||||
self.mb_timer.close()
|
||||
self.ifc.rx_set_cb(None)
|
||||
self.ifc.prot_set_timeout_cb(None)
|
||||
self.ifc.prot_set_init_new_client_conn_cb(None)
|
||||
self.ifc.prot_set_update_header_cb(None)
|
||||
self.ifc = None
|
||||
super().close()
|
||||
|
||||
async def send_start_cmd(self, snr: int, host: str,
|
||||
@@ -230,9 +245,10 @@ class SolarmanV5(Message):
|
||||
self._read()
|
||||
while True:
|
||||
if not self.header_valid:
|
||||
self.__parse_header(self._recv_buffer, len(self._recv_buffer))
|
||||
self.__parse_header(self.ifc.rx_peek(),
|
||||
self.ifc.rx_len())
|
||||
|
||||
if self.header_valid and len(self._recv_buffer) >= \
|
||||
if self.header_valid and self.ifc.rx_len() >= \
|
||||
(self.header_len + self.data_len+2):
|
||||
self.__process_complete_received_msg()
|
||||
self.__flush_recv_msg()
|
||||
@@ -243,10 +259,10 @@ class SolarmanV5(Message):
|
||||
log_lvl = self.log_lvl.get(self.control, logging.WARNING)
|
||||
if callable(log_lvl):
|
||||
log_lvl = log_lvl()
|
||||
hex_dump_memory(log_lvl, f'Received from {self.addr}:',
|
||||
self._recv_buffer, self.header_len +
|
||||
self.data_len+2)
|
||||
if self.__trailer_is_ok(self._recv_buffer, self.header_len
|
||||
self.ifc.rx_log(log_lvl, f'Received from {self.addr}:')
|
||||
# self._recv_buffer, self.header_len +
|
||||
# self.data_len+2)
|
||||
if self.__trailer_is_ok(self.ifc.rx_peek(), self.header_len
|
||||
+ self.data_len + 2):
|
||||
if self.state == State.init:
|
||||
self.state = State.received
|
||||
@@ -259,9 +275,8 @@ class SolarmanV5(Message):
|
||||
return
|
||||
tsun = Config.get('solarman')
|
||||
if tsun['enabled']:
|
||||
self._forward_buffer += buffer[:buflen]
|
||||
hex_dump_memory(logging.DEBUG, 'Store for forwarding:',
|
||||
buffer, buflen)
|
||||
self.ifc.fwd_add(buffer[:buflen])
|
||||
self.ifc.fwd_log(logging.DEBUG, 'Store for forwarding:')
|
||||
|
||||
fnc = self.switch.get(self.control, self.msg_unknown)
|
||||
logger.info(self.__flow_str(self.server_side, 'forwrd') +
|
||||
@@ -317,7 +332,7 @@ class SolarmanV5(Message):
|
||||
|
||||
self.inc_counter('Invalid_Msg_Format')
|
||||
# erase broken recv buffer
|
||||
self._recv_buffer = bytearray()
|
||||
self.ifc.rx_clear()
|
||||
return
|
||||
self.header_valid = True
|
||||
|
||||
@@ -329,11 +344,11 @@ class SolarmanV5(Message):
|
||||
'Drop packet w invalid stop byte from '
|
||||
f'{self.addr}:', buf, buf_len)
|
||||
self.inc_counter('Invalid_Msg_Format')
|
||||
if len(self._recv_buffer) > (self.data_len+13):
|
||||
if self.ifc.rx_len() > (self.data_len+13):
|
||||
next_start = buf[self.data_len+13]
|
||||
if next_start != 0xa5:
|
||||
# erase broken recv buffer
|
||||
self._recv_buffer = bytearray()
|
||||
self.ifc.rx_clear()
|
||||
|
||||
return False
|
||||
|
||||
@@ -349,21 +364,22 @@ class SolarmanV5(Message):
|
||||
|
||||
def __build_header(self, ctrl) -> None:
|
||||
'''build header for new transmit message'''
|
||||
self.send_msg_ofs = len(self._send_buffer)
|
||||
self.send_msg_ofs = self.ifc.tx_len()
|
||||
|
||||
self._send_buffer += struct.pack(
|
||||
'<BHHHL', 0xA5, 0, ctrl, self.seq.get_send(), self.snr)
|
||||
self.ifc.tx_add(struct.pack(
|
||||
'<BHHHL', 0xA5, 0, ctrl, self.seq.get_send(), self.snr))
|
||||
fnc = self.switch.get(ctrl, self.msg_unknown)
|
||||
logger.info(self.__flow_str(self.server_side, 'tx') +
|
||||
f' Ctl: {int(ctrl):#04x} Msg: {fnc.__name__!r}')
|
||||
|
||||
def __finish_send_msg(self) -> None:
|
||||
'''finish the transmit message, set lenght and checksum'''
|
||||
_len = len(self._send_buffer) - self.send_msg_ofs
|
||||
struct.pack_into('<H', self._send_buffer, self.send_msg_ofs+1, _len-11)
|
||||
check = sum(self._send_buffer[self.send_msg_ofs+1:self.send_msg_ofs +
|
||||
_len]) & 0xff
|
||||
self._send_buffer += struct.pack('<BB', check, 0x15) # crc & stop
|
||||
_len = self.ifc.tx_len() - self.send_msg_ofs
|
||||
struct.pack_into('<H', self.ifc.tx_peek(), self.send_msg_ofs+1,
|
||||
_len-11)
|
||||
check = sum(self.ifc.tx_peek()[
|
||||
self.send_msg_ofs+1:self.send_msg_ofs + _len]) & 0xff
|
||||
self.ifc.tx_add(struct.pack('<BB', check, 0x15)) # crc & stop
|
||||
|
||||
def _update_header(self, _forward_buffer):
|
||||
'''update header for message before forwarding,
|
||||
@@ -394,15 +410,14 @@ class SolarmanV5(Message):
|
||||
f' Msg: {fnc.__name__!r}')
|
||||
|
||||
def __flush_recv_msg(self) -> None:
|
||||
self._recv_buffer = self._recv_buffer[(self.header_len +
|
||||
self.data_len+2):]
|
||||
self.ifc.rx_get(self.header_len + self.data_len+2)
|
||||
self.header_valid = False
|
||||
|
||||
def __send_ack_rsp(self, msgtype, ftype, ack=1):
|
||||
self.__build_header(msgtype)
|
||||
self._send_buffer += struct.pack('<BBLL', ftype, ack,
|
||||
self._timestamp(),
|
||||
self._heartbeat())
|
||||
self.ifc.tx_add(struct.pack('<BBLL', ftype, ack,
|
||||
self._timestamp(),
|
||||
self._heartbeat()))
|
||||
self.__finish_send_msg()
|
||||
|
||||
def send_modbus_cb(self, pdu: bytearray, log_lvl: int, state: str):
|
||||
@@ -411,14 +426,12 @@ class SolarmanV5(Message):
|
||||
' cause the state is not UP anymore')
|
||||
return
|
||||
self.__build_header(0x4510)
|
||||
self._send_buffer += struct.pack('<BHLLL', self.MB_RTU_CMD,
|
||||
self.sensor_list, 0, 0, 0)
|
||||
self._send_buffer += pdu
|
||||
self.ifc.tx_add(struct.pack('<BHLLL', self.MB_RTU_CMD,
|
||||
self.sensor_list, 0, 0, 0))
|
||||
self.ifc.tx_add(pdu)
|
||||
self.__finish_send_msg()
|
||||
hex_dump_memory(log_lvl, f'Send Modbus {state}:{self.addr}:',
|
||||
self._send_buffer, len(self._send_buffer))
|
||||
self.writer.write(self._send_buffer)
|
||||
self._send_buffer = bytearray(0) # self._send_buffer[sent:]
|
||||
self.ifc.tx_log(log_lvl, f'Send Modbus {state}:{self.addr}:')
|
||||
self.ifc.tx_flush()
|
||||
|
||||
def _send_modbus_cmd(self, func, addr, val, log_lvl) -> None:
|
||||
if self.state != State.up:
|
||||
@@ -460,17 +473,18 @@ class SolarmanV5(Message):
|
||||
|
||||
self.forward_at_cmd_resp = False
|
||||
self.__build_header(0x4510)
|
||||
self._send_buffer += struct.pack(f'<BHLLL{len(at_cmd)}sc', self.AT_CMD,
|
||||
0x0002, 0, 0, 0,
|
||||
at_cmd.encode('utf-8'), b'\r')
|
||||
self.ifc.tx_add(struct.pack(f'<BHLLL{len(at_cmd)}sc', self.AT_CMD,
|
||||
0x0002, 0, 0, 0,
|
||||
at_cmd.encode('utf-8'), b'\r'))
|
||||
self.__finish_send_msg()
|
||||
self.ifc.tx_log(logging.INFO, 'Send AT Command:')
|
||||
try:
|
||||
await self.async_write('Send AT Command:')
|
||||
self.ifc.tx_flush()
|
||||
except Exception:
|
||||
self._send_buffer = bytearray(0)
|
||||
self.ifc.tx_clear()
|
||||
|
||||
def __forward_msg(self):
|
||||
self.forward(self._recv_buffer, 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):
|
||||
db = self.db
|
||||
@@ -491,7 +505,7 @@ class SolarmanV5(Message):
|
||||
def __process_data(self, ftype, ts):
|
||||
inv_update = False
|
||||
msg_type = self.control >> 8
|
||||
for key, update in self.db.parse(self._recv_buffer, msg_type, ftype,
|
||||
for key, update in self.db.parse(self.ifc.rx_peek(), msg_type, ftype,
|
||||
self.node_id):
|
||||
if update:
|
||||
if key == 'inverter':
|
||||
@@ -510,7 +524,7 @@ class SolarmanV5(Message):
|
||||
self.__forward_msg()
|
||||
|
||||
def msg_dev_ind(self):
|
||||
data = self._recv_buffer[self.header_len:]
|
||||
data = self.ifc.rx_peek()[self.header_len:]
|
||||
result = struct.unpack_from(self.HDR_FMT, data, 0)
|
||||
ftype = result[0] # always 2
|
||||
total = result[1]
|
||||
@@ -531,7 +545,7 @@ class SolarmanV5(Message):
|
||||
self.__send_ack_rsp(0x1110, ftype)
|
||||
|
||||
def msg_data_ind(self):
|
||||
data = self._recv_buffer
|
||||
data = self.ifc.rx_peek()
|
||||
result = struct.unpack_from('<BHLLLHL', data, self.header_len)
|
||||
ftype = result[0] # 1 or 0x81
|
||||
sensor = result[1]
|
||||
@@ -559,7 +573,7 @@ class SolarmanV5(Message):
|
||||
self.new_state_up()
|
||||
|
||||
def msg_sync_start(self):
|
||||
data = self._recv_buffer[self.header_len:]
|
||||
data = self.ifc.rx_peek()[self.header_len:]
|
||||
result = struct.unpack_from(self.HDR_FMT, data, 0)
|
||||
ftype = result[0]
|
||||
total = result[1]
|
||||
@@ -572,8 +586,8 @@ class SolarmanV5(Message):
|
||||
self.__send_ack_rsp(0x1310, ftype)
|
||||
|
||||
def msg_command_req(self):
|
||||
data = self._recv_buffer[self.header_len:
|
||||
self.header_len+self.data_len]
|
||||
data = self.ifc.rx_peek()[self.header_len:
|
||||
self.header_len+self.data_len]
|
||||
result = struct.unpack_from('<B', data, 0)
|
||||
ftype = result[0]
|
||||
if ftype == self.AT_CMD:
|
||||
@@ -585,9 +599,9 @@ class SolarmanV5(Message):
|
||||
self.forward_at_cmd_resp = True
|
||||
|
||||
elif ftype == self.MB_RTU_CMD:
|
||||
if self.remote_stream.mb.recv_req(data[15:],
|
||||
self.remote_stream.
|
||||
__forward_msg):
|
||||
rstream = self.ifc.remote.stream
|
||||
if rstream.mb.recv_req(data[15:],
|
||||
rstream.__forward_msg):
|
||||
self.inc_counter('Modbus_Command')
|
||||
else:
|
||||
logger.error('Invalid Modbus Msg')
|
||||
@@ -601,7 +615,7 @@ class SolarmanV5(Message):
|
||||
self.mqtt.publish(key, data))
|
||||
|
||||
def get_cmd_rsp_log_lvl(self) -> int:
|
||||
ftype = self._recv_buffer[self.header_len]
|
||||
ftype = self.ifc.rx_peek()[self.header_len]
|
||||
if ftype == self.AT_CMD:
|
||||
if self.forward_at_cmd_resp:
|
||||
return logging.INFO
|
||||
@@ -613,8 +627,8 @@ class SolarmanV5(Message):
|
||||
return logging.WARNING
|
||||
|
||||
def msg_command_rsp(self):
|
||||
data = self._recv_buffer[self.header_len:
|
||||
self.header_len+self.data_len]
|
||||
data = self.ifc.rx_peek()[self.header_len:
|
||||
self.header_len+self.data_len]
|
||||
ftype = data[0]
|
||||
if ftype == self.AT_CMD:
|
||||
if not self.forward_at_cmd_resp:
|
||||
@@ -650,7 +664,7 @@ class SolarmanV5(Message):
|
||||
self.__build_model_name()
|
||||
|
||||
def msg_hbeat_ind(self):
|
||||
data = self._recv_buffer[self.header_len:]
|
||||
data = self.ifc.rx_peek()[self.header_len:]
|
||||
result = struct.unpack_from('<B', data, 0)
|
||||
ftype = result[0]
|
||||
|
||||
@@ -659,7 +673,7 @@ class SolarmanV5(Message):
|
||||
self.new_state_up()
|
||||
|
||||
def msg_sync_end(self):
|
||||
data = self._recv_buffer[self.header_len:]
|
||||
data = self.ifc.rx_peek()[self.header_len:]
|
||||
result = struct.unpack_from(self.HDR_FMT, data, 0)
|
||||
ftype = result[0]
|
||||
total = result[1]
|
||||
@@ -672,7 +686,7 @@ class SolarmanV5(Message):
|
||||
self.__send_ack_rsp(0x1810, ftype)
|
||||
|
||||
def msg_response(self):
|
||||
data = self._recv_buffer[self.header_len:]
|
||||
data = self.ifc.rx_peek()[self.header_len:]
|
||||
result = struct.unpack_from('<BBLL', data, 0)
|
||||
ftype = result[0] # always 2
|
||||
valid = result[1] == 1 # status
|
||||
|
||||
@@ -378,15 +378,19 @@ class Infos:
|
||||
|
||||
return None # unknwon idx, not in info_defs
|
||||
|
||||
def inc_counter(self, counter: str) -> None:
|
||||
@classmethod
|
||||
def inc_counter(cls, counter: str) -> None:
|
||||
'''inc proxy statistic counter'''
|
||||
db_dict = self.stat['proxy']
|
||||
db_dict = cls.stat['proxy']
|
||||
db_dict[counter] += 1
|
||||
cls.new_stat_data['proxy'] = True
|
||||
|
||||
def dec_counter(self, counter: str) -> None:
|
||||
@classmethod
|
||||
def dec_counter(cls, counter: str) -> None:
|
||||
'''dec proxy statistic counter'''
|
||||
db_dict = self.stat['proxy']
|
||||
db_dict = cls.stat['proxy']
|
||||
db_dict[counter] -= 1
|
||||
cls.new_stat_data['proxy'] = True
|
||||
|
||||
def ha_proxy_confs(self, ha_prfx: str, node_id: str, snr: str) \
|
||||
-> Generator[tuple[str, str, str, str], None, None]:
|
||||
|
||||
183
app/src/inverter_base.py
Normal file
183
app/src/inverter_base.py
Normal file
@@ -0,0 +1,183 @@
|
||||
import weakref
|
||||
import asyncio
|
||||
import logging
|
||||
import traceback
|
||||
import json
|
||||
import gc
|
||||
from aiomqtt import MqttCodeError
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
|
||||
if __name__ == "app.src.inverter_base":
|
||||
from app.src.inverter_ifc import InverterIfc
|
||||
from app.src.proxy import Proxy
|
||||
from app.src.async_stream import StreamPtr
|
||||
from app.src.async_stream import AsyncStreamClient
|
||||
from app.src.async_stream import AsyncStreamServer
|
||||
from app.src.config import Config
|
||||
from app.src.infos import Infos
|
||||
else: # pragma: no cover
|
||||
from inverter_ifc import InverterIfc
|
||||
from proxy import Proxy
|
||||
from async_stream import StreamPtr
|
||||
from async_stream import AsyncStreamClient
|
||||
from async_stream import AsyncStreamServer
|
||||
from config import Config
|
||||
from infos import Infos
|
||||
|
||||
logger_mqtt = logging.getLogger('mqtt')
|
||||
|
||||
|
||||
class InverterBase(InverterIfc, Proxy):
|
||||
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
config_id: str, prot_class,
|
||||
client_mode: bool = False):
|
||||
Proxy.__init__(self)
|
||||
self._registry.append(weakref.ref(self))
|
||||
self.addr = writer.get_extra_info('peername')
|
||||
self.config_id = config_id
|
||||
self.prot_class = prot_class
|
||||
self.__ha_restarts = -1
|
||||
self.remote = StreamPtr(None)
|
||||
ifc = AsyncStreamServer(reader, writer,
|
||||
self.async_publ_mqtt,
|
||||
self.create_remote,
|
||||
self.remote)
|
||||
|
||||
self.local = StreamPtr(
|
||||
self.prot_class(self.addr, ifc, True, client_mode), ifc
|
||||
)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> None:
|
||||
logging.debug(f'InverterBase.__exit__() {self.addr}')
|
||||
self.__del_remote()
|
||||
|
||||
self.local.stream.close()
|
||||
self.local.stream = None
|
||||
self.local.ifc.close()
|
||||
self.local.ifc = None
|
||||
|
||||
# now explicitly call garbage collector to release unreachable objects
|
||||
unreachable_obj = gc.collect()
|
||||
logging.debug(
|
||||
f'InverterBase.__exit: freed unreachable obj: {unreachable_obj}')
|
||||
|
||||
def __del_remote(self):
|
||||
if self.remote.stream:
|
||||
self.remote.stream.close()
|
||||
self.remote.stream = None
|
||||
|
||||
if self.remote.ifc:
|
||||
self.remote.ifc.close()
|
||||
self.remote.ifc = None
|
||||
|
||||
async def disc(self, shutdown_started=False) -> None:
|
||||
if self.remote.stream:
|
||||
self.remote.stream.shutdown_started = shutdown_started
|
||||
if self.remote.ifc:
|
||||
await self.remote.ifc.disc()
|
||||
if self.local.stream:
|
||||
self.local.stream.shutdown_started = shutdown_started
|
||||
if self.local.ifc:
|
||||
await self.local.ifc.disc()
|
||||
|
||||
def healthy(self) -> bool:
|
||||
logging.debug('InverterBase healthy()')
|
||||
|
||||
if self.local.ifc and not self.local.ifc.healthy():
|
||||
return False
|
||||
if self.remote.ifc and not self.remote.ifc.healthy():
|
||||
return False
|
||||
return True
|
||||
|
||||
async def create_remote(self) -> None:
|
||||
'''Establish a client connection to the TSUN cloud'''
|
||||
|
||||
tsun = Config.get(self.config_id)
|
||||
host = tsun['host']
|
||||
port = tsun['port']
|
||||
addr = (host, port)
|
||||
stream = self.local.stream
|
||||
|
||||
try:
|
||||
logging.info(f'[{stream.node_id}] Connect to {addr}')
|
||||
connect = asyncio.open_connection(host, port)
|
||||
reader, writer = await connect
|
||||
ifc = AsyncStreamClient(
|
||||
reader, writer, self.local, self.__del_remote)
|
||||
|
||||
self.remote.ifc = ifc
|
||||
if hasattr(stream, 'id_str'):
|
||||
self.remote.stream = self.prot_class(
|
||||
addr, ifc, server_side=False,
|
||||
client_mode=False, id_str=stream.id_str)
|
||||
else:
|
||||
self.remote.stream = self.prot_class(
|
||||
addr, ifc, server_side=False,
|
||||
client_mode=False)
|
||||
|
||||
logging.info(f'[{self.remote.stream.node_id}:'
|
||||
f'{self.remote.stream.conn_no}] '
|
||||
f'Connected to {addr}')
|
||||
asyncio.create_task(self.remote.ifc.client_loop(addr))
|
||||
|
||||
except (ConnectionRefusedError, TimeoutError) as error:
|
||||
logging.info(f'{error}')
|
||||
except Exception:
|
||||
Infos.inc_counter('SW_Exception')
|
||||
logging.error(
|
||||
f"Inverter: Exception for {addr}:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
|
||||
async def async_publ_mqtt(self) -> None:
|
||||
'''publish data to MQTT broker'''
|
||||
stream = self.local.stream
|
||||
if not stream or not stream.unique_id:
|
||||
return
|
||||
# check if new inverter or collector infos are available or when the
|
||||
# home assistant has changed the status back to online
|
||||
try:
|
||||
if (('inverter' in stream.new_data and stream.new_data['inverter'])
|
||||
or ('collector' in stream.new_data and
|
||||
stream.new_data['collector'])
|
||||
or self.mqtt.ha_restarts != self.__ha_restarts):
|
||||
await self._register_proxy_stat_home_assistant()
|
||||
await self.__register_home_assistant(stream)
|
||||
self.__ha_restarts = self.mqtt.ha_restarts
|
||||
|
||||
for key in stream.new_data:
|
||||
await self.__async_publ_mqtt_packet(stream, key)
|
||||
for key in Infos.new_stat_data:
|
||||
await Proxy._async_publ_mqtt_proxy_stat(key)
|
||||
|
||||
except MqttCodeError as error:
|
||||
logging.error(f'Mqtt except: {error}')
|
||||
except Exception:
|
||||
Infos.inc_counter('SW_Exception')
|
||||
logging.error(
|
||||
f"Inverter: Exception:\n"
|
||||
f"{traceback.format_exc()}")
|
||||
|
||||
async def __async_publ_mqtt_packet(self, stream, key):
|
||||
db = stream.db.db
|
||||
if key in db and stream.new_data[key]:
|
||||
data_json = json.dumps(db[key])
|
||||
node_id = stream.node_id
|
||||
logger_mqtt.debug(f'{key}: {data_json}')
|
||||
await self.mqtt.publish(f'{self.entity_prfx}{node_id}{key}', data_json) # noqa: E501
|
||||
stream.new_data[key] = False
|
||||
|
||||
async def __register_home_assistant(self, stream) -> None:
|
||||
'''register all our topics at home assistant'''
|
||||
for data_json, component, node_id, id in stream.db.ha_confs(
|
||||
self.entity_prfx, stream.node_id, stream.unique_id,
|
||||
stream.sug_area):
|
||||
logger_mqtt.debug(f"MQTT Register: cmp:'{component}'"
|
||||
f" node_id:'{node_id}' {data_json}")
|
||||
await self.mqtt.publish(f"{self.discovery_prfx}{component}"
|
||||
f"/{node_id}{id}/config", data_json)
|
||||
|
||||
stream.db.reg_clr_at_midnight(f'{self.entity_prfx}{stream.node_id}')
|
||||
40
app/src/inverter_ifc.py
Normal file
40
app/src/inverter_ifc.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from abc import abstractmethod
|
||||
import logging
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
|
||||
if __name__ == "app.src.inverter_ifc":
|
||||
from app.src.iter_registry import AbstractIterMeta
|
||||
else: # pragma: no cover
|
||||
from iter_registry import AbstractIterMeta
|
||||
|
||||
logger_mqtt = logging.getLogger('mqtt')
|
||||
|
||||
|
||||
class InverterIfc(metaclass=AbstractIterMeta):
|
||||
_registry = []
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, reader: StreamReader, writer: StreamWriter,
|
||||
config_id: str, prot_class,
|
||||
client_mode: bool):
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def __enter__(self):
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def __exit__(self, exc_type, exc, tb):
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def healthy(self) -> bool:
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
async def disc(self, shutdown_started=False) -> None:
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
async def create_remote(self) -> None:
|
||||
pass # pragma: no cover
|
||||
9
app/src/iter_registry.py
Normal file
9
app/src/iter_registry.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class AbstractIterMeta(ABCMeta):
|
||||
def __iter__(cls):
|
||||
for ref in cls._registry:
|
||||
obj = ref()
|
||||
if obj is not None:
|
||||
yield obj
|
||||
@@ -1,13 +1,15 @@
|
||||
import logging
|
||||
import weakref
|
||||
from typing import Callable, Generator
|
||||
from typing import Callable
|
||||
from enum import Enum
|
||||
|
||||
|
||||
if __name__ == "app.src.messages":
|
||||
from app.src.protocol_ifc import ProtocolIfc
|
||||
from app.src.infos import Infos, Register
|
||||
from app.src.modbus import Modbus
|
||||
else: # pragma: no cover
|
||||
from protocol_ifc import ProtocolIfc
|
||||
from infos import Infos, Register
|
||||
from modbus import Modbus
|
||||
|
||||
@@ -33,13 +35,9 @@ def __asc_val(n, data, data_len):
|
||||
return line
|
||||
|
||||
|
||||
def hex_dump_memory(level, info, data, data_len):
|
||||
def hex_dump(data, data_len) -> list:
|
||||
n = 0
|
||||
lines = []
|
||||
lines.append(info)
|
||||
tracer = logging.getLogger('tracer')
|
||||
if not tracer.isEnabledFor(level):
|
||||
return
|
||||
|
||||
for i in range(0, data_len, 16):
|
||||
line = ' '
|
||||
@@ -50,17 +48,26 @@ def hex_dump_memory(level, info, data, data_len):
|
||||
line += __asc_val(n, data, data_len)
|
||||
lines.append(line)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def hex_dump_str(data, data_len):
|
||||
lines = hex_dump(data, data_len)
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def hex_dump_memory(level, info, data, data_len):
|
||||
lines = []
|
||||
lines.append(info)
|
||||
tracer = logging.getLogger('tracer')
|
||||
if not tracer.isEnabledFor(level):
|
||||
return
|
||||
|
||||
lines += hex_dump(data, data_len)
|
||||
|
||||
tracer.log(level, '\n'.join(lines))
|
||||
|
||||
|
||||
class IterRegistry(type):
|
||||
def __iter__(cls) -> Generator['Message', None, None]:
|
||||
for ref in cls._registry:
|
||||
obj = ref()
|
||||
if obj is not None:
|
||||
yield obj
|
||||
|
||||
|
||||
class State(Enum):
|
||||
'''state of the logical connection'''
|
||||
init = 0
|
||||
@@ -75,8 +82,13 @@ class State(Enum):
|
||||
'''connection closed'''
|
||||
|
||||
|
||||
class Message(metaclass=IterRegistry):
|
||||
_registry = []
|
||||
class Message(ProtocolIfc):
|
||||
MAX_START_TIME = 400
|
||||
'''maximum time without a received msg in sec'''
|
||||
MAX_INV_IDLE_TIME = 120
|
||||
'''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:
|
||||
Callable[[bytes, int, str], None], mb_timeout: int):
|
||||
@@ -92,15 +104,21 @@ class Message(metaclass=IterRegistry):
|
||||
self.header_len = 0
|
||||
self.data_len = 0
|
||||
self.unique_id = 0
|
||||
self.node_id = '' # will be overwritten in the child class's __init__
|
||||
self._node_id = ''
|
||||
self.sug_area = ''
|
||||
self._recv_buffer = bytearray(0)
|
||||
self._send_buffer = bytearray(0)
|
||||
self._forward_buffer = bytearray(0)
|
||||
self.new_data = {}
|
||||
self.state = State.init
|
||||
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
|
||||
don't use asyncio
|
||||
@@ -109,10 +127,6 @@ class Message(metaclass=IterRegistry):
|
||||
# to our _recv_buffer
|
||||
return # pragma: no cover
|
||||
|
||||
def _update_header(self, _forward_buffer):
|
||||
'''callback for updating the header of the forward buffer'''
|
||||
pass # pragma: no cover
|
||||
|
||||
def _set_mqtt_timestamp(self, key, ts: float | None):
|
||||
if key not in self.new_data or \
|
||||
not self.new_data[key]:
|
||||
@@ -128,6 +142,16 @@ class Message(metaclass=IterRegistry):
|
||||
# logger.info(f'update: key: {key} ts:{tstr}'
|
||||
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
|
||||
'''
|
||||
|
||||
@@ -117,10 +117,6 @@ class Modbus():
|
||||
while not self.que.empty():
|
||||
self.que.get_nowait()
|
||||
|
||||
def __del__(self):
|
||||
"""log statistics on the deleting of a MODBUS instance"""
|
||||
logging.debug(f'Modbus __del__:\n {self.counter}')
|
||||
|
||||
def build_msg(self, addr: int, func: int, reg: int, val: int,
|
||||
log_lvl=logging.DEBUG) -> None:
|
||||
"""Build MODBUS RTU request frame and add it to the tx queue
|
||||
|
||||
@@ -5,9 +5,11 @@ import asyncio
|
||||
if __name__ == "app.src.modbus_tcp":
|
||||
from app.src.config import Config
|
||||
from app.src.gen3plus.inverter_g3p import InverterG3P
|
||||
from app.src.infos import Infos
|
||||
else: # pragma: no cover
|
||||
from config import Config
|
||||
from gen3plus.inverter_g3p import InverterG3P
|
||||
from infos import Infos
|
||||
|
||||
logger = logging.getLogger('conn')
|
||||
|
||||
@@ -17,23 +19,26 @@ class ModbusConn():
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.addr = (host, port)
|
||||
self.stream = None
|
||||
self.inverter = None
|
||||
|
||||
async def __aenter__(self) -> 'InverterG3P':
|
||||
'''Establish a client connection to the TSUN cloud'''
|
||||
connection = asyncio.open_connection(self.host, self.port)
|
||||
reader, writer = await connection
|
||||
self.stream = InverterG3P(reader, writer, self.addr,
|
||||
client_mode=True)
|
||||
logging.info(f'[{self.stream.node_id}:{self.stream.conn_no}] '
|
||||
self.inverter = InverterG3P(reader, writer,
|
||||
client_mode=True)
|
||||
self.inverter.__enter__()
|
||||
stream = self.inverter.local.stream
|
||||
logging.info(f'[{stream.node_id}:{stream.conn_no}] '
|
||||
f'Connected to {self.addr}')
|
||||
self.stream.inc_counter('Inverter_Cnt')
|
||||
await self.stream.publish_outstanding_mqtt()
|
||||
return self.stream
|
||||
Infos.inc_counter('Inverter_Cnt')
|
||||
await self.inverter.local.ifc.publish_outstanding_mqtt()
|
||||
return self.inverter
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
self.stream.dec_counter('Inverter_Cnt')
|
||||
await self.stream.publish_outstanding_mqtt()
|
||||
Infos.dec_counter('Inverter_Cnt')
|
||||
await self.inverter.local.ifc.publish_outstanding_mqtt()
|
||||
self.inverter.__exit__(exc_type, exc, tb)
|
||||
|
||||
|
||||
class ModbusTcp():
|
||||
@@ -58,20 +63,22 @@ class ModbusTcp():
|
||||
'''Loop for receiving messages from the TSUN cloud (client-side)'''
|
||||
while True:
|
||||
try:
|
||||
async with ModbusConn(host, port) as stream:
|
||||
async with ModbusConn(host, port) as inverter:
|
||||
stream = inverter.local.stream
|
||||
await stream.send_start_cmd(snr, host)
|
||||
await stream.loop()
|
||||
await stream.ifc.loop()
|
||||
logger.info(f'[{stream.node_id}:{stream.conn_no}] '
|
||||
f'Connection closed - Shutdown: '
|
||||
f'{stream.shutdown_started}')
|
||||
if stream.shutdown_started:
|
||||
return
|
||||
del inverter # decrease ref counter after the with block
|
||||
|
||||
except (ConnectionRefusedError, TimeoutError) as error:
|
||||
logging.debug(f'Inv-conn:{error}')
|
||||
|
||||
except OSError as error:
|
||||
if error.errno == 113:
|
||||
if error.errno == 113: # pragma: no cover
|
||||
logging.debug(f'os-error:{error}')
|
||||
else:
|
||||
logging.info(f'os-error: {error}')
|
||||
|
||||
@@ -44,9 +44,6 @@ class Mqtt(metaclass=Singleton):
|
||||
def ha_restarts(self, value):
|
||||
self._ha_restarts = value
|
||||
|
||||
def __del__(self):
|
||||
logger_mqtt.debug('MQTT: __del__')
|
||||
|
||||
async def close(self) -> None:
|
||||
logger_mqtt.debug('MQTT: close')
|
||||
self.task.cancel()
|
||||
|
||||
21
app/src/protocol_ifc.py
Normal file
21
app/src/protocol_ifc.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from abc import abstractmethod
|
||||
|
||||
if __name__ == "app.src.protocol_ifc":
|
||||
from app.src.iter_registry import AbstractIterMeta
|
||||
from app.src.async_ifc import AsyncIfc
|
||||
else: # pragma: no cover
|
||||
from iter_registry import AbstractIterMeta
|
||||
from async_ifc import AsyncIfc
|
||||
|
||||
|
||||
class ProtocolIfc(metaclass=AbstractIterMeta):
|
||||
_registry = []
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, addr, ifc: "AsyncIfc", server_side: bool,
|
||||
client_mode: bool = False, id_str=b''):
|
||||
pass # pragma: no cover
|
||||
|
||||
@abstractmethod
|
||||
def close(self):
|
||||
pass # pragma: no cover
|
||||
@@ -1,7 +1,8 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
if __name__ == "app.src.inverter":
|
||||
|
||||
if __name__ == "app.src.proxy":
|
||||
from app.src.config import Config
|
||||
from app.src.mqtt import Mqtt
|
||||
from app.src.infos import Infos
|
||||
@@ -13,10 +14,32 @@ else: # pragma: no cover
|
||||
logger_mqtt = logging.getLogger('mqtt')
|
||||
|
||||
|
||||
class Inverter():
|
||||
class Proxy():
|
||||
'''class Proxy is a baseclass
|
||||
|
||||
The class has some class method for managing common resources like a
|
||||
connection to the MQTT broker or proxy error counter which are common
|
||||
for all inverter connection
|
||||
|
||||
Instances of the class are connections to an inverter and can have an
|
||||
optional link to an remote connection to the TSUN cloud. A remote
|
||||
connection dies with the inverter connection.
|
||||
|
||||
class methods:
|
||||
class_init(): initialize the common resources of the proxy (MQTT
|
||||
broker, Proxy DB, etc). Must be called before the
|
||||
first inverter instance can be created
|
||||
class_close(): release the common resources of the proxy. Should not
|
||||
be called before any instances of the class are
|
||||
destroyed
|
||||
|
||||
methods:
|
||||
create_remote(): Establish a client connection to the TSUN cloud
|
||||
async_publ_mqtt(): Publish data to MQTT broker
|
||||
'''
|
||||
@classmethod
|
||||
def class_init(cls) -> None:
|
||||
logging.debug('Inverter.class_init')
|
||||
logging.debug('Proxy.class_init')
|
||||
# initialize the proxy statistics
|
||||
Infos.static_init()
|
||||
cls.db_stat = Infos()
|
||||
@@ -38,7 +61,7 @@ class Inverter():
|
||||
# reset at midnight when you restart the proxy just before
|
||||
# midnight!
|
||||
inverters = Config.get('inverters')
|
||||
# logger.debug(f'Inverters: {inverters}')
|
||||
# logger.debug(f'Proxys: {inverters}')
|
||||
for inv in inverters.values():
|
||||
if (type(inv) is dict):
|
||||
node_id = inv['node_id']
|
||||
@@ -77,7 +100,7 @@ class Inverter():
|
||||
|
||||
@classmethod
|
||||
def class_close(cls, loop) -> None: # pragma: no cover
|
||||
logging.debug('Inverter.class_close')
|
||||
logging.debug('Proxy.class_close')
|
||||
logging.info('Close MQTT Task')
|
||||
loop.run_until_complete(cls.mqtt.close())
|
||||
cls.mqtt = None
|
||||
@@ -5,8 +5,8 @@ import os
|
||||
from asyncio import StreamReader, StreamWriter
|
||||
from aiohttp import web
|
||||
from logging import config # noqa F401
|
||||
from messages import Message
|
||||
from inverter import Inverter
|
||||
from proxy import Proxy
|
||||
from inverter_ifc import InverterIfc
|
||||
from gen3.inverter_g3 import InverterG3
|
||||
from gen3plus.inverter_g3p import InverterG3P
|
||||
from scheduler import Schedule
|
||||
@@ -38,9 +38,9 @@ async def healthy(request):
|
||||
|
||||
if proxy_is_up:
|
||||
# logging.info('web reqeust healthy()')
|
||||
for stream in Message:
|
||||
for inverter in InverterIfc:
|
||||
try:
|
||||
res = stream.healthy()
|
||||
res = inverter.healthy()
|
||||
if not res:
|
||||
return web.Response(status=503, text="I have a problem")
|
||||
except Exception as err:
|
||||
@@ -70,18 +70,11 @@ async def webserver(addr, port):
|
||||
logging.debug('HTTP cleanup done')
|
||||
|
||||
|
||||
async def handle_client(reader: StreamReader, writer: StreamWriter):
|
||||
async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class):
|
||||
'''Handles a new incoming connection and starts an async loop'''
|
||||
|
||||
addr = writer.get_extra_info('peername')
|
||||
await InverterG3(reader, writer, addr).server_loop(addr)
|
||||
|
||||
|
||||
async def handle_client_v2(reader: StreamReader, writer: StreamWriter):
|
||||
'''Handles a new incoming connection and starts an async loop'''
|
||||
|
||||
addr = writer.get_extra_info('peername')
|
||||
await InverterG3P(reader, writer, addr).server_loop(addr)
|
||||
with inv_class(reader, writer) as inv:
|
||||
await inv.local.ifc.server_loop()
|
||||
|
||||
|
||||
async def handle_shutdown(web_task):
|
||||
@@ -94,25 +87,13 @@ async def handle_shutdown(web_task):
|
||||
#
|
||||
# first, disc all open TCP connections gracefully
|
||||
#
|
||||
for stream in Message:
|
||||
stream.shutdown_started = True
|
||||
try:
|
||||
await asyncio.wait_for(stream.disc(), 2)
|
||||
except Exception:
|
||||
pass
|
||||
for inverter in InverterIfc:
|
||||
await inverter.disc(True)
|
||||
|
||||
logging.info('Proxy disconnecting done')
|
||||
|
||||
#
|
||||
# second, close all open TCP connections
|
||||
#
|
||||
for stream in Message:
|
||||
stream.close()
|
||||
|
||||
await asyncio.sleep(0.1) # give time for closing
|
||||
logging.info('Proxy closing done')
|
||||
|
||||
#
|
||||
# third, cancel the web server
|
||||
# second, cancel the web server
|
||||
#
|
||||
web_task.cancel()
|
||||
await web_task
|
||||
@@ -171,17 +152,19 @@ if __name__ == "__main__":
|
||||
ConfigErr = Config.class_init()
|
||||
if ConfigErr is not None:
|
||||
logging.info(f'ConfigErr: {ConfigErr}')
|
||||
Inverter.class_init()
|
||||
Proxy.class_init()
|
||||
Schedule.start()
|
||||
mb_tcp = ModbusTcp(loop)
|
||||
ModbusTcp(loop)
|
||||
|
||||
#
|
||||
# Create tasks for our listening servers. These must be tasks! If we call
|
||||
# start_server directly out of our main task, the eventloop will be blocked
|
||||
# and we can't receive and handle the UNIX signals!
|
||||
#
|
||||
loop.create_task(asyncio.start_server(handle_client, '0.0.0.0', 5005))
|
||||
loop.create_task(asyncio.start_server(handle_client_v2, '0.0.0.0', 10000))
|
||||
for inv_class, port in [(InverterG3, 5005), (InverterG3P, 10000)]:
|
||||
loop.create_task(asyncio.start_server(lambda r, w, i=inv_class:
|
||||
handle_client(r, w, i),
|
||||
'0.0.0.0', port))
|
||||
web_task = loop.create_task(webserver('0.0.0.0', 8127))
|
||||
|
||||
#
|
||||
@@ -202,7 +185,7 @@ if __name__ == "__main__":
|
||||
pass
|
||||
finally:
|
||||
logging.info("Event loop is stopped")
|
||||
Inverter.class_close(loop)
|
||||
Proxy.class_close(loop)
|
||||
logging.debug('Close event loop')
|
||||
loop.close()
|
||||
logging.info(f'Finally, exit Server "{serv_name}"')
|
||||
|
||||
532
app/tests/test_async_stream.py
Normal file
532
app/tests/test_async_stream.py
Normal file
@@ -0,0 +1,532 @@
|
||||
# test_with_pytest.py
|
||||
import pytest
|
||||
import asyncio
|
||||
import gc
|
||||
import time
|
||||
|
||||
from app.src.infos import Infos
|
||||
from app.src.inverter_base import InverterBase
|
||||
from app.src.async_stream import AsyncStreamServer, AsyncStreamClient, StreamPtr
|
||||
from app.src.messages import Message
|
||||
from app.tests.test_modbus_tcp import FakeReader, FakeWriter
|
||||
from app.tests.test_inverter_base import config_conn, patch_open_connection
|
||||
|
||||
pytest_plugins = ('pytest_asyncio',)
|
||||
|
||||
# initialize the proxy statistics
|
||||
Infos.static_init()
|
||||
|
||||
class FakeProto(Message):
|
||||
def __init__(self, server_side):
|
||||
super().__init__(server_side, None, 10)
|
||||
self.conn_no = 0
|
||||
|
||||
def fake_reader_fwd():
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_13_BYTES
|
||||
reader.on_recv.set()
|
||||
return reader
|
||||
|
||||
def test_timeout_cb():
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
def timeout():
|
||||
return 13
|
||||
|
||||
ifc = AsyncStreamClient(reader, writer, None, None)
|
||||
assert 360 == ifc._AsyncStream__timeout()
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
assert 13 == ifc._AsyncStream__timeout()
|
||||
ifc.prot_set_timeout_cb(None)
|
||||
assert 360 == ifc._AsyncStream__timeout()
|
||||
|
||||
# call healthy outside the contexter manager (__exit__() was called)
|
||||
assert ifc.healthy()
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
def test_health():
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
ifc = AsyncStreamClient(reader, writer, None, None)
|
||||
ifc.proc_start = time.time()
|
||||
assert ifc.healthy()
|
||||
ifc.proc_start = time.time() -10
|
||||
assert not ifc.healthy()
|
||||
ifc.proc_start = None
|
||||
assert ifc.healthy()
|
||||
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_cb():
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
def timeout():
|
||||
return 0.1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamClient(reader, writer, None, closed)
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
await ifc.client_loop('')
|
||||
assert cnt == 1
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
await ifc.client_loop('')
|
||||
assert cnt == 1 # check that the closed method would not be called
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamClient(reader, writer, None, None)
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
await ifc.client_loop('')
|
||||
assert cnt == 0
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_13_BYTES
|
||||
reader.on_recv.set()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
def timeout():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
def app_read():
|
||||
nonlocal ifc
|
||||
ifc.proc_start -= 3
|
||||
return 0.01 # async wait of 0.01
|
||||
cnt = 0
|
||||
ifc = AsyncStreamClient(reader, writer, None, closed)
|
||||
ifc.proc_max = 0
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
ifc.rx_set_cb(app_read)
|
||||
await ifc.client_loop('')
|
||||
print('End loop')
|
||||
assert ifc.proc_max >= 3
|
||||
assert 13 == ifc.rx_len()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_13_BYTES
|
||||
reader.on_recv.set()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
def timeout():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
def app_read():
|
||||
nonlocal ifc
|
||||
ifc.proc_start -= 3
|
||||
return 0.01 # async wait of 0.01
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamClient(reader, writer, None, closed)
|
||||
ifc.proc_max = 10
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
ifc.rx_set_cb(app_read)
|
||||
ifc.tx_add(b'test-data-resp')
|
||||
assert 14 == ifc.tx_len()
|
||||
await ifc.client_loop('')
|
||||
print('End loop')
|
||||
assert ifc.proc_max >= 3
|
||||
assert 13 == ifc.rx_len()
|
||||
assert 0 == ifc.tx_len()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_publ_mqtt_cb():
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_13_BYTES
|
||||
reader.on_recv.set()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
def timeout():
|
||||
return 0.1
|
||||
async def publ_mqtt():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(reader, writer, publ_mqtt, None, None)
|
||||
assert ifc.async_publ_mqtt
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
await ifc.server_loop()
|
||||
assert cnt == 3 # 2 calls in server_loop() and 1 in loop()
|
||||
assert ifc.async_publ_mqtt
|
||||
ifc.close() # clears the closed callback
|
||||
assert not ifc.async_publ_mqtt
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_remote_cb():
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
def timeout():
|
||||
return 0.1
|
||||
async def create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(reader, writer, None, create_remote, None)
|
||||
assert ifc.create_remote
|
||||
await ifc.create_remote()
|
||||
assert cnt == 1
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
await ifc.server_loop()
|
||||
assert not ifc.create_remote
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sw_exception():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_SW_EXCEPT
|
||||
reader.on_recv.set()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
def timeout():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
cnt = 0
|
||||
ifc = AsyncStreamClient(reader, writer, None, closed)
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
await ifc.client_loop('')
|
||||
print('End loop')
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_os_error():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_OS_ERROR
|
||||
|
||||
reader.on_recv.set()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
def timeout():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
cnt = 0
|
||||
ifc = AsyncStreamClient(reader, writer, None, closed)
|
||||
ifc.prot_set_timeout_cb(timeout)
|
||||
await ifc.client_loop('')
|
||||
print('End loop')
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
class TestType():
|
||||
FWD_NO_EXCPT = 1
|
||||
FWD_SW_EXCPT = 2
|
||||
FWD_TIMEOUT = 3
|
||||
FWD_OS_ERROR = 4
|
||||
FWD_OS_ERROR_NO_STREAM = 5
|
||||
FWD_RUNTIME_ERROR = 6
|
||||
FWD_RUNTIME_ERROR_NO_STREAM = 7
|
||||
|
||||
def create_remote(remote, test_type, with_close_hdr:bool = False):
|
||||
def update_hdr(buf):
|
||||
return
|
||||
def callback():
|
||||
if test_type == TestType.FWD_SW_EXCPT:
|
||||
remote.unknown_var += 1
|
||||
elif test_type == TestType.FWD_TIMEOUT:
|
||||
raise TimeoutError
|
||||
elif test_type == TestType.FWD_OS_ERROR:
|
||||
raise ConnectionRefusedError
|
||||
elif test_type == TestType.FWD_OS_ERROR_NO_STREAM:
|
||||
remote.stream = None
|
||||
raise ConnectionRefusedError
|
||||
elif test_type == TestType.FWD_RUNTIME_ERROR:
|
||||
raise RuntimeError("Peer closed")
|
||||
elif test_type == TestType.FWD_RUNTIME_ERROR_NO_STREAM:
|
||||
remote.stream = None
|
||||
raise RuntimeError("Peer closed")
|
||||
|
||||
def close():
|
||||
return
|
||||
if with_close_hdr:
|
||||
close_hndl = close
|
||||
else:
|
||||
close_hndl = None
|
||||
|
||||
remote.ifc = AsyncStreamClient(
|
||||
FakeReader(), FakeWriter(), StreamPtr(None), close_hndl)
|
||||
remote.ifc.prot_set_update_header_cb(update_hdr)
|
||||
remote.ifc.prot_set_init_new_client_conn_cb(callback)
|
||||
remote.stream = FakeProto(False)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote, ifc
|
||||
create_remote(remote, TestType.FWD_NO_EXCPT)
|
||||
ifc.fwd_add(b'test-forward_msg2 ')
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_with_conn():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote, ifc
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
create_remote(remote, TestType.FWD_NO_EXCPT)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 0
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_no_conn():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_sw_except():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_SW_EXCPT)
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_os_error():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_OS_ERROR)
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_os_error2():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_OS_ERROR, True)
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_os_error3():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_OS_ERROR_NO_STREAM)
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_runtime_error():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_RUNTIME_ERROR)
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_runtime_error2():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_RUNTIME_ERROR, True)
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forward_runtime_error3():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_RUNTIME_ERROR_NO_STREAM, True)
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
ifc = AsyncStreamServer(fake_reader_fwd(), FakeWriter(), None, _create_remote, remote)
|
||||
ifc.fwd_add(b'test-forward_msg')
|
||||
await ifc.server_loop()
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
43
app/tests/test_byte_fifo.py
Normal file
43
app/tests/test_byte_fifo.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# test_with_pytest.py
|
||||
|
||||
from app.src.byte_fifo import ByteFifo
|
||||
|
||||
def test_fifo():
|
||||
read = ByteFifo()
|
||||
assert 0 == len(read)
|
||||
read += b'12'
|
||||
assert 2 == len(read)
|
||||
read += bytearray("34", encoding='UTF8')
|
||||
assert 4 == len(read)
|
||||
assert b'12' == read.peek(2)
|
||||
assert 4 == len(read)
|
||||
assert b'1234' == read.peek()
|
||||
assert 4 == len(read)
|
||||
assert b'12' == read.get(2)
|
||||
assert 2 == len(read)
|
||||
assert b'34' == read.get()
|
||||
assert 0 == len(read)
|
||||
|
||||
def test_fifo_fmt():
|
||||
read = ByteFifo()
|
||||
read += b'1234'
|
||||
assert b'1234' == read.peek()
|
||||
assert " 0000 | 31 32 33 34 | 1234" == f'{read}'
|
||||
|
||||
def test_fifo_observer():
|
||||
read = ByteFifo()
|
||||
|
||||
def _read():
|
||||
assert b'1234' == read.get(4)
|
||||
|
||||
read += b'12'
|
||||
assert 2 == len(read)
|
||||
read()
|
||||
read.reg_trigger(_read)
|
||||
read += b'34'
|
||||
assert 4 == len(read)
|
||||
read()
|
||||
assert 0 == len(read)
|
||||
assert b'' == read.peek(2)
|
||||
assert b'' == read.get(2)
|
||||
assert 0 == len(read)
|
||||
@@ -1,84 +0,0 @@
|
||||
# test_with_pytest.py
|
||||
import pytest
|
||||
import asyncio
|
||||
|
||||
from mock import patch
|
||||
from app.src.async_stream import AsyncStream
|
||||
from app.src.gen3.connection_g3 import ConnectionG3
|
||||
from app.src.gen3.talent import Talent
|
||||
|
||||
@pytest.fixture
|
||||
def patch_async_init():
|
||||
with patch.object(AsyncStream, '__init__') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_talent_init():
|
||||
with patch.object(Talent, '__init__') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_healthy():
|
||||
with patch.object(AsyncStream, 'healthy') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_async_close():
|
||||
with patch.object(AsyncStream, 'close') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_talent_close():
|
||||
with patch.object(Talent, 'close') as conn:
|
||||
yield conn
|
||||
|
||||
class FakeReader():
|
||||
def __init__(self):
|
||||
self.on_recv = asyncio.Event()
|
||||
async def read(self, max_len: int):
|
||||
await self.on_recv.wait()
|
||||
return b''
|
||||
def feed_eof(self):
|
||||
return
|
||||
|
||||
|
||||
class FakeWriter():
|
||||
def write(self, buf: bytes):
|
||||
return
|
||||
def get_extra_info(self, sel: str):
|
||||
if sel == 'peername':
|
||||
return 'remote.intern'
|
||||
elif sel == 'sockname':
|
||||
return 'sock:1234'
|
||||
assert False
|
||||
def is_closing(self):
|
||||
return False
|
||||
def close(self):
|
||||
return
|
||||
async def wait_closed(self):
|
||||
return
|
||||
|
||||
|
||||
|
||||
def test_method_calls(patch_async_init, patch_talent_init, patch_healthy, patch_async_close, patch_talent_close):
|
||||
spy1 = patch_async_init
|
||||
spy2 = patch_talent_init
|
||||
spy3 = patch_healthy
|
||||
spy4 = patch_async_close
|
||||
spy5 = patch_talent_close
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
id_str = "id_string"
|
||||
addr = ('proxy.local', 10000)
|
||||
conn = ConnectionG3(reader, writer, addr,
|
||||
remote_stream= None, server_side=True, id_str=id_str)
|
||||
spy1.assert_called_once_with(conn, reader, writer, addr)
|
||||
spy2.assert_called_once_with(conn, True, id_str)
|
||||
conn.healthy()
|
||||
|
||||
spy3.assert_called_once()
|
||||
|
||||
conn.close()
|
||||
spy4.assert_called_once()
|
||||
spy5.assert_called_once()
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# test_with_pytest.py
|
||||
import pytest
|
||||
import asyncio
|
||||
|
||||
from mock import patch
|
||||
from app.src.singleton import Singleton
|
||||
from app.src.async_stream import AsyncStream
|
||||
from app.src.gen3plus.connection_g3p import ConnectionG3P
|
||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||
|
||||
@pytest.fixture
|
||||
def patch_async_init():
|
||||
with patch.object(AsyncStream, '__init__') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_solarman_init():
|
||||
with patch.object(SolarmanV5, '__init__') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def module_init():
|
||||
Singleton._instances.clear()
|
||||
yield
|
||||
|
||||
@pytest.fixture
|
||||
def patch_healthy():
|
||||
with patch.object(AsyncStream, 'healthy') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_async_close():
|
||||
with patch.object(AsyncStream, 'close') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_solarman_close():
|
||||
with patch.object(SolarmanV5, 'close') as conn:
|
||||
yield conn
|
||||
|
||||
class FakeReader():
|
||||
def __init__(self):
|
||||
self.on_recv = asyncio.Event()
|
||||
async def read(self, max_len: int):
|
||||
await self.on_recv.wait()
|
||||
return b''
|
||||
def feed_eof(self):
|
||||
return
|
||||
|
||||
|
||||
class FakeWriter():
|
||||
def write(self, buf: bytes):
|
||||
return
|
||||
def get_extra_info(self, sel: str):
|
||||
if sel == 'peername':
|
||||
return 'remote.intern'
|
||||
elif sel == 'sockname':
|
||||
return 'sock:1234'
|
||||
assert False
|
||||
def is_closing(self):
|
||||
return False
|
||||
def close(self):
|
||||
return
|
||||
async def wait_closed(self):
|
||||
return
|
||||
|
||||
|
||||
|
||||
def test_method_calls(patch_async_init, patch_solarman_init, patch_healthy, patch_async_close, patch_solarman_close):
|
||||
spy1 = patch_async_init
|
||||
spy2 = patch_solarman_init
|
||||
spy3 = patch_healthy
|
||||
spy4 = patch_async_close
|
||||
spy5 = patch_solarman_close
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
addr = ('proxy.local', 10000)
|
||||
conn = ConnectionG3P(reader, writer, addr,
|
||||
remote_stream= None, server_side=True, client_mode=False)
|
||||
spy1.assert_called_once_with(conn, reader, writer, addr)
|
||||
spy2.assert_called_once_with(conn, True, False)
|
||||
conn.healthy()
|
||||
|
||||
spy3.assert_called_once()
|
||||
|
||||
conn.close()
|
||||
spy4.assert_called_once()
|
||||
spy5.assert_called_once()
|
||||
|
||||
304
app/tests/test_inverter_base.py
Normal file
304
app/tests/test_inverter_base.py
Normal file
@@ -0,0 +1,304 @@
|
||||
# test_with_pytest.py
|
||||
import pytest
|
||||
import asyncio
|
||||
import gc
|
||||
|
||||
from mock import patch
|
||||
from enum import Enum
|
||||
from app.src.infos import Infos
|
||||
from app.src.config import Config
|
||||
from app.src.gen3.talent import Talent
|
||||
from app.src.inverter_base import InverterBase
|
||||
from app.src.singleton import Singleton
|
||||
from app.src.async_stream import AsyncStream, AsyncStreamClient
|
||||
|
||||
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
|
||||
|
||||
pytest_plugins = ('pytest_asyncio',)
|
||||
|
||||
# initialize the proxy statistics
|
||||
Infos.static_init()
|
||||
|
||||
@pytest.fixture
|
||||
def config_conn():
|
||||
Config.act_config = {
|
||||
'mqtt':{
|
||||
'host': test_hostname,
|
||||
'port': test_port,
|
||||
'user': '',
|
||||
'passwd': ''
|
||||
},
|
||||
'ha':{
|
||||
'auto_conf_prefix': 'homeassistant',
|
||||
'discovery_prefix': 'homeassistant',
|
||||
'entity_prefix': 'tsun',
|
||||
'proxy_node_id': 'test_1',
|
||||
'proxy_unique_id': ''
|
||||
},
|
||||
'tsun':{'enabled': True, 'host': 'test_cloud.local', 'port': 1234}, 'inverters':{'allow_all':True}
|
||||
}
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def module_init():
|
||||
Singleton._instances.clear()
|
||||
yield
|
||||
|
||||
class FakeReader():
|
||||
def __init__(self):
|
||||
self.on_recv = asyncio.Event()
|
||||
async def read(self, max_len: int):
|
||||
await self.on_recv.wait()
|
||||
return b''
|
||||
def feed_eof(self):
|
||||
return
|
||||
|
||||
|
||||
class FakeWriter():
|
||||
def write(self, buf: bytes):
|
||||
return
|
||||
def get_extra_info(self, sel: str):
|
||||
if sel == 'peername':
|
||||
return 'remote.intern'
|
||||
elif sel == 'sockname':
|
||||
return 'sock:1234'
|
||||
assert False
|
||||
def is_closing(self):
|
||||
return False
|
||||
def close(self):
|
||||
return
|
||||
async def wait_closed(self):
|
||||
return
|
||||
|
||||
class TestType(Enum):
|
||||
RD_TEST_0_BYTES = 1
|
||||
RD_TEST_TIMEOUT = 2
|
||||
RD_TEST_EXCEPT = 3
|
||||
|
||||
|
||||
test = TestType.RD_TEST_0_BYTES
|
||||
|
||||
@pytest.fixture
|
||||
def patch_open_connection():
|
||||
async def new_conn(conn):
|
||||
await asyncio.sleep(0)
|
||||
return FakeReader(), FakeWriter()
|
||||
|
||||
def new_open(host: str, port: int):
|
||||
global test
|
||||
if test == TestType.RD_TEST_TIMEOUT:
|
||||
raise ConnectionRefusedError
|
||||
elif test == TestType.RD_TEST_EXCEPT:
|
||||
raise ValueError("Value cannot be negative") # Compliant
|
||||
return new_conn(None)
|
||||
|
||||
with patch.object(asyncio, 'open_connection', new_open) as conn:
|
||||
yield conn
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patch_healthy():
|
||||
with patch.object(AsyncStream, 'healthy') as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_unhealthy():
|
||||
def new_healthy(self):
|
||||
return False
|
||||
with patch.object(AsyncStream, 'healthy', new_healthy) as conn:
|
||||
yield conn
|
||||
@pytest.fixture
|
||||
def patch_unhealthy_remote():
|
||||
def new_healthy(self):
|
||||
return False
|
||||
with patch.object(AsyncStreamClient, 'healthy', new_healthy) as conn:
|
||||
yield conn
|
||||
|
||||
def test_inverter_iter():
|
||||
InverterBase._registry.clear()
|
||||
cnt = 0
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||
for inv in InverterBase:
|
||||
assert inv == inverter
|
||||
cnt += 1
|
||||
del inv
|
||||
del inverter
|
||||
assert cnt == 1
|
||||
|
||||
for inv in InverterBase:
|
||||
assert False
|
||||
|
||||
def test_method_calls(patch_healthy):
|
||||
spy = patch_healthy
|
||||
InverterBase._registry.clear()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||
assert inverter.local.stream
|
||||
assert inverter.local.ifc
|
||||
# call healthy inside the contexter manager
|
||||
for inv in InverterBase:
|
||||
assert inv.healthy()
|
||||
del inv
|
||||
spy.assert_called_once()
|
||||
|
||||
# outside context manager the health function of AsyncStream is not reachable
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
assert inv.healthy()
|
||||
cnt += 1
|
||||
del inv
|
||||
assert cnt == 1
|
||||
spy.assert_called_once() # counter don't increase and keep one!
|
||||
|
||||
del inverter
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
def test_unhealthy(patch_unhealthy):
|
||||
_ = patch_unhealthy
|
||||
InverterBase._registry.clear()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||
assert inverter.local.stream
|
||||
assert inverter.local.ifc
|
||||
# call healthy inside the contexter manager
|
||||
assert not inverter.healthy()
|
||||
|
||||
# outside context manager the unhealth AsyncStream is released
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
|
||||
cnt += 1
|
||||
del inv
|
||||
assert cnt == 1
|
||||
|
||||
del inverter
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
def test_unhealthy_remote(patch_unhealthy_remote):
|
||||
_ = patch_unhealthy
|
||||
InverterBase._registry.clear()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||
assert inverter.local.stream
|
||||
assert inverter.local.ifc
|
||||
# call healthy inside the contexter manager
|
||||
assert not inverter.healthy()
|
||||
|
||||
# outside context manager the unhealth AsyncStream is released
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
|
||||
cnt += 1
|
||||
del inv
|
||||
assert cnt == 1
|
||||
|
||||
del inverter
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_conn(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream
|
||||
assert inverter.remote.ifc
|
||||
# call healthy inside the contexter manager
|
||||
assert inverter.healthy()
|
||||
|
||||
# call healthy outside the contexter manager (__exit__() was called)
|
||||
assert inverter.healthy()
|
||||
del inverter
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unhealthy_remote(config_conn, patch_open_connection, patch_unhealthy_remote):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
_ = patch_unhealthy_remote
|
||||
assert asyncio.get_running_loop()
|
||||
InverterBase._registry.clear()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||
assert inverter.local.stream
|
||||
assert inverter.local.ifc
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream
|
||||
assert inverter.remote.ifc
|
||||
assert inverter.local.ifc.healthy()
|
||||
assert not inverter.remote.ifc.healthy()
|
||||
# call healthy inside the contexter manager
|
||||
assert not inverter.healthy()
|
||||
|
||||
# outside context manager the unhealth AsyncStream is released
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
|
||||
cnt += 1
|
||||
del inv
|
||||
assert cnt == 1
|
||||
|
||||
del inverter
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_disc(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
|
||||
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream
|
||||
# call disc inside the contexter manager
|
||||
await inverter.disc()
|
||||
|
||||
# call disc outside the contexter manager (__exit__() was called)
|
||||
await inverter.disc()
|
||||
del inverter
|
||||
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
@@ -1,15 +1,17 @@
|
||||
# test_with_pytest.py
|
||||
import pytest
|
||||
import asyncio
|
||||
import sys,gc
|
||||
|
||||
from mock import patch
|
||||
from enum import Enum
|
||||
from app.src.infos import Infos
|
||||
from app.src.config import Config
|
||||
from app.src.inverter import Inverter
|
||||
from app.src.proxy import Proxy
|
||||
from app.src.inverter_base import InverterBase
|
||||
from app.src.singleton import Singleton
|
||||
from app.src.gen3.connection_g3 import ConnectionG3
|
||||
from app.src.gen3.inverter_g3 import InverterG3
|
||||
from app.src.async_stream import AsyncStream
|
||||
|
||||
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
|
||||
|
||||
@@ -42,16 +44,6 @@ def module_init():
|
||||
Singleton._instances.clear()
|
||||
yield
|
||||
|
||||
@pytest.fixture
|
||||
def patch_conn_init():
|
||||
with patch.object(ConnectionG3, '__init__', return_value= None) as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_conn_close():
|
||||
with patch.object(ConnectionG3, 'close') as conn:
|
||||
yield conn
|
||||
|
||||
class FakeReader():
|
||||
def __init__(self):
|
||||
self.on_recv = asyncio.Event()
|
||||
@@ -103,133 +95,132 @@ def patch_open_connection():
|
||||
with patch.object(asyncio, 'open_connection', new_open) as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_healthy():
|
||||
with patch.object(AsyncStream, 'healthy') as conn:
|
||||
yield conn
|
||||
|
||||
def test_method_calls(patch_conn_init, patch_conn_close):
|
||||
spy1 = patch_conn_init
|
||||
spy2 = patch_conn_close
|
||||
def test_method_calls(patch_healthy):
|
||||
spy = patch_healthy
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
addr = ('proxy.local', 10000)
|
||||
inverter = InverterG3(reader, writer, addr)
|
||||
inverter.l_addr = ''
|
||||
inverter.r_addr = ''
|
||||
InverterBase._registry.clear()
|
||||
|
||||
spy1.assert_called_once()
|
||||
spy1.assert_called_once_with(reader, writer, addr, None, True)
|
||||
|
||||
inverter.close()
|
||||
spy2.assert_called_once()
|
||||
with InverterG3(reader, writer) as inverter:
|
||||
assert inverter.local.stream
|
||||
assert inverter.local.ifc
|
||||
for inv in InverterBase:
|
||||
inv.healthy()
|
||||
del inv
|
||||
spy.assert_called_once()
|
||||
del inverter
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_conn(config_conn, patch_open_connection, patch_conn_close):
|
||||
async def test_remote_conn(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
with InverterG3(FakeReader(), FakeWriter()) as inverter:
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream
|
||||
del inverter
|
||||
|
||||
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
|
||||
|
||||
await inverter.async_create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote_stream
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_except(config_conn, patch_open_connection, patch_conn_close):
|
||||
async def test_remote_except(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
global test
|
||||
test = TestType.RD_TEST_TIMEOUT
|
||||
|
||||
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
|
||||
with InverterG3(FakeReader(), FakeWriter()) as inverter:
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream==None
|
||||
|
||||
await inverter.async_create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote_stream==None
|
||||
test = TestType.RD_TEST_EXCEPT
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream==None
|
||||
del inverter
|
||||
|
||||
test = TestType.RD_TEST_EXCEPT
|
||||
await inverter.async_create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote_stream==None
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
cnt = 0
|
||||
for inv in InverterBase:
|
||||
print(f'InverterBase refs:{gc.get_referrers(inv)}')
|
||||
cnt += 1
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mqtt_publish(config_conn, patch_open_connection, patch_conn_close):
|
||||
async def test_mqtt_publish(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
Inverter.class_init()
|
||||
Proxy.class_init()
|
||||
|
||||
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
|
||||
inverter._Talent__set_serial_no(serial_no= "123344")
|
||||
|
||||
inverter.new_data['inverter'] = True
|
||||
inverter.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['inverter'] == False
|
||||
with InverterG3(FakeReader(), FakeWriter()) as inverter:
|
||||
stream = inverter.local.stream
|
||||
await inverter.async_publ_mqtt() # check call with invalid unique_id
|
||||
stream._Talent__set_serial_no(serial_no= "123344")
|
||||
|
||||
inverter.new_data['env'] = True
|
||||
inverter.db.db['env'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['env'] == False
|
||||
stream.new_data['inverter'] = True
|
||||
stream.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['inverter'] == False
|
||||
|
||||
Infos.new_stat_data['proxy'] = True
|
||||
await inverter.async_publ_mqtt()
|
||||
assert Infos.new_stat_data['proxy'] == False
|
||||
stream.new_data['env'] = True
|
||||
stream.db.db['env'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['env'] == False
|
||||
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
Infos.new_stat_data['proxy'] = True
|
||||
await inverter.async_publ_mqtt()
|
||||
assert Infos.new_stat_data['proxy'] == False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err, patch_conn_close):
|
||||
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
_ = patch_mqtt_err
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
Inverter.class_init()
|
||||
Proxy.class_init()
|
||||
|
||||
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
|
||||
inverter._Talent__set_serial_no(serial_no= "123344")
|
||||
|
||||
inverter.new_data['inverter'] = True
|
||||
inverter.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['inverter'] == True
|
||||
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
with InverterG3(FakeReader(), FakeWriter()) as inverter:
|
||||
stream = inverter.local.stream
|
||||
stream._Talent__set_serial_no(serial_no= "123344")
|
||||
stream.new_data['inverter'] = True
|
||||
stream.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['inverter'] == True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except, patch_conn_close):
|
||||
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
_ = patch_mqtt_except
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
Inverter.class_init()
|
||||
Proxy.class_init()
|
||||
|
||||
inverter = InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000))
|
||||
inverter._Talent__set_serial_no(serial_no= "123344")
|
||||
|
||||
inverter.new_data['inverter'] = True
|
||||
inverter.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['inverter'] == True
|
||||
with InverterG3(FakeReader(), FakeWriter()) as inverter:
|
||||
stream = inverter.local.stream
|
||||
stream._Talent__set_serial_no(serial_no= "123344")
|
||||
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
stream.new_data['inverter'] = True
|
||||
stream.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['inverter'] == True
|
||||
|
||||
@@ -6,9 +6,9 @@ from mock import patch
|
||||
from enum import Enum
|
||||
from app.src.infos import Infos
|
||||
from app.src.config import Config
|
||||
from app.src.inverter import Inverter
|
||||
from app.src.proxy import Proxy
|
||||
from app.src.inverter_base import InverterBase
|
||||
from app.src.singleton import Singleton
|
||||
from app.src.gen3plus.connection_g3p import ConnectionG3P
|
||||
from app.src.gen3plus.inverter_g3p import InverterG3P
|
||||
|
||||
from app.tests.test_modbus_tcp import patch_mqtt_err, patch_mqtt_except, test_port, test_hostname
|
||||
@@ -43,16 +43,6 @@ def module_init():
|
||||
Singleton._instances.clear()
|
||||
yield
|
||||
|
||||
@pytest.fixture
|
||||
def patch_conn_init():
|
||||
with patch.object(ConnectionG3P, '__init__', return_value= None) as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_conn_close():
|
||||
with patch.object(ConnectionG3P, 'close') as conn:
|
||||
yield conn
|
||||
|
||||
class FakeReader():
|
||||
def __init__(self):
|
||||
self.on_recv = asyncio.Event()
|
||||
@@ -104,133 +94,103 @@ def patch_open_connection():
|
||||
with patch.object(asyncio, 'open_connection', new_open) as conn:
|
||||
yield conn
|
||||
|
||||
|
||||
def test_method_calls(patch_conn_init, patch_conn_close):
|
||||
spy1 = patch_conn_init
|
||||
spy2 = patch_conn_close
|
||||
def test_method_calls():
|
||||
reader = FakeReader()
|
||||
writer = FakeWriter()
|
||||
addr = ('proxy.local', 10000)
|
||||
inverter = InverterG3P(reader, writer, addr, client_mode=False)
|
||||
inverter.l_addr = ''
|
||||
inverter.r_addr = ''
|
||||
InverterBase._registry.clear()
|
||||
|
||||
spy1.assert_called_once()
|
||||
spy1.assert_called_once_with(reader, writer, addr, None, server_side=True, client_mode=False)
|
||||
|
||||
inverter.close()
|
||||
spy2.assert_called_once()
|
||||
with InverterG3P(reader, writer, client_mode=False) as inverter:
|
||||
assert inverter.local.stream
|
||||
assert inverter.local.ifc
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_conn(config_conn, patch_open_connection, patch_conn_close):
|
||||
async def test_remote_conn(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
|
||||
|
||||
await inverter.async_create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote_stream
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_except(config_conn, patch_open_connection, patch_conn_close):
|
||||
async def test_remote_except(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
global test
|
||||
test = TestType.RD_TEST_TIMEOUT
|
||||
|
||||
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
|
||||
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream==None
|
||||
|
||||
await inverter.async_create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote_stream==None
|
||||
|
||||
test = TestType.RD_TEST_EXCEPT
|
||||
await inverter.async_create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote_stream==None
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
test = TestType.RD_TEST_EXCEPT
|
||||
await inverter.create_remote()
|
||||
await asyncio.sleep(0)
|
||||
assert inverter.remote.stream==None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mqtt_publish(config_conn, patch_open_connection, patch_conn_close):
|
||||
async def test_mqtt_publish(config_conn, patch_open_connection):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
Inverter.class_init()
|
||||
Proxy.class_init()
|
||||
|
||||
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
|
||||
inverter._SolarmanV5__set_serial_no(snr= 123344)
|
||||
|
||||
inverter.new_data['inverter'] = True
|
||||
inverter.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['inverter'] == False
|
||||
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
|
||||
stream = inverter.local.stream
|
||||
await inverter.async_publ_mqtt() # check call with invalid unique_id
|
||||
stream._SolarmanV5__set_serial_no(snr= 123344)
|
||||
|
||||
inverter.new_data['env'] = True
|
||||
inverter.db.db['env'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['env'] == False
|
||||
stream.new_data['inverter'] = True
|
||||
stream.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['inverter'] == False
|
||||
|
||||
Infos.new_stat_data['proxy'] = True
|
||||
await inverter.async_publ_mqtt()
|
||||
assert Infos.new_stat_data['proxy'] == False
|
||||
stream.new_data['env'] = True
|
||||
stream.db.db['env'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['env'] == False
|
||||
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
Infos.new_stat_data['proxy'] = True
|
||||
await inverter.async_publ_mqtt()
|
||||
assert Infos.new_stat_data['proxy'] == False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err, patch_conn_close):
|
||||
async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
_ = patch_mqtt_err
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
Inverter.class_init()
|
||||
Proxy.class_init()
|
||||
|
||||
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
|
||||
inverter._SolarmanV5__set_serial_no(snr= 123344)
|
||||
|
||||
inverter.new_data['inverter'] = True
|
||||
inverter.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['inverter'] == True
|
||||
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
|
||||
stream = inverter.local.stream
|
||||
stream._SolarmanV5__set_serial_no(snr= 123344)
|
||||
stream.new_data['inverter'] = True
|
||||
stream.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['inverter'] == True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except, patch_conn_close):
|
||||
async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except):
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
_ = patch_mqtt_except
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
spy1 = patch_conn_close
|
||||
|
||||
Inverter.class_init()
|
||||
Proxy.class_init()
|
||||
|
||||
inverter = InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False)
|
||||
inverter._SolarmanV5__set_serial_no(snr= 123344)
|
||||
|
||||
inverter.new_data['inverter'] = True
|
||||
inverter.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert inverter.new_data['inverter'] == True
|
||||
with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter:
|
||||
stream = inverter.local.stream
|
||||
stream._SolarmanV5__set_serial_no(snr= 123344)
|
||||
|
||||
inverter.close()
|
||||
spy1.assert_called_once()
|
||||
stream.new_data['inverter'] = True
|
||||
stream.db.db['inverter'] = {}
|
||||
await inverter.async_publ_mqtt()
|
||||
assert stream.new_data['inverter'] == True
|
||||
|
||||
@@ -9,12 +9,9 @@ from app.src.singleton import Singleton
|
||||
from app.src.config import Config
|
||||
from app.src.infos import Infos
|
||||
from app.src.mqtt import Mqtt
|
||||
from app.src.inverter_base import InverterBase
|
||||
from app.src.messages import Message, State
|
||||
from app.src.inverter import Inverter
|
||||
from app.src.modbus_tcp import ModbusConn, ModbusTcp
|
||||
from app.src.mqtt import Mqtt
|
||||
from app.src.messages import Message, State
|
||||
from app.src.inverter import Inverter
|
||||
from app.src.proxy import Proxy
|
||||
from app.src.modbus_tcp import ModbusConn, ModbusTcp
|
||||
|
||||
|
||||
@@ -75,55 +72,93 @@ def config_conn(test_hostname, test_port):
|
||||
}
|
||||
|
||||
|
||||
class TestType(Enum):
|
||||
class FakeReader():
|
||||
RD_TEST_0_BYTES = 1
|
||||
RD_TEST_TIMEOUT = 2
|
||||
RD_TEST_13_BYTES = 3
|
||||
RD_TEST_SW_EXCEPT = 4
|
||||
RD_TEST_OS_ERROR = 5
|
||||
|
||||
|
||||
test = TestType.RD_TEST_0_BYTES
|
||||
|
||||
|
||||
class FakeReader():
|
||||
def __init__(self):
|
||||
self.on_recv = asyncio.Event()
|
||||
self.test = self.RD_TEST_0_BYTES
|
||||
|
||||
async def read(self, max_len: int):
|
||||
print(f'fakeReader test: {self.test}')
|
||||
await self.on_recv.wait()
|
||||
if test == TestType.RD_TEST_0_BYTES:
|
||||
if self.test == self.RD_TEST_0_BYTES:
|
||||
return b''
|
||||
elif test == TestType.RD_TEST_TIMEOUT:
|
||||
elif self.test == self.RD_TEST_13_BYTES:
|
||||
print('fakeReader return 13 bytes')
|
||||
self.test = self.RD_TEST_0_BYTES
|
||||
return b'test-data-req'
|
||||
elif self.test == self.RD_TEST_TIMEOUT:
|
||||
raise TimeoutError
|
||||
elif self.test == self.RD_TEST_SW_EXCEPT:
|
||||
self.test = self.RD_TEST_0_BYTES
|
||||
self.unknown_var += 1
|
||||
elif self.test == self.RD_TEST_OS_ERROR:
|
||||
self.test = self.RD_TEST_0_BYTES
|
||||
raise ConnectionRefusedError
|
||||
|
||||
def feed_eof(self):
|
||||
return
|
||||
|
||||
|
||||
class FakeWriter():
|
||||
def __init__(self, conn='remote.intern'):
|
||||
self.conn = conn
|
||||
self.closing = False
|
||||
def write(self, buf: bytes):
|
||||
return
|
||||
async def drain(self):
|
||||
await asyncio.sleep(0)
|
||||
def get_extra_info(self, sel: str):
|
||||
if sel == 'peername':
|
||||
return 'remote.intern'
|
||||
return self.conn
|
||||
elif sel == 'sockname':
|
||||
return 'sock:1234'
|
||||
assert False
|
||||
def is_closing(self):
|
||||
return False
|
||||
return self.closing
|
||||
def close(self):
|
||||
return
|
||||
self.closing = True
|
||||
async def wait_closed(self):
|
||||
return
|
||||
await asyncio.sleep(0)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patch_open():
|
||||
async def new_conn(conn):
|
||||
await asyncio.sleep(0)
|
||||
return FakeReader(), FakeWriter()
|
||||
return FakeReader(), FakeWriter(conn)
|
||||
|
||||
def new_open(host: str, port: int):
|
||||
global test
|
||||
if test == TestType.RD_TEST_TIMEOUT:
|
||||
raise TimeoutError
|
||||
return new_conn(None)
|
||||
return new_conn(f'{host}:{port}')
|
||||
|
||||
with patch.object(asyncio, 'open_connection', new_open) as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_open_timeout():
|
||||
def new_open(host: str, port: int):
|
||||
raise TimeoutError
|
||||
|
||||
with patch.object(asyncio, 'open_connection', new_open) as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_open_value_error():
|
||||
def new_open(host: str, port: int):
|
||||
raise ValueError
|
||||
|
||||
with patch.object(asyncio, 'open_connection', new_open) as conn:
|
||||
yield conn
|
||||
|
||||
@pytest.fixture
|
||||
def patch_open_conn_abort():
|
||||
def new_open(host: str, port: int):
|
||||
raise ConnectionAbortedError
|
||||
|
||||
with patch.object(asyncio, 'open_connection', new_open) as conn:
|
||||
yield conn
|
||||
@@ -154,13 +189,18 @@ async def test_modbus_conn(patch_open):
|
||||
_ = patch_open
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
|
||||
async with ModbusConn('test.local', 1234) as stream:
|
||||
async with ModbusConn('test.local', 1234) as inverter:
|
||||
stream = inverter.local.stream
|
||||
assert stream.node_id == 'G3P'
|
||||
assert stream.addr == ('test.local', 1234)
|
||||
assert type(stream.reader) is FakeReader
|
||||
assert type(stream.writer) is FakeWriter
|
||||
assert stream.addr == ('test.local:1234')
|
||||
assert type(stream.ifc._reader) is FakeReader
|
||||
assert type(stream.ifc._writer) is FakeWriter
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 1
|
||||
|
||||
del inverter
|
||||
|
||||
for _ in InverterBase:
|
||||
assert False
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -171,13 +211,47 @@ async def test_modbus_no_cnf():
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modbus_cnf1(config_conn, patch_open):
|
||||
async def test_modbus_timeout(config_conn, patch_open_timeout):
|
||||
_ = config_conn
|
||||
_ = patch_open
|
||||
global test
|
||||
_ = patch_open_timeout
|
||||
assert asyncio.get_running_loop()
|
||||
Inverter.class_init()
|
||||
test = TestType.RD_TEST_TIMEOUT
|
||||
Proxy.class_init()
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
loop = asyncio.get_event_loop()
|
||||
ModbusTcp(loop)
|
||||
await asyncio.sleep(0.01)
|
||||
for m in Message:
|
||||
if (m.node_id == 'inv_2'):
|
||||
assert False
|
||||
|
||||
await asyncio.sleep(0.01)
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modbus_value_err(config_conn, patch_open_value_error):
|
||||
_ = config_conn
|
||||
_ = patch_open_value_error
|
||||
assert asyncio.get_running_loop()
|
||||
Proxy.class_init()
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
loop = asyncio.get_event_loop()
|
||||
ModbusTcp(loop)
|
||||
await asyncio.sleep(0.01)
|
||||
for m in Message:
|
||||
if (m.node_id == 'inv_2'):
|
||||
assert False
|
||||
|
||||
await asyncio.sleep(0.01)
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modbus_conn_abort(config_conn, patch_open_conn_abort):
|
||||
_ = config_conn
|
||||
_ = patch_open_conn_abort
|
||||
assert asyncio.get_running_loop()
|
||||
Proxy.class_init()
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
loop = asyncio.get_event_loop()
|
||||
@@ -195,10 +269,8 @@ async def test_modbus_cnf2(config_conn, patch_no_mqtt, patch_open):
|
||||
_ = config_conn
|
||||
_ = patch_open
|
||||
_ = patch_no_mqtt
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
Inverter.class_init()
|
||||
test = TestType.RD_TEST_0_BYTES
|
||||
Proxy.class_init()
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
ModbusTcp(asyncio.get_event_loop())
|
||||
@@ -209,7 +281,7 @@ async def test_modbus_cnf2(config_conn, patch_no_mqtt, patch_open):
|
||||
test += 1
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 1
|
||||
m.shutdown_started = True
|
||||
m.reader.on_recv.set()
|
||||
m.ifc._reader.on_recv.set()
|
||||
del m
|
||||
|
||||
assert 1 == test
|
||||
@@ -221,10 +293,8 @@ async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open):
|
||||
_ = config_conn
|
||||
_ = patch_open
|
||||
_ = patch_no_mqtt
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
Inverter.class_init()
|
||||
test = TestType.RD_TEST_0_BYTES
|
||||
Proxy.class_init()
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
ModbusTcp(asyncio.get_event_loop(), tim_restart= 0)
|
||||
@@ -236,13 +306,13 @@ async def test_modbus_cnf3(config_conn, patch_no_mqtt, patch_open):
|
||||
test += 1
|
||||
if test == 1:
|
||||
m.shutdown_started = False
|
||||
m.reader.on_recv.set()
|
||||
m.ifc._reader.on_recv.set()
|
||||
await asyncio.sleep(0.1)
|
||||
assert m.state == State.closed
|
||||
await asyncio.sleep(0.1)
|
||||
else:
|
||||
m.shutdown_started = True
|
||||
m.reader.on_recv.set()
|
||||
m.ifc._reader.on_recv.set()
|
||||
del m
|
||||
|
||||
assert 2 == test
|
||||
@@ -254,10 +324,8 @@ async def test_mqtt_err(config_conn, patch_mqtt_err, patch_open):
|
||||
_ = config_conn
|
||||
_ = patch_open
|
||||
_ = patch_mqtt_err
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
Inverter.class_init()
|
||||
test = TestType.RD_TEST_0_BYTES
|
||||
Proxy.class_init()
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
ModbusTcp(asyncio.get_event_loop(), tim_restart= 0)
|
||||
@@ -269,13 +337,14 @@ async def test_mqtt_err(config_conn, patch_mqtt_err, patch_open):
|
||||
test += 1
|
||||
if test == 1:
|
||||
m.shutdown_started = False
|
||||
m.reader.on_recv.set()
|
||||
m.ifc._reader.on_recv.set()
|
||||
await asyncio.sleep(0.1)
|
||||
assert m.state == State.closed
|
||||
await asyncio.sleep(0.1)
|
||||
await asyncio.sleep(0.1)
|
||||
else:
|
||||
m.shutdown_started = True
|
||||
m.reader.on_recv.set()
|
||||
m.ifc._reader.on_recv.set()
|
||||
del m
|
||||
|
||||
await asyncio.sleep(0.01)
|
||||
@@ -286,10 +355,8 @@ async def test_mqtt_except(config_conn, patch_mqtt_except, patch_open):
|
||||
_ = config_conn
|
||||
_ = patch_open
|
||||
_ = patch_mqtt_except
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
Inverter.class_init()
|
||||
test = TestType.RD_TEST_0_BYTES
|
||||
Proxy.class_init()
|
||||
|
||||
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
|
||||
ModbusTcp(asyncio.get_event_loop(), tim_restart= 0)
|
||||
@@ -301,13 +368,13 @@ async def test_mqtt_except(config_conn, patch_mqtt_except, patch_open):
|
||||
test += 1
|
||||
if test == 1:
|
||||
m.shutdown_started = False
|
||||
m.reader.on_recv.set()
|
||||
m.ifc._reader.on_recv.set()
|
||||
await asyncio.sleep(0.1)
|
||||
assert m.state == State.closed
|
||||
await asyncio.sleep(0.1)
|
||||
else:
|
||||
m.shutdown_started = True
|
||||
m.reader.on_recv.set()
|
||||
m.ifc._reader.on_recv.set()
|
||||
del m
|
||||
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
@@ -5,6 +5,7 @@ import aiomqtt
|
||||
import logging
|
||||
|
||||
from mock import patch, Mock
|
||||
from app.src.async_stream import AsyncIfcImpl
|
||||
from app.src.singleton import Singleton
|
||||
from app.src.mqtt import Mqtt
|
||||
from app.src.modbus import Modbus
|
||||
@@ -44,7 +45,7 @@ def config_no_conn(test_port):
|
||||
|
||||
@pytest.fixture
|
||||
def spy_at_cmd():
|
||||
conn = SolarmanV5(server_side=True, client_mode= False)
|
||||
conn = SolarmanV5(('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl())
|
||||
conn.node_id = 'inv_2/'
|
||||
with patch.object(conn, 'send_at_cmd', wraps=conn.send_at_cmd) as wrapped_conn:
|
||||
yield wrapped_conn
|
||||
@@ -52,7 +53,7 @@ def spy_at_cmd():
|
||||
|
||||
@pytest.fixture
|
||||
def spy_modbus_cmd():
|
||||
conn = SolarmanV5(server_side=True, client_mode= False)
|
||||
conn = SolarmanV5(('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl())
|
||||
conn.node_id = 'inv_1/'
|
||||
with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn:
|
||||
yield wrapped_conn
|
||||
@@ -60,7 +61,7 @@ def spy_modbus_cmd():
|
||||
|
||||
@pytest.fixture
|
||||
def spy_modbus_cmd_client():
|
||||
conn = SolarmanV5(server_side=False, client_mode= False)
|
||||
conn = SolarmanV5(('test.local', 1234), server_side=False, client_mode= False, ifc=AsyncIfcImpl())
|
||||
conn.node_id = 'inv_1/'
|
||||
with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn:
|
||||
yield wrapped_conn
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
|
||||
from mock import patch, Mock
|
||||
from app.src.singleton import Singleton
|
||||
from app.src.inverter import Inverter
|
||||
from app.src.proxy import Proxy
|
||||
from app.src.mqtt import Mqtt
|
||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||
from app.src.config import Config
|
||||
@@ -18,7 +18,7 @@ pytest_plugins = ('pytest_asyncio',)
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def module_init():
|
||||
def new_init(cls, cb_mqtt_is_up):
|
||||
cb_mqtt_is_up()
|
||||
pass # empty test methos
|
||||
|
||||
Singleton._instances.clear()
|
||||
with patch.object(Mqtt, '__init__', new_init):
|
||||
@@ -63,12 +63,13 @@ def config_conn(test_hostname, test_port):
|
||||
async def test_inverter_cb(config_conn):
|
||||
_ = config_conn
|
||||
|
||||
with patch.object(Inverter, '_cb_mqtt_is_up', wraps=Inverter._cb_mqtt_is_up) as spy:
|
||||
print('call Inverter.class_init')
|
||||
Inverter.class_init()
|
||||
assert 'homeassistant/' == Inverter.discovery_prfx
|
||||
assert 'tsun/' == Inverter.entity_prfx
|
||||
assert 'test_1/' == Inverter.proxy_node_id
|
||||
with patch.object(Proxy, '_cb_mqtt_is_up', wraps=Proxy._cb_mqtt_is_up) as spy:
|
||||
print('call Proxy.class_init')
|
||||
Proxy.class_init()
|
||||
assert 'homeassistant/' == Proxy.discovery_prfx
|
||||
assert 'tsun/' == Proxy.entity_prfx
|
||||
assert 'test_1/' == Proxy.proxy_node_id
|
||||
await Proxy._cb_mqtt_is_up()
|
||||
spy.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -76,8 +77,8 @@ async def test_mqtt_is_up(config_conn):
|
||||
_ = config_conn
|
||||
|
||||
with patch.object(Mqtt, 'publish') as spy:
|
||||
Inverter.class_init()
|
||||
await Inverter._cb_mqtt_is_up()
|
||||
Proxy.class_init()
|
||||
await Proxy._cb_mqtt_is_up()
|
||||
spy.assert_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -85,6 +86,6 @@ async def test_mqtt_proxy_statt_invalid(config_conn):
|
||||
_ = config_conn
|
||||
|
||||
with patch.object(Mqtt, 'publish') as spy:
|
||||
Inverter.class_init()
|
||||
await Inverter._async_publ_mqtt_proxy_stat('InValId_kEy')
|
||||
Proxy.class_init()
|
||||
await Proxy._async_publ_mqtt_proxy_stat('InValId_kEy')
|
||||
spy.assert_not_called()
|
||||
@@ -5,6 +5,7 @@ import asyncio
|
||||
import logging
|
||||
import random
|
||||
from math import isclose
|
||||
from app.src.async_stream import AsyncIfcImpl, StreamPtr
|
||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||
from app.src.config import Config
|
||||
from app.src.infos import Infos, Register
|
||||
@@ -20,13 +21,6 @@ Infos.static_init()
|
||||
timestamp = int(time.time()) # 1712861197
|
||||
heartbeat = 60
|
||||
|
||||
class Writer():
|
||||
def __init__(self):
|
||||
self.sent_pdu = b''
|
||||
|
||||
def write(self, pdu: bytearray):
|
||||
self.sent_pdu = pdu
|
||||
|
||||
|
||||
class Mqtt():
|
||||
def __init__(self):
|
||||
@@ -38,14 +32,21 @@ class Mqtt():
|
||||
self.data = data
|
||||
|
||||
|
||||
class FakeIfc(AsyncIfcImpl):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.remote = StreamPtr(None)
|
||||
|
||||
class MemoryStream(SolarmanV5):
|
||||
def __init__(self, msg, chunks = (0,), server_side: bool = True):
|
||||
super().__init__(server_side, client_mode=False)
|
||||
_ifc = FakeIfc()
|
||||
super().__init__(('test.local', 1234), _ifc, server_side, client_mode=False)
|
||||
if server_side:
|
||||
self.mb.timeout = 0.4 # overwrite for faster testing
|
||||
self.mb_first_timeout = 0.5
|
||||
self.mb_timeout = 0.5
|
||||
self.writer = Writer()
|
||||
self.sent_pdu = b''
|
||||
self.ifc.tx_fifo.reg_trigger(self.write_cb)
|
||||
self.mqtt = Mqtt()
|
||||
self.__msg = msg
|
||||
self.__msg_len = len(msg)
|
||||
@@ -64,6 +65,11 @@ class MemoryStream(SolarmanV5):
|
||||
self.data = ''
|
||||
self.msg_recvd = []
|
||||
|
||||
def write_cb(self):
|
||||
if self.test_exception_async_write:
|
||||
raise RuntimeError("Peer closed.")
|
||||
self.sent_pdu = self.ifc.tx_fifo.get()
|
||||
|
||||
def _timestamp(self):
|
||||
return timestamp
|
||||
|
||||
@@ -86,25 +92,21 @@ class MemoryStream(SolarmanV5):
|
||||
chunk_len = self.__chunks[self.__chunk_idx]
|
||||
self.__chunk_idx += 1
|
||||
if chunk_len!=0:
|
||||
self._recv_buffer += self.__msg[self.__offs:chunk_len]
|
||||
self.ifc.rx_fifo += self.__msg[self.__offs:chunk_len]
|
||||
copied_bytes = chunk_len - self.__offs
|
||||
self.__offs = chunk_len
|
||||
else:
|
||||
self._recv_buffer += self.__msg[self.__offs:]
|
||||
self.ifc.rx_fifo += self.__msg[self.__offs:]
|
||||
copied_bytes = self.__msg_len - self.__offs
|
||||
self.__offs = self.__msg_len
|
||||
except Exception:
|
||||
pass # ignore exceptions here
|
||||
return copied_bytes
|
||||
|
||||
async def async_write(self, headline=''):
|
||||
if self.test_exception_async_write:
|
||||
raise RuntimeError("Peer closed.")
|
||||
|
||||
def createClientStream(self, msg, chunks = (0,)):
|
||||
c = MemoryStream(msg, chunks, False)
|
||||
self.remote_stream = c
|
||||
c. remote_stream = self
|
||||
self.ifc.remote.stream = c
|
||||
c.ifc.remote.stream = self
|
||||
return c
|
||||
|
||||
def _SolarmanV5__flush_recv_msg(self) -> None:
|
||||
@@ -680,6 +682,7 @@ def config_tsun_inv1():
|
||||
Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}}
|
||||
|
||||
def test_read_message(device_ind_msg):
|
||||
Config.act_config = {'solarman':{'enabled': True}}
|
||||
m = MemoryStream(device_ind_msg, (0,))
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
@@ -690,9 +693,9 @@ def test_read_message(device_ind_msg):
|
||||
assert m.control == 0x4110
|
||||
assert str(m.seq) == '01:00'
|
||||
assert m.data_len == 0xd4
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -711,9 +714,9 @@ def test_invalid_start_byte(invalid_start_byte, device_ind_msg):
|
||||
assert m.control == 0x4110
|
||||
assert str(m.seq) == '01:00'
|
||||
assert m.data_len == 0xd4
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -731,9 +734,9 @@ def test_invalid_stop_byte(invalid_stop_byte):
|
||||
assert m.control == 0x4110
|
||||
assert str(m.seq) == '01:00'
|
||||
assert m.data_len == 0xd4
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -756,9 +759,9 @@ def test_invalid_stop_byte2(invalid_stop_byte, device_ind_msg):
|
||||
assert m.msg_recvd[1]['data_len']==0xd4
|
||||
|
||||
assert m.unique_id == None
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -778,9 +781,9 @@ def test_invalid_stop_start_byte(invalid_stop_byte, invalid_start_byte):
|
||||
assert m.control == 0x4110
|
||||
assert str(m.seq) == '01:00'
|
||||
assert m.data_len == 0xd4
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -802,9 +805,9 @@ def test_invalid_checksum(invalid_checksum, device_ind_msg):
|
||||
assert m.msg_recvd[1]['control']==0x4110
|
||||
assert m.msg_recvd[1]['seq']=='01:00'
|
||||
assert m.msg_recvd[1]['data_len']==0xd4
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -824,8 +827,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]['seq']=='01:01'
|
||||
assert m.msg_recvd[1]['data_len']==0xd4
|
||||
assert m._send_buffer==device_rsp_msg+device_rsp_msg
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==device_rsp_msg+device_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -896,12 +899,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 '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None)
|
||||
assert 0x02b0 == m.sensor_list
|
||||
assert m._forward_buffer==device_ind_msg+inverter_ind_msg
|
||||
assert m._send_buffer==device_rsp_msg+inverter_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==device_ind_msg+inverter_ind_msg
|
||||
assert m.ifc.tx_fifo.get()==device_rsp_msg+inverter_rsp_msg
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._init_new_client_conn()
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
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):
|
||||
@@ -922,12 +924,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]['data_len']==0x199
|
||||
assert m.time_ofs == 0x33e447a0
|
||||
assert m._forward_buffer==inverter_ind_msg+inverter_ind_msg_81
|
||||
assert m._send_buffer==inverter_rsp_msg+inverter_rsp_msg_81
|
||||
assert m.ifc.fwd_fifo.get()==inverter_ind_msg+inverter_ind_msg_81
|
||||
assert m.ifc.tx_fifo.get()==inverter_rsp_msg+inverter_rsp_msg_81
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._init_new_client_conn()
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_read_two_messages3(config_tsun_allow_all, device_ind_msg2, device_rsp_msg2, inverter_ind_msg, inverter_rsp_msg):
|
||||
@@ -952,12 +953,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 '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None)
|
||||
assert 0x02b0 == m.sensor_list
|
||||
assert m._forward_buffer==inverter_ind_msg+device_ind_msg2
|
||||
assert m._send_buffer==inverter_rsp_msg+device_rsp_msg2
|
||||
assert m.ifc.fwd_fifo.get()==inverter_ind_msg+device_ind_msg2
|
||||
assert m.ifc.tx_fifo.get()==inverter_rsp_msg+device_rsp_msg2
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._init_new_client_conn()
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_unkown_frame_code(config_tsun_inv1, inverter_ind_msg_81, inverter_rsp_msg_81):
|
||||
@@ -972,9 +972,9 @@ def test_unkown_frame_code(config_tsun_inv1, inverter_ind_msg_81, inverter_rsp_m
|
||||
assert m.control == 0x4210
|
||||
assert str(m.seq) == '03:03'
|
||||
assert m.data_len == 0x199
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==inverter_rsp_msg_81
|
||||
assert m._forward_buffer==inverter_ind_msg_81
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==inverter_rsp_msg_81
|
||||
assert m.ifc.fwd_fifo.get()==inverter_ind_msg_81
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -990,9 +990,9 @@ def test_unkown_message(config_tsun_inv1, unknown_msg):
|
||||
assert m.control == 0x5110
|
||||
assert str(m.seq) == '84:10'
|
||||
assert m.data_len == 0x0a
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==unknown_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==unknown_msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1008,9 +1008,9 @@ def test_device_rsp(config_tsun_inv1, device_rsp_msg):
|
||||
assert m.control == 0x1110
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m.data_len == 0x0a
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1026,9 +1026,9 @@ def test_inverter_rsp(config_tsun_inv1, inverter_rsp_msg):
|
||||
assert m.control == 0x1210
|
||||
assert str(m.seq) == '02:02'
|
||||
assert m.data_len == 0x0a
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1043,9 +1043,9 @@ def test_heartbeat_ind(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp_msg):
|
||||
assert m.control == 0x4710
|
||||
assert str(m.seq) == '84:11' # value after sending response
|
||||
assert m.data_len == 0x01
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==heartbeat_rsp_msg
|
||||
assert m._forward_buffer==heartbeat_ind_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==heartbeat_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==heartbeat_ind_msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1061,9 +1061,9 @@ def test_heartbeat_ind2(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp_msg):
|
||||
assert m.control == 0x4710
|
||||
assert str(m.seq) == '84:11' # value after sending response
|
||||
assert m.data_len == 0x01
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==heartbeat_rsp_msg
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==heartbeat_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1079,9 +1079,9 @@ def test_heartbeat_rsp(config_tsun_inv1, heartbeat_rsp_msg):
|
||||
assert m.control == 0x1710
|
||||
assert str(m.seq) == '11:84' # value after sending response
|
||||
assert m.data_len == 0x0a
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1096,15 +1096,15 @@ def test_sync_start_ind(config_tsun_inv1, sync_start_ind_msg, sync_start_rsp_msg
|
||||
assert m.control == 0x4310
|
||||
assert str(m.seq) == '0d:0d' # value after sending response
|
||||
assert m.data_len == 47
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==sync_start_rsp_msg
|
||||
assert m._forward_buffer==sync_start_ind_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==sync_start_rsp_msg
|
||||
assert m.ifc.fwd_fifo.peek()==sync_start_ind_msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
|
||||
m.seq.server_side = False # simulate forawding to TSUN cloud
|
||||
m._update_header(m._forward_buffer)
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert str(m.seq) == '0d:0e' # value after forwarding indication
|
||||
assert m._forward_buffer==sync_start_fwd_msg
|
||||
assert m.ifc.fwd_fifo.get()==sync_start_fwd_msg
|
||||
|
||||
m.close()
|
||||
|
||||
@@ -1120,9 +1120,9 @@ def test_sync_start_rsp(config_tsun_inv1, sync_start_rsp_msg):
|
||||
assert m.control == 0x1310
|
||||
assert str(m.seq) == '0d:0d' # value after sending response
|
||||
assert m.data_len == 0x0a
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1137,9 +1137,9 @@ def test_sync_end_ind(config_tsun_inv1, sync_end_ind_msg, sync_end_rsp_msg):
|
||||
assert m.control == 0x4810
|
||||
assert str(m.seq) == '07:07' # value after sending response
|
||||
assert m.data_len == 60
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==sync_end_rsp_msg
|
||||
assert m._forward_buffer==sync_end_ind_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==sync_end_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==sync_end_ind_msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1155,9 +1155,9 @@ def test_sync_end_rsp(config_tsun_inv1, sync_end_rsp_msg):
|
||||
assert m.control == 0x1810
|
||||
assert str(m.seq) == '07:07' # value after sending response
|
||||
assert m.data_len == 0x0a
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1175,9 +1175,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 0 == m.sensor_list # must not been set by an inverter data ind
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m.ifc.tx_clear() # clear send buffer for next test
|
||||
m._init_new_client_conn()
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_build_modell_1600(config_tsun_allow_all, inverter_ind_msg1600):
|
||||
@@ -1241,9 +1241,9 @@ def test_build_logger_modell(config_tsun_allow_all, device_ind_msg):
|
||||
|
||||
def test_msg_iterator():
|
||||
Message._registry.clear()
|
||||
m1 = SolarmanV5(server_side=True, client_mode=False)
|
||||
m2 = SolarmanV5(server_side=True, client_mode=False)
|
||||
m3 = SolarmanV5(server_side=True, client_mode=False)
|
||||
m1 = SolarmanV5(('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
|
||||
m2 = SolarmanV5(('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
|
||||
m3 = SolarmanV5(('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
|
||||
m3.close()
|
||||
del m3
|
||||
test1 = 0
|
||||
@@ -1261,7 +1261,7 @@ def test_msg_iterator():
|
||||
assert test2 == 1
|
||||
|
||||
def test_proxy_counter():
|
||||
m = SolarmanV5(server_side=True, client_mode=False)
|
||||
m = SolarmanV5(('test.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
|
||||
assert m.new_data == {}
|
||||
m.db.stat['proxy']['Unknown_Msg'] = 0
|
||||
Infos.new_stat_data['proxy'] = False
|
||||
@@ -1285,16 +1285,14 @@ async def test_msg_build_modbus_req(config_tsun_inv1, device_ind_msg, device_rsp
|
||||
m.read()
|
||||
assert m.control == 0x4110
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m._send_buffer==device_rsp_msg
|
||||
assert m._forward_buffer==device_ind_msg
|
||||
assert m.ifc.tx_fifo.get()==device_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==device_ind_msg
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._forward_buffer = bytearray(0) # clear send buffer for next test
|
||||
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
|
||||
assert 0 == m.send_msg_ofs
|
||||
assert m._forward_buffer == b''
|
||||
assert m.writer.sent_pdu == b'' # modbus command must be ignore, cause connection is still not up
|
||||
assert m._send_buffer == b'' # modbus command must be ignore, cause connection is still not up
|
||||
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.ifc.tx_fifo.get() == b'' # modbus command must be ignore, cause connection is still not up
|
||||
|
||||
m.append_msg(inverter_ind_msg)
|
||||
m.read()
|
||||
@@ -1304,24 +1302,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[1]['control']==0x4210
|
||||
assert m.msg_recvd[1]['seq']=='02:02'
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==inverter_rsp_msg
|
||||
assert m._forward_buffer==inverter_ind_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==inverter_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==inverter_ind_msg
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._forward_buffer = bytearray(0) # clear send buffer for next test
|
||||
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
|
||||
assert 0 == m.send_msg_ofs
|
||||
assert m._forward_buffer == b''
|
||||
assert m.writer.sent_pdu == msg_modbus_cmd
|
||||
assert m._send_buffer == b''
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m.test_exception_async_write = True
|
||||
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
|
||||
assert 0 == m.send_msg_ofs
|
||||
assert m._forward_buffer == b''
|
||||
assert m._send_buffer == b''
|
||||
assert m.ifc.fwd_fifo.get() == b''
|
||||
assert m.sent_pdu == msg_modbus_cmd
|
||||
assert m.ifc.tx_fifo.get()== b''
|
||||
m.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -1331,14 +1320,13 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
|
||||
m.read() # read device ind
|
||||
assert m.control == 0x4110
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m._send_buffer==device_rsp_msg
|
||||
assert m._forward_buffer==device_ind_msg
|
||||
assert m.ifc.tx_fifo.get()==device_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==device_ind_msg
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._forward_buffer = bytearray(0) # clear send buffer for next test
|
||||
await m.send_at_cmd('AT+TIME=214028,1,60,120')
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.sent_pdu == b''
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m.mqtt.key == ''
|
||||
assert m.mqtt.data == ""
|
||||
@@ -1347,34 +1335,37 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
|
||||
m.read() # read inverter ind
|
||||
assert m.control == 0x4210
|
||||
assert str(m.seq) == '02:02'
|
||||
assert m._send_buffer==inverter_rsp_msg
|
||||
assert m._forward_buffer==inverter_ind_msg
|
||||
assert m.ifc.tx_fifo.get()==inverter_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==inverter_ind_msg
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._forward_buffer = bytearray(0) # clear send buffer for next test
|
||||
await m.send_at_cmd('AT+TIME=214028,1,60,120')
|
||||
assert m._send_buffer==at_command_ind_msg
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get() == b''
|
||||
assert m.ifc.tx_fifo.get()== b''
|
||||
assert m.sent_pdu == at_command_ind_msg
|
||||
m.sent_pdu = bytearray()
|
||||
|
||||
assert str(m.seq) == '02:03'
|
||||
assert m.mqtt.key == ''
|
||||
assert m.mqtt.data == ""
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m.append_msg(at_command_rsp_msg)
|
||||
m.read() # read at resp
|
||||
assert m.control == 0x1510
|
||||
assert str(m.seq) == '03:03'
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.key == 'at_resp'
|
||||
assert m.data == "+ok"
|
||||
|
||||
m.sent_pdu = bytearray()
|
||||
m.test_exception_async_write = True
|
||||
await m.send_at_cmd('AT+TIME=214028,1,60,120')
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.sent_pdu == b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.sent_pdu == b''
|
||||
assert str(m.seq) == '03:04'
|
||||
assert m.forward_at_cmd_resp == False
|
||||
assert m.mqtt.key == ''
|
||||
@@ -1388,14 +1379,12 @@ async def test_at_cmd_blocked(config_tsun_allow_all, device_ind_msg, device_rsp_
|
||||
m.read()
|
||||
assert m.control == 0x4110
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m._send_buffer==device_rsp_msg
|
||||
assert m._forward_buffer==device_ind_msg
|
||||
assert m.ifc.tx_fifo.get()==device_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==device_ind_msg
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._forward_buffer = bytearray(0) # clear send buffer for next test
|
||||
await m.send_at_cmd('AT+WEBU')
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m.mqtt.key == ''
|
||||
assert m.mqtt.data == ""
|
||||
@@ -1404,16 +1393,14 @@ async def test_at_cmd_blocked(config_tsun_allow_all, device_ind_msg, device_rsp_
|
||||
m.read()
|
||||
assert m.control == 0x4210
|
||||
assert str(m.seq) == '02:02'
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==inverter_rsp_msg
|
||||
assert m._forward_buffer==inverter_ind_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==inverter_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==inverter_ind_msg
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m._forward_buffer = bytearray(0) # clear send buffer for next test
|
||||
await m.send_at_cmd('AT+WEBU')
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert str(m.seq) == '02:02'
|
||||
assert m.forward_at_cmd_resp == False
|
||||
assert m.mqtt.key == 'at_resp'
|
||||
@@ -1435,9 +1422,9 @@ def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg):
|
||||
assert m.control == 0x4510
|
||||
assert str(m.seq) == '03:02'
|
||||
assert m.data_len == 39
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==at_command_ind_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==at_command_ind_msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
assert m.db.stat['proxy']['AT_Command'] == 1
|
||||
assert m.db.stat['proxy']['AT_Command_Blocked'] == 0
|
||||
@@ -1459,9 +1446,9 @@ def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block):
|
||||
assert m.control == 0x4510
|
||||
assert str(m.seq) == '03:02'
|
||||
assert m.data_len == 23
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
assert m.db.stat['proxy']['AT_Command'] == 0
|
||||
assert m.db.stat['proxy']['AT_Command_Blocked'] == 1
|
||||
@@ -1481,8 +1468,8 @@ def test_msg_at_command_rsp1(config_tsun_inv1, at_command_rsp_msg):
|
||||
assert str(m.seq) == '03:03'
|
||||
assert m.header_len==11
|
||||
assert m.data_len==17
|
||||
assert m._forward_buffer==at_command_rsp_msg
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==at_command_rsp_msg
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1500,8 +1487,8 @@ def test_msg_at_command_rsp2(config_tsun_inv1, at_command_rsp_msg):
|
||||
assert str(m.seq) == '03:03'
|
||||
assert m.header_len==11
|
||||
assert m.data_len==17
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1525,9 +1512,9 @@ def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd, msg_modbus_cmd_fwd):
|
||||
assert str(c.seq) == '03:02'
|
||||
assert c.header_len==11
|
||||
assert c.data_len==23
|
||||
assert c._forward_buffer==b''
|
||||
assert c._send_buffer==b''
|
||||
assert m.writer.sent_pdu == msg_modbus_cmd_fwd
|
||||
assert c.ifc.fwd_fifo.get()==b''
|
||||
assert c.ifc.tx_fifo.get()==b''
|
||||
assert m.sent_pdu == msg_modbus_cmd_fwd
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['AT_Command'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 1
|
||||
@@ -1552,9 +1539,9 @@ def test_msg_modbus_req2(config_tsun_inv1, msg_modbus_cmd_crc_err):
|
||||
assert str(c.seq) == '03:02'
|
||||
assert c.header_len==11
|
||||
assert c.data_len==23
|
||||
assert c._forward_buffer==b''
|
||||
assert c._send_buffer==b''
|
||||
assert m.writer.sent_pdu==b''
|
||||
assert c.ifc.fwd_fifo.get()==b''
|
||||
assert c.ifc.tx_fifo.get()==b''
|
||||
assert m.sent_pdu==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['AT_Command'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
@@ -1575,8 +1562,8 @@ def test_msg_unknown_cmd_req(config_tsun_inv1, msg_unknown_cmd):
|
||||
assert str(m.seq) == '03:02'
|
||||
assert m.header_len==11
|
||||
assert m.data_len==23
|
||||
assert m._forward_buffer==msg_unknown_cmd
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_unknown_cmd
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['AT_Command'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
@@ -1596,8 +1583,8 @@ def test_msg_modbus_rsp1(config_tsun_inv1, msg_modbus_rsp):
|
||||
assert str(m.seq) == '03:03'
|
||||
assert m.header_len==11
|
||||
assert m.data_len==59
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1620,21 +1607,20 @@ def test_msg_modbus_rsp2(config_tsun_inv1, msg_modbus_rsp):
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.mb.err == 0
|
||||
assert m.msg_count == 1
|
||||
assert m._forward_buffer==msg_modbus_rsp
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
|
||||
assert m.new_data['inverter'] == True
|
||||
m.new_data['inverter'] = False
|
||||
|
||||
m.mb.req_pend = True
|
||||
m._forward_buffer = bytearray()
|
||||
m.append_msg(msg_modbus_rsp)
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.mb.err == 0
|
||||
assert m.msg_count == 2
|
||||
assert m._forward_buffer==msg_modbus_rsp
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
|
||||
assert m.new_data['inverter'] == False
|
||||
|
||||
@@ -1658,20 +1644,19 @@ def test_msg_modbus_rsp3(config_tsun_inv1, msg_modbus_rsp):
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.mb.err == 0
|
||||
assert m.msg_count == 1
|
||||
assert m._forward_buffer==msg_modbus_rsp
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
|
||||
assert m.new_data['inverter'] == True
|
||||
m.new_data['inverter'] = False
|
||||
|
||||
m._forward_buffer = bytearray()
|
||||
m.append_msg(msg_modbus_rsp)
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.mb.err == 5
|
||||
assert m.msg_count == 2
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.get_db_value(Register.VERSION) == 'V4.0.10'
|
||||
assert m.new_data['inverter'] == False
|
||||
|
||||
@@ -1689,8 +1674,8 @@ def test_msg_unknown_rsp(config_tsun_inv1, msg_unknown_cmd_rsp):
|
||||
assert str(m.seq) == '03:03'
|
||||
assert m.header_len==11
|
||||
assert m.data_len==59
|
||||
assert m._forward_buffer==msg_unknown_cmd_rsp
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_unknown_cmd_rsp
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1703,8 +1688,8 @@ def test_msg_modbus_invalid(config_tsun_inv1, msg_modbus_invalid):
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1726,8 +1711,8 @@ def test_msg_modbus_fragment(config_tsun_inv1, msg_modbus_rsp):
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m._forward_buffer==msg_modbus_rsp
|
||||
assert m._send_buffer == b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp
|
||||
assert m.ifc.tx_fifo.get()== b''
|
||||
assert m.mb.err == 0
|
||||
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
|
||||
@@ -1750,28 +1735,27 @@ async def test_modbus_polling(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp
|
||||
assert m.control == 0x4710
|
||||
assert str(m.seq) == '84:11' # value after sending response
|
||||
assert m.data_len == 0x01
|
||||
assert m._recv_buffer==b''
|
||||
assert m._send_buffer==heartbeat_rsp_msg
|
||||
assert m._forward_buffer==heartbeat_ind_msg
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==heartbeat_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==heartbeat_ind_msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
assert m.state == State.up
|
||||
assert isclose(m.mb_timeout, 0.5)
|
||||
assert next(m.mb_timer.exp_count) == 0
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.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._send_buffer==b''
|
||||
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.tx_fifo.get()==b''
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.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._send_buffer==b''
|
||||
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.tx_fifo.get()==b''
|
||||
m.state = State.closed
|
||||
m.writer.sent_pdu = bytearray()
|
||||
m.sent_pdu = bytearray()
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.sent_pdu==bytearray(b'')
|
||||
assert m._send_buffer==b''
|
||||
assert m.sent_pdu==bytearray(b'')
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert next(m.mb_timer.exp_count) == 4
|
||||
m.close()
|
||||
|
||||
@@ -1785,7 +1769,7 @@ async def test_start_client_mode(config_tsun_inv1, str_test_ip):
|
||||
assert m.mb_timer.tim == None
|
||||
assert asyncio.get_running_loop() == m.mb_timer.loop
|
||||
await m.send_start_cmd(get_sn_int(), str_test_ip, m.mb_first_timeout)
|
||||
assert m.writer.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x01\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf1\x15')
|
||||
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x01\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf1\x15')
|
||||
assert m.db.get_db_value(Register.IP_ADDRESS) == str_test_ip
|
||||
assert isclose(m.db.get_db_value(Register.POLLING_INTERVAL), 0.5)
|
||||
assert m.db.get_db_value(Register.HEARTBEAT_INTERVAL) == 120
|
||||
@@ -1793,16 +1777,29 @@ async def test_start_client_mode(config_tsun_inv1, str_test_ip):
|
||||
assert m.state == State.up
|
||||
assert m.no_forwarding == True
|
||||
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert isclose(m.mb_timeout, 0.5)
|
||||
assert next(m.mb_timer.exp_count) == 0
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.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._send_buffer==b''
|
||||
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.tx_fifo.get()==b''
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.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._send_buffer==b''
|
||||
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.tx_fifo.get()==b''
|
||||
assert next(m.mb_timer.exp_count) == 3
|
||||
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()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# test_with_pytest.py
|
||||
import pytest, logging, asyncio
|
||||
from math import isclose
|
||||
from app.src.async_stream import AsyncIfcImpl, StreamPtr
|
||||
from app.src.gen3.talent import Talent, Control
|
||||
from app.src.config import Config
|
||||
from app.src.infos import Infos, Register
|
||||
@@ -15,22 +16,21 @@ Infos.static_init()
|
||||
|
||||
tracer = logging.getLogger('tracer')
|
||||
|
||||
|
||||
class Writer():
|
||||
class FakeIfc(AsyncIfcImpl):
|
||||
def __init__(self):
|
||||
self.sent_pdu = b''
|
||||
|
||||
def write(self, pdu: bytearray):
|
||||
self.sent_pdu = pdu
|
||||
super().__init__()
|
||||
self.remote = StreamPtr(None)
|
||||
|
||||
class MemoryStream(Talent):
|
||||
def __init__(self, msg, chunks = (0,), server_side: bool = True):
|
||||
super().__init__(server_side)
|
||||
self.ifc = FakeIfc()
|
||||
super().__init__(('test.local', 1234), self.ifc, server_side)
|
||||
if server_side:
|
||||
self.mb.timeout = 0.4 # overwrite for faster testing
|
||||
self.mb_first_timeout = 0.5
|
||||
self.mb_timeout = 0.5
|
||||
self.writer = Writer()
|
||||
self.sent_pdu = b''
|
||||
self.ifc.tx_fifo.reg_trigger(self.write_cb)
|
||||
self.__msg = msg
|
||||
self.__msg_len = len(msg)
|
||||
self.__chunks = chunks
|
||||
@@ -39,9 +39,11 @@ class MemoryStream(Talent):
|
||||
self.msg_count = 0
|
||||
self.addr = 'Test: SrvSide'
|
||||
self.send_msg_ofs = 0
|
||||
self.test_exception_async_write = False
|
||||
self.msg_recvd = []
|
||||
self.remote_stream = None
|
||||
|
||||
def write_cb(self):
|
||||
self.sent_pdu = self.ifc.tx_fifo.get()
|
||||
|
||||
|
||||
def append_msg(self, msg):
|
||||
self.__msg += msg
|
||||
@@ -54,11 +56,11 @@ class MemoryStream(Talent):
|
||||
chunk_len = self.__chunks[self.__chunk_idx]
|
||||
self.__chunk_idx += 1
|
||||
if chunk_len!=0:
|
||||
self._recv_buffer += self.__msg[self.__offs:chunk_len]
|
||||
self.ifc.rx_fifo += self.__msg[self.__offs:chunk_len]
|
||||
copied_bytes = chunk_len - self.__offs
|
||||
self.__offs = chunk_len
|
||||
else:
|
||||
self._recv_buffer += self.__msg[self.__offs:]
|
||||
self.ifc.rx_fifo += self.__msg[self.__offs:]
|
||||
copied_bytes = self.__msg_len - self.__offs
|
||||
self.__offs = self.__msg_len
|
||||
except Exception:
|
||||
@@ -73,8 +75,8 @@ class MemoryStream(Talent):
|
||||
|
||||
def createClientStream(self, msg, chunks = (0,)):
|
||||
c = MemoryStream(msg, chunks, False)
|
||||
self.remote_stream = c
|
||||
c. remote_stream = self
|
||||
self.ifc.remote.stream = c
|
||||
c.ifc.remote.stream = self
|
||||
return c
|
||||
|
||||
def _Talent__flush_recv_msg(self) -> None:
|
||||
@@ -91,22 +93,26 @@ class MemoryStream(Talent):
|
||||
|
||||
self.msg_count += 1
|
||||
|
||||
async def async_write(self, headline=''):
|
||||
if self.test_exception_async_write:
|
||||
raise RuntimeError("Peer closed.")
|
||||
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def msg_contact_info(): # Contact Info message
|
||||
Config.act_config = {'tsun':{'enabled': True}}
|
||||
return b'\x00\x00\x00\x2c\x10R170000000000001\x91\x00\x08solarhub\x0fsolarhub\x40123456'
|
||||
|
||||
@pytest.fixture
|
||||
def msg_contact_info_empty(): # Contact Info message with empty string
|
||||
return b'\x00\x00\x00\x15\x10R170000000000001\x91\x00\x00\x00'
|
||||
|
||||
@pytest.fixture
|
||||
def msg_contact_info_long_id(): # Contact Info message with longer ID
|
||||
Config.act_config = {'tsun':{'enabled': True}}
|
||||
return b'\x00\x00\x00\x2d\x11R1700000000000011\x91\x00\x08solarhub\x0fsolarhub\x40123456'
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def msg_contact_info_broken(): # Contact Info message with invalid string coding
|
||||
return b'\x00\x00\x00\x2a\x10R170000000000001\x91\x00solarhubsolarhub\x40123456'
|
||||
|
||||
@pytest.fixture
|
||||
def msg2_contact_info(): # two Contact Info messages
|
||||
return b'\x00\x00\x00\x2c\x10R170000000000001\x91\x00\x08solarhub\x0fsolarhub\x40123456\x00\x00\x00\x2c\x10R170000000000002\x91\x00\x08solarhub\x0fsolarhub\x40123456'
|
||||
@@ -728,6 +734,7 @@ def multiple_recv_buf(): # There are three message in the buffer, but the second
|
||||
return msg
|
||||
|
||||
def test_read_message(msg_contact_info):
|
||||
Config.act_config = {'tsun':{'enabled': True}}
|
||||
m = MemoryStream(msg_contact_info, (0,))
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
@@ -738,7 +745,9 @@ def test_read_message(msg_contact_info):
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==25
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_read_message_twice(config_no_tsun_inv1, msg_inverter_ind):
|
||||
@@ -758,7 +767,7 @@ def test_read_message_twice(config_no_tsun_inv1, msg_inverter_ind):
|
||||
assert m.msg_recvd[1]['data_len']==120
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert m._forward_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_read_message_long_id(msg_contact_info_long_id):
|
||||
@@ -782,6 +791,7 @@ def test_read_message_long_id(msg_contact_info_long_id):
|
||||
|
||||
|
||||
def test_read_message_in_chunks(msg_contact_info):
|
||||
Config.act_config = {'tsun':{'enabled': True}}
|
||||
m = MemoryStream(msg_contact_info, (4,23,0))
|
||||
m.read() # read 4 bytes, header incomplere
|
||||
assert not m.header_valid # must be invalid, since header not complete
|
||||
@@ -801,6 +811,7 @@ def test_read_message_in_chunks(msg_contact_info):
|
||||
m.close()
|
||||
|
||||
def test_read_message_in_chunks2(msg_contact_info):
|
||||
Config.act_config = {'tsun':{'enabled': True}}
|
||||
m = MemoryStream(msg_contact_info, (4,10,0))
|
||||
m.read() # read 4 bytes, header incomplere
|
||||
assert not m.header_valid
|
||||
@@ -841,15 +852,87 @@ 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]['header_len']==23
|
||||
assert m.msg_recvd[1]['data_len']==25
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==msg_contact_rsp + msg_contact_rsp2
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==msg_contact_rsp + msg_contact_rsp2
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m.ifc.tx_clear() # clear send buffer for next test
|
||||
m.contact_name = b'solarhub'
|
||||
m.contact_mail = b'solarhub@123456'
|
||||
m._init_new_client_conn()
|
||||
assert m._send_buffer==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()
|
||||
|
||||
def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp):
|
||||
_ = config_tsun_allow_all
|
||||
m = MemoryStream(msg_contact_info, (0,))
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.contact_name == b'solarhub'
|
||||
assert m.contact_mail == b'solarhub@123456'
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert int(m.ctrl)==145
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==25
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==msg_contact_rsp
|
||||
m.close()
|
||||
|
||||
def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_contact_rsp):
|
||||
_ = config_tsun_allow_all
|
||||
m = MemoryStream(msg_contact_info_broken, (0,))
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.contact_name == b''
|
||||
assert m.contact_mail == b''
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert int(m.ctrl)==145
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==23
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==msg_contact_rsp
|
||||
m.close()
|
||||
|
||||
def test_conttact_req(config_tsun_allow_all, msg_contact_info, msg_contact_rsp):
|
||||
_ = config_tsun_allow_all
|
||||
m = MemoryStream(msg_contact_info, (0,))
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.contact_name == b'solarhub'
|
||||
assert m.contact_mail == b'solarhub@123456'
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert int(m.ctrl)==145
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==25
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==msg_contact_rsp
|
||||
m.close()
|
||||
|
||||
def test_contact_broken_req(config_tsun_allow_all, msg_contact_info_broken, msg_contact_rsp):
|
||||
_ = config_tsun_allow_all
|
||||
m = MemoryStream(msg_contact_info_broken, (0,))
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.contact_name == b''
|
||||
assert m.contact_mail == b''
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert int(m.ctrl)==145
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==23
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==msg_contact_rsp
|
||||
m.close()
|
||||
|
||||
def test_msg_contact_resp(config_tsun_inv1, msg_contact_rsp):
|
||||
@@ -867,8 +950,8 @@ def test_msg_contact_resp(config_tsun_inv1, msg_contact_rsp):
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -887,8 +970,8 @@ def test_msg_contact_resp_2(config_tsun_inv1, msg_contact_rsp):
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==msg_contact_rsp
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_contact_rsp
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -907,8 +990,8 @@ def test_msg_contact_resp_3(config_tsun_inv1, msg_contact_rsp):
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==msg_contact_rsp
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_contact_rsp
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -925,8 +1008,8 @@ def test_msg_contact_invalid(config_tsun_inv1, msg_contact_invalid):
|
||||
assert m.msg_id==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==msg_contact_invalid
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_contact_invalid
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -946,8 +1029,8 @@ def test_msg_get_time(config_tsun_inv1, msg_get_time):
|
||||
assert m.ts_offset==0
|
||||
assert m.data_len==0
|
||||
assert m.state==State.pend
|
||||
assert m._forward_buffer==msg_get_time
|
||||
assert m._send_buffer==b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00'
|
||||
assert m.ifc.fwd_fifo.get()==msg_get_time
|
||||
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
|
||||
m.close()
|
||||
|
||||
@@ -967,8 +1050,8 @@ def test_msg_get_time_autark(config_no_tsun_inv1, msg_get_time):
|
||||
assert m.ts_offset==0
|
||||
assert m.data_len==0
|
||||
assert m.state==State.received
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==bytearray(b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00')
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
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
|
||||
m.close()
|
||||
|
||||
@@ -978,7 +1061,7 @@ def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
|
||||
m = MemoryStream(msg_time_rsp, (0,), False)
|
||||
s = MemoryStream(b'', (0,), True)
|
||||
assert s.ts_offset==0
|
||||
m.remote_stream = s
|
||||
m.ifc.remote.stream = s
|
||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
@@ -991,10 +1074,10 @@ def test_msg_time_resp(config_tsun_inv1, msg_time_rsp):
|
||||
assert m.ts_offset==3600000
|
||||
assert s.ts_offset==3600000
|
||||
assert m.data_len==8
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.remote_stream = None
|
||||
m.ifc.remote.stream = None
|
||||
s.close()
|
||||
m.close()
|
||||
|
||||
@@ -1012,8 +1095,8 @@ def test_msg_time_resp_autark(config_no_tsun_inv1, msg_time_rsp):
|
||||
assert m.header_len==23
|
||||
assert m.ts_offset==3600000
|
||||
assert m.data_len==8
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1031,8 +1114,8 @@ def test_msg_time_inv_resp(config_tsun_inv1, msg_time_rsp_inv):
|
||||
assert m.header_len==23
|
||||
assert m.ts_offset==0
|
||||
assert m.data_len==4
|
||||
assert m._forward_buffer==msg_time_rsp_inv
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_time_rsp_inv
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1050,8 +1133,8 @@ def test_msg_time_invalid(config_tsun_inv1, msg_time_invalid):
|
||||
assert m.header_len==23
|
||||
assert m.ts_offset==0
|
||||
assert m.data_len==0
|
||||
assert m._forward_buffer==msg_time_invalid
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_time_invalid
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -1069,8 +1152,8 @@ def test_msg_time_invalid_autark(config_no_tsun_inv1, msg_time_invalid):
|
||||
assert m.ts_offset==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==0
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -1093,8 +1176,8 @@ def test_msg_act_time(config_no_modbus_poll, msg_act_time, msg_act_time_ack):
|
||||
assert m.header_len==23
|
||||
assert m.data_len==9
|
||||
assert m.state == State.up
|
||||
assert m._forward_buffer==msg_act_time
|
||||
assert m._send_buffer==msg_act_time_ack
|
||||
assert m.ifc.fwd_fifo.get()==msg_act_time
|
||||
assert m.ifc.tx_fifo.get()==msg_act_time_ack
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert 125 == m.db.get_db_value(Register.POLLING_INTERVAL, 0)
|
||||
m.close()
|
||||
@@ -1117,8 +1200,8 @@ def test_msg_act_time2(config_tsun_inv1, msg_act_time, msg_act_time_ack):
|
||||
assert m.ts_offset==0
|
||||
assert m.header_len==23
|
||||
assert m.data_len==9
|
||||
assert m._forward_buffer==msg_act_time
|
||||
assert m._send_buffer==msg_act_time_ack
|
||||
assert m.ifc.fwd_fifo.get()==msg_act_time
|
||||
assert m.ifc.tx_fifo.get()==msg_act_time_ack
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert 123 == m.db.get_db_value(Register.POLLING_INTERVAL, 0)
|
||||
m.close()
|
||||
@@ -1138,8 +1221,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.header_len==23
|
||||
assert m.data_len==9
|
||||
assert m._forward_buffer==msg_act_time_ofs
|
||||
assert m._send_buffer==msg_act_time_ack
|
||||
assert m.ifc.fwd_fifo.get()==msg_act_time_ofs
|
||||
assert m.ifc.tx_fifo.get()==msg_act_time_ack
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1158,8 +1241,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.header_len==23
|
||||
assert m.data_len==9
|
||||
assert m._forward_buffer==msg_act_time
|
||||
assert m._send_buffer==msg_act_time_ack
|
||||
assert m.ifc.fwd_fifo.get()==msg_act_time
|
||||
assert m.ifc.tx_fifo.get()==msg_act_time_ack
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1178,8 +1261,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.header_len==23
|
||||
assert m.data_len==9
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==msg_act_time_ack
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==msg_act_time_ack
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1196,8 +1279,8 @@ def test_msg_act_time_ack(config_tsun_inv1, msg_act_time_ack):
|
||||
assert m.msg_id==153
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1214,8 +1297,8 @@ def test_msg_act_time_cmd(config_tsun_inv1, msg_act_time_cmd):
|
||||
assert m.msg_id==153
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==msg_act_time_cmd
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_act_time_cmd
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -1232,8 +1315,8 @@ def test_msg_act_time_inv(config_tsun_inv1, msg_act_time_inv):
|
||||
assert m.msg_id==153
|
||||
assert m.header_len==23
|
||||
assert m.data_len==8
|
||||
assert m._forward_buffer==msg_act_time_inv
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_act_time_inv
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1251,12 +1334,12 @@ def test_msg_cntrl_ind(config_tsun_inv1, msg_controller_ind, msg_controller_ind_
|
||||
assert m.header_len==23
|
||||
assert m.data_len==284
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_controller_ind
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.peek()==msg_controller_ind
|
||||
m.ts_offset = -4096
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_controller_ind_ts_offs
|
||||
assert m._send_buffer==msg_controller_ack
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_controller_ind_ts_offs
|
||||
assert m.ifc.tx_fifo.get()==msg_controller_ack
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1273,8 +1356,8 @@ def test_msg_cntrl_ack(config_tsun_inv1, msg_controller_ack):
|
||||
assert m.msg_id==113
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1292,12 +1375,12 @@ def test_msg_cntrl_invalid(config_tsun_inv1, msg_controller_invalid):
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_controller_invalid
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.peek()==msg_controller_invalid
|
||||
m.ts_offset = -4096
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_controller_invalid
|
||||
assert m._send_buffer==b''
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_controller_invalid
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -1316,12 +1399,12 @@ def test_msg_inv_ind(config_tsun_inv1, msg_inverter_ind, msg_inverter_ind_ts_off
|
||||
assert m.header_len==23
|
||||
assert m.data_len==120
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_inverter_ind
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.peek()==msg_inverter_ind
|
||||
m.ts_offset = +256
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_inverter_ind_ts_offs
|
||||
assert m._send_buffer==msg_inverter_ack
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ind_ts_offs
|
||||
assert m.ifc.tx_fifo.get()==msg_inverter_ack
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1343,9 +1426,9 @@ def test_msg_inv_ind1(config_tsun_inv1, msg_inverter_ind2, msg_inverter_ind_ts_o
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1263
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_inverter_ind2
|
||||
assert m._send_buffer==msg_inverter_ack
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ind2
|
||||
assert m.ifc.tx_fifo.get()==msg_inverter_ack
|
||||
assert m.db.get_db_value(Register.TS_GRID) == 1691243349
|
||||
m.close()
|
||||
|
||||
@@ -1367,9 +1450,9 @@ def test_msg_inv_ind2(config_tsun_inv1, msg_inverter_ind_new, msg_inverter_ind_t
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1165
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_inverter_ind_new
|
||||
assert m._send_buffer==msg_inverter_ack
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ind_new
|
||||
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.TS_GRID) == None
|
||||
m.db.db['grid'] = {'Output_Power': 100}
|
||||
@@ -1395,9 +1478,9 @@ def test_msg_inv_ind3(config_tsun_inv1, msg_inverter_ind_0w, msg_inverter_ack):
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1263
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_inverter_ind_0w
|
||||
assert m._send_buffer==msg_inverter_ack
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ind_0w
|
||||
assert m.ifc.tx_fifo.get()==msg_inverter_ack
|
||||
assert m.db.get_db_value(Register.INVERTER_STATUS) == 1
|
||||
assert isclose(m.db.db['grid']['Output_Power'], 0.5)
|
||||
m.close()
|
||||
@@ -1419,8 +1502,8 @@ def test_msg_inv_ack(config_tsun_inv1, msg_inverter_ack):
|
||||
assert m.msg_id==4
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
m.close()
|
||||
|
||||
@@ -1438,12 +1521,12 @@ def test_msg_inv_invalid(config_tsun_inv1, msg_inverter_invalid):
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_inverter_invalid
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.peek()==msg_inverter_invalid
|
||||
m.ts_offset = 256
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_inverter_invalid
|
||||
assert m._send_buffer==b''
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_invalid
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
m.close()
|
||||
|
||||
@@ -1462,12 +1545,12 @@ def test_msg_ota_req(config_tsun_inv1, msg_ota_req):
|
||||
assert m.header_len==23
|
||||
assert m.data_len==259
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_ota_req
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.peek()==msg_ota_req
|
||||
m.ts_offset = 4096
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_ota_req
|
||||
assert m._send_buffer==b''
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_ota_req
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['OTA_Start_Msg'] == 1
|
||||
m.close()
|
||||
@@ -1489,12 +1572,12 @@ def test_msg_ota_ack(config_tsun_inv1, msg_ota_ack):
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_ota_ack
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.peek()==msg_ota_ack
|
||||
m.ts_offset = 256
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_ota_ack
|
||||
assert m._send_buffer==b''
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_ota_ack
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['OTA_Start_Msg'] == 0
|
||||
m.close()
|
||||
@@ -1514,12 +1597,12 @@ def test_msg_ota_invalid(config_tsun_inv1, msg_ota_invalid):
|
||||
assert m.header_len==23
|
||||
assert m.data_len==1
|
||||
m.ts_offset = 0
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._forward_buffer==msg_ota_invalid
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.peek()==msg_ota_invalid
|
||||
m.ts_offset = 4096
|
||||
assert m._forward_buffer==msg_ota_invalid
|
||||
m._update_header(m._forward_buffer)
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_ota_invalid
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
assert m.db.stat['proxy']['OTA_Start_Msg'] == 0
|
||||
m.close()
|
||||
@@ -1537,8 +1620,8 @@ def test_msg_unknown(config_tsun_inv1, msg_unknown):
|
||||
assert m.msg_id==23
|
||||
assert m.header_len==23
|
||||
assert m.data_len==4
|
||||
assert m._forward_buffer==msg_unknown
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_unknown
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert 1 == m.db.stat['proxy']['Unknown_Msg']
|
||||
m.close()
|
||||
|
||||
@@ -1558,9 +1641,9 @@ def test_ctrl_byte():
|
||||
|
||||
|
||||
def test_msg_iterator():
|
||||
m1 = Talent(server_side=True)
|
||||
m2 = Talent(server_side=True)
|
||||
m3 = Talent(server_side=True)
|
||||
m1 = Talent(('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True)
|
||||
m2 = Talent(('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True)
|
||||
m3 = Talent(('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True)
|
||||
m3.close()
|
||||
del m3
|
||||
test1 = 0
|
||||
@@ -1662,12 +1745,12 @@ def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd):
|
||||
assert c.msg_id==119
|
||||
assert c.header_len==23
|
||||
assert c.data_len==13
|
||||
assert c._forward_buffer==b''
|
||||
assert c._send_buffer==b''
|
||||
assert c.ifc.fwd_fifo.get()==b''
|
||||
assert c.ifc.tx_fifo.get()==b''
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.writer.sent_pdu == msg_modbus_cmd
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.sent_pdu == msg_modbus_cmd
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 1
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
@@ -1692,12 +1775,12 @@ def test_msg_modbus_req2(config_tsun_inv1, msg_modbus_cmd):
|
||||
assert c.msg_id==119
|
||||
assert c.header_len==23
|
||||
assert c.data_len==13
|
||||
assert c._forward_buffer==b''
|
||||
assert c._send_buffer==b''
|
||||
assert c.ifc.fwd_fifo.get()==b''
|
||||
assert c.ifc.tx_fifo.get()==b''
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.writer.sent_pdu == b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.sent_pdu == b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 1
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
@@ -1721,11 +1804,11 @@ def test_msg_modbus_req3(config_tsun_inv1, msg_modbus_cmd_crc_err):
|
||||
assert c.msg_id==119
|
||||
assert c.header_len==23
|
||||
assert c.data_len==13
|
||||
assert c._forward_buffer==b''
|
||||
assert c._send_buffer==b''
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.writer.sent_pdu ==b''
|
||||
assert c.ifc.fwd_fifo.get()==b''
|
||||
assert c.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.sent_pdu ==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
||||
@@ -1746,8 +1829,8 @@ def test_msg_modbus_rsp1(config_tsun_inv1, msg_modbus_rsp):
|
||||
assert m.msg_id==119
|
||||
assert m.header_len==23
|
||||
assert m.data_len==13
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1768,8 +1851,8 @@ def test_msg_modbus_cloud_rsp(config_tsun_inv1, msg_modbus_rsp):
|
||||
assert m.msg_id==119
|
||||
assert m.header_len==23
|
||||
assert m.data_len==13
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Msg'] == 1
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
@@ -1796,8 +1879,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 m.mb.err == 5
|
||||
assert m.msg_count == 2
|
||||
assert m._forward_buffer==msg_modbus_rsp20
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp20
|
||||
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.get_db_value(Register.VERSION) == 'V5.1.09'
|
||||
assert m.db.get_db_value(Register.TS_GRID) == m._utc()
|
||||
@@ -1826,8 +1909,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 m.mb.err == 5
|
||||
assert m.msg_count == 2
|
||||
assert m._forward_buffer==msg_modbus_rsp21
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp21
|
||||
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.get_db_value(Register.VERSION) == 'V5.1.0E'
|
||||
assert m.db.get_db_value(Register.TS_GRID) == m._utc()
|
||||
@@ -1855,9 +1938,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 m.mb.err == 0
|
||||
assert m.msg_count == 1
|
||||
assert m._forward_buffer==msg_modbus_rsp21
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp21
|
||||
assert m.modbus_elms == 19
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.db == db_values
|
||||
assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E'
|
||||
assert m.db.get_db_value(Register.TS_GRID) == m._utc()
|
||||
@@ -1880,8 +1963,8 @@ def test_msg_modbus_rsp_new(config_tsun_inv1, msg_modbus_rsp20_new):
|
||||
assert m.msg_id==135
|
||||
assert m.header_len==23
|
||||
assert m.data_len==107
|
||||
assert m._forward_buffer==b''
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1900,8 +1983,8 @@ def test_msg_modbus_invalid(config_tsun_inv1, msg_modbus_inv):
|
||||
assert m.msg_id==119
|
||||
assert m.header_len==23
|
||||
assert m.data_len==13
|
||||
assert m._forward_buffer==msg_modbus_inv
|
||||
assert m._send_buffer==b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_inv
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
@@ -1929,8 +2012,8 @@ def test_msg_modbus_fragment(config_tsun_inv1, msg_modbus_rsp20):
|
||||
assert m.msg_id == 119
|
||||
assert m.header_len == 23
|
||||
assert m.data_len == 50
|
||||
assert m._forward_buffer==msg_modbus_rsp20
|
||||
assert m._send_buffer == b''
|
||||
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp20
|
||||
assert m.ifc.tx_fifo.get() == b''
|
||||
assert m.mb.err == 0
|
||||
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
|
||||
@@ -1944,24 +2027,16 @@ async def test_msg_build_modbus_req(config_tsun_inv1, msg_modbus_cmd):
|
||||
m.id_str = b"R170000000000001"
|
||||
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
|
||||
assert 0 == m.send_msg_ofs
|
||||
assert m._forward_buffer == b''
|
||||
assert m._send_buffer == b''
|
||||
assert m.writer.sent_pdu == b''
|
||||
assert m.ifc.fwd_fifo.get() == b''
|
||||
assert m.ifc.tx_fifo.get() == b''
|
||||
assert m.sent_pdu == b''
|
||||
|
||||
m.state = State.up
|
||||
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
|
||||
assert 0 == m.send_msg_ofs
|
||||
assert m._forward_buffer == b''
|
||||
assert m._send_buffer == b''
|
||||
assert m.writer.sent_pdu == msg_modbus_cmd
|
||||
|
||||
m.writer.sent_pdu = bytearray(0) # clear send buffer for next test
|
||||
m.test_exception_async_write = True
|
||||
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
|
||||
assert 0 == m.send_msg_ofs
|
||||
assert m._forward_buffer == b''
|
||||
assert m._send_buffer == b''
|
||||
assert m.writer.sent_pdu == b''
|
||||
assert m.ifc.fwd_fifo.get() == b''
|
||||
assert m.ifc.tx_fifo.get() == b''
|
||||
assert m.sent_pdu == msg_modbus_cmd
|
||||
m.close()
|
||||
|
||||
def test_modbus_no_polling(config_no_modbus_poll, msg_get_time):
|
||||
@@ -1979,8 +2054,8 @@ def test_modbus_no_polling(config_no_modbus_poll, msg_get_time):
|
||||
assert m.header_len==23
|
||||
assert m.ts_offset==0
|
||||
assert m.data_len==0
|
||||
assert m._forward_buffer==msg_get_time
|
||||
assert m._send_buffer==b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x89\xc6,_\x00'
|
||||
assert m.ifc.fwd_fifo.get()==msg_get_time
|
||||
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
|
||||
m.close()
|
||||
|
||||
@@ -2003,25 +2078,25 @@ async def test_modbus_polling(config_tsun_inv1, msg_inverter_ind):
|
||||
assert m.header_len==23
|
||||
assert m.ts_offset==0
|
||||
assert m.data_len==120
|
||||
assert m._forward_buffer==msg_inverter_ind
|
||||
assert m._send_buffer==b'\x00\x00\x00\x14\x10R170000000000001\x99\x04\x01'
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ind
|
||||
assert m.ifc.tx_fifo.get()==b'\x00\x00\x00\x14\x10R170000000000001\x99\x04\x01'
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
|
||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||
m.ifc.tx_clear() # clear send buffer for next test
|
||||
assert isclose(m.mb_timeout, 0.5)
|
||||
assert next(m.mb_timer.exp_count) == 0
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde'
|
||||
assert m._send_buffer==b''
|
||||
assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde'
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde'
|
||||
assert m._send_buffer==b''
|
||||
assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x030\x00\x000J\xde'
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.writer.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x03\x20\x00\x00`N"'
|
||||
assert m._send_buffer==b''
|
||||
assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x03\x20\x00\x00`N"'
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert next(m.mb_timer.exp_count) == 4
|
||||
m.close()
|
||||
|
||||
@@ -2064,3 +2139,15 @@ def test_multiiple_recv_buf(config_tsun_allow_all, multiple_recv_buf):
|
||||
assert m.db.stat['proxy']['Invalid_Data_Type'] == 1
|
||||
|
||||
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()
|
||||
|
||||
@@ -83,7 +83,7 @@ services:
|
||||
- ${PROJECT_DIR:-./}tsun-proxy/log:/home/tsun-proxy/log
|
||||
- ${PROJECT_DIR:-./}tsun-proxy/config:/home/tsun-proxy/config
|
||||
healthcheck:
|
||||
test: wget --no-verbose --tries=1 --spider http://localhost:8127/-/healthy || exit 1
|
||||
test: wget --no-verbose --tries=1 --spider http://127.0.0.1:8127/-/healthy || exit 1
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
networks:
|
||||
|
||||
11
tsun.code-workspace
Normal file
11
tsun.code-workspace
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../wiki"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
||||
Reference in New Issue
Block a user