def __init__(self, lib_location=None): if self.cdll: return self.lib_location = lib_location or self.lib_location or find_library('snap7') if not self.lib_location: msg = "can't find snap7 library. If installed, try running ldconfig" raise Snap7Exception(msg) self.cdll = cdll.LoadLibrary(self.lib_location)
def set_value( self, byte_index: Union[str, int], type: str, value: Union[bool, str, int, float]) -> Union[bytearray, None]: bytearray_ = self.get_bytearray() if type == 'BOOL' and isinstance(value, bool): """ mypy conform style: if isinstance(byte_index, str): byte_index, bool_index = byte_index.split('.') return set_bool(bytearray_, self.get_offset(byte_index), int(bool_index), value) """ byte_index, bool_index = str(byte_index).split(".") return set_bool(bytearray_, self.get_offset(byte_index), int(bool_index), value) byte_index = self.get_offset(byte_index) if type.startswith('STRING') and isinstance(value, str): max_size = re.search(r'\d+', type) if max_size is None: raise Snap7Exception( "Max size could not be determinate. re.search() returned None" ) max_size_grouped = max_size.group(0) max_size_int = int(max_size_grouped) return set_string(bytearray_, byte_index, value, max_size_int) elif type == 'REAL': return set_real(bytearray_, byte_index, value) elif type == 'DWORD' and isinstance(value, int): return set_dword(bytearray_, byte_index, value) elif type == 'DINT' and isinstance(value, int): return set_dint(bytearray_, byte_index, value) elif type == 'INT' and isinstance(value, int): return set_int(bytearray_, byte_index, value) elif type == 'WORD' and isinstance(value, int): return set_word(bytearray_, byte_index, value) elif type == 'USINT' and isinstance(value, int): return set_usint(bytearray_, byte_index, value) elif type == 'SINT' and isinstance(value, int): return set_sint(bytearray_, byte_index, value) if type == 'USINT' and isinstance(value, int): return set_usint(bytearray_, byte_index, value) if type == 'SINT' and isinstance(value, int): return set_sint(bytearray_, byte_index, value) raise ValueError
def check_error(code: int, context: str = "client"): """ check if the error code is set. If so, a Python log message is generated and an error is raised. """ if code and code != 1: error = error_text(code, context) logger.error(error) raise Snap7Exception(error)
def as_list_blocks_of_type(self, blocktype, data, count) -> int: blocktype = snap7.types.block_types.get(blocktype) if not blocktype: raise Snap7Exception("The blocktype parameter was invalid") result = self._library.Cli_AsListBlocksOfType(self._pointer, blocktype, byref(data), byref(count)) check_error(result, context="client") return result
def __init__(self, lib_location: Optional[str] = None): if self.cdll: # type: ignore return self.lib_location = (lib_location or self.lib_location or find_in_package() or find_library('snap7') or find_locally('snap7')) if not self.lib_location: msg = "can't find snap7 library. If installed, try running ldconfig" raise Snap7Exception(msg) self.cdll = cdll.LoadLibrary(self.lib_location)
def _as_check_loop(self, check_times=20) -> int: check_status = ctypes.c_int(-1) # preparing Server values for i in range(check_times): self.client.check_as_completion(ctypes.byref(check_status)) if check_status.value == 0: break time.sleep(0.5) else: raise Snap7Exception(f"Async Request not finished after {check_times} times - Fail") return check_status.value
def set_connection_type(self, connection_type): """ Sets the connection resource type, i.e the way in which the Clients connects to a PLC. :param connection_type: 1 for PG, 2 for OP, 3 to 10 for S7 Basic """ result = self._library.Cli_SetConnectionType(self._pointer, c_uint16(connection_type)) if result != 0: raise Snap7Exception("The parameter was invalid")
def get_cpu_state(self) -> str: """ Retrieves CPU state from client """ state = c_int(0) self._library.Cli_GetPlcStatus(self._pointer, byref(state)) try: status_string = cpu_statuses[state.value] except KeyError: raise Snap7Exception(f"The cpu state ({state.value}) is invalid") logger.debug(f"CPU state is {status_string}") return status_string
def set_connection_type(self, connection_type: int): """Sets the connection resource type, i.e the way in which the Clients connects to a PLC. Args: connection_type: 1 for PG, 2 for OP, 3 to 10 for S7 Basic Raises: :obj:`Snap7Exception`: if the snap7 error code is diferent from 0. """ result = self.library.Cli_SetConnectionType(self.pointer, c_uint16(connection_type)) if result != 0: raise Snap7Exception("The parameter was invalid")
def set_connection_params(self, address, local_tsap, remote_tsap): """ Sets internally (IP, LocalTSAP, RemoteTSAP) Coordinates. This function must be called just before Cli_Connect(). :param address: PLC/Equipment IPV4 Address, for example "192.168.1.12" :param local_tsap: Local TSAP (PC TSAP) :param remote_tsap: Remote TSAP (PLC TSAP) """ assert re.match(ipv4, address), f'{address} is invalid ipv4' result = self._library.Cli_SetConnectionParams(self._pointer, address, c_uint16(local_tsap), c_uint16(remote_tsap)) if result != 0: raise Snap7Exception("The parameter was invalid")
def check_error(code: int, context: str = "client") -> None: """Check if the error code is set. If so, a Python log message is generated and an error is raised. Args: code: error code number. context: context in which is called. Raises: Snap7Exception: if the code exists and is diferent from 1. """ if code and code != 1: error = error_text(code, context) logger.error(error) raise Snap7Exception(error)
def get_block_info(self, blocktype, db_number) -> TS7BlockInfo: """Returns the block information for the specified block.""" blocktype = snap7.types.block_types.get(blocktype) if not blocktype: raise Snap7Exception("The blocktype parameter was invalid") logger.debug( f"retrieving block info for block {db_number} of type {blocktype}") data = TS7BlockInfo() result = self._library.Cli_GetAgBlockInfo(self._pointer, blocktype, db_number, byref(data)) check_error(result, context="client") return data
def set_connection_params(self, ip_address, tsap_snap7, tsap_logo): """ Sets internally (IP, LocalTSAP, RemoteTSAP) Coordinates. This function must be called just before Cli_Connect(). :param ip_address: IP ip_address of server :param tsap_snap7: TSAP SNAP7 Client (e.g. 10.00 = 0x1000) :param tsap_logo: TSAP Logo Server (e.g. 20.00 = 0x2000) """ assert re.match(ipv4, ip_address), f'{ip_address} is invalid ipv4' result = self.library.Cli_SetConnectionParams(self.pointer, ip_address.encode(), c_uint16(tsap_snap7), c_uint16(tsap_logo)) if result != 0: raise Snap7Exception("The parameter was invalid")
def check_as_b_send_completion(self) -> Tuple[str, c_int32]: """ Checks if the current asynchronous send job was completed and terminates immediately. """ op_result = c_int32() result = self._library.Par_CheckAsBSendCompletion( self._pointer, byref(op_result)) return_values = { 0: "job complete", 1: "job in progress", -2: "invalid handled supplied", } if result == -2: raise Snap7Exception("The Client parameter was invalid") return return_values[result], op_result
def __init__(self, lib_location: Optional[str] = None): """ Loads the snap7 library using ctypes cdll. Args: lib_location: full path to the `snap7.dll` file. Optional. Raises: Snap7Exception: if `lib_location` is not found. """ if self.cdll: # type: ignore return self.lib_location = (lib_location or self.lib_location or find_in_package() or find_library('snap7') or find_locally('snap7')) if not self.lib_location: msg = "can't find snap7 library. If installed, try running ldconfig" raise Snap7Exception(msg) self.cdll = cdll.LoadLibrary(self.lib_location)
def list_blocks_of_type(self, blocktype, size: int) -> Union[int, Array]: """This function returns the AG list of a specified block type.""" blocktype = snap7.types.block_types.get(blocktype) if not blocktype: raise Snap7Exception("The blocktype parameter was invalid") logger.debug(f"listing blocks of type: {blocktype} size: {size}") if size == 0: return 0 data = (c_uint16 * size)() count = c_int(size) result = self._library.Cli_ListBlocksOfType(self._pointer, blocktype, byref(data), byref(count)) logger.debug(f"number of items found: {count}") check_error(result, context="client") return data
def set_connection_params(self, ip_address: str, tsap_snap7: int, tsap_logo: int): """Sets internally (IP, LocalTSAP, RemoteTSAP) Coordinates. Notes: This function must be called just before Cli_Connect(). Args: ip_address: IP ip_address of server tsap_snap7: TSAP SNAP7 Client (e.g. 10.00 = 0x1000) tsap_logo: TSAP Logo Server (e.g. 20.00 = 0x2000) Raises: :obj:`ValueError`: if the `ip_address` is not an IPV4. :obj:`Snap7Exception`: if the snap7 error code is diferent from 0. """ if not re.match(ipv4, ip_address): raise ValueError(f"{ip_address} is invalid ipv4") result = self.library.Cli_SetConnectionParams(self.pointer, ip_address.encode(), c_uint16(tsap_snap7), c_uint16(tsap_logo)) if result != 0: raise Snap7Exception("The parameter was invalid")
def get_value(self, byte_index: Union[str, int], type_: str) -> Union[ValueError, int, float, str, datetime]: """ Gets the value for a specific type. Args: byte_index: byte index from where start reading. type_: type of data to read. Raises: :obj:`Snap7Exception`: if reading a `string` when checking the lenght of the string. :obj:`ValueError`: if the `type_` is not handled. Returns: Value read according to the `type_` """ bytearray_ = self.get_bytearray() # set parsing non case-sensitive type_ = type_.upper() if type_ == 'BOOL': byte_index, bool_index = str(byte_index).split('.') return get_bool(bytearray_, self.get_offset(byte_index), int(bool_index)) # remove 4 from byte index since # first 4 bytes are used by db byte_index = self.get_offset(byte_index) if type_.startswith('STRING'): max_size = re.search(r'\d+', type_) if max_size is None: raise Snap7Exception( "Max size could not be determinate. re.search() returned None" ) max_size_grouped = max_size.group(0) max_size_int = int(max_size_grouped) return get_string(bytearray_, byte_index, max_size_int) elif type_ == 'REAL': return get_real(bytearray_, byte_index) elif type_ == 'DWORD': return get_dword(bytearray_, byte_index) elif type_ == 'UDINT': return get_udint(bytearray_, byte_index) elif type_ == 'DINT': return get_dint(bytearray_, byte_index) elif type_ == 'UINT': return get_uint(bytearray_, byte_index) elif type_ == 'INT': return get_int(bytearray_, byte_index) elif type_ == 'WORD': return get_word(bytearray_, byte_index) elif type_ == 'BYTE': return get_byte(bytearray_, byte_index) elif type_ == 'S5TIME': data_s5time = get_s5time(bytearray_, byte_index) return data_s5time elif type_ == 'DATE_AND_TIME': data_dt = get_dt(bytearray_, byte_index) return data_dt elif type_ == 'USINT': return get_usint(bytearray_, byte_index) elif type_ == 'SINT': return get_sint(bytearray_, byte_index) # add these three not implemented data typ to avoid # 'Unable to get repr for class<snap7.util.DB_ROW>' error elif type_ == 'TIME': return get_time(bytearray_, byte_index) elif type_ == 'DATE': return 'read DATE not implemented' elif type_ == 'TIME_OF_DAY': return 'read TIME_OF_DAY not implemented' raise ValueError
def set_value( self, byte_index: Union[str, int], type: str, value: Union[bool, str, int, float]) -> Union[bytearray, None]: """Sets the value for a specific type in the specified byte index. Args: byte_index: byte index to start writing to. type: type of value to write. value: value to write. Raises: :obj:`Snap7Exception`: if reading a `string` when checking the lenght of the string. :obj:`ValueError`: if the `type_` is not handled. Returns: Buffer data with the value written. Optional. """ bytearray_ = self.get_bytearray() if type == 'BOOL' and isinstance(value, bool): byte_index, bool_index = str(byte_index).split(".") return set_bool(bytearray_, self.get_offset(byte_index), int(bool_index), value) byte_index = self.get_offset(byte_index) if type.startswith('STRING') and isinstance(value, str): max_size = re.search(r'\d+', type) if max_size is None: raise Snap7Exception( "Max size could not be determinate. re.search() returned None" ) max_size_grouped = max_size.group(0) max_size_int = int(max_size_grouped) return set_string(bytearray_, byte_index, value, max_size_int) elif type == 'REAL': return set_real(bytearray_, byte_index, value) elif type == 'DWORD' and isinstance(value, int): return set_dword(bytearray_, byte_index, value) elif type == 'UDINT' and isinstance(value, int): return set_udint(bytearray_, byte_index, value) elif type == 'DINT' and isinstance(value, int): return set_dint(bytearray_, byte_index, value) elif type == 'UINT' and isinstance(value, int): return set_uint(bytearray_, byte_index, value) elif type == 'INT' and isinstance(value, int): return set_int(bytearray_, byte_index, value) elif type == 'WORD' and isinstance(value, int): return set_word(bytearray_, byte_index, value) elif type == 'USINT' and isinstance(value, int): return set_usint(bytearray_, byte_index, value) elif type == 'SINT' and isinstance(value, int): return set_sint(bytearray_, byte_index, value) elif type == 'USINT' and isinstance(value, int): return set_usint(bytearray_, byte_index, value) elif type == 'SINT' and isinstance(value, int): return set_sint(bytearray_, byte_index, value) elif type == 'TIME' and isinstance(value, str): return set_time(bytearray_, byte_index, value) raise ValueError
def get_value(self, byte_index: Union[str, int], type_: str) -> Union[ValueError, int, float, str, datetime]: bytearray_ = self.get_bytearray() if type_ == 'BOOL': byte_index, bool_index = str(byte_index).split('.') return get_bool(bytearray_, self.get_offset(byte_index), int(bool_index)) # remove 4 from byte index since # first 4 bytes are used by db byte_index = self.get_offset(byte_index) if type_.startswith('STRING'): max_size = re.search(r'\d+', type_) if max_size is None: raise Snap7Exception( "Max size could not be determinate. re.search() returned None" ) max_size_grouped = max_size.group(0) max_size_int = int(max_size_grouped) return get_string(bytearray_, byte_index, max_size_int) elif type_ == 'REAL': return get_real(bytearray_, byte_index) elif type_ == 'DWORD': return get_dword(bytearray_, byte_index) elif type_ == 'DINT': return get_dint(bytearray_, byte_index) elif type_ == 'INT': return get_int(bytearray_, byte_index) elif type_ == 'WORD': return get_word(bytearray_, byte_index) elif type_ == 'S5TIME': data_s5time = get_s5time(bytearray_, byte_index) return data_s5time elif type_ == 'DATE_AND_TIME': data_dt = get_dt(bytearray_, byte_index) return data_dt elif type_ == 'USINT': return get_usint(bytearray_, byte_index) elif type_ == 'SINT': return get_sint(bytearray_, byte_index) # add these three not implemented data typ to avoid # 'Unable to get repr for class<snap7.util.DB_ROW>' error elif type_ == 'TIME': return 'read TIME not implemented' elif type_ == 'DATE': return 'read DATE not implemented' elif type_ == 'TIME_OF_DAY': return 'read TIME_OF_DAY not implemented' raise ValueError