diff --git a/app/proxy.svg b/app/proxy.svg index 88a5606..6493fc7 100644 --- a/app/proxy.svg +++ b/app/proxy.svg @@ -4,519 +4,193 @@ - - + + G - + A0 - - - -You can stick notes -on diagrams too! + + + +You can stick notes +on diagrams too! A1 - -IterRegistry - - -__iter__ + +IterRegistry + + +__iter__ A3 - -Inverter - -cls.db_stat -cls.entity_prfx -cls.discovery_prfx -cls.proxy_node_id -cls.proxy_unique_id -cls.mqtt:Mqtt -__ha_restarts - -async_create_remote(inv_prot, conn_class)async_publ_mqtt() + +Inverter + +cls.db_stat +cls.entity_prfx +cls.discovery_prfx +cls.proxy_node_id +cls.proxy_unique_id +cls.mqtt:Mqtt +__ha_restarts + +async_create_remote(inv_prot, conn_class)async_publ_mqtt() A1->A3 - - - - - -A18 - -Message - -node_id - -inc_counter() -dec_counter() - - - -A1->A18 - - + + A2 - -Mqtt -<<Singleton>> - -<static>ha_restarts -<static>__client -<static>__cb_MqttIsUp - -<async>publish() -<async>close() + +Mqtt +<<Singleton>> + +<static>ha_restarts +<static>__client +<static>__cb_MqttIsUp + +<async>publish() +<async>close() A3->A2 - - - + + + A4 - -InverterBase - - -<async>disc(shutdown_started) -<async>async_create_remote() -healthy() + +InverterBase + + +<async>disc(shutdown_started) +<async>async_create_remote() +healthy() A3->A4 - - + + A5 - -InverterG3 - -addr -remote:StreamPtr -local:StreamPtr - -async_create_remote() -close() + +InverterG3 + +addr +remote:StreamPtr +local:StreamPtr + +async_create_remote() +close() A4->A5 - - + + A6 - -InverterG3P - -addr -remote:StreamPtr -local:StreamPtr - -async_create_remote() -close() + +InverterG3P + +addr +remote:StreamPtr +local:StreamPtr + +async_create_remote() +close() A4->A6 - - - - - -A10 - -AsyncStreamServer - -async_create_remote - -<async>server_loop() -<async>_async_forward() -<async>publish_outstanding_mqtt() -close() - - - -A5->A10 - - - -local - - - -A11 - -AsyncStreamClient - - -<async>client_loop() -<async>_async_forward()) - - - -A5->A11 - - -remote - - - -A6->A10 - - - -local - - - -A6->A11 - - -remote + + A7 - -<<AsyncIfc>> - - -set_node_id() -get_conn_no() -tx_add() -tx_flush() -tx_get() -tx_peek() -tx_log() -tx_clear() -tx_len() -fwd_add() -fwd_flush() -fwd_log() -fwd_clear() -rx_get() -rx_peek() -rx_log() -rx_clear() -rx_len() -rx_set_cb() -prot_set_timeout_cb() + +<<AsyncIfc>> + + + +A5->A7 + + +1..2 A8 - -AsyncIfcImpl - -fwd_fifo:ByteFifo -tx_fifo:ByteFifo -rx_fifo:ByteFifo -conn_no:Count -node_id -timeout_cb + +<<MessageProt>> - + -A7->A8 - - +A5->A8 + + +1..2 + + + +A6->A7 + + +1..2 + + + +A6->A8 + + +1..2 + + + +A8->A7 + + +use A9 - -AsyncStream - -reader -writer -addr -r_addr -l_addr - -<async>loop -disc() -close() -healthy() -__async_read() -__async_write() -__async_forward() + +ModbusConn + +host +port +addr +stream:InverterG3P + - - -A8->A9 - - - - - -A9->A10 - - - - - -A9->A11 - - - - - -A12 - -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() - - - -A12->A5 - - -remote - - - -A12->A5 - - - -local - - - -A12->A7 - - -use - - - -A15 - -InfosG3 - - -ha_confs() -parse() - - - -A12->A15 - - - - - -A17 - -ConnectionG3 - - - -A12->A17 - - - - - -A13 - -SolarmanV5 - -ifc:AsyncIfc -conn_no -addr -control -serial -snr -db:InfosG3P -mb:Modbus -switch - -msg_unknown() -healthy() -close() - - - -A13->A6 - - -remote - - - -A13->A6 - - - -local - - - -A13->A7 - - -use - - - -A16 - -InfosG3P - - -ha_confs() -parse() - - - -A13->A16 - - - - - -A14 - -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 - - - -A14->A15 - - - - - -A14->A16 - - - - - -A18->A12 - - - - - -A18->A13 - - - - - -A19 - -Modbus - -que -snd_handler -rsp_handler -timeout -max_retires -last_xxx -err -retry_cnt -req_pend -tim - -build_msg() -recv_req() -recv_resp() -close() - - - -A19->A12 - - -has -1 - - - -A19->A13 - - -has -1 - - - -A20 - -ModbusConn - -host -port -addr -stream:InverterG3P - - - - -A20->A6 - - -1 -has + + +A9->A6 + + +1 +has diff --git a/app/proxy.yuml b/app/proxy.yuml index 85bf0d0..daf04aa 100644 --- a/app/proxy.yuml +++ b/app/proxy.yuml @@ -15,46 +15,17 @@ [IterRegistry]^[Inverter|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;close():void;inc_counter():void;dec_counter():void] -[<>||set_node_id();get_conn_no();;tx_add();tx_flush();tx_get();tx_peek();tx_log();tx_clear();tx_len();;fwd_add();fwd_flush();fwd_log();fwd_clear();rx_get();rx_peek();rx_log();rx_clear();rx_len();rx_set_cb();;prot_set_timeout_cb()] -[AsyncIfcImpl|fwd_fifo:ByteFifo;tx_fifo:ByteFifo;rx_fifo:ByteFifo;conn_no:Count;node_id;timeout_cb] -[AsyncStream|reader;writer;addr;r_addr;l_addr|;loop;disc();close();healthy();;__async_read();__async_write();__async_forward()] -[AsyncStreamServer|async_create_remote|server_loop();_async_forward();publish_outstanding_mqtt();close()] -[AsyncStreamClient||client_loop();_async_forward())] -[<>]^-.-[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][AsyncStreamClient] -[Talent]<-local++[InverterG3] -[InverterG3]++local->[AsyncStreamServer] +[InverterG3]-1..2>[<>] +[InverterG3]-1..2>[<>] -[SolarmanV5|ifc:AsyncIfc;conn_no;addr;;control;serial;snr;db:InfosG3P;mb:Modbus;switch|msg_unknown();;healthy();close()] -[SolarmanV5][AsyncStreamClient] -[SolarmanV5]<-local++[InverterG3P] -[InverterG3P]++local->[AsyncStreamServer] +[InverterG3P]-1..2>[<>] +[InverterG3P]-1..2>[<>] -[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()] +[<>]use-.->[<>] -[Talent]^[ConnectionG3] -[Talent]use->[<>] -[Talent]->[InfosG3] -[SolarmanV5]use->[<>] -[SolarmanV5]->[InfosG3P] - -[IterRegistry]^[Message|node_id|inc_counter();dec_counter()] -[Message]^[Talent] -[Message]^[SolarmanV5] - -[Modbus|que;;snd_handler;rsp_handler;timeout;max_retires;last_xxx;err;retry_cnt;req_pend;tim|build_msg();recv_req();recv_resp();close()] -[Modbus]<1-has[SolarmanV5] -[Modbus]<1-has[Talent] [ModbusConn|host;port;addr;stream:InverterG3P;|]has-1>[InverterG3P] diff --git a/app/proxy_2.svg b/app/proxy_2.svg new file mode 100644 index 0000000..63e6657 --- /dev/null +++ b/app/proxy_2.svg @@ -0,0 +1,418 @@ + + + + + + +G + + + +A0 + + + +You can stick notes +on diagrams too! + + + +A1 + +IterRegistry + + +__iter__ + + + +A14 + +Message + +node_id + +inc_counter() +dec_counter() + + + +A1->A14 + + + + + +A2 + +InverterG3 + +addr +remote:StreamPtr +local:StreamPtr + +async_create_remote() +close() + + + +A7 + +AsyncStreamServer + +async_create_remote + +<async>server_loop() +<async>_async_forward() +<async>publish_outstanding_mqtt() +close() + + + +A2->A7 + + + +local + + + +A8 + +AsyncStreamClient + + +<async>client_loop() +<async>_async_forward()) + + + +A2->A8 + + +remote + + + +A3 + +InverterG3P + +addr +remote:StreamPtr +local:StreamPtr + +async_create_remote() +close() + + + +A3->A7 + + + +local + + + +A3->A8 + + +remote + + + +A4 + +<<AsyncIfc>> + + +set_node_id() +get_conn_no() +tx_add() +tx_flush() +tx_get() +tx_peek() +tx_log() +tx_clear() +tx_len() +fwd_add() +fwd_flush() +fwd_log() +fwd_clear() +rx_get() +rx_peek() +rx_log() +rx_clear() +rx_len() +rx_set_cb() +prot_set_timeout_cb() + + + +A5 + +AsyncIfcImpl + +fwd_fifo:ByteFifo +tx_fifo:ByteFifo +rx_fifo:ByteFifo +conn_no:Count +node_id +timeout_cb + + + +A4->A5 + + + + + +A6 + +AsyncStream + +reader +writer +addr +r_addr +l_addr + +<async>loop +disc() +close() +healthy() +__async_read() +__async_write() +__async_forward() + + + +A5->A6 + + + + + +A6->A7 + + + + + +A6->A8 + + + + + +A9 + +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() + + + +A9->A2 + + +remote + + + +A9->A2 + + + +local + + + +A9->A4 + + +use + + + +A12 + +InfosG3 + + +ha_confs() +parse() + + + +A9->A12 + + + + + +A10 + +SolarmanV5 + +ifc:AsyncIfc +conn_no +addr +control +serial +snr +db:InfosG3P +mb:Modbus +switch + +msg_unknown() +healthy() +close() + + + +A10->A3 + + +remote + + + +A10->A3 + + + +local + + + +A10->A4 + + +use + + + +A13 + +InfosG3P + + +ha_confs() +parse() + + + +A10->A13 + + + + + +A11 + +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 + + + +A11->A12 + + + + + +A11->A13 + + + + + +A14->A9 + + + + + +A14->A10 + + + + + +A15 + +Modbus + +que +snd_handler +rsp_handler +timeout +max_retires +last_xxx +err +retry_cnt +req_pend +tim + +build_msg() +recv_req() +recv_resp() +close() + + + +A15->A9 + + +has +1 + + + +A15->A10 + + +has +1 + + + diff --git a/app/proxy_2.yuml b/app/proxy_2.yuml new file mode 100644 index 0000000..2d96500 --- /dev/null +++ b/app/proxy_2.yuml @@ -0,0 +1,49 @@ +// {type:class} +// {direction:topDown} +// {generate:true} + +[note: You can stick notes on diagrams too!{bg:cornsilk}] +[IterRegistry||__iter__] + +[InverterG3|addr;remote:StreamPtr;local:StreamPtr|async_create_remote();;close()] +[InverterG3P|addr;remote:StreamPtr;local:StreamPtr|async_create_remote();;close()] + +[<>||set_node_id();get_conn_no();;tx_add();tx_flush();tx_get();tx_peek();tx_log();tx_clear();tx_len();;fwd_add();fwd_flush();fwd_log();fwd_clear();rx_get();rx_peek();rx_log();rx_clear();rx_len();rx_set_cb();;prot_set_timeout_cb()] +[AsyncIfcImpl|fwd_fifo:ByteFifo;tx_fifo:ByteFifo;rx_fifo:ByteFifo;conn_no:Count;node_id;timeout_cb] +[AsyncStream|reader;writer;addr;r_addr;l_addr|;loop;disc();close();healthy();;__async_read();__async_write();__async_forward()] +[AsyncStreamServer|async_create_remote|server_loop();_async_forward();publish_outstanding_mqtt();close()] +[AsyncStreamClient||client_loop();_async_forward())] +[<>]^-.-[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][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][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->[<>] +[Talent]->[InfosG3] +[SolarmanV5]use->[<>] +[SolarmanV5]->[InfosG3P] + +[IterRegistry]^[Message|node_id|inc_counter();dec_counter()] +[Message]^[Talent] +[Message]^[SolarmanV5] + +[Modbus|que;;snd_handler;rsp_handler;timeout;max_retires;last_xxx;err;retry_cnt;req_pend;tim|build_msg();recv_req();recv_resp();close()] +[Modbus]<1-has[SolarmanV5] +[Modbus]<1-has[Talent] diff --git a/app/src/gen3/inverter_g3.py b/app/src/gen3/inverter_g3.py index b0d64fd..de9c519 100644 --- a/app/src/gen3/inverter_g3.py +++ b/app/src/gen3/inverter_g3.py @@ -1,35 +1,13 @@ -import logging from asyncio import StreamReader, StreamWriter if __name__ == "app.src.gen3.inverter_g3": from app.src.inverter_base import InverterBase - from app.src.async_stream import StreamPtr - from app.src.async_stream import AsyncStreamServer from app.src.gen3.talent import Talent else: # pragma: no cover from inverter_base import InverterBase - from async_stream import StreamPtr - from async_stream import AsyncStreamServer from gen3.talent import Talent -logger_mqtt = logging.getLogger('mqtt') - - class InverterG3(InverterBase): - def __init__(self, reader: StreamReader, writer: StreamWriter, addr): - super().__init__() - self.addr = addr - self.remote = StreamPtr(None) - ifc = AsyncStreamServer(reader, writer, - self.async_publ_mqtt, - self.async_create_remote, - self.remote) - - self.local = StreamPtr( - Talent(addr, ifc, True, False), ifc - ) - - async def async_create_remote(self) -> None: - await InverterBase.async_create_remote( - self, 'tsun', Talent) + def __init__(self, reader: StreamReader, writer: StreamWriter): + super().__init__(reader, writer, 'tsun', Talent) diff --git a/app/src/gen3plus/inverter_g3p.py b/app/src/gen3plus/inverter_g3p.py index 21474eb..cc27bb4 100644 --- a/app/src/gen3plus/inverter_g3p.py +++ b/app/src/gen3plus/inverter_g3p.py @@ -1,36 +1,15 @@ -import logging from asyncio import StreamReader, StreamWriter if __name__ == "app.src.gen3plus.inverter_g3p": from app.src.inverter_base import InverterBase - from app.src.async_stream import StreamPtr - from app.src.async_stream import AsyncStreamServer from app.src.gen3plus.solarman_v5 import SolarmanV5 else: # pragma: no cover from inverter_base import InverterBase - from async_stream import StreamPtr - from async_stream import AsyncStreamServer from gen3plus.solarman_v5 import SolarmanV5 -logger_mqtt = logging.getLogger('mqtt') - - class InverterG3P(InverterBase): - def __init__(self, reader: StreamReader, writer: StreamWriter, addr, + def __init__(self, reader: StreamReader, writer: StreamWriter, client_mode: bool = False): - super().__init__() - self.addr = addr - self.remote = StreamPtr(None) - ifc = AsyncStreamServer(reader, writer, - self.async_publ_mqtt, - self.async_create_remote, - self.remote) - - self.local = StreamPtr( - SolarmanV5(addr, ifc, True, client_mode), ifc - ) - - async def async_create_remote(self) -> None: - await InverterBase.async_create_remote( - self, 'solarman', SolarmanV5) + super().__init__(reader, writer, 'solarman', + SolarmanV5, client_mode) diff --git a/app/src/inverter_base.py b/app/src/inverter_base.py index 828846b..026b91b 100644 --- a/app/src/inverter_base.py +++ b/app/src/inverter_base.py @@ -3,15 +3,20 @@ import logging import traceback import json from aiomqtt import MqttCodeError +from asyncio import StreamReader, StreamWriter if __name__ == "app.src.inverter_base": from app.src.inverter import Inverter + from app.src.async_stream import StreamPtr from app.src.async_stream import AsyncStreamClient + from app.src.async_stream import AsyncStreamServer from app.src.config import Config from app.src.infos import Infos else: # pragma: no cover from inverter import Inverter + from async_stream import StreamPtr from async_stream import AsyncStreamClient + from async_stream import AsyncStreamServer from config import Config from infos import Infos @@ -19,9 +24,23 @@ logger_mqtt = logging.getLogger('mqtt') class InverterBase(Inverter): - def __init__(self): + def __init__(self, reader: StreamReader, writer: StreamWriter, + config_id: str, prot_class, + client_mode: bool = False): super().__init__() + 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.async_create_remote, + self.remote) + + self.local = StreamPtr( + self.prot_class(self.addr, ifc, True, client_mode), ifc + ) def __enter__(self): return self @@ -66,9 +85,10 @@ class InverterBase(Inverter): return False return True - async def async_create_remote(self, inv_prot: str, conn_class) -> None: + async def async_create_remote(self) -> None: '''Establish a client connection to the TSUN cloud''' - tsun = Config.get(inv_prot) + + tsun = Config.get(self.config_id) host = tsun['host'] port = tsun['port'] addr = (host, port) @@ -83,11 +103,11 @@ class InverterBase(Inverter): self.remote.ifc = ifc if hasattr(stream, 'id_str'): - self.remote.stream = conn_class( + self.remote.stream = self.prot_class( addr, ifc, server_side=False, client_mode=False, id_str=stream.id_str) else: - self.remote.stream = conn_class( + self.remote.stream = self.prot_class( addr, ifc, server_side=False, client_mode=False) diff --git a/app/src/modbus_tcp.py b/app/src/modbus_tcp.py index 8bacce3..f6e8245 100644 --- a/app/src/modbus_tcp.py +++ b/app/src/modbus_tcp.py @@ -25,7 +25,7 @@ class ModbusConn(): '''Establish a client connection to the TSUN cloud''' connection = asyncio.open_connection(self.host, self.port) reader, writer = await connection - self.inverter = InverterG3P(reader, writer, self.addr, + self.inverter = InverterG3P(reader, writer, client_mode=True) self.inverter.__enter__() stream = self.inverter.local.stream diff --git a/app/src/server.py b/app/src/server.py index a475845..0ff5202 100644 --- a/app/src/server.py +++ b/app/src/server.py @@ -72,8 +72,7 @@ async def webserver(addr, port): async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class): '''Handles a new incoming connection and starts an async loop''' - addr = writer.get_extra_info('peername') - with inv_class(reader, writer, addr) as inv: + with inv_class(reader, writer) as inv: await inv.local.ifc.server_loop() diff --git a/app/tests/test_inverter_base.py b/app/tests/test_inverter_base.py new file mode 100644 index 0000000..0a4099f --- /dev/null +++ b/app/tests/test_inverter_base.py @@ -0,0 +1,241 @@ +# test_with_pytest.py +import pytest +import asyncio +import sys,gc +import weakref + +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.singleton import Singleton +from app.src.protocol_ifc import ProtocolIfc +from app.src.inverter_base import InverterBase +from app.src.messages import Message +from app.src.async_stream import AsyncStream + +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 FakeProtocol(ProtocolIfc): + def __init__(self, addr, ifc, server_side: bool, + client_mode: bool = False, id_str=b''): + # self._registry.append(weakref.ref(self)) + pass # empty mockup + + def close(self): + pass # empty mockup + +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 get_test_inverter(): + reader = FakeReader() + writer = FakeWriter() + with InverterBase(reader, writer, 'tsun', FakeProtocol) as inverter: + yield inverter + +@pytest.fixture +def patch_healthy(): + with patch.object(AsyncStream, 'healthy') as conn: + yield conn + +def test_method_calls(get_test_inverter, patch_healthy): + spy = patch_healthy + with get_test_inverter as inverter: + assert inverter.local.stream + assert inverter.local.ifc + for inv in Inverter: + inv.healthy() + del inv + spy.assert_called_once() + del inverter + cnt = 0 + for inv in Inverter: + print(f'inv:{gc.get_referrers()}') + 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() + + with InverterBase(FakeReader(), FakeWriter()) as inverter: + await inverter.async_create_remote() + await asyncio.sleep(0) + assert inverter.remote.stream + del inverter + + cnt = 0 + for inv in Inverter: + print(f'Inverter refs:{gc.get_referrers(inv)}') + cnt += 1 + assert cnt == 0 + +@pytest.mark.asyncio +async def test_remote_except(config_conn, patch_open_connection): + _ = config_conn + _ = patch_open_connection + assert asyncio.get_running_loop() + + global test + test = TestType.RD_TEST_TIMEOUT + + with InverterBase(FakeReader(), FakeWriter()) as inverter: + 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 + del inverter + + cnt = 0 + for inv in Inverter: + print(f'Inverter refs:{gc.get_referrers(inv)}') + cnt += 1 + assert cnt == 0 + +@pytest.mark.asyncio +async def test_mqtt_publish(config_conn, patch_open_connection): + _ = config_conn + _ = patch_open_connection + assert asyncio.get_running_loop() + + Inverter.class_init() + + with InverterBase(FakeReader(), FakeWriter()) as inverter: + stream = inverter.local.stream + await inverter.async_publ_mqtt() # check call with invalid unique_id + stream._Talent__set_serial_no(serial_no= "123344") + + stream.new_data['inverter'] = True + stream.db.db['inverter'] = {} + await inverter.async_publ_mqtt() + assert stream.new_data['inverter'] == False + + stream.new_data['env'] = True + stream.db.db['env'] = {} + await inverter.async_publ_mqtt() + assert stream.new_data['env'] == False + + 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): + _ = config_conn + _ = patch_open_connection + _ = patch_mqtt_err + assert asyncio.get_running_loop() + + Inverter.class_init() + + with InverterBase(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): + _ = config_conn + _ = patch_open_connection + _ = patch_mqtt_except + assert asyncio.get_running_loop() + + Inverter.class_init() + + with InverterBase(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 diff --git a/app/tests/test_inverter_g3.py b/app/tests/test_inverter_g3.py index f4721c7..2fbe4e8 100644 --- a/app/tests/test_inverter_g3.py +++ b/app/tests/test_inverter_g3.py @@ -103,8 +103,9 @@ def test_method_calls(patch_healthy): spy = patch_healthy reader = FakeReader() writer = FakeWriter() - addr = ('proxy.local', 10000) - with InverterG3(reader, writer, addr) as inverter: + Inverter._registry.clear() + + with InverterG3(reader, writer) as inverter: assert inverter.local.stream assert inverter.local.ifc for inv in Inverter: @@ -123,7 +124,7 @@ async def test_remote_conn(config_conn, patch_open_connection): _ = patch_open_connection assert asyncio.get_running_loop() - with InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000)) as inverter: + with InverterG3(FakeReader(), FakeWriter()) as inverter: await inverter.async_create_remote() await asyncio.sleep(0) assert inverter.remote.stream @@ -144,7 +145,7 @@ async def test_remote_except(config_conn, patch_open_connection): global test test = TestType.RD_TEST_TIMEOUT - with InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000)) as inverter: + with InverterG3(FakeReader(), FakeWriter()) as inverter: await inverter.async_create_remote() await asyncio.sleep(0) assert inverter.remote.stream==None @@ -169,7 +170,7 @@ async def test_mqtt_publish(config_conn, patch_open_connection): Inverter.class_init() - with InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000)) as inverter: + 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") @@ -197,7 +198,7 @@ async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err): Inverter.class_init() - with InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000)) as inverter: + with InverterG3(FakeReader(), FakeWriter()) as inverter: stream = inverter.local.stream stream._Talent__set_serial_no(serial_no= "123344") stream.new_data['inverter'] = True @@ -214,7 +215,7 @@ async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except Inverter.class_init() - with InverterG3(FakeReader(), FakeWriter(), ('proxy.local', 10000)) as inverter: + with InverterG3(FakeReader(), FakeWriter()) as inverter: stream = inverter.local.stream stream._Talent__set_serial_no(serial_no= "123344") diff --git a/app/tests/test_inverter_g3p.py b/app/tests/test_inverter_g3p.py index 2238b66..603d356 100644 --- a/app/tests/test_inverter_g3p.py +++ b/app/tests/test_inverter_g3p.py @@ -96,8 +96,9 @@ def patch_open_connection(): def test_method_calls(): reader = FakeReader() writer = FakeWriter() - addr = ('proxy.local', 10000) - with InverterG3P(reader, writer, addr, client_mode=False) as inverter: + Inverter._registry.clear() + + with InverterG3P(reader, writer, client_mode=False) as inverter: assert inverter.local.stream assert inverter.local.ifc @@ -107,7 +108,7 @@ async def test_remote_conn(config_conn, patch_open_connection): _ = patch_open_connection assert asyncio.get_running_loop() - with InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False) as inverter: + with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter: await inverter.async_create_remote() await asyncio.sleep(0) assert inverter.remote.stream @@ -121,7 +122,7 @@ async def test_remote_except(config_conn, patch_open_connection): global test test = TestType.RD_TEST_TIMEOUT - with InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False) as inverter: + with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter: await inverter.async_create_remote() await asyncio.sleep(0) assert inverter.remote.stream==None @@ -139,7 +140,7 @@ async def test_mqtt_publish(config_conn, patch_open_connection): Inverter.class_init() - with InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False) as inverter: + 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) @@ -167,7 +168,7 @@ async def test_mqtt_err(config_conn, patch_open_connection, patch_mqtt_err): Inverter.class_init() - with InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False) as inverter: + 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 @@ -184,7 +185,7 @@ async def test_mqtt_except(config_conn, patch_open_connection, patch_mqtt_except Inverter.class_init() - with InverterG3P(FakeReader(), FakeWriter(), ('proxy.local', 10000), client_mode=False) as inverter: + with InverterG3P(FakeReader(), FakeWriter(), client_mode=False) as inverter: stream = inverter.local.stream stream._SolarmanV5__set_serial_no(snr= 123344) diff --git a/app/tests/test_modbus_tcp.py b/app/tests/test_modbus_tcp.py index 22887e8..5611a03 100644 --- a/app/tests/test_modbus_tcp.py +++ b/app/tests/test_modbus_tcp.py @@ -93,11 +93,13 @@ class FakeReader(): class FakeWriter(): + def __init__(self, conn='remote.intern'): + self.conn = conn def write(self, buf: bytes): return def get_extra_info(self, sel: str): if sel == 'peername': - return 'remote.intern' + return self.conn elif sel == 'sockname': return 'sock:1234' assert False @@ -113,13 +115,13 @@ class FakeWriter(): 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 @@ -153,7 +155,7 @@ async def test_modbus_conn(patch_open): async with ModbusConn('test.local', 1234) as inverter: stream = inverter.local.stream assert stream.node_id == 'G3P' - assert stream.addr == ('test.local', 1234) + assert stream.addr == ('test.local:1234') assert type(stream.ifc._reader) is FakeReader assert type(stream.ifc._writer) is FakeWriter assert Infos.stat['proxy']['Inverter_Cnt'] == 1