Whole file

marcelblijleven/goodwe

The author described this change as Fix multiple inverters communication. It counts as a record because the checks below fail on the code as it stood at 7fe0fb58e and pass on 4867b4acd, with nothing else changed between the two runs.

Fix saved2022-01-08
Sharing licenceMIT · LICENSE
Change size+12 11

What the code was meant to do, written into the code itself as a save note

Fix multiple inverters communication

The change

1616 class UdpInverterProtocol(asyncio.DatagramProtocol):
1717 def __init__(
1818 self,
19+ response_future: Future,
1920 command: ProtocolCommand,
2021 timeout: int,
2122 retries: int
2223 ):
2324 super().__init__()
25+ self.response_future: Future = response_future
2426 self.command: ProtocolCommand = command
2527 self._transport: asyncio.transports.DatagramTransport | None = None
2628 self._retry_timeout: int = timeout
3739 if exc is not None:
3840 logger.debug(f'Socket closed with error: {exc}')
3941 # Cancel Future on connection lost
40- if not self.command.response_future.done():
41- self.command.response_future.cancel()
42+ if not self.response_future.done():
43+ self.response_future.cancel()
4244
4345 def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None:
4446 """On datagram received"""
4547 if self.command.validator(data):
4648 logger.debug(f'Received: {data.hex()}')
47- self.command.response_future.set_result(data)
49+ self.response_future.set_result(data)
4850 else:
4951 logger.debug(f'Received invalid response: {data.hex()}')
5052 self._retries += 1
5355 def error_received(self, exc: Exception) -> None:
5456 """On error received"""
5557 logger.debug(f'Received error: {exc}')
56- self.command.response_future.set_exception(exc)
58+ self.response_future.set_exception(exc)
5759
5860 def _send_request(self) -> None:
5961 """Send message via transport"""
6466
6567 def _retry_mechanism(self) -> None:
6668 """Retry mechanism to prevent hanging transport"""
67- if self.command.response_future.done():
69+ if self.response_future.done():
6870 self._transport.close()
6971 elif self._retries < self._max_retries:
7072 logger.debug('Failed to receive response to %s in time (%ds).', self.command, self._retry_timeout)
7274 self._send_request()
7375 else:
7476 logger.debug('Max number of retries (%d) reached, request %s failed.', self._max_retries, self.command)
75- self.command.response_future.set_exception(MaxRetriesException)
77+ self.response_future.set_exception(MaxRetriesException)
7678
7779
7880 class ProtocolCommand:
8183 def __init__(self, request: bytes, validator: Callable[[bytes], bool]):
8284 self.request: bytes = request
8385 self.validator: Callable[[bytes], bool] = validator
84- self.response_future: Future | None = None
8586
8687 def __repr__(self):
8788 return self.request.hex()
9596 Return raw response data
9697 """
9798 loop = asyncio.get_running_loop()
98- self.response_future = loop.create_future()
99+ response_future = loop.create_future()
99100 transport, _ = await loop.create_datagram_endpoint(
100- lambda: UdpInverterProtocol(self, timeout, retries),
101+ lambda: UdpInverterProtocol(response_future, self, timeout, retries),
101102 remote_addr=(host, GOODWE_UDP_PORT),
102103 )
103104 try:
104- await self.response_future
105- result = self.response_future.result()
105+ await response_future
106+ result = response_future.result()
106107 if result is not None:
107108 return result
108109 else:

The check that tells the two apart

failpass·tests/test_protocol.py::TestUDPClientProtocol::test_connection_lost
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_connection_lost_not_done
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_connection_made
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_datagram_received
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_error_received
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_modbus_read_command
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_modbus_write_command
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_retry_mechanism
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_retry_mechanism_max_retries
failpass·tests/test_protocol.py::TestUDPClientProtocol::test_retry_mechanism_two_retries

Check file tests/test_protocol.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.

Origin and history

The code before it7fe0fb58e6581dc682e1104f0beed8870d0caccd
Broken version dated2022-01-08
Modulegoodwe.protocol
Units changedProtocolCommand, UdpInverterProtocol
Fingerprint14d538f124e7fa65
Checked2026-08-18 by goldset/0.1

Every field above is generated by our program. None of it is written by hand.

Other bugs found in marcelblijleven/goodwe