1616from typing import TYPE_CHECKING , Any , Optional , List , Tuple , Callable , Union , Type
1717
1818from . import constants # To access all constants, use package notation
19- from .constants import PLCDataType
19+ from .constants import PLCDataType , ads_type_to_ctype
2020from .pyads_ex import adsGetSymbolInfo , ADSError
21- from .structs import NotificationAttrib
21+ from .structs import NotificationAttrib , SAdsSymbolEntry
2222
2323# ads.Connection relies on structs.AdsSymbol (but in type hints only), so use
2424# this 'if' to only include it when type hinting (False during execution)
@@ -125,7 +125,7 @@ def __init__(
125125 self .name = name
126126 self .index_offset = index_offset
127127 self .index_group = index_group
128- self .symbol_type = symbol_type
128+ self .symbol_type = None # Apply ` symbol_type` later
129129 self .comment = comment
130130 self ._value : Any = None
131131
@@ -137,15 +137,14 @@ def __init__(
137137 from .ads import size_of_structure
138138 self ._structure_size = size_of_structure (self .structure_def * self .array_size )
139139
140+ self .plc_type : Optional [Type [PLCDataType ]] = None
141+
140142 if missing_info :
141143 self ._create_symbol_from_info () # Perform remote lookup
142144
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 :
145+ # Apply user-provided type (overriding auto detect if any):
146+ if symbol_type is not None :
147+ self .symbol_type = symbol_type
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
@@ -166,12 +165,8 @@ def _create_symbol_from_info(self) -> None:
166165 if info .comment :
167166 self .comment = info .comment
168167
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
168+ self .plc_type = self .get_type_from_info (info )
169+ self .symbol_type = info .symbol_type # Also save the type as string
175170
176171 def _check_for_open_connection (self ) -> None :
177172 """Assert the current object is ready to read from/write to.
@@ -195,6 +190,12 @@ def read(self) -> Any:
195190 structure_size = self ._structure_size ,
196191 array_size = self .array_size )
197192 else :
193+ if self .plc_type is None :
194+ raise ADSError (
195+ None ,
196+ f"Cannot read data with unknown datatype for symbol "
197+ f"{ self .name } ({ self .symbol_type } )"
198+ )
198199 self ._value = self ._plc .read (self .index_group , self .index_offset , self .plc_type )
199200
200201 return self ._value
@@ -218,6 +219,12 @@ def write(self, new_value: Optional[Any] = None) -> None:
218219 self ._plc .write_structure_by_name (self .name , new_value , self .structure_def ,
219220 structure_size = self ._structure_size , array_size = self .array_size )
220221 else :
222+ if self .plc_type is None :
223+ raise ADSError (
224+ None ,
225+ f"Cannot write data with unknown datatype for symbol "
226+ f"{ self .name } ({ self .symbol_type } )"
227+ )
221228 self ._plc .write (self .index_group , self .index_offset , new_value , self .plc_type )
222229
223230 def __repr__ (self ) -> str :
@@ -286,6 +293,30 @@ def _value_callback(self, notification: Any, data_name: Any) -> None:
286293 )
287294 self ._value = value
288295
296+ @classmethod
297+ def get_type_from_info (cls , info : SAdsSymbolEntry ) -> Optional [Type [PLCDataType ]]:
298+ """Get PLCTYPE_* from symbol info struct
299+
300+ Also see :meth:`~get_type_from_str`.
301+ """
302+ plc_type = cls .get_type_from_str (info .symbol_type )
303+ if plc_type is not None :
304+ return plc_type
305+
306+ # Failed to detect by name (e.g. type is enum)
307+
308+ # Use `ADST_*` integer to detect type instead
309+ plc_type = ads_type_to_ctype .get (info .dataType , None )
310+ if plc_type is not None :
311+ array_size = int (info .size / sizeof (plc_type ))
312+ # However, `dataType` is always a scalar, even if the object is an array:
313+ if array_size > 1 :
314+ plc_type = plc_type * array_size
315+
316+ return plc_type
317+
318+ return None
319+
289320 @staticmethod
290321 def get_type_from_str (type_str : str ) -> Optional [Type [PLCDataType ]]:
291322 """Get PLCTYPE_* from PLC name string
0 commit comments