diff --git a/app/docu/proxy_2.svg b/app/docu/proxy_2.svg index 232983f..0298327 100644 --- a/app/docu/proxy_2.svg +++ b/app/docu/proxy_2.svg @@ -4,368 +4,380 @@ - + G - + A0 - - - -Example of -instantiation for a -GEN3 inverter! + + + +Example of +instantiation for a +GEN3 inverter! A1 - -<<AbstractIterMeta>> - - -__iter__() - - - -A14 - -<<ProtocolIfc>> - -_registry - -close() - - - -A1->A14 - - - - - -A2 - -InverterG3 - -addr -remote:StreamPtr -local:StreamPtr - -create_remote() -close() - - - -A3 - -local:StreamPtr - - - -A2->A3 - - - - - - -A4 - -remote:StreamPtr - - - -A2->A4 - - - - - - -A8 - -AsyncStreamServer - -create_remote - -<async>server_loop() -<async>_async_forward() -<async>publish_outstanding_mqtt() -close() - - - -A3->A8 - - - - - - -A9 - -AsyncStreamClient - - -<async>client_loop() -<async>_async_forward()) - - - -A4->A9 - - -0..1 - - - -A5 - -<<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() - - - -A6 - -AsyncIfcImpl - -fwd_fifo:ByteFifo -tx_fifo:ByteFifo -rx_fifo:ByteFifo -conn_no:Count -node_id -timeout_cb - - - -A5->A6 - - - - - -A7 - -AsyncStream - -reader -writer -addr -r_addr -l_addr - -<async>loop -disc() -close() -healthy() -__async_read() -__async_write() -__async_forward() - - - -A6->A7 - - - - - -A7->A8 - - - - - -A7->A9 - - - - - -A10 - -Talent - -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() - - - -A10->A3 - - - - - - -A10->A4 - - -0..1 - - - -A12 - -InfosG3 - - -ha_confs() -parse() - - - -A10->A12 - - - - - -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 - - - - - -A13 - -Message - -server_side:bool -mb:Modbus -ifc:AsyncIfc -node_id -header_valid:bool -header_len -data_len -unique_id -sug_area:str -new_data:dict -state:State -shutdown_started:bool -modbus_elms -mb_timer:Timer -mb_timeout -mb_first_timeout -modbus_polling:bool - -_set_mqtt_timestamp() -_timeout() -_send_modbus_cmd() -<async> end_modbus_cmd() -close() -inc_counter() -dec_counter() - - - -A13->A5 - - -use - - - -A13->A10 - - - - - -A14->A13 - - + +<<AbstractIterMeta>> + + +__iter__() 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() + +<<ProtocolIfc>> + +_registry + +close() - + + +A1->A15 + + + + + +A2 + +InverterBase + +addr +remote:StreamPtr +local:StreamPtr + +create_remote() +close() + + + +A3 + +InverterG3 + + + +A2->A3 + + + + + +A4 + +local:StreamPtr + + + +A2->A4 + + + + + + +A5 + +remote:StreamPtr + + + +A2->A5 + + + + + + +A9 + +AsyncStreamServer + +create_remote + +<async>server_loop() +<async>_async_forward() +<async>publish_outstanding_mqtt() +close() + + + +A4->A9 + + + + + + +A10 + +AsyncStreamClient + + +<async>client_loop() +<async>_async_forward()) + + + +A5->A10 + + +0..1 + + + +A6 + +<<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() + + + +A7 + +AsyncIfcImpl + +fwd_fifo:ByteFifo +tx_fifo:ByteFifo +rx_fifo:ByteFifo +conn_no:Count +node_id +timeout_cb + + + +A6->A7 + + + + + +A8 + +AsyncStream + +reader +writer +addr +r_addr +l_addr + +<async>loop +disc() +close() +healthy() +__async_read() +__async_write() +__async_forward() + + + +A7->A8 + + + + + +A8->A9 + + + + + +A8->A10 + + + + + +A11 + +Talent + +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() + + + +A11->A4 + + + + + + +A11->A5 + + +0..1 + + + +A13 + +InfosG3 + + +ha_confs() +parse() + + + +A11->A13 + + + + + +A12 + +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 + + + +A12->A13 + + + + + +A14 + +Message + +server_side:bool +mb:Modbus +ifc:AsyncIfc +node_id +header_valid:bool +header_len +data_len +unique_id +sug_area:str +new_data:dict +state:State +shutdown_started:bool +modbus_elms +mb_timer:Timer +mb_timeout +mb_first_timeout +modbus_polling:bool + +_set_mqtt_timestamp() +_timeout() +_send_modbus_cmd() +<async> end_modbus_cmd() +close() +inc_counter() +dec_counter() + + + +A14->A6 + + +use + + -A15->A13 - - -has -0..1 +A14->A11 + + + + + +A15->A14 + + + + + +A16 + +Modbus + +que +snd_handler +rsp_handler +timeout +max_retires +last_xxx +err +retry_cnt +req_pend +tim + +build_msg() +recv_req() +recv_resp() +close() + + + +A16->A14 + + +has +0..1 diff --git a/app/docu/proxy_2.yuml b/app/docu/proxy_2.yuml index 5138428..6c50022 100644 --- a/app/docu/proxy_2.yuml +++ b/app/docu/proxy_2.yuml @@ -5,10 +5,10 @@ [note: Example of instantiation for a GEN3 inverter!{bg:cornsilk}] [<>||__iter__()] -[InverterG3|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] -[InverterG3]++->[local:StreamPtr] -[InverterG3]++->[remote:StreamPtr] - +[InverterBase|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] +[InverterBase]^[InverterG3] +[InverterBase]++->[local:StreamPtr] +[InverterBase]++->[remote:StreamPtr] [<>||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|;loop;disc();close();healthy();;__async_read();__async_write();__async_forward()] diff --git a/app/docu/proxy_3.svg b/app/docu/proxy_3.svg index 8be4969..d429bed 100644 --- a/app/docu/proxy_3.svg +++ b/app/docu/proxy_3.svg @@ -4,364 +4,379 @@ - + G - + A0 - - - -Example of -instantiation for a -GEN3PLUS inverter! + + + +Example of +instantiation for a +GEN3PLUS inverter! A1 - -<<AbstractIterMeta>> - - -__iter__() - - - -A14 - -<<ProtocolIfc>> - -_registry - -close() - - - -A1->A14 - - - - - -A2 - -InverterG3P - -addr -remote:StreamPtr -local:StreamPtr - -create_remote() -close() - - - -A3 - -local:StreamPtr - - - -A2->A3 - - - - - - -A4 - -remote:StreamPtr - - - -A2->A4 - - - - - - -A8 - -AsyncStreamServer - -create_remote - -<async>server_loop() -<async>_async_forward() -<async>publish_outstanding_mqtt() -close() - - - -A3->A8 - - - - - - -A9 - -AsyncStreamClient - - -<async>client_loop() -<async>_async_forward()) - - - -A4->A9 - - -0..1 - - - -A5 - -<<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() - - - -A6 - -AsyncIfcImpl - -fwd_fifo:ByteFifo -tx_fifo:ByteFifo -rx_fifo:ByteFifo -conn_no:Count -node_id -timeout_cb - - - -A5->A6 - - - - - -A7 - -AsyncStream - -reader -writer -addr -r_addr -l_addr - -<async>loop -disc() -close() -healthy() -__async_read() -__async_write() -__async_forward() - - - -A6->A7 - - - - - -A7->A8 - - - - - -A7->A9 - - - - - -A10 - -SolarmanV5 - -conn_no -addr -control -serial -snr -db:InfosG3P -switch - -msg_unknown() -healthy() -close() - - - -A10->A3 - - - - - - -A10->A4 - - -0..1 - - - -A12 - -InfosG3P - -client_mode:bool - -ha_confs() -parse() -calc() -build() - - - -A10->A12 - - - - - -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 - - - - - -A13 - -Message - -server_side:bool -mb:Modbus -ifc:AsyncIfc -node_id -header_valid:bool -header_len -data_len -unique_id -sug_area:str -new_data:dict -state:State -shutdown_started:bool -modbus_elms -mb_timer:Timer -mb_timeout -mb_first_timeout -modbus_polling:bool - -_set_mqtt_timestamp() -_timeout() -_send_modbus_cmd() -<async> end_modbus_cmd() -close() -inc_counter() -dec_counter() - - - -A13->A5 - - -use - - - -A13->A10 - - - - - -A14->A13 - - + +<<AbstractIterMeta>> + + +__iter__() 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() + +<<ProtocolIfc>> + +_registry + +close() - + + +A1->A15 + + + + + +A2 + +InverterBase + +addr +remote:StreamPtr +local:StreamPtr + +create_remote() +close() + + + +A3 + +InverterG3P + +forward_at_cmd_resp + + + +A2->A3 + + + + + +A4 + +local:StreamPtr + + + +A2->A4 + + + + + + +A5 + +remote:StreamPtr + + + +A2->A5 + + + + + + +A9 + +AsyncStreamServer + +create_remote + +<async>server_loop() +<async>_async_forward() +<async>publish_outstanding_mqtt() +close() + + + +A4->A9 + + + + + + +A10 + +AsyncStreamClient + + +<async>client_loop() +<async>_async_forward()) + + + +A5->A10 + + +0..1 + + + +A6 + +<<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() + + + +A7 + +AsyncIfcImpl + +fwd_fifo:ByteFifo +tx_fifo:ByteFifo +rx_fifo:ByteFifo +conn_no:Count +node_id +timeout_cb + + + +A6->A7 + + + + + +A8 + +AsyncStream + +reader +writer +addr +r_addr +l_addr + +<async>loop +disc() +close() +healthy() +__async_read() +__async_write() +__async_forward() + + + +A7->A8 + + + + + +A8->A9 + + + + + +A8->A10 + + + + + +A11 + +SolarmanV5 + +conn_no +addr +inverter:InverterG3P +control +serial +snr +db:InfosG3P +switch + +msg_unknown() +healthy() +close() + + + +A11->A4 + + + + + + +A11->A5 + + +0..1 + + + +A13 + +InfosG3P + +client_mode:bool + +ha_confs() +parse() +calc() +build() + + + +A11->A13 + + + + + +A12 + +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 + + + +A12->A13 + + + + + +A14 + +Message + +server_side:bool +mb:Modbus +ifc:AsyncIfc +node_id +header_valid:bool +header_len +data_len +unique_id +sug_area:str +new_data:dict +state:State +shutdown_started:bool +modbus_elms +mb_timer:Timer +mb_timeout +mb_first_timeout +modbus_polling:bool + +_set_mqtt_timestamp() +_timeout() +_send_modbus_cmd() +<async> end_modbus_cmd() +close() +inc_counter() +dec_counter() + + + +A14->A6 + + +use + + -A15->A13 - - -has -0..1 +A14->A11 + + + + + +A15->A14 + + + + + +A16 + +Modbus + +que +snd_handler +rsp_handler +timeout +max_retires +last_xxx +err +retry_cnt +req_pend +tim + +build_msg() +recv_req() +recv_resp() +close() + + + +A16->A14 + + +has +0..1 diff --git a/app/docu/proxy_3.yuml b/app/docu/proxy_3.yuml index 3703658..18bcec1 100644 --- a/app/docu/proxy_3.yuml +++ b/app/docu/proxy_3.yuml @@ -5,9 +5,10 @@ [note: Example of instantiation for a GEN3PLUS inverter!{bg:cornsilk}] [<>||__iter__()] -[InverterG3P|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] -[InverterG3P]++->[local:StreamPtr] -[InverterG3P]++->[remote:StreamPtr] +[InverterBase|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] +[InverterBase]^[InverterG3P|forward_at_cmd_resp;] +[InverterBase]++->[local:StreamPtr] +[InverterBase]++->[remote:StreamPtr] [<>||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] @@ -19,7 +20,7 @@ [AsyncStream]^[AsyncStreamServer] [AsyncStream]^[AsyncStreamClient] -[SolarmanV5|conn_no;addr;;control;serial;snr;db:InfosG3P;switch|msg_unknown();;healthy();close()] +[SolarmanV5|conn_no;addr;inverter:InverterG3P;control;serial;snr;db:InfosG3P;switch|msg_unknown();;healthy();close()] [SolarmanV5]<-++[local:StreamPtr] [local:StreamPtr]++->[AsyncStreamServer] [SolarmanV5]<-0..1[remote:StreamPtr] diff --git a/app/src/gen3/talent.py b/app/src/gen3/talent.py index e799a36..292dfdf 100644 --- a/app/src/gen3/talent.py +++ b/app/src/gen3/talent.py @@ -34,10 +34,11 @@ class Control: class Talent(Message): TXT_UNKNOWN_CTRL = 'Unknown Ctrl' - def __init__(self, addr, ifc: "AsyncIfc", server_side: bool, + def __init__(self, inverter, addr, ifc: "AsyncIfc", server_side: bool, client_mode: bool = False, id_str=b''): super().__init__('G3', ifc, server_side, self.send_modbus_cb, mb_timeout=15) + _ = inverter 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) diff --git a/app/src/gen3plus/inverter_g3p.py b/app/src/gen3plus/inverter_g3p.py index f3680c9..6a3f55d 100644 --- a/app/src/gen3plus/inverter_g3p.py +++ b/app/src/gen3plus/inverter_g3p.py @@ -8,6 +8,15 @@ from gen3plus.solarman_emu import SolarmanEmu class InverterG3P(InverterBase): def __init__(self, reader: StreamReader, writer: StreamWriter, client_mode: bool = False): + # shared value between both inverter connections + self.forward_at_cmd_resp = False + '''Flag if response for the last at command must be send to the cloud. + + False: send result only to the MQTT broker, cause the AT+ command + came from there + True: send response packet to the cloud, cause the AT+ command + came from the cloud''' + remote_prot = None if client_mode: remote_prot = SolarmanEmu diff --git a/app/src/gen3plus/solarman_emu.py b/app/src/gen3plus/solarman_emu.py index 7462388..93bb874 100644 --- a/app/src/gen3plus/solarman_emu.py +++ b/app/src/gen3plus/solarman_emu.py @@ -10,11 +10,12 @@ logger = logging.getLogger('msg') class SolarmanEmu(SolarmanBase): - def __init__(self, addr, ifc: "AsyncIfc", + def __init__(self, inverter, addr, ifc: "AsyncIfc", server_side: bool, client_mode: bool): super().__init__(addr, ifc, server_side=False, _send_modbus_cb=None, mb_timeout=8) + _ = inverter logging.debug('SolarmanEmu.init()') self.db = ifc.remote.stream.db self.snr = ifc.remote.stream.snr diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index 9b4bee8..7c8b997 100644 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -253,13 +253,13 @@ class SolarmanV5(SolarmanBase): HDR_FMT = ' we get a memory leak + self.inverter = None self.switch.clear() self.log_lvl.clear() super().close() @@ -519,7 +520,7 @@ class SolarmanV5(SolarmanBase): await Proxy.mqtt.publish(f'{Proxy.entity_prfx}{node_id}{key}', data_json) # noqa: E501 return - self.forward_at_cmd_resp = False + self.inverter.forward_at_cmd_resp = False self._build_header(0x4510) self.ifc.tx_add(struct.pack(f' int: ftype = self.ifc.rx_peek()[self.header_len] - if ftype == self.AT_CMD: - if self.forward_at_cmd_resp: + if ftype == self.AT_CMD or \ + ftype == self.AT_CMD_RSP: + if self.inverter.forward_at_cmd_resp: return logging.INFO return logging.DEBUG elif ftype == self.MB_RTU_CMD \ @@ -680,7 +682,7 @@ class SolarmanV5(SolarmanBase): ftype = data[0] if ftype == self.AT_CMD or \ ftype == self.AT_CMD_RSP: - if not self.forward_at_cmd_resp: + if not self.inverter.forward_at_cmd_resp: data_json = data[14:].decode("utf-8") node_id = self.node_id key = 'at_resp' diff --git a/app/src/inverter_base.py b/app/src/inverter_base.py index a4036c9..18c8902 100644 --- a/app/src/inverter_base.py +++ b/app/src/inverter_base.py @@ -41,7 +41,7 @@ class InverterBase(InverterIfc, Proxy): self.remote) self.local = StreamPtr( - prot_class(self.addr, ifc, True, client_mode), ifc + prot_class(self, self.addr, ifc, True, client_mode), ifc ) def __enter__(self): @@ -122,11 +122,11 @@ class InverterBase(InverterIfc, Proxy): self.remote.ifc = ifc if hasattr(stream, 'id_str'): self.remote.stream = self.prot_class( - addr, ifc, server_side=False, + self, 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, + self, addr, ifc, server_side=False, client_mode=False) logging.info(f'[{self.remote.stream.node_id}:' diff --git a/app/tests/test_inverter_g3.py b/app/tests/test_inverter_g3.py index e591d04..626ba7d 100644 --- a/app/tests/test_inverter_g3.py +++ b/app/tests/test_inverter_g3.py @@ -155,6 +155,7 @@ async def test_remote_except(config_conn, patch_open_connection): await asyncio.sleep(0) assert inverter.remote.stream==None del inverter + test = MockType.RD_TEST_0_BYTES cnt = 0 for inv in InverterBase: diff --git a/app/tests/test_inverter_g3p.py b/app/tests/test_inverter_g3p.py index 5b19121..f16a2d8 100644 --- a/app/tests/test_inverter_g3p.py +++ b/app/tests/test_inverter_g3p.py @@ -133,6 +133,9 @@ async def test_remote_except(config_conn, patch_open_connection): await asyncio.sleep(0) assert inverter.remote.stream==None + test = MockType.RD_TEST_0_BYTES + + @pytest.mark.asyncio async def test_mqtt_publish(config_conn, patch_open_connection): _ = config_conn diff --git a/app/tests/test_modbus_tcp.py b/app/tests/test_modbus_tcp.py index 1c69b60..dcb7e3e 100644 --- a/app/tests/test_modbus_tcp.py +++ b/app/tests/test_modbus_tcp.py @@ -190,7 +190,8 @@ def patch_mqtt_except(): yield conn @pytest.mark.asyncio -async def test_modbus_conn(patch_open): +async def test_modbus_conn(config_conn, patch_open): + _ = config_conn _ = patch_open assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @@ -210,6 +211,7 @@ async def test_modbus_conn(patch_open): @pytest.mark.asyncio async def test_modbus_no_cnf(): + _ = config_conn assert Infos.stat['proxy']['Inverter_Cnt'] == 0 loop = asyncio.get_event_loop() ModbusTcp(loop) diff --git a/app/tests/test_mqtt.py b/app/tests/test_mqtt.py index 85f0ab2..421fadb 100644 --- a/app/tests/test_mqtt.py +++ b/app/tests/test_mqtt.py @@ -47,7 +47,7 @@ def config_no_conn(test_port): @pytest.fixture def spy_at_cmd(): - conn = SolarmanV5(('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl()) + conn = SolarmanV5(None, ('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 @@ -55,7 +55,7 @@ def spy_at_cmd(): @pytest.fixture def spy_modbus_cmd(): - conn = SolarmanV5(('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl()) + conn = SolarmanV5(None, ('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 @@ -63,7 +63,7 @@ def spy_modbus_cmd(): @pytest.fixture def spy_modbus_cmd_client(): - conn = SolarmanV5(('test.local', 1234), server_side=False, client_mode= False, ifc=AsyncIfcImpl()) + conn = SolarmanV5(None, ('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 diff --git a/app/tests/test_solarman.py b/app/tests/test_solarman.py index e9a28b1..0c3c86c 100644 --- a/app/tests/test_solarman.py +++ b/app/tests/test_solarman.py @@ -4,7 +4,9 @@ import time import asyncio import logging import random +from asyncio import StreamReader, StreamWriter from math import isclose + from async_stream import AsyncIfcImpl, StreamPtr from gen3plus.solarman_v5 import SolarmanV5, SolarmanBase from cnf.config import Config @@ -12,6 +14,11 @@ from infos import Infos, Register from modbus import Modbus from messages import State, Message from proxy import Proxy +from test_inverter_g3p import FakeReader, FakeWriter, patch_open_connection +from inverter_base import InverterBase +from test_modbus_tcp import test_port, test_hostname + + pytest_plugins = ('pytest_asyncio',) @@ -43,10 +50,14 @@ class FakeIfc(AsyncIfcImpl): async def create_remote(self): await asyncio.sleep(0) +class FakeInverter(): + def __init__(self): + self.forward_at_cmd_resp = False + class MemoryStream(SolarmanV5): - def __init__(self, msg, chunks = (0,), server_side: bool = True): + def __init__(self, msg, chunks = (0,), server_side: bool = True, inverter=FakeInverter()): _ifc = FakeIfc() - super().__init__(('test.local', 1234), _ifc, server_side, client_mode=False) + super().__init__(inverter, ('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 @@ -816,7 +827,7 @@ def config_tsun_allow_all(): @pytest.fixture def config_no_tsun_inv1(): - Config.act_config = {'solarman':{'enabled': False},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}} + Config.act_config = {'solarman':{'enabled': False},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1/', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}} @pytest.fixture def config_tsun_inv1(): @@ -828,21 +839,21 @@ def config_tsun_inv1(): 'proxy_node_id': 'test_1', 'proxy_unique_id': '' }, - 'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}} + 'solarman':{'enabled': True, 'host': 'test_cloud.local', 'port': 1234},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1/', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}} Proxy.class_init() Proxy.mqtt = Mqtt() @pytest.fixture def config_tsun_scan(): - Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'modbus_scanning': {'start': 0xffc0, 'step': 0x40, 'bytes':20}, 'suggested_area':'roof', 'sensor_list': 0}}} + Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1/', 'modbus_polling': True, 'modbus_scanning': {'start': 0xffc0, 'step': 0x40, 'bytes':20}, 'suggested_area':'roof', 'sensor_list': 0}}} @pytest.fixture def config_tsun_scan_dcu(): - Config.act_config = {'solarman':{'enabled': True},'inverters':{'4100000000000001':{'monitor_sn': 2070233888, 'node_id':'inv1', 'modbus_polling': True, 'modbus_scanning': {'start': 0x0000, 'step': 0x100, 'bytes':0x2d}, 'client_mode': {'host': '192.168.1.1.'}, 'suggested_area':'roof', 'sensor_list': 0}}} + Config.act_config = {'solarman':{'enabled': True},'inverters':{'4100000000000001':{'monitor_sn': 2070233888, 'node_id':'inv1/', 'modbus_polling': True, 'modbus_scanning': {'start': 0x0000, 'step': 0x100, 'bytes':0x2d}, 'client_mode': {'host': '192.168.1.1.'}, 'suggested_area':'roof', 'sensor_list': 0}}} @pytest.fixture def config_tsun_dcu1(): - Config.act_config = {'solarman':{'enabled': True},'batteries':{'4100000000000001':{'monitor_sn': 2070233888, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}} + Config.act_config = {'solarman':{'enabled': True},'batteries':{'4100000000000001':{'monitor_sn': 2070233888, 'node_id':'inv1/', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}} def test_read_message(device_ind_msg): Config.act_config = {'solarman':{'enabled': True}} @@ -1432,9 +1443,9 @@ def test_build_logger_modell(config_tsun_allow_all, device_ind_msg): def test_msg_iterator(): Message._registry.clear() - m1 = SolarmanV5(('test1.local', 1234), 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) + m1 = SolarmanV5(None, ('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) + m2 = SolarmanV5(None, ('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) + m3 = SolarmanV5(None, ('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) m3.close() del m3 test1 = 0 @@ -1452,7 +1463,7 @@ def test_msg_iterator(): assert test2 == 1 def test_proxy_counter(): - m = SolarmanV5(('test.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) + m = SolarmanV5(None, ('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 @@ -1559,7 +1570,7 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv 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.inverter.forward_at_cmd_resp == False assert Proxy.mqtt.key == '' assert Proxy.mqtt.data == "" m.close() @@ -1594,12 +1605,12 @@ async def test_at_cmd_blocked(config_tsun_allow_all, device_ind_msg, device_rsp_ 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.inverter.forward_at_cmd_resp == False assert Proxy.mqtt.key == 'tsun/at_resp' assert Proxy.mqtt.data == "'AT+WEBU' is forbidden" m.close() -def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg): +def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg, at_command_rsp_msg): _ = config_tsun_inv1 m = MemoryStream(at_command_ind_msg, (0,), False) m.db.stat['proxy']['Unknown_Ctrl'] = 0 @@ -1621,6 +1632,17 @@ def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg): assert m.db.stat['proxy']['AT_Command'] == 1 assert m.db.stat['proxy']['AT_Command_Blocked'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0 + + m.append_msg(at_command_rsp_msg) + m.read() # read at resp + assert m.control == 0x1510 + assert str(m.seq) == '03:03' + assert m.ifc.rx_get()==b'' + assert m.ifc.tx_fifo.get()==b'' + assert m.ifc.fwd_fifo.get()==at_command_rsp_msg + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" + m.close() def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block): @@ -1630,6 +1652,7 @@ def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block): m.db.stat['proxy']['AT_Command'] = 0 m.db.stat['proxy']['AT_Command_Blocked'] = 0 m.db.stat['proxy']['Modbus_Command'] = 0 + m.inverter.forward_at_cmd_resp = False 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 @@ -1645,6 +1668,9 @@ def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block): assert m.db.stat['proxy']['AT_Command'] == 0 assert m.db.stat['proxy']['AT_Command_Blocked'] == 1 assert m.db.stat['proxy']['Modbus_Command'] == 0 + assert m.inverter.forward_at_cmd_resp == False + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" m.close() def test_msg_at_command_rsp1(config_tsun_inv1, at_command_rsp_msg): @@ -1652,7 +1678,7 @@ def test_msg_at_command_rsp1(config_tsun_inv1, at_command_rsp_msg): m = MemoryStream(at_command_rsp_msg) m.db.stat['proxy']['Unknown_Ctrl'] = 0 m.db.stat['proxy']['Modbus_Command'] = 0 - m.forward_at_cmd_resp = True + m.inverter.forward_at_cmd_resp = True 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 @@ -1671,7 +1697,7 @@ def test_msg_at_command_rsp2(config_tsun_inv1, at_command_rsp_msg): m = MemoryStream(at_command_rsp_msg) m.db.stat['proxy']['Unknown_Ctrl'] = 0 m.db.stat['proxy']['Modbus_Command'] = 0 - m.forward_at_cmd_resp = False + m.inverter.forward_at_cmd_resp = False 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 @@ -1683,6 +1709,8 @@ def test_msg_at_command_rsp2(config_tsun_inv1, 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 + assert Proxy.mqtt.key == 'tsun/inv1/at_resp' + assert Proxy.mqtt.data == "+ok" m.close() def test_msg_at_command_rsp3(config_tsun_inv1, at_command_interim_rsp_msg): @@ -1692,7 +1720,7 @@ def test_msg_at_command_rsp3(config_tsun_inv1, at_command_interim_rsp_msg): m.db.stat['proxy']['Modbus_Command'] = 0 m.db.stat['proxy']['Invalid_Msg_Format'] = 0 m.db.stat['proxy']['Unknown_Msg'] = 0 - m.forward_at_cmd_resp = True + m.inverter.forward_at_cmd_resp = True 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 @@ -1706,6 +1734,8 @@ def test_msg_at_command_rsp3(config_tsun_inv1, at_command_interim_rsp_msg): assert m.db.stat['proxy']['Modbus_Command'] == 0 assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0 assert m.db.stat['proxy']['Unknown_Msg'] == 0 + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" m.close() def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd, msg_modbus_cmd_fwd): @@ -2218,4 +2248,100 @@ def test_timestamp(): m = MemoryStream(b'') ts = m._timestamp() ts_emu = m._emu_timestamp() - assert ts == ts_emu + 24*60*60 \ No newline at end of file + assert ts == ts_emu + 24*60*60 + +class MemoryStream2(MemoryStream): + def __init__(self, inverter, addr, ifc, + server_side: bool, client_mode: bool): + super().__init__(b'', inverter=inverter) + + +class InverterTest(InverterBase): + def __init__(self, reader: StreamReader, writer: StreamWriter, + client_mode: bool = False): + remote_prot = None + super().__init__(reader, writer, 'solarman', + MemoryStream2, client_mode, remote_prot) + + def forward(self, src, dst) -> None: + """forward handler transmits data over the remote connection""" + # dst.ifc.update_header_cb(src.fwd_fifo.peek()) + + dst.ifc.tx_add(src.ifc.fwd_fifo.get()) + + +@pytest.mark.asyncio +async def test_proxy_at_cmd(config_tsun_inv1, patch_open_connection, at_command_ind_msg, at_command_rsp_msg): + _ = config_tsun_inv1 + _ = patch_open_connection + assert asyncio.get_running_loop() + + with InverterTest(FakeReader(), FakeWriter(), client_mode=False) as inverter: + await inverter.create_remote() + await asyncio.sleep(0) + r = inverter.remote.stream + l = inverter.local.stream + + l.db.stat['proxy']['AT_Command'] = 0 + l.db.stat['proxy']['Unknown_Ctrl'] = 0 + l.db.stat['proxy']['AT_Command_Blocked'] = 0 + l.db.stat['proxy']['Modbus_Command'] = 0 + inverter.forward_at_cmd_resp = False + r.append_msg(at_command_ind_msg) + r.read() # read complete msg, and dispatch msg + assert inverter.forward_at_cmd_resp + inverter.forward(r,l) + + assert l.ifc.tx_fifo.get()==at_command_ind_msg + + assert l.db.stat['proxy']['Invalid_Msg_Format'] == 0 + assert l.db.stat['proxy']['AT_Command'] == 1 + assert l.db.stat['proxy']['AT_Command_Blocked'] == 0 + assert l.db.stat['proxy']['Modbus_Command'] == 0 + + l.append_msg(at_command_rsp_msg) + l.read() # read at resp + assert l.ifc.fwd_fifo.peek()==at_command_rsp_msg + inverter.forward(l,r) + assert r.ifc.tx_fifo.get()==at_command_rsp_msg + + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" + +@pytest.mark.asyncio +async def test_proxy_at_blocked(config_tsun_inv1, patch_open_connection, at_command_ind_msg_block, at_command_rsp_msg): + _ = config_tsun_inv1 + _ = patch_open_connection + assert asyncio.get_running_loop() + + with InverterTest(FakeReader(), FakeWriter(), client_mode=False) as inverter: + await inverter.create_remote() + await asyncio.sleep(0) + r = inverter.remote.stream + l = inverter.local.stream + + l.db.stat['proxy']['AT_Command'] = 0 + l.db.stat['proxy']['Unknown_Ctrl'] = 0 + l.db.stat['proxy']['AT_Command_Blocked'] = 0 + l.db.stat['proxy']['Modbus_Command'] = 0 + inverter.forward_at_cmd_resp = False + r.append_msg(at_command_ind_msg_block) + r.read() # read complete msg, and dispatch msg + assert not inverter.forward_at_cmd_resp + inverter.forward(r,l) + + assert l.ifc.tx_fifo.get()==b'' + + assert l.db.stat['proxy']['Invalid_Msg_Format'] == 0 + assert l.db.stat['proxy']['AT_Command'] == 0 + assert l.db.stat['proxy']['AT_Command_Blocked'] == 1 + assert l.db.stat['proxy']['Modbus_Command'] == 0 + + l.append_msg(at_command_rsp_msg) + l.read() # read at resp + assert l.ifc.fwd_fifo.peek()==b'' + inverter.forward(l,r) + assert r.ifc.tx_fifo.get()==b'' + + assert Proxy.mqtt.key == 'tsun/inv1/at_resp' + assert Proxy.mqtt.data == "+ok" diff --git a/app/tests/test_solarman_emu.py b/app/tests/test_solarman_emu.py index 41e0e48..a62fbdc 100644 --- a/app/tests/test_solarman_emu.py +++ b/app/tests/test_solarman_emu.py @@ -6,7 +6,7 @@ from gen3plus.solarman_v5 import SolarmanV5, SolarmanBase from gen3plus.solarman_emu import SolarmanEmu from infos import Infos, Register -from test_solarman import FakeIfc, MemoryStream, get_sn_int, get_sn, correct_checksum, config_tsun_inv1, msg_modbus_rsp +from test_solarman import FakeIfc, FakeInverter, MemoryStream, get_sn_int, get_sn, correct_checksum, config_tsun_inv1, msg_modbus_rsp from test_infos_g3p import str_test_ip, bytes_test_ip timestamp = 0x3224c8bc @@ -19,10 +19,10 @@ class InvStream(MemoryStream): return timestamp class CldStream(SolarmanEmu): - def __init__(self, inv: InvStream): + def __init__(self, inv: InvStream, inverter=FakeInverter()): _ifc = FakeIfc() _ifc.remote.stream = inv - super().__init__(('test.local', 1234), _ifc, server_side=False, client_mode=False) + super().__init__(inverter, ('test.local', 1234), _ifc, server_side=False, client_mode=False) self.__msg = b'' self.__msg_len = 0 self.__offs = 0 diff --git a/app/tests/test_talent.py b/app/tests/test_talent.py index 2e7a4f0..225c38e 100644 --- a/app/tests/test_talent.py +++ b/app/tests/test_talent.py @@ -25,7 +25,7 @@ class FakeIfc(AsyncIfcImpl): class MemoryStream(Talent): def __init__(self, msg, chunks = (0,), server_side: bool = True): self.ifc = FakeIfc() - super().__init__(('test.local', 1234), self.ifc, server_side) + super().__init__(None, ('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 @@ -2026,9 +2026,9 @@ def test_ctrl_byte(): def test_msg_iterator(): - 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) + m1 = Talent(None, ('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True) + m2 = Talent(None, ('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True) + m3 = Talent(None, ('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True) m3.close() del m3 test1 = 0