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
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