Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion bydhvs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,14 @@ class BYDHVS:
BUFFER_SIZE = 1024
# Packet structure constants
MIN_PACKET_LENGTH = 5
MODBUS_EXCEPTION_PACKET_LENGTH = 5
# Request needs at least address and function code bytes
MIN_MODBUS_REQUEST_LENGTH = 2
PACKET_HEADER_SIZE = 3
PACKET_CRC_SIZE = 2
PACKET5_MIN_LENGTH = 133
MODBUS_ADDRESS = 1
MODBUS_EXCEPTION_FLAG = 0x80
FUNCTION_CODE_READ = 3
FUNCTION_CODE_WRITE = 16

Expand Down Expand Up @@ -657,10 +661,51 @@ async def _send_and_receive(
data = await self._receive_response()
if data and self._check_packet(data):
return data
_LOGGER.error("Invalid or no data received in %s", state_name)

if data and self._is_modbus_exception_response(data, request):
exception_code = data[2]
_LOGGER.warning(
"Modbus exception response in %s for tower %d "
"(exception=0x%02X, len=%d)",
state_name,
self.current_tower,
exception_code,
len(data),
)
self._state = 0
return None

if data:
_LOGGER.error(
"Invalid CRC or malformed packet in %s for tower %d (len=%d)",
state_name,
self.current_tower,
len(data),
)
else:
_LOGGER.error("Invalid or no data received in %s", state_name)
self._state = 0
return None

def _is_modbus_exception_response(self, data: bytes, request: bytes) -> bool:
"""Check if response is a valid Modbus exception packet.

A valid Modbus exception response is 5 bytes:
address, function|0x80, exception_code, crc_lo, crc_hi.
"""
if (
len(data) != self.MODBUS_EXCEPTION_PACKET_LENGTH
or len(request) < self.MIN_MODBUS_REQUEST_LENGTH
):
return False
if request[0] != self.MODBUS_ADDRESS:
return False
if data[0] != self.MODBUS_ADDRESS:
return False
if data[1] != (request[1] | self.MODBUS_EXCEPTION_FLAG):
return False
return CRC16(data) == 0

async def __aenter__(self) -> 'BYDHVS':
"""Async context manager entry."""
return self
Expand Down