initial
This commit is contained in:
22
env/lib/python3.11/site-packages/pyModbusTCP/__init__.py
vendored
Normal file
22
env/lib/python3.11/site-packages/pyModbusTCP/__init__.py
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Python package: Client and Server for ModBus/TCP
|
||||
# Version: 0.2.0
|
||||
# Website: https://github.com/sourceperl/pyModbusTCP
|
||||
# Date: 2022-06-05
|
||||
# License: MIT (http://http://opensource.org/licenses/mit-license.php)
|
||||
# Description: Client/Server ModBus/TCP
|
||||
# Support functions 3 and 16 (class 0)
|
||||
# 1,2,4,5,6 (Class 1)
|
||||
# 15
|
||||
# Charset: utf-8
|
||||
|
||||
from .constants import VERSION
|
||||
|
||||
|
||||
__all__ = ['constants', 'client', 'server', 'utils']
|
||||
__title__ = 'pyModbusTCP'
|
||||
__description__ = 'A simple Modbus/TCP library for Python.'
|
||||
__url__ = 'https://github.com/sourceperl/pyModbusTCP'
|
||||
__version__ = VERSION
|
||||
__license__ = 'MIT'
|
||||
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/client.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/client.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/constants.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/constants.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/server.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/server.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/utils.cpython-311.pyc
vendored
Normal file
BIN
env/lib/python3.11/site-packages/pyModbusTCP/__pycache__/utils.cpython-311.pyc
vendored
Normal file
Binary file not shown.
813
env/lib/python3.11/site-packages/pyModbusTCP/client.py
vendored
Normal file
813
env/lib/python3.11/site-packages/pyModbusTCP/client.py
vendored
Normal file
@@ -0,0 +1,813 @@
|
||||
""" pyModbusTCP Client """
|
||||
|
||||
from .constants import READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, READ_INPUT_REGISTERS, \
|
||||
WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS, WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, \
|
||||
EXP_TXT, EXP_DETAILS, EXP_NONE, \
|
||||
MB_ERR_TXT, MB_NO_ERR, MB_SEND_ERR, MB_RECV_ERR, MB_TIMEOUT_ERR, MB_EXCEPT_ERR, MB_CONNECT_ERR, \
|
||||
MB_SOCK_CLOSE_ERR, VERSION
|
||||
from .utils import byte_length, set_bit, valid_host
|
||||
import socket
|
||||
from socket import AF_UNSPEC, SOCK_STREAM
|
||||
import struct
|
||||
import random
|
||||
|
||||
|
||||
class ModbusClient(object):
|
||||
"""Modbus TCP client"""
|
||||
|
||||
class _InternalError(Exception):
|
||||
pass
|
||||
|
||||
class _NetworkError(_InternalError):
|
||||
def __init__(self, code, message):
|
||||
self.code = code
|
||||
self.message = message
|
||||
|
||||
class _ModbusExcept(_InternalError):
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
def __init__(self, host='localhost', port=502, unit_id=1, timeout=30.0,
|
||||
debug=False, auto_open=True, auto_close=False):
|
||||
"""Constructor.
|
||||
|
||||
:param host: hostname or IPv4/IPv6 address server address
|
||||
:type host: str
|
||||
:param port: TCP port number
|
||||
:type port: int
|
||||
:param unit_id: unit ID
|
||||
:type unit_id: int
|
||||
:param timeout: socket timeout in seconds
|
||||
:type timeout: float
|
||||
:param debug: debug state
|
||||
:type debug: bool
|
||||
:param auto_open: auto TCP connect
|
||||
:type auto_open: bool
|
||||
:param auto_close: auto TCP close)
|
||||
:type auto_close: bool
|
||||
:return: Object ModbusClient
|
||||
:rtype: ModbusClient
|
||||
"""
|
||||
# private
|
||||
# internal variables
|
||||
self._host = None
|
||||
self._port = None
|
||||
self._unit_id = None
|
||||
self._timeout = None
|
||||
self._debug = None
|
||||
self._auto_open = None
|
||||
self._auto_close = None
|
||||
self._sock = None # socket
|
||||
self._transaction_id = 0 # MBAP transaction ID
|
||||
self._version = VERSION # this package version number
|
||||
self._last_error = MB_NO_ERR # last error code
|
||||
self._last_except = EXP_NONE # last except code
|
||||
# public
|
||||
# constructor arguments: validate them with property setters
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.unit_id = unit_id
|
||||
self.timeout = timeout
|
||||
self.debug = debug
|
||||
self.auto_open = auto_open
|
||||
self.auto_close = auto_close
|
||||
|
||||
def __repr__(self):
|
||||
r_str = 'ModbusClient(host=\'%s\', port=%d, unit_id=%d, timeout=%.2f, debug=%s, auto_open=%s, auto_close=%s)'
|
||||
r_str %= (self.host, self.port, self.unit_id, self.timeout, self.debug, self.auto_open, self.auto_close)
|
||||
return r_str
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""Return the current package version as a str."""
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def last_error(self):
|
||||
"""Last error code."""
|
||||
return self._last_error
|
||||
|
||||
@property
|
||||
def last_error_as_txt(self):
|
||||
"""Human-readable text that describe last error."""
|
||||
return MB_ERR_TXT.get(self._last_error, 'unknown error')
|
||||
|
||||
@property
|
||||
def last_except(self):
|
||||
"""Return the last modbus exception code."""
|
||||
return self._last_except
|
||||
|
||||
@property
|
||||
def last_except_as_txt(self):
|
||||
"""Short human-readable text that describe last modbus exception."""
|
||||
default_str = 'unreferenced exception 0x%X' % self._last_except
|
||||
return EXP_TXT.get(self._last_except, default_str)
|
||||
|
||||
@property
|
||||
def last_except_as_full_txt(self):
|
||||
"""Verbose human-readable text that describe last modbus exception."""
|
||||
default_str = 'unreferenced exception 0x%X' % self._last_except
|
||||
return EXP_DETAILS.get(self._last_except, default_str)
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
"""Get or set the server to connect to.
|
||||
|
||||
This can be any string with a valid IPv4 / IPv6 address or hostname.
|
||||
Setting host to a new value will close the current socket.
|
||||
"""
|
||||
return self._host
|
||||
|
||||
@host.setter
|
||||
def host(self, value):
|
||||
# check type
|
||||
if type(value) is not str:
|
||||
raise TypeError('host must be a str')
|
||||
# check value
|
||||
if valid_host(value):
|
||||
if self._host != value:
|
||||
self.close()
|
||||
self._host = value
|
||||
return
|
||||
# can't be set
|
||||
raise ValueError('host can\'t be set (not a valid IP address or hostname)')
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
"""Get or set the current TCP port (default is 502).
|
||||
|
||||
Setting port to a new value will close the current socket.
|
||||
"""
|
||||
return self._port
|
||||
|
||||
@port.setter
|
||||
def port(self, value):
|
||||
# check type
|
||||
if type(value) is not int:
|
||||
raise TypeError('port must be an int')
|
||||
# check validity
|
||||
if 0 < value < 65536:
|
||||
if self._port != value:
|
||||
self.close()
|
||||
self._port = value
|
||||
return
|
||||
# can't be set
|
||||
raise ValueError('port can\'t be set (valid if 0 < port < 65536)')
|
||||
|
||||
@property
|
||||
def unit_id(self):
|
||||
"""Get or set the modbus unit identifier (default is 1).
|
||||
|
||||
Any int from 0 to 255 is valid.
|
||||
"""
|
||||
return self._unit_id
|
||||
|
||||
@unit_id.setter
|
||||
def unit_id(self, value):
|
||||
# check type
|
||||
if type(value) is not int:
|
||||
raise TypeError('unit_id must be an int')
|
||||
# check validity
|
||||
if 0 <= value <= 255:
|
||||
self._unit_id = value
|
||||
return
|
||||
# can't be set
|
||||
raise ValueError('unit_id can\'t be set (valid from 0 to 255)')
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""Get or set requests timeout (default is 30 seconds).
|
||||
|
||||
The argument may be a floating point number for sub-second precision.
|
||||
Setting timeout to a new value will close the current socket.
|
||||
"""
|
||||
return self._timeout
|
||||
|
||||
@timeout.setter
|
||||
def timeout(self, value):
|
||||
# enforce type
|
||||
value = float(value)
|
||||
# check validity
|
||||
if 0 < value < 3600:
|
||||
if self._timeout != value:
|
||||
self.close()
|
||||
self._timeout = value
|
||||
return
|
||||
# can't be set
|
||||
raise ValueError('timeout can\'t be set (valid between 0 and 3600)')
|
||||
|
||||
@property
|
||||
def debug(self):
|
||||
"""Get or set the debug flag (True = turn on)."""
|
||||
return self._debug
|
||||
|
||||
@debug.setter
|
||||
def debug(self, value):
|
||||
# enforce type
|
||||
self._debug = bool(value)
|
||||
|
||||
@property
|
||||
def auto_open(self):
|
||||
"""Get or set automatic TCP connect mode (True = turn on)."""
|
||||
return self._auto_open
|
||||
|
||||
@auto_open.setter
|
||||
def auto_open(self, value):
|
||||
# enforce type
|
||||
self._auto_open = bool(value)
|
||||
|
||||
@property
|
||||
def auto_close(self):
|
||||
"""Get or set automatic TCP close after each request mode (True = turn on)."""
|
||||
return self._auto_close
|
||||
|
||||
@auto_close.setter
|
||||
def auto_close(self, value):
|
||||
# enforce type
|
||||
self._auto_close = bool(value)
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
"""Get current status of the TCP connection (True = open)."""
|
||||
if self._sock:
|
||||
return self._sock.fileno() > 0
|
||||
else:
|
||||
return False
|
||||
|
||||
def open(self):
|
||||
"""Connect to modbus server (open TCP connection).
|
||||
|
||||
:returns: connect status (True on success)
|
||||
:rtype: bool
|
||||
"""
|
||||
try:
|
||||
self._open()
|
||||
return True
|
||||
except ModbusClient._NetworkError as e:
|
||||
self._req_except_handler(e)
|
||||
return False
|
||||
|
||||
def _open(self):
|
||||
"""Connect to modbus server (open TCP connection)."""
|
||||
# open an already open socket -> reset it
|
||||
if self.is_open:
|
||||
self.close()
|
||||
# init socket and connect
|
||||
# list available sockets on the target host/port
|
||||
# AF_xxx : AF_INET -> IPv4, AF_INET6 -> IPv6,
|
||||
# AF_UNSPEC -> IPv6 (priority on some system) or 4
|
||||
# list available socket on target host
|
||||
for res in socket.getaddrinfo(self.host, self.port, AF_UNSPEC, SOCK_STREAM):
|
||||
af, sock_type, proto, canon_name, sa = res
|
||||
try:
|
||||
self._sock = socket.socket(af, sock_type, proto)
|
||||
except socket.error:
|
||||
continue
|
||||
try:
|
||||
self._sock.settimeout(self.timeout)
|
||||
self._sock.connect(sa)
|
||||
except socket.error:
|
||||
self._sock.close()
|
||||
continue
|
||||
break
|
||||
# check connect status
|
||||
if not self.is_open:
|
||||
raise ModbusClient._NetworkError(MB_CONNECT_ERR, 'connection refused')
|
||||
|
||||
def close(self):
|
||||
"""Close current TCP connection."""
|
||||
if self._sock:
|
||||
self._sock.close()
|
||||
|
||||
def custom_request(self, pdu):
|
||||
"""Send a custom modbus request.
|
||||
|
||||
:param pdu: a modbus PDU (protocol data unit)
|
||||
:type pdu: bytes
|
||||
:returns: modbus frame PDU or None if error
|
||||
:rtype: bytes or None
|
||||
"""
|
||||
# make request
|
||||
try:
|
||||
return self._req_pdu(pdu)
|
||||
# handle errors during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return None
|
||||
|
||||
def read_coils(self, bit_addr, bit_nb=1):
|
||||
"""Modbus function READ_COILS (0x01).
|
||||
|
||||
:param bit_addr: bit address (0 to 65535)
|
||||
:type bit_addr: int
|
||||
:param bit_nb: number of bits to read (1 to 2000)
|
||||
:type bit_nb: int
|
||||
:returns: bits list or None if error
|
||||
:rtype: list of bool or None
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(bit_addr) <= 0xffff:
|
||||
raise ValueError('bit_addr out of range (valid from 0 to 65535)')
|
||||
if not 1 <= int(bit_nb) <= 2000:
|
||||
raise ValueError('bit_nb out of range (valid from 1 to 2000)')
|
||||
if int(bit_addr) + int(bit_nb) > 0x10000:
|
||||
raise ValueError('read after end of modbus address space')
|
||||
# make request
|
||||
try:
|
||||
tx_pdu = struct.pack('>BHH', READ_COILS, bit_addr, bit_nb)
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)
|
||||
# field "byte count" from PDU
|
||||
byte_count = rx_pdu[1]
|
||||
# coils PDU part
|
||||
rx_pdu_coils = rx_pdu[2:]
|
||||
# check rx_byte_count: match nb of bits request and check buffer size
|
||||
if byte_count < byte_length(bit_nb) or byte_count != len(rx_pdu_coils):
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')
|
||||
# allocate coils list to return
|
||||
ret_coils = [False] * bit_nb
|
||||
# populate it with coils value from the rx PDU
|
||||
for i in range(bit_nb):
|
||||
ret_coils[i] = bool((rx_pdu_coils[i // 8] >> i % 8) & 0x01)
|
||||
# return read coils
|
||||
return ret_coils
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return None
|
||||
|
||||
def read_discrete_inputs(self, bit_addr, bit_nb=1):
|
||||
"""Modbus function READ_DISCRETE_INPUTS (0x02).
|
||||
|
||||
:param bit_addr: bit address (0 to 65535)
|
||||
:type bit_addr: int
|
||||
:param bit_nb: number of bits to read (1 to 2000)
|
||||
:type bit_nb: int
|
||||
:returns: bits list or None if error
|
||||
:rtype: list of bool or None
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(bit_addr) <= 0xffff:
|
||||
raise ValueError('bit_addr out of range (valid from 0 to 65535)')
|
||||
if not 1 <= int(bit_nb) <= 2000:
|
||||
raise ValueError('bit_nb out of range (valid from 1 to 2000)')
|
||||
if int(bit_addr) + int(bit_nb) > 0x10000:
|
||||
raise ValueError('read after end of modbus address space')
|
||||
# make request
|
||||
try:
|
||||
tx_pdu = struct.pack('>BHH', READ_DISCRETE_INPUTS, bit_addr, bit_nb)
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)
|
||||
# extract field "byte count"
|
||||
byte_count = rx_pdu[1]
|
||||
# frame with bits value -> bits[] list
|
||||
rx_pdu_d_inputs = rx_pdu[2:]
|
||||
# check rx_byte_count: match nb of bits request and check buffer size
|
||||
if byte_count < byte_length(bit_nb) or byte_count != len(rx_pdu_d_inputs):
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')
|
||||
# allocate a bit_nb size list
|
||||
bits = [False] * bit_nb
|
||||
# fill bits list with bit items
|
||||
for i in range(bit_nb):
|
||||
bits[i] = bool((rx_pdu_d_inputs[i // 8] >> i % 8) & 0x01)
|
||||
# return bits list
|
||||
return bits
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return None
|
||||
|
||||
def read_holding_registers(self, reg_addr, reg_nb=1):
|
||||
"""Modbus function READ_HOLDING_REGISTERS (0x03).
|
||||
|
||||
:param reg_addr: register address (0 to 65535)
|
||||
:type reg_addr: int
|
||||
:param reg_nb: number of registers to read (1 to 125)
|
||||
:type reg_nb: int
|
||||
:returns: registers list or None if fail
|
||||
:rtype: list of int or None
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(reg_addr) <= 0xffff:
|
||||
raise ValueError('reg_addr out of range (valid from 0 to 65535)')
|
||||
if not 1 <= int(reg_nb) <= 125:
|
||||
raise ValueError('reg_nb out of range (valid from 1 to 125)')
|
||||
if int(reg_addr) + int(reg_nb) > 0x10000:
|
||||
raise ValueError('read after end of modbus address space')
|
||||
# make request
|
||||
try:
|
||||
tx_pdu = struct.pack('>BHH', READ_HOLDING_REGISTERS, reg_addr, reg_nb)
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)
|
||||
# extract field "byte count"
|
||||
byte_count = rx_pdu[1]
|
||||
# frame with regs value
|
||||
f_regs = rx_pdu[2:]
|
||||
# check rx_byte_count: buffer size must be consistent and have at least the requested number of registers
|
||||
if byte_count < 2 * reg_nb or byte_count != len(f_regs):
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')
|
||||
# allocate a reg_nb size list
|
||||
registers = [0] * reg_nb
|
||||
# fill registers list with register items
|
||||
for i in range(reg_nb):
|
||||
registers[i] = struct.unpack('>H', f_regs[i * 2:i * 2 + 2])[0]
|
||||
# return registers list
|
||||
return registers
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return None
|
||||
|
||||
def read_input_registers(self, reg_addr, reg_nb=1):
|
||||
"""Modbus function READ_INPUT_REGISTERS (0x04).
|
||||
|
||||
:param reg_addr: register address (0 to 65535)
|
||||
:type reg_addr: int
|
||||
:param reg_nb: number of registers to read (1 to 125)
|
||||
:type reg_nb: int
|
||||
:returns: registers list or None if fail
|
||||
:rtype: list of int or None
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(reg_addr) <= 0xffff:
|
||||
raise ValueError('reg_addr out of range (valid from 0 to 65535)')
|
||||
if not 1 <= int(reg_nb) <= 125:
|
||||
raise ValueError('reg_nb out of range (valid from 1 to 125)')
|
||||
if int(reg_addr) + int(reg_nb) > 0x10000:
|
||||
raise ValueError('read after end of modbus address space')
|
||||
# make request
|
||||
try:
|
||||
tx_pdu = struct.pack('>BHH', READ_INPUT_REGISTERS, reg_addr, reg_nb)
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=3)
|
||||
# extract field "byte count"
|
||||
byte_count = rx_pdu[1]
|
||||
# frame with regs value
|
||||
f_regs = rx_pdu[2:]
|
||||
# check rx_byte_count: buffer size must be consistent and have at least the requested number of registers
|
||||
if byte_count < 2 * reg_nb or byte_count != len(f_regs):
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')
|
||||
# allocate a reg_nb size list
|
||||
registers = [0] * reg_nb
|
||||
# fill registers list with register items
|
||||
for i in range(reg_nb):
|
||||
registers[i] = struct.unpack('>H', f_regs[i * 2:i * 2 + 2])[0]
|
||||
# return registers list
|
||||
return registers
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return None
|
||||
|
||||
def write_single_coil(self, bit_addr, bit_value):
|
||||
"""Modbus function WRITE_SINGLE_COIL (0x05).
|
||||
|
||||
:param bit_addr: bit address (0 to 65535)
|
||||
:type bit_addr: int
|
||||
:param bit_value: bit value to write
|
||||
:type bit_value: bool
|
||||
:returns: True if write ok
|
||||
:rtype: bool
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(bit_addr) <= 0xffff:
|
||||
raise ValueError('bit_addr out of range (valid from 0 to 65535)')
|
||||
# make request
|
||||
try:
|
||||
# format "bit value" field for PDU
|
||||
bit_value_raw = (0x0000, 0xff00)[bool(bit_value)]
|
||||
# make a request
|
||||
tx_pdu = struct.pack('>BHH', WRITE_SINGLE_COIL, bit_addr, bit_value_raw)
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)
|
||||
# decode reply
|
||||
resp_coil_addr, resp_coil_value = struct.unpack('>HH', rx_pdu[1:5])
|
||||
# check server reply
|
||||
if (resp_coil_addr != bit_addr) or (resp_coil_value != bit_value_raw):
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'server reply does not match the request')
|
||||
return True
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return False
|
||||
|
||||
def write_single_register(self, reg_addr, reg_value):
|
||||
"""Modbus function WRITE_SINGLE_REGISTER (0x06).
|
||||
|
||||
:param reg_addr: register address (0 to 65535)
|
||||
:type reg_addr: int
|
||||
:param reg_value: register value to write
|
||||
:type reg_value: int
|
||||
:returns: True if write ok
|
||||
:rtype: bool
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(reg_addr) <= 0xffff:
|
||||
raise ValueError('reg_addr out of range (valid from 0 to 65535)')
|
||||
if not 0 <= int(reg_value) <= 0xffff:
|
||||
raise ValueError('reg_value out of range (valid from 0 to 65535)')
|
||||
# make request
|
||||
try:
|
||||
# make a request
|
||||
tx_pdu = struct.pack('>BHH', WRITE_SINGLE_REGISTER, reg_addr, reg_value)
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)
|
||||
# decode reply
|
||||
resp_reg_addr, resp_reg_value = struct.unpack('>HH', rx_pdu[1:5])
|
||||
# check server reply
|
||||
if (resp_reg_addr != reg_addr) or (resp_reg_value != reg_value):
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'server reply does not match the request')
|
||||
return True
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return False
|
||||
|
||||
def write_multiple_coils(self, bits_addr, bits_value):
|
||||
"""Modbus function WRITE_MULTIPLE_COILS (0x0F).
|
||||
|
||||
:param bits_addr: bits address (0 to 65535)
|
||||
:type bits_addr: int
|
||||
:param bits_value: bits values to write
|
||||
:type bits_value: list
|
||||
:returns: True if write ok
|
||||
:rtype: bool
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(bits_addr) <= 0xffff:
|
||||
raise ValueError('bit_addr out of range (valid from 0 to 65535)')
|
||||
if not 1 <= len(bits_value) <= 1968:
|
||||
raise ValueError('number of coils out of range (valid from 1 to 1968)')
|
||||
if int(bits_addr) + len(bits_value) > 0x10000:
|
||||
raise ValueError('write after end of modbus address space')
|
||||
# make request
|
||||
try:
|
||||
# build PDU coils part
|
||||
# allocate a list of bytes
|
||||
byte_l = [0] * byte_length(len(bits_value))
|
||||
# populate byte list with coils values
|
||||
for i, item in enumerate(bits_value):
|
||||
if item:
|
||||
byte_l[i // 8] = set_bit(byte_l[i // 8], i % 8)
|
||||
# format PDU coils part with byte list
|
||||
pdu_coils_part = struct.pack('%dB' % len(byte_l), *byte_l)
|
||||
# concatenate PDU parts
|
||||
tx_pdu = struct.pack('>BHHB', WRITE_MULTIPLE_COILS, bits_addr, len(bits_value), len(pdu_coils_part))
|
||||
tx_pdu += pdu_coils_part
|
||||
# make a request
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)
|
||||
# response decode
|
||||
resp_write_addr, resp_write_count = struct.unpack('>HH', rx_pdu[1:5])
|
||||
# check response fields
|
||||
write_ok = resp_write_addr == bits_addr and resp_write_count == len(bits_value)
|
||||
return write_ok
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return False
|
||||
|
||||
def write_multiple_registers(self, regs_addr, regs_value):
|
||||
"""Modbus function WRITE_MULTIPLE_REGISTERS (0x10).
|
||||
|
||||
:param regs_addr: registers address (0 to 65535)
|
||||
:type regs_addr: int
|
||||
:param regs_value: registers values to write
|
||||
:type regs_value: list
|
||||
:returns: True if write ok
|
||||
:rtype: bool
|
||||
"""
|
||||
# check params
|
||||
if not 0 <= int(regs_addr) <= 0xffff:
|
||||
raise ValueError('regs_addr out of range (valid from 0 to 65535)')
|
||||
if not 1 <= len(regs_value) <= 123:
|
||||
raise ValueError('number of registers out of range (valid from 1 to 123)')
|
||||
if int(regs_addr) + len(regs_value) > 0x10000:
|
||||
raise ValueError('write after end of modbus address space')
|
||||
# make request
|
||||
try:
|
||||
# init PDU registers part
|
||||
pdu_regs_part = b''
|
||||
# populate it with register values
|
||||
for reg in regs_value:
|
||||
# check current register value
|
||||
if not 0 <= int(reg) <= 0xffff:
|
||||
raise ValueError('regs_value list contains out of range values')
|
||||
# pack register for build frame
|
||||
pdu_regs_part += struct.pack('>H', reg)
|
||||
bytes_nb = len(pdu_regs_part)
|
||||
# concatenate PDU parts
|
||||
tx_pdu = struct.pack('>BHHB', WRITE_MULTIPLE_REGISTERS, regs_addr, len(regs_value), bytes_nb)
|
||||
tx_pdu += pdu_regs_part
|
||||
# make a request
|
||||
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=5)
|
||||
# response decode
|
||||
resp_write_addr, resp_write_count = struct.unpack('>HH', rx_pdu[1:5])
|
||||
# check response fields
|
||||
write_ok = resp_write_addr == regs_addr and resp_write_count == len(regs_value)
|
||||
return write_ok
|
||||
# handle error during request
|
||||
except ModbusClient._InternalError as e:
|
||||
self._req_except_handler(e)
|
||||
return False
|
||||
|
||||
def _send(self, frame):
|
||||
"""Send frame over current socket.
|
||||
|
||||
:param frame: modbus frame to send (MBAP + PDU)
|
||||
:type frame: bytes
|
||||
"""
|
||||
# check socket
|
||||
if not self.is_open:
|
||||
raise ModbusClient._NetworkError(MB_SOCK_CLOSE_ERR, 'try to send on a close socket')
|
||||
# send
|
||||
try:
|
||||
self._sock.send(frame)
|
||||
except socket.timeout:
|
||||
self._sock.close()
|
||||
raise ModbusClient._NetworkError(MB_TIMEOUT_ERR, 'timeout error')
|
||||
except socket.error:
|
||||
self._sock.close()
|
||||
raise ModbusClient._NetworkError(MB_SEND_ERR, 'send error')
|
||||
|
||||
def _send_pdu(self, pdu):
|
||||
"""Convert modbus PDU to frame and send it.
|
||||
|
||||
:param pdu: modbus frame PDU
|
||||
:type pdu: bytes
|
||||
"""
|
||||
# for auto_open mode, check TCP and open on need
|
||||
if self.auto_open and not self.is_open:
|
||||
self._open()
|
||||
# add MBAP header to PDU
|
||||
tx_frame = self._add_mbap(pdu)
|
||||
# send frame with error check
|
||||
self._send(tx_frame)
|
||||
# debug
|
||||
self._debug_dump('Tx', tx_frame)
|
||||
|
||||
def _recv(self, size):
|
||||
"""Receive data over current socket.
|
||||
|
||||
:param size: number of bytes to receive
|
||||
:type size: int
|
||||
:returns: receive data or None if error
|
||||
:rtype: bytes
|
||||
"""
|
||||
try:
|
||||
r_buffer = self._sock.recv(size)
|
||||
except socket.timeout:
|
||||
self._sock.close()
|
||||
raise ModbusClient._NetworkError(MB_TIMEOUT_ERR, 'timeout error')
|
||||
except socket.error:
|
||||
r_buffer = b''
|
||||
# handle recv error
|
||||
if not r_buffer:
|
||||
self._sock.close()
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'recv error')
|
||||
return r_buffer
|
||||
|
||||
def _recv_all(self, size):
|
||||
"""Receive data over current socket, loop until all bytes is received (avoid TCP frag).
|
||||
|
||||
:param size: number of bytes to receive
|
||||
:type size: int
|
||||
:returns: receive data or None if error
|
||||
:rtype: bytes
|
||||
"""
|
||||
r_buffer = b''
|
||||
while len(r_buffer) < size:
|
||||
r_buffer += self._recv(size - len(r_buffer))
|
||||
return r_buffer
|
||||
|
||||
def _recv_pdu(self, min_len=2):
|
||||
"""Receive the modbus PDU (Protocol Data Unit).
|
||||
|
||||
:param min_len: minimal length of the PDU
|
||||
:type min_len: int
|
||||
:returns: modbus frame PDU or None if error
|
||||
:rtype: bytes or None
|
||||
"""
|
||||
# receive 7 bytes header (MBAP)
|
||||
rx_mbap = self._recv_all(7)
|
||||
# decode MBAP
|
||||
(f_transaction_id, f_protocol_id, f_length, f_unit_id) = struct.unpack('>HHHB', rx_mbap)
|
||||
# check MBAP fields
|
||||
f_transaction_err = f_transaction_id != self._transaction_id
|
||||
f_protocol_err = f_protocol_id != 0
|
||||
f_length_err = f_length >= 256
|
||||
f_unit_id_err = f_unit_id != self.unit_id
|
||||
# checking error status of fields
|
||||
if f_transaction_err or f_protocol_err or f_length_err or f_unit_id_err:
|
||||
self.close()
|
||||
self._debug_dump('Rx', rx_mbap)
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'MBAP checking error')
|
||||
# recv PDU
|
||||
rx_pdu = self._recv_all(f_length - 1)
|
||||
# for auto_close mode, close socket after each request
|
||||
if self.auto_close:
|
||||
self.close()
|
||||
# dump frame
|
||||
self._debug_dump('Rx', rx_mbap + rx_pdu)
|
||||
# body decode
|
||||
# check PDU length for global minimal frame (an except frame: func code + exp code)
|
||||
if len(rx_pdu) < 2:
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'PDU length is too short')
|
||||
# extract function code
|
||||
rx_fc = rx_pdu[0]
|
||||
# check except status
|
||||
if rx_fc >= 0x80:
|
||||
exp_code = rx_pdu[1]
|
||||
raise ModbusClient._ModbusExcept(exp_code)
|
||||
# check PDU length for specific request set in min_len (keep this after except checking)
|
||||
if len(rx_pdu) < min_len:
|
||||
raise ModbusClient._NetworkError(MB_RECV_ERR, 'PDU length is too short for current request')
|
||||
# if no error, return PDU
|
||||
return rx_pdu
|
||||
|
||||
def _add_mbap(self, pdu):
|
||||
"""Return full modbus frame with MBAP (modbus application protocol header) append to PDU.
|
||||
|
||||
:param pdu: modbus PDU (protocol data unit)
|
||||
:type pdu: bytes
|
||||
:returns: full modbus frame
|
||||
:rtype: bytes
|
||||
"""
|
||||
# build MBAP
|
||||
self._transaction_id = random.randint(0, 65535)
|
||||
protocol_id = 0
|
||||
length = len(pdu) + 1
|
||||
mbap = struct.pack('>HHHB', self._transaction_id, protocol_id, length, self.unit_id)
|
||||
# full modbus/TCP frame = [MBAP]PDU
|
||||
return mbap + pdu
|
||||
|
||||
def _req_pdu(self, tx_pdu, rx_min_len=2):
|
||||
"""Request processing (send and recv PDU).
|
||||
|
||||
:param tx_pdu: modbus PDU (protocol data unit) to send
|
||||
:type tx_pdu: bytes
|
||||
:param rx_min_len: min length of receive PDU
|
||||
:type rx_min_len: int
|
||||
:returns: the receive PDU or None if error
|
||||
:rtype: bytes
|
||||
"""
|
||||
# init request engine
|
||||
self._req_init()
|
||||
# send PDU
|
||||
self._send_pdu(tx_pdu)
|
||||
# return receive PDU
|
||||
return self._recv_pdu(min_len=rx_min_len)
|
||||
|
||||
def _req_init(self):
|
||||
"""Reset request status flags."""
|
||||
self._last_error = MB_NO_ERR
|
||||
self._last_except = EXP_NONE
|
||||
|
||||
def _req_except_handler(self, _except):
|
||||
"""Global handler for internal exceptions."""
|
||||
# on request network error
|
||||
if isinstance(_except, ModbusClient._NetworkError):
|
||||
self._last_error = _except.code
|
||||
self._debug_msg(_except.message)
|
||||
# on request modbus except
|
||||
if isinstance(_except, ModbusClient._ModbusExcept):
|
||||
self._last_error = MB_EXCEPT_ERR
|
||||
self._last_except = _except.code
|
||||
self._debug_msg('modbus exception (code %d "%s")' % (self.last_except, self.last_except_as_txt))
|
||||
|
||||
def _debug_msg(self, msg):
|
||||
"""Print debug message if debug mode is on.
|
||||
|
||||
:param msg: debug message
|
||||
:type msg: str
|
||||
"""
|
||||
if self.debug:
|
||||
print(msg)
|
||||
|
||||
def _debug_dump(self, label, frame):
|
||||
"""Print debug dump if debug mode is on.
|
||||
|
||||
:param label: head label
|
||||
:type label: str
|
||||
:param frame: modbus frame
|
||||
:type frame: bytes
|
||||
"""
|
||||
if self.debug:
|
||||
self._pretty_dump(label, frame)
|
||||
|
||||
@staticmethod
|
||||
def _pretty_dump(label, frame):
|
||||
"""Dump a modbus frame.
|
||||
|
||||
modbus/TCP format: [MBAP] PDU
|
||||
|
||||
:param label: head label
|
||||
:type label: str
|
||||
:param frame: modbus frame
|
||||
:type frame: bytes
|
||||
"""
|
||||
# split data string items to a list of hex value
|
||||
dump = ['%02X' % c for c in frame]
|
||||
# format message
|
||||
dump_mbap = ' '.join(dump[0:7])
|
||||
dump_pdu = ' '.join(dump[7:])
|
||||
msg = '[%s] %s' % (dump_mbap, dump_pdu)
|
||||
# print result
|
||||
print(label)
|
||||
print(msg)
|
||||
87
env/lib/python3.11/site-packages/pyModbusTCP/constants.py
vendored
Normal file
87
env/lib/python3.11/site-packages/pyModbusTCP/constants.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
""" pyModbusTCP package constants definition """
|
||||
|
||||
# Package version
|
||||
VERSION = '0.2.0'
|
||||
# Modbus/TCP
|
||||
MODBUS_PORT = 502
|
||||
# Modbus function code
|
||||
READ_COILS = 0x01
|
||||
READ_DISCRETE_INPUTS = 0x02
|
||||
READ_HOLDING_REGISTERS = 0x03
|
||||
READ_INPUT_REGISTERS = 0x04
|
||||
WRITE_SINGLE_COIL = 0x05
|
||||
WRITE_SINGLE_REGISTER = 0x06
|
||||
WRITE_MULTIPLE_COILS = 0x0F
|
||||
WRITE_MULTIPLE_REGISTERS = 0x10
|
||||
MODBUS_ENCAPSULATED_INTERFACE = 0x2B
|
||||
SUPPORTED_FUNCTION_CODES = (READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, READ_INPUT_REGISTERS,
|
||||
WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS)
|
||||
# Modbus except code
|
||||
EXP_NONE = 0x00
|
||||
EXP_ILLEGAL_FUNCTION = 0x01
|
||||
EXP_DATA_ADDRESS = 0x02
|
||||
EXP_DATA_VALUE = 0x03
|
||||
EXP_SLAVE_DEVICE_FAILURE = 0x04
|
||||
EXP_ACKNOWLEDGE = 0x05
|
||||
EXP_SLAVE_DEVICE_BUSY = 0x06
|
||||
EXP_NEGATIVE_ACKNOWLEDGE = 0x07
|
||||
EXP_MEMORY_PARITY_ERROR = 0x08
|
||||
EXP_GATEWAY_PATH_UNAVAILABLE = 0x0A
|
||||
EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 0x0B
|
||||
# Exception as short human-readable
|
||||
EXP_TXT = {
|
||||
EXP_NONE: 'no exception',
|
||||
EXP_ILLEGAL_FUNCTION: 'illegal function',
|
||||
EXP_DATA_ADDRESS: 'illegal data address',
|
||||
EXP_DATA_VALUE: 'illegal data value',
|
||||
EXP_SLAVE_DEVICE_FAILURE: 'slave device failure',
|
||||
EXP_ACKNOWLEDGE: 'acknowledge',
|
||||
EXP_SLAVE_DEVICE_BUSY: 'slave device busy',
|
||||
EXP_NEGATIVE_ACKNOWLEDGE: 'negative acknowledge',
|
||||
EXP_MEMORY_PARITY_ERROR: 'memory parity error',
|
||||
EXP_GATEWAY_PATH_UNAVAILABLE: 'gateway path unavailable',
|
||||
EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND: 'gateway target device failed to respond'
|
||||
}
|
||||
# Exception as details human-readable
|
||||
EXP_DETAILS = {
|
||||
EXP_NONE: 'The last request produced no exceptions.',
|
||||
EXP_ILLEGAL_FUNCTION: 'Function code received in the query is not recognized or allowed by slave.',
|
||||
EXP_DATA_ADDRESS: 'Data address of some or all the required entities are not allowed or do not exist in slave.',
|
||||
EXP_DATA_VALUE: 'Value is not accepted by slave.',
|
||||
EXP_SLAVE_DEVICE_FAILURE: 'Unrecoverable error occurred while slave was attempting to perform requested action.',
|
||||
EXP_ACKNOWLEDGE: 'Slave has accepted request and is processing it, but a long duration of time is required. '
|
||||
'This response is returned to prevent a timeout error from occurring in the master. '
|
||||
'Master can next issue a Poll Program Complete message to determine whether processing '
|
||||
'is completed.',
|
||||
EXP_SLAVE_DEVICE_BUSY: 'Slave is engaged in processing a long-duration command. Master should retry later.',
|
||||
EXP_NEGATIVE_ACKNOWLEDGE: 'Slave cannot perform the programming functions. '
|
||||
'Master should request diagnostic or error information from slave.',
|
||||
EXP_MEMORY_PARITY_ERROR: 'Slave detected a parity error in memory. '
|
||||
'Master can retry the request, but service may be required on the slave device.',
|
||||
EXP_GATEWAY_PATH_UNAVAILABLE: 'Specialized for Modbus gateways, this indicates a misconfiguration on gateway.',
|
||||
EXP_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND: 'Specialized for Modbus gateways, sent when slave fails to respond.'
|
||||
}
|
||||
# Module error codes
|
||||
MB_NO_ERR = 0
|
||||
MB_RESOLVE_ERR = 1
|
||||
MB_CONNECT_ERR = 2
|
||||
MB_SEND_ERR = 3
|
||||
MB_RECV_ERR = 4
|
||||
MB_TIMEOUT_ERR = 5
|
||||
MB_FRAME_ERR = 6
|
||||
MB_EXCEPT_ERR = 7
|
||||
MB_CRC_ERR = 8
|
||||
MB_SOCK_CLOSE_ERR = 9
|
||||
# Module error as short human-readable
|
||||
MB_ERR_TXT = {
|
||||
MB_NO_ERR: 'no error',
|
||||
MB_RESOLVE_ERR: 'name resolve error',
|
||||
MB_CONNECT_ERR: 'connect error',
|
||||
MB_SEND_ERR: 'socket send error',
|
||||
MB_RECV_ERR: 'socket recv error',
|
||||
MB_TIMEOUT_ERR: 'recv timeout occur',
|
||||
MB_FRAME_ERR: 'frame format error',
|
||||
MB_EXCEPT_ERR: 'modbus exception',
|
||||
MB_CRC_ERR: 'bad CRC on receive frame',
|
||||
MB_SOCK_CLOSE_ERR: 'socket is closed'
|
||||
}
|
||||
1024
env/lib/python3.11/site-packages/pyModbusTCP/server.py
vendored
Normal file
1024
env/lib/python3.11/site-packages/pyModbusTCP/server.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
319
env/lib/python3.11/site-packages/pyModbusTCP/utils.py
vendored
Normal file
319
env/lib/python3.11/site-packages/pyModbusTCP/utils.py
vendored
Normal file
@@ -0,0 +1,319 @@
|
||||
""" pyModbusTCP utils functions """
|
||||
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
|
||||
|
||||
###############
|
||||
# bits function
|
||||
###############
|
||||
def get_bits_from_int(val_int, val_size=16):
|
||||
"""Get the list of bits of val_int integer (default size is 16 bits).
|
||||
|
||||
Return bits list, the least significant bit first. Use list.reverse() for msb first.
|
||||
|
||||
:param val_int: integer value
|
||||
:type val_int: int
|
||||
:param val_size: bit length of integer (word = 16, long = 32) (optional)
|
||||
:type val_size: int
|
||||
:returns: list of boolean "bits" (the least significant first)
|
||||
:rtype: list
|
||||
"""
|
||||
bits = []
|
||||
# populate bits list with bool items of val_int
|
||||
for i in range(val_size):
|
||||
bits.append(bool((val_int >> i) & 0x01))
|
||||
# return bits list
|
||||
return bits
|
||||
|
||||
|
||||
# short alias
|
||||
int2bits = get_bits_from_int
|
||||
|
||||
|
||||
def byte_length(bit_length):
|
||||
"""Return the number of bytes needs to contain a bit_length structure.
|
||||
|
||||
:param bit_length: the number of bits
|
||||
:type bit_length: int
|
||||
:returns: the number of bytes
|
||||
:rtype: int
|
||||
"""
|
||||
return (bit_length + 7) // 8
|
||||
|
||||
|
||||
def test_bit(value, offset):
|
||||
"""Test a bit at offset position.
|
||||
|
||||
:param value: value of integer to test
|
||||
:type value: int
|
||||
:param offset: bit offset (0 is lsb)
|
||||
:type offset: int
|
||||
:returns: value of bit at offset position
|
||||
:rtype: bool
|
||||
"""
|
||||
mask = 1 << offset
|
||||
return bool(value & mask)
|
||||
|
||||
|
||||
def set_bit(value, offset):
|
||||
"""Set a bit at offset position.
|
||||
|
||||
:param value: value of integer where set the bit
|
||||
:type value: int
|
||||
:param offset: bit offset (0 is lsb)
|
||||
:type offset: int
|
||||
:returns: value of integer with bit set
|
||||
:rtype: int
|
||||
"""
|
||||
mask = 1 << offset
|
||||
return int(value | mask)
|
||||
|
||||
|
||||
def reset_bit(value, offset):
|
||||
"""Reset a bit at offset position.
|
||||
|
||||
:param value: value of integer where reset the bit
|
||||
:type value: int
|
||||
:param offset: bit offset (0 is lsb)
|
||||
:type offset: int
|
||||
:returns: value of integer with bit reset
|
||||
:rtype: int
|
||||
"""
|
||||
mask = ~(1 << offset)
|
||||
return int(value & mask)
|
||||
|
||||
|
||||
def toggle_bit(value, offset):
|
||||
"""Return an integer with the bit at offset position inverted.
|
||||
|
||||
:param value: value of integer where invert the bit
|
||||
:type value: int
|
||||
:param offset: bit offset (0 is lsb)
|
||||
:type offset: int
|
||||
:returns: value of integer with bit inverted
|
||||
:rtype: int
|
||||
"""
|
||||
mask = 1 << offset
|
||||
return int(value ^ mask)
|
||||
|
||||
|
||||
########################
|
||||
# Word convert functions
|
||||
########################
|
||||
def word_list_to_long(val_list, big_endian=True, long_long=False):
|
||||
"""Word list (16 bits) to long (32 bits) or long long (64 bits) list.
|
||||
|
||||
By default, word_list_to_long() use big endian order. For use little endian, set
|
||||
big_endian param to False. Output format could be long long with long_long.
|
||||
option set to True.
|
||||
|
||||
:param val_list: list of 16 bits int value
|
||||
:type val_list: list
|
||||
:param big_endian: True for big endian/False for little (optional)
|
||||
:type big_endian: bool
|
||||
:param long_long: True for long long 64 bits, default is long 32 bits (optional)
|
||||
:type long_long: bool
|
||||
:returns: list of 32 bits int value
|
||||
:rtype: list
|
||||
"""
|
||||
long_list = []
|
||||
block_size = 4 if long_long else 2
|
||||
# populate long_list (len is half or quarter of 16 bits val_list) with 32 or 64 bits value
|
||||
for index in range(int(len(val_list) / block_size)):
|
||||
start = block_size * index
|
||||
long = 0
|
||||
if big_endian:
|
||||
if long_long:
|
||||
long += (val_list[start] << 48) + (val_list[start + 1] << 32)
|
||||
long += (val_list[start + 2] << 16) + (val_list[start + 3])
|
||||
else:
|
||||
long += (val_list[start] << 16) + val_list[start + 1]
|
||||
else:
|
||||
if long_long:
|
||||
long += (val_list[start + 3] << 48) + (val_list[start + 2] << 32)
|
||||
long += (val_list[start + 1] << 16) + val_list[start]
|
||||
long_list.append(long)
|
||||
# return long list
|
||||
return long_list
|
||||
|
||||
|
||||
# short alias
|
||||
words2longs = word_list_to_long
|
||||
|
||||
|
||||
def long_list_to_word(val_list, big_endian=True, long_long=False):
|
||||
"""Long (32 bits) or long long (64 bits) list to word (16 bits) list.
|
||||
|
||||
By default long_list_to_word() use big endian order. For use little endian, set
|
||||
big_endian param to False. Input format could be long long with long_long
|
||||
param to True.
|
||||
|
||||
:param val_list: list of 32 bits int value
|
||||
:type val_list: list
|
||||
:param big_endian: True for big endian/False for little (optional)
|
||||
:type big_endian: bool
|
||||
:param long_long: True for long long 64 bits, default is long 32 bits (optional)
|
||||
:type long_long: bool
|
||||
:returns: list of 16 bits int value
|
||||
:rtype: list
|
||||
"""
|
||||
word_list = []
|
||||
# populate 16 bits word_list with 32 or 64 bits value of val_list
|
||||
for val in val_list:
|
||||
block_l = [val & 0xffff, (val >> 16) & 0xffff]
|
||||
if long_long:
|
||||
block_l.append((val >> 32) & 0xffff)
|
||||
block_l.append((val >> 48) & 0xffff)
|
||||
if big_endian:
|
||||
block_l.reverse()
|
||||
word_list.extend(block_l)
|
||||
# return long list
|
||||
return word_list
|
||||
|
||||
|
||||
# short alias
|
||||
longs2words = long_list_to_word
|
||||
|
||||
|
||||
##########################
|
||||
# 2's complement functions
|
||||
##########################
|
||||
def get_2comp(val_int, val_size=16):
|
||||
"""Get the 2's complement of Python int val_int.
|
||||
|
||||
:param val_int: int value to apply 2's complement
|
||||
:type val_int: int
|
||||
:param val_size: bit size of int value (word = 16, long = 32) (optional)
|
||||
:type val_size: int
|
||||
:returns: 2's complement result
|
||||
:rtype: int
|
||||
:raises ValueError: if mismatch between val_int and val_size
|
||||
"""
|
||||
# avoid overflow
|
||||
if not (-1 << val_size - 1) <= val_int < (1 << val_size):
|
||||
err_msg = 'could not compute two\'s complement for %i on %i bits'
|
||||
err_msg %= (val_int, val_size)
|
||||
raise ValueError(err_msg)
|
||||
# test negative int
|
||||
if val_int < 0:
|
||||
val_int += 1 << val_size
|
||||
# test MSB (do two's comp if set)
|
||||
elif val_int & (1 << (val_size - 1)):
|
||||
val_int -= 1 << val_size
|
||||
return val_int
|
||||
|
||||
|
||||
# short alias
|
||||
twos_c = get_2comp
|
||||
|
||||
|
||||
def get_list_2comp(val_list, val_size=16):
|
||||
"""Get the 2's complement of Python list val_list.
|
||||
|
||||
:param val_list: list of int value to apply 2's complement
|
||||
:type val_list: list
|
||||
:param val_size: bit size of int value (word = 16, long = 32) (optional)
|
||||
:type val_size: int
|
||||
:returns: 2's complement result
|
||||
:rtype: list
|
||||
"""
|
||||
return [get_2comp(val, val_size) for val in val_list]
|
||||
|
||||
|
||||
# short alias
|
||||
twos_c_l = get_list_2comp
|
||||
|
||||
|
||||
###############################
|
||||
# IEEE floating-point functions
|
||||
###############################
|
||||
def decode_ieee(val_int, double=False):
|
||||
"""Decode Python int (32 bits integer) as an IEEE single or double precision format.
|
||||
|
||||
Support NaN.
|
||||
|
||||
:param val_int: a 32 or 64 bits integer as an int Python value
|
||||
:type val_int: int
|
||||
:param double: set to decode as a 64 bits double precision,
|
||||
default is 32 bits single (optional)
|
||||
:type double: bool
|
||||
:returns: float result
|
||||
:rtype: float
|
||||
"""
|
||||
if double:
|
||||
return struct.unpack("d", struct.pack("Q", val_int))[0]
|
||||
else:
|
||||
return struct.unpack("f", struct.pack("I", val_int))[0]
|
||||
|
||||
|
||||
def encode_ieee(val_float, double=False):
|
||||
"""Encode Python float to int (32 bits integer) as an IEEE single or double precision format.
|
||||
|
||||
Support NaN.
|
||||
|
||||
:param val_float: float value to convert
|
||||
:type val_float: float
|
||||
:param double: set to encode as a 64 bits double precision,
|
||||
default is 32 bits single (optional)
|
||||
:type double: bool
|
||||
:returns: IEEE 32 bits (single precision) as Python int
|
||||
:rtype: int
|
||||
"""
|
||||
if double:
|
||||
return struct.unpack("Q", struct.pack("d", val_float))[0]
|
||||
else:
|
||||
return struct.unpack("I", struct.pack("f", val_float))[0]
|
||||
|
||||
|
||||
################
|
||||
# misc functions
|
||||
################
|
||||
def crc16(frame):
|
||||
"""Compute CRC16.
|
||||
|
||||
:param frame: frame
|
||||
:type frame: bytes
|
||||
:returns: CRC16
|
||||
:rtype: int
|
||||
"""
|
||||
crc = 0xFFFF
|
||||
for item in frame:
|
||||
next_byte = item
|
||||
crc ^= next_byte
|
||||
for _ in range(8):
|
||||
lsb = crc & 1
|
||||
crc >>= 1
|
||||
if lsb:
|
||||
crc ^= 0xA001
|
||||
return crc
|
||||
|
||||
|
||||
def valid_host(host_str):
|
||||
"""Validate a host string.
|
||||
|
||||
Can be an IPv4/6 address or a valid hostname.
|
||||
|
||||
:param host_str: the host string to test
|
||||
:type host_str: str
|
||||
:returns: True if host_str is valid
|
||||
:rtype: bool
|
||||
"""
|
||||
# IPv4 valid address ?
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET, host_str)
|
||||
return True
|
||||
except socket.error:
|
||||
pass
|
||||
# IPv6 valid address ?
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, host_str)
|
||||
return True
|
||||
except socket.error:
|
||||
pass
|
||||
# valid hostname ?
|
||||
if re.match(r'^[a-z][a-z0-9.\-]+$', host_str):
|
||||
return True
|
||||
# on invalid host
|
||||
return False
|
||||
Reference in New Issue
Block a user