Skip to content

Commit

Permalink
Improve uart_read_frame (brainelectronics#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
hmaerki authored Dec 31, 2023
1 parent e891a2e commit fd694b5
Showing 1 changed file with 29 additions and 35 deletions.
64 changes: 29 additions & 35 deletions umodbus/asynchronous/serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,46 +98,37 @@ async def get_request(self,
async def _uart_read_frame(self,
timeout: Optional[int] = None) -> bytearray:
"""@see RTUServer._uart_read_frame"""
t1char_ms = max(1, self._t1char//1000)

received_bytes = bytearray()

# set default timeout to at twice the inter-frame delay
if timeout == 0 or timeout is None:
timeout = 2 * self._inter_frame_delay # in microseconds
# Wait here till the next frame starts
while not self._uart.any():
await asyncio.sleep_ms(t1char_ms)

start_us = time.ticks_us()
received_bytes = bytearray()
last_read_us = time.ticks_us()

# stay inside this while loop at least for the timeout time
while (time.ticks_diff(time.ticks_us(), start_us) <= timeout):
while True:
# check amount of available characters
if self._uart.any():
# remember this time in microseconds
last_byte_ts = time.ticks_us()

# do not stop reading and appending the result to the buffer
# until the time between two frames elapsed
while time.ticks_diff(time.ticks_us(), last_byte_ts) <= self._inter_frame_delay:
# WiPy only
# r = self._uart.readall()
r = self._uart.read()

# if something has been read after the first iteration of
# this inner while loop (within self._inter_frame_delay)
if r is not None:
# append the new read stuff to the buffer
received_bytes.extend(r)

# update the timestamp of the last byte being read
last_byte_ts = time.ticks_us()
else:
await asyncio.sleep_ms(self._inter_frame_delay // 10) # 175 ms, arbitrary for now

# if something has been read before the overall timeout is reached
if len(received_bytes) > 0:
chars_ready = self._uart.any()
if chars_ready:
# WiPy only
# r = self._uart.readall()
r = self._uart.read(chars_ready)
if r is not None:
last_read_us = time.ticks_us()
received_bytes.extend(r)
continue

silence_us = time.ticks_diff(time.ticks_us(), last_read_us)
if silence_us > self._inter_frame_delay:
# The Modbus Specification: The frame is complete after
# silence of 1.5 times a character.
# Here we use 'self._inter_frame_delay' which on the save side.
return received_bytes

# return the result in case the overall timeout has been reached
return received_bytes
# Here, I am using a blocking sleep in favor of 'asyncio.sleep_ms()'.
# The communication proved to be much more stable.
time.sleep_ms(t1char_ms)

async def _send(self,
modbus_pdu: bytes,
Expand All @@ -156,7 +147,10 @@ async def _post_send(self, sleep_time_us: float) -> None:
@see CommonRTUFunctions._post_send
"""

await hybrid_sleep(sleep_time_us)
# Do NOT use 'hybrid_sleep()' as it may fall back to 'asyncio.sleep()'.
# The sleep MUST NOT TAKE TOO LONG as this might squelsh the subsequent
# frame sent by the client.
time.sleep_us(sleep_time_us)
if self._ctrlPin:
self._ctrlPin.off()

Expand Down

0 comments on commit fd694b5

Please sign in to comment.