Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fall back to discontinous reads of discrete outputs #31

Draft
wants to merge 1 commit into
base: sim-exceptions
Choose a base branch
from
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
67 changes: 47 additions & 20 deletions productivity/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ def __init__(self, address, tag_filepath, timeout=1):

"""
super().__init__(address, timeout)
self.discontinuous_discrete_output = False
self.discontinuous_discrete_output_write = False
self.discontinuous_discrete_output_read = False
self.tags = self._load_tags(tag_filepath)
self.addresses = self._calculate_addresses(self.tags)
self.map = {data['address']['start']: tag for tag, data in self.tags.items()}
Expand Down Expand Up @@ -205,7 +206,7 @@ async def _write_discrete_values(self, discrete_to_write: dict
read then updated and written back.

"""
if len(discrete_to_write) == 1 or self.discontinuous_discrete_output:
if len(discrete_to_write) == 1 or self.discontinuous_discrete_output_write:
return [await self.write_coil(self.tags[key]['address']['start'] - 1, val)
for key, val in discrete_to_write.items()]

Expand All @@ -216,33 +217,59 @@ async def _write_discrete_values(self, discrete_to_write: dict
return [await self.write_coils(
self.addresses['discrete_output']['address'], vals)]

def _log_modbus_exception(self, address, count, output: bool, response):
"""Parse a modbus ExceptionResponse and log it."""
func = response.function_code
if (output and func != 129) or (output is False and func != 130):
raise ValueError(f"Received function code {func} which does not match request")
excep = response.exception_code
read_type = "coil(s)" if output else "discrete input(s)"
logging.error(f"Received MODBUS exception code {excep} when reading "
f"{count} {read_type} at {address}\n")

async def _read_discrete_discontinous(self, addresses: dict, output=True) -> dict:
"""Read discrete values from the PLC, one at a time."""
result = {}
start = addresses['address']
end = addresses['count']
for a in (a for a in range(start, end) if a + 1 in self.map):
response = await self.read_coils(a, 1)
if isinstance(response, ExceptionResponse):
self._log_modbus_exception(a, 1, output, response)
else:
result[self.map[a + 1]] = response.bits[0]
return result

async def _read_discrete(self, addresses: dict, output=True) -> dict:
"""Handle reading discrete values from the PLC."""
result = {}
if output:
response = await self.read_coils(**addresses)
current = addresses['address'] + 1
if self.discontinuous_discrete_output_read:
result = await self._read_discrete_discontinous(addresses, output)
return result
else:
response = await self.read_coils(**addresses)
current = addresses['address'] + 1
else:
response = await self.read_discrete_inputs(**addresses)
current = addresses['address'] + 100001

end = current + addresses['count']
if isinstance(response, ExceptionResponse):
func = response.function_code
if (output and func != 129) or (output is False and func != 130):
raise ValueError(f"Received function code {func} which does not match request")
excep = response.exception_code
read_type = "coil(s)" if output else "discrete input(s)"
logging.error(f"Received MODBUS exception code {excep} when reading "
f"{addresses['count']} {read_type} at {addresses['address']}")
return {}
for bit in response.bits:
if current > end:
break
elif current in self.map:
result[self.map[current]] = bit
current += 1
return result
self.discontinuous_discrete_output_read = True
self._log_modbus_exception(addresses['address'], addresses['count'],
output, response)
logging.warning("Fallback to discontinuous reads of discrete outputs may timeout.")
result = await self._read_discrete_discontinous(addresses, output)
return result
else:
for bit in response.bits:
if current > end:
break
elif current in self.map:
result[self.map[current]] = bit
current += 1
return result

async def _read_registers(self, a_type: str) -> dict:
"""Handle reading input or holding registers from the PLC."""
Expand Down Expand Up @@ -370,7 +397,7 @@ def _calculate_addresses(self, tags: dict) -> dict:
"numat/productivity.")

if 'discrete_output' in output and do_count / 2 < output['discrete_output']['count']:
self.discontinuous_discrete_output = True
self.discontinuous_discrete_output_write = True
logging.warning(
"Warning: Your tags file has gaps in discrete output modbus addresses."
" This driver will fall back to setting values in this range serially "
Expand Down