import re
import struct
from datetime import date
from typing import Union
from .getters import get_bool
[docs]
def set_bool(bytearray_: bytearray, byte_index: int, bool_index: int, value: bool) -> bytearray:
"""Set boolean value on location in bytearray.
Args:
bytearray_: buffer to write to.
byte_index: byte index to write to.
bool_index: bit index to write to.
value: value to write.
Examples:
>>> buffer = bytearray([0b00000000])
>>> set_bool(buffer, 0, 0, True)
>>> buffer
bytearray(b"\\x01")
"""
if value not in {0, 1, True, False}:
raise TypeError(f"Value value:{value} is not a boolean expression.")
current_value = get_bool(bytearray_, byte_index, bool_index)
index_value = 1 << bool_index
# check if bool already has correct value
if current_value == value:
return bytearray_
if value:
# make sure index_v is IN current byte
bytearray_[byte_index] += index_value
else:
# make sure index_v is NOT in current byte
bytearray_[byte_index] -= index_value
return bytearray_
[docs]
def set_byte(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
"""Set value in bytearray to byte
Args:
bytearray_: buffer to write to.
byte_index: byte index to write.
_int: value to write.
Returns:
buffer with the written value.
Examples:
>>> buffer = bytearray([0b00000000])
>>> set_byte(buffer, 0, 255)
bytearray(b"\\xFF")
"""
_int = int(_int)
_bytes = struct.pack("B", _int)
bytearray_[byte_index : byte_index + 1] = _bytes
return bytearray_
[docs]
def set_word(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
"""Set value in bytearray to word
Notes:
Word datatype is 2 bytes long.
Args:
bytearray_: buffer to be written.
byte_index: byte index to start write from.
_int: value to write.
Return:
buffer with the written value
"""
_int = int(_int)
_bytes = struct.unpack("2B", struct.pack(">H", _int))
bytearray_[byte_index : byte_index + 2] = _bytes
return bytearray_
[docs]
def set_int(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
"""Set value in bytearray to int
Notes:
An datatype `int` in the PLC consists of two `bytes`.
Args:
bytearray_: buffer to write on.
byte_index: byte index to start writing from.
_int: int value to write.
Returns:
Buffer with the written value.
Examples:
>>> data = bytearray(2)
>>> set_int(data, 0, 255)
bytearray(b'\\x00\\xff')
"""
# make sure were dealing with an int
_int = int(_int)
_bytes = struct.unpack("2B", struct.pack(">h", _int))
bytearray_[byte_index : byte_index + 2] = _bytes
return bytearray_
[docs]
def set_uint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
"""Set value in bytearray to unsigned int
Notes:
An datatype `uint` in the PLC consists of two `bytes`.
Args:
bytearray_: buffer to write on.
byte_index: byte index to start writing from.
_int: int value to write.
Returns:
Buffer with the written value.
Examples:
>>> from snap7.util import set_uint
>>> data = bytearray(2)
>>> set_uint(data, 0, 65535)
bytearray(b'\\xff\\xff')
"""
# make sure were dealing with an int
_int = int(_int)
_bytes = struct.unpack("2B", struct.pack(">H", _int))
bytearray_[byte_index : byte_index + 2] = _bytes
return bytearray_
[docs]
def set_real(bytearray_: bytearray, byte_index: int, real: Union[bool, str, float, int]) -> bytearray:
"""Set Real value
Notes:
Datatype `real` is represented in 4 bytes in the PLC.
The packed representation uses the `IEEE 754 binary32`.
Args:
bytearray_: buffer to write to.
byte_index: byte index to start writing from.
real: value to be written.
Returns:
Buffer with the value written.
Examples:
>>> data = bytearray(4)
>>> set_real(data, 0, 123.321)
bytearray(b'B\\xf6\\xa4Z')
"""
real_packed = struct.pack(">f", float(real))
_bytes = struct.unpack("4B", real_packed)
for i, b in enumerate(_bytes):
bytearray_[byte_index + i] = b
return bytearray_
[docs]
def set_fstring(bytearray_: bytearray, byte_index: int, value: str, max_length: int) -> None:
"""Set space-padded fixed-length string value
Args:
bytearray_: buffer to write to.
byte_index: byte index to start writing from.
value: string to write.
max_length: maximum string length, i.e. the fixed size of the string.
Raises:
:obj:`TypeError`: if the `value` is not a :obj:`str`.
:obj:`ValueError`: if the length of the `value` is larger than the `max_size`
or 'value' contains non-ascii characters.
Examples:
>>> data = bytearray(20)
>>> set_fstring(data, 0, "hello world", 15)
>>> data
bytearray(b'hello world \x00\x00\x00\x00\x00')
"""
if not value.isascii():
raise ValueError("Value contains non-ascii values.")
# FAIL HARD WHEN trying to write too much data into PLC
size = len(value)
if size > max_length:
raise ValueError(f"size {size} > max_length {max_length} {value}")
i = 0
# fill array which chr integers
for i, c in enumerate(value):
bytearray_[byte_index + i] = ord(c)
# fill the rest with empty space
for r in range(i + 1, max_length):
bytearray_[byte_index + r] = ord(" ")
[docs]
def set_string(bytearray_: bytearray, byte_index: int, value: str, max_size: int = 254) -> None:
"""Set string value
Args:
bytearray_: buffer to write to.
byte_index: byte index to start writing from.
value: string to write.
max_size: maximum possible string size, max. 254 as default.
Raises:
:obj:`TypeError`: if the `value` is not a :obj:`str`.
:obj:`ValueError`: if the length of the `value` is larger than the `max_size`
or 'max_size' is greater than 254 or 'value' contains non-ascii characters.
Examples:
>>> from snap7.util import set_string
>>> data = bytearray(20)
>>> set_string(data, 0, "hello world", 254)
>>> data
bytearray(b'\\xff\\x0bhello world\\x00\\x00\\x00\\x00\\x00\\x00\\x00')
"""
if not isinstance(value, str):
raise TypeError(f"Value value:{value} is not from Type string")
if max_size > 254:
raise ValueError(f"max_size: {max_size} > max. allowed 254 chars")
if not value.isascii():
raise ValueError(
"Value contains non-ascii values, which is not compatible with PLC Type STRING."
"Check encoding of value or try set_wstring() (utf-16 encoding needed)."
)
size = len(value)
# FAIL HARD WHEN trying to write too much data into PLC
if size > max_size:
raise ValueError(f"size {size} > max_size {max_size} {value}")
# set max string size
bytearray_[byte_index] = max_size
# set len count on first position
bytearray_[byte_index + 1] = len(value)
i = 0
# fill array which chr integers
for i, c in enumerate(value):
bytearray_[byte_index + 2 + i] = ord(c)
# fill the rest with empty space
for r in range(i + 1, bytearray_[byte_index] - 2):
bytearray_[byte_index + 2 + r] = ord(" ")
[docs]
def set_dword(bytearray_: bytearray, byte_index: int, dword: int) -> None:
"""Set a DWORD to the buffer.
Notes:
Datatype `dword` consists in 8 bytes in the PLC.
The maximum value posible is `4294967295`
Args:
bytearray_: buffer to write to.
byte_index: byte index from where to write.
dword: value to write.
Examples:
>>> data = bytearray(4)
>>> set_dword(data,0, 4294967295)
>>> data
bytearray(b'\\xff\\xff\\xff\\xff')
"""
dword = int(dword)
_bytes = struct.unpack("4B", struct.pack(">I", dword))
for i, b in enumerate(_bytes):
bytearray_[byte_index + i] = b
[docs]
def set_dint(bytearray_: bytearray, byte_index: int, dint: int) -> None:
"""Set value in bytearray to dint
Notes:
Datatype `dint` consists in 4 bytes in the PLC.
Maximum possible value is 2147483647.
Lower posible value is -2147483648.
Args:
bytearray_: buffer to write.
byte_index: byte index from where to start writing.
dint: double integer value
Examples:
>>> data = bytearray(4)
>>> set_dint(data, 0, 2147483647)
>>> data
bytearray(b'\\x7f\\xff\\xff\\xff')
"""
dint = int(dint)
_bytes = struct.unpack("4B", struct.pack(">i", dint))
for i, b in enumerate(_bytes):
bytearray_[byte_index + i] = b
[docs]
def set_udint(bytearray_: bytearray, byte_index: int, udint: int) -> None:
"""Set value in bytearray to unsigned dint
Notes:
Datatype `dint` consists in 4 bytes in the PLC.
Maximum possible value is 4294967295.
Minimum posible value is 0.
Args:
bytearray_: buffer to write.
byte_index: byte index from where to start writing.
udint: unsigned double integer value
Examples:
>>> data = bytearray(4)
>>> set_udint(data, 0, 4294967295)
>>> data
bytearray(b'\\xff\\xff\\xff\\xff')
"""
udint = int(udint)
_bytes = struct.unpack("4B", struct.pack(">I", udint))
for i, b in enumerate(_bytes):
bytearray_[byte_index + i] = b
[docs]
def set_time(bytearray_: bytearray, byte_index: int, time_string: str) -> bytearray:
"""Set value in bytearray to time
Notes:
Datatype `time` consists in 4 bytes in the PLC.
Maximum possible value is T#24D_20H_31M_23S_647MS(2147483647).
Lower posible value is T#-24D_20H_31M_23S_648MS(-2147483648).
Args:
bytearray_: buffer to write.
byte_index: byte index from where to start writing.
time_string: time value in string
Examples:
>>> data = bytearray(4)
>>> set_time(data, 0, '-22:3:57:28.192')
>>> data
bytearray(b'\x8d\xda\xaf\x00')
"""
sign = 1
if re.fullmatch(
r"(-?(2[0-3]|1?\d):(2[0-3]|1?\d|\d):([1-5]?\d):[1-5]?\d.\d{1,3})|"
r"(-24:(20|1?\d):(3[0-1]|[0-2]?\d):(2[0-3]|1?\d).(64[0-8]|6[0-3]\d|[0-5]\d{1,2}))|"
r"(24:(20|1?\d):(3[0-1]|[0-2]?\d):(2[0-3]|1?\d).(64[0-7]|6[0-3]\d|[0-5]\d{1,2}))",
time_string,
):
data_list = re.split("[: .]", time_string)
days: str = data_list[0]
hours: int = int(data_list[1])
minutes: int = int(data_list[2])
seconds: int = int(data_list[3])
milli_seconds: int = int(data_list[4].ljust(3, "0"))
if re.match(r"^-\d{1,2}$", days):
sign = -1
time_int = (
(int(days) * sign * 3600 * 24 + (hours % 24) * 3600 + (minutes % 60) * 60 + seconds % 60) * 1000 + milli_seconds
) * sign
bytes_array = time_int.to_bytes(4, byteorder="big", signed=True)
bytearray_[byte_index : byte_index + 4] = bytes_array
return bytearray_
else:
raise ValueError("time value out of range, please check the value interval")
[docs]
def set_usint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
"""Set unsigned small int
Notes:
Datatype `usint` (Unsigned small int) consists on 1 byte in the PLC.
Maximum posible value is 255.
Lower posible value is 0.
Args:
bytearray_: buffer to write.
byte_index: byte index from where to start writing.
_int: value to write.
Returns:
Buffer with the written value.
Examples:
>>> data = bytearray(1)
>>> set_usint(data, 0, 255)
bytearray(b'\\xff')
"""
_int = int(_int)
_bytes = struct.unpack("B", struct.pack(">B", _int))
bytearray_[byte_index] = _bytes[0]
return bytearray_
[docs]
def set_sint(bytearray_: bytearray, byte_index: int, _int: int) -> bytearray:
"""Set small int to the buffer.
Notes:
Datatype `sint` (Small int) consists in 1 byte in the PLC.
Maximum value posible is 127.
Lowest value posible is -128.
Args:
bytearray_: buffer to write to.
byte_index: byte index from where to start writing.
_int: value to write.
Returns:
Buffer with the written value.
Examples:
>>> data = bytearray(1)
>>> set_sint(data, 0, 127)
bytearray(b'\\x7f')
"""
_int = int(_int)
_bytes = struct.unpack("B", struct.pack(">b", _int))
bytearray_[byte_index] = _bytes[0]
return bytearray_
[docs]
def set_lreal(bytearray_: bytearray, byte_index: int, lreal: float) -> bytearray:
"""Set the long real
Notes:
Datatype `lreal` (long real) consists in 8 bytes in the PLC.
Negative Range: -1.7976931348623158e+308 to -2.2250738585072014e-308
Positive Range: +2.2250738585072014e-308 to +1.7976931348623158e+308
Zero: ±0
Args:
bytearray_: buffer to read from.
byte_index: byte index from where to start reading.
lreal: float value to set
Returns:
Value to write.
Examples:
write lreal value (here as example 12345.12345) to DB1.10 of a PLC
>>> data = set_lreal(data, 12345.12345)
>>> from snap7 import Client
>>> Client().db_write(db_number=1, start=10, data=data)
"""
lreal = float(lreal)
struct.pack_into(">d", bytearray_, byte_index, lreal)
return bytearray_
def set_lword(bytearray_: bytearray, byte_index: int, lword: bytearray) -> bytearray:
"""Set the long word
THIS VALUE IS NEITHER TESTED NOR VERIFIED BY A REAL PLC AT THE MOMENT
Notes:
Datatype `lword` (long word) consists in 8 bytes in the PLC.
Maximum value posible is bytearray(b"\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF\\xFF")
Lowest value posible is bytearray(b"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00")
Args:
bytearray_: buffer to read from.
byte_index: byte index from where to start reading.
lword: Value to write
Returns:
Bytearray conform value.
Examples:
read lword value (here as example 0xAB\0xCD) from DB1.10 of a PLC
>>> data = set_lword(data, 0, bytearray(b"\\x00\\x00\\x00\\x00\\x00\\x00\\xAB\\xCD"))
bytearray(b"\\x00\\x00\\x00\\x00\\x00\\x00\\xAB\\xCD")
>>> from snap7 import Client
>>> Client().db_write(db_number=1, start=10, data=data)
"""
# data = bytearray_[byte_index:byte_index + 4]
# dword = struct.unpack('8B', struct.pack('>Q', *data))[0]
# return bytearray(dword)
raise NotImplementedError
def set_char(bytearray_: bytearray, byte_index: int, chr_: str) -> Union[ValueError, bytearray]:
"""Set char value in a bytearray.
Notes:
Datatype `char` in the PLC is represented in 1 byte. It has to be in ASCII-format
Args:
bytearray_: buffer to read from.
byte_index: byte index to start reading from.
chr_: Char to be set
Returns:
Value read.
Examples:
Read 1 Byte raw from DB1.10, where a char value is stored. Return Python compatible value.
>>> data = set_char(data, 0, 'C')
>>> from snap7 import Client
>>> Client().db_write(db_number=1, start=10, data=data)
'bytearray('0x43')
"""
if chr_.isascii():
bytearray_[byte_index] = ord(chr_)
return bytearray_
raise ValueError(f"chr_ : {chr_} contains a None-Ascii value, but ASCII-only is allowed.")
[docs]
def set_date(bytearray_: bytearray, byte_index: int, date_: date) -> bytearray:
"""Set value in bytearray to date
Notes:
Datatype `date` consists in the number of days elapsed from 1990-01-01.
It is stored as an int (2 bytes) in the PLC.
Args:
bytearray_: buffer to write.
byte_index: byte index from where to start writing.
date_: date object
Examples:
>>> data = bytearray(2)
>>> set_date(data, 0, date(2024, 3, 27))
>>> data
bytearray(b'\x30\xd8')
"""
if date_ < date(1990, 1, 1):
raise ValueError("date is lower than specification allows.")
elif date_ > date(2168, 12, 31):
raise ValueError("date is higher than specification allows.")
_days = (date_ - date(1990, 1, 1)).days
_bytes = struct.unpack("2B", struct.pack(">h", _days))
bytearray_[byte_index : byte_index + 2] = _bytes
return bytearray_