Skip to content

Commit b249f3d

Browse files
committed
Added fallback to type detection to allow automatic ENUM usage + Added exception raising for unknown types
1 parent fe00679 commit b249f3d

File tree

2 files changed

+62
-21
lines changed

2 files changed

+62
-21
lines changed

pyads/connection.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
_dict_slice_generator,
9292
bytes_from_dict,
9393
size_of_structure,
94+
ADSError,
9495
)
9596
from .symbol import AdsSymbol
9697
from .utils import decode_ads
@@ -173,16 +174,16 @@ def _query_plc_datatype_from_name(self, data_name: str,
173174
174175
If cache_symbol_info is True then the SymbolInfo will be cached and adsGetSymbolInfo
175176
will only used once.
176-
177177
"""
178+
info = None
178179
if cache_symbol_info:
179180
info = self._symbol_info_cache.get(data_name)
180-
if info is None:
181-
info = adsGetSymbolInfo(self._port, self._adr, data_name)
182-
self._symbol_info_cache[data_name] = info
183-
else:
181+
if info is None:
184182
info = adsGetSymbolInfo(self._port, self._adr, data_name)
185-
return AdsSymbol.get_type_from_str(info.symbol_type)
183+
if cache_symbol_info:
184+
self._symbol_info_cache[data_name] = info
185+
186+
return AdsSymbol.get_type_from_info(info)
186187

187188
def open(self) -> None:
188189
"""Connect to the TwinCAT message router."""
@@ -537,6 +538,10 @@ def read_by_name(
537538
plc_datatype = self._query_plc_datatype_from_name(data_name,
538539
cache_symbol_info)
539540

541+
if plc_datatype is None:
542+
# `adsSyncReadReqEx2()` will fail for a None type
543+
raise ADSError(None, f"Failed to detect datatype for `{data_name}`")
544+
540545
return adsSyncReadByNameEx(
541546
self._port,
542547
self._adr,
@@ -682,6 +687,10 @@ def write_by_name(
682687
plc_datatype = self._query_plc_datatype_from_name(data_name,
683688
cache_symbol_info)
684689

690+
if plc_datatype is None:
691+
# `adsSyncWriteReqEx()` will fail for a None type
692+
raise ADSError(None, f"Failed to detect datatype for `{data_name}`")
693+
685694
return adsSyncWriteByNameEx(
686695
self._port, self._adr, data_name, value, plc_datatype, handle=handle
687696
)

pyads/symbol.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
from typing import TYPE_CHECKING, Any, Optional, List, Tuple, Callable, Union, Type
1717

1818
from . import constants # To access all constants, use package notation
19-
from .constants import PLCDataType
19+
from .constants import PLCDataType, ads_type_to_ctype
2020
from .pyads_ex import adsGetSymbolInfo
21-
from .structs import NotificationAttrib
21+
from .structs import NotificationAttrib, SAdsSymbolEntry
22+
from .ads import ADSError
2223

2324
# ads.Connection relies on structs.AdsSymbol (but in type hints only), so use
2425
# this 'if' to only include it when type hinting (False during execution)
@@ -125,7 +126,7 @@ def __init__(
125126
self.name = name
126127
self.index_offset = index_offset
127128
self.index_group = index_group
128-
self.symbol_type = symbol_type
129+
self.symbol_type = None # Apply `symbol_type` later
129130
self.comment = comment
130131
self._value: Any = None
131132

@@ -137,19 +138,18 @@ def __init__(
137138
from .ads import size_of_structure
138139
self._structure_size = size_of_structure(self.structure_def * self.array_size)
139140

141+
self.plc_type: Optional[Type[PLCDataType]] = None
142+
140143
if missing_info:
141144
self._create_symbol_from_info() # Perform remote lookup
142145

143-
# Now `self.symbol_type` should have a value, find the actual PLCTYPE
144-
# from it.
145-
# This is relevant for both lookup and full user definition.
146-
147-
self.plc_type: Optional[Type[PLCDataType]] = None
148-
if self.symbol_type is not None:
146+
# Apply user-provided type (overriding auto detect if any):
147+
if symbol_type is not None:
149148
if isinstance(self.symbol_type, str): # Perform lookup if string
150149
self.plc_type = AdsSymbol.get_type_from_str(self.symbol_type)
151150
else: # Otherwise `symbol_type` is probably a pyads.PLCTYPE_* constant
152151
self.plc_type = self.symbol_type
152+
self.symbol_type = symbol_type
153153

154154
self.auto_update = auto_update
155155

@@ -166,12 +166,8 @@ def _create_symbol_from_info(self) -> None:
166166
if info.comment:
167167
self.comment = info.comment
168168

169-
# info.dataType is an integer mapping to a type in
170-
# constants.ads_type_to_ctype.
171-
# However, this type ignores whether the variable is really an array!
172-
# So are not going to be using this and instead rely on the textual
173-
# type
174-
self.symbol_type = info.symbol_type # Save the type as string
169+
self.plc_type = self.get_type_from_info(info)
170+
self.symbol_type = info.symbol_type # Also save the type as string
175171

176172
def _check_for_open_connection(self) -> None:
177173
"""Assert the current object is ready to read from/write to.
@@ -195,6 +191,12 @@ def read(self) -> Any:
195191
structure_size=self._structure_size,
196192
array_size=self.array_size)
197193
else:
194+
if self.plc_type is None:
195+
raise ADSError(
196+
None,
197+
f"Cannot read data with unknown datatype for symbol "
198+
f"{self.name} ({self.symbol_type})"
199+
)
198200
self._value = self._plc.read(self.index_group, self.index_offset, self.plc_type)
199201

200202
return self._value
@@ -218,6 +220,12 @@ def write(self, new_value: Optional[Any] = None) -> None:
218220
self._plc.write_structure_by_name(self.name, new_value, self.structure_def,
219221
structure_size=self._structure_size, array_size=self.array_size)
220222
else:
223+
if self.plc_type is None:
224+
raise ADSError(
225+
None,
226+
f"Cannot write data with unknown datatype for symbol "
227+
f"{self.name} ({self.symbol_type})"
228+
)
221229
self._plc.write(self.index_group, self.index_offset, new_value, self.plc_type)
222230

223231
def __repr__(self) -> str:
@@ -283,6 +291,30 @@ def _value_callback(self, notification: Any, data_name: Any) -> None:
283291
)
284292
self._value = value
285293

294+
@classmethod
295+
def get_type_from_info(cls, info: SAdsSymbolEntry) -> Optional[Type[PLCDataType]]:
296+
"""Get PLCTYPE_* from symbol info struct
297+
298+
Also see :meth:`~get_type_from_str`.
299+
"""
300+
plc_type = cls.get_type_from_str(info.symbol_type)
301+
if plc_type is not None:
302+
return plc_type
303+
304+
# Failed to detect by name (e.g. type is enum)
305+
306+
# Use `ADST_*` integer to detect type instead
307+
plc_type = ads_type_to_ctype.get(info.dataType, None)
308+
if plc_type is not None:
309+
array_size = int(info.size / sizeof(plc_type))
310+
# However, `dataType` is always a scalar, even if the object is an array:
311+
if array_size > 1:
312+
plc_type = plc_type * array_size
313+
314+
return plc_type
315+
316+
return None
317+
286318
@staticmethod
287319
def get_type_from_str(type_str: str) -> Optional[Type[PLCDataType]]:
288320
"""Get PLCTYPE_* from PLC name string

0 commit comments

Comments
 (0)