import re
import struct
from datetime import date, datetime, timedelta
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)
bytearray_[byte_index : byte_index + 1] = struct.pack("B", _int)
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)
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", _int)
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)
bytearray_[byte_index : byte_index + 2] = struct.pack(">h", _int)
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)
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", _int)
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')
"""
bytearray_[byte_index : byte_index + 4] = struct.pack(">f", float(real))
return bytearray_
[docs]
def set_fstring(bytearray_: bytearray, byte_index: int, value: str, max_length: int) -> bytearray:
"""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}")
# fill array with chr integers
for i, c in enumerate(value):
bytearray_[byte_index + i] = ord(c)
# fill the rest with empty space
for r in range(len(value), max_length):
bytearray_[byte_index + r] = ord(" ")
return bytearray_
[docs]
def set_string(bytearray_: bytearray, byte_index: int, value: str, max_size: int = 254) -> bytearray:
"""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 ascii characters > 255.
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 any(ord(char) < 0 or ord(char) > 255 for char in value):
raise ValueError(
"Value contains ascii values > 255, which is not compatible with PLC Type STRING. "
"Check encoding of value or try set_wstring()."
)
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)
# fill array with 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(len(value), bytearray_[byte_index]):
bytearray_[byte_index + 2 + r] = ord(" ")
return bytearray_
[docs]
def set_dword(bytearray_: bytearray, byte_index: int, dword: int) -> bytearray:
"""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)
bytearray_[byte_index : byte_index + 4] = struct.pack(">I", dword)
return bytearray_
[docs]
def set_dint(bytearray_: bytearray, byte_index: int, dint: int) -> bytearray:
"""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)
bytearray_[byte_index : byte_index + 4] = struct.pack(">i", dint)
return bytearray_
[docs]
def set_udint(bytearray_: bytearray, byte_index: int, udint: int) -> bytearray:
"""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)
bytearray_[byte_index : byte_index + 4] = struct.pack(">I", udint)
return bytearray_
[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)
bytearray_[byte_index] = struct.pack(">B", _int)[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)
bytearray_[byte_index] = struct.pack(">b", _int)[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_
[docs]
def set_lword(bytearray_: bytearray, byte_index: int, lword: int) -> bytearray:
"""Set the long word
Notes:
Datatype `lword` (long word) consists in 8 bytes in the PLC.
Maximum value is 18446744073709551615 (0xFFFFFFFFFFFFFFFF).
Minimum value is 0.
Args:
bytearray_: buffer to write to.
byte_index: byte index from where to start writing.
lword: unsigned 64-bit value to write.
Returns:
Buffer with the written value.
Examples:
>>> data = bytearray(8)
>>> set_lword(data, 0, 0xABCD)
>>> data
bytearray(b'\\x00\\x00\\x00\\x00\\x00\\x00\\xab\\xcd')
"""
lword = int(lword)
bytearray_[byte_index : byte_index + 8] = struct.pack(">Q", lword)
return bytearray_
[docs]
def set_char(bytearray_: bytearray, byte_index: int, chr_: str) -> 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 write to.
byte_index: byte index from where to start writing.
chr_: `char` to write.
Returns:
Buffer with the written value.
Examples:
write `char` (here as example 'C') to DB1.10 of a PLC
>>> data = bytearray(1)
>>> set_char(data, 0, 'C')
>>> data
bytearray('0x43')
"""
if not isinstance(chr_, str):
raise TypeError(f"Value value:{chr_} is not from Type string")
if len(chr_) > 1:
raise ValueError(f"size chr_ : {chr_} > 1")
elif len(chr_) < 1:
raise ValueError(f"size chr_ : {chr_} < 1")
if 0 <= ord(chr_) <= 255:
bytearray_[byte_index] = ord(chr_)
return bytearray_
else:
raise ValueError(f"chr_ : {chr_} contains ascii value > 255, which is not compatible with PLC Type CHAR.")
[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
bytearray_[byte_index : byte_index + 2] = struct.pack(">h", _days)
return bytearray_
[docs]
def set_wchar(bytearray_: bytearray, byte_index: int, chr_: str) -> bytearray:
"""Set wchar value in a bytearray.
Notes:
Datatype `wchar` in the PLC is represented in 2 bytes as UTF-16-BE.
Args:
bytearray_: buffer to write to.
byte_index: byte index from where to start writing.
chr_: single character to write.
Returns:
Buffer with the written value.
Examples:
>>> data = bytearray(2)
>>> set_wchar(data, 0, 'C')
>>> data
bytearray(b'\\x00C')
"""
if not isinstance(chr_, str):
raise TypeError(f"Value value:{chr_} is not from Type string")
if len(chr_) != 1:
raise ValueError(f"Expected single character, got length {len(chr_)}")
encoded = chr_.encode("utf-16-be")
bytearray_[byte_index : byte_index + 2] = encoded
return bytearray_
[docs]
def set_wstring(bytearray_: bytearray, byte_index: int, value: str, max_size: int = 16382) -> None:
"""Set wstring value
Notes:
Byte 0-1: max size (number of characters, 2-byte big-endian).
Byte 2-3: current length (number of characters, 2-byte big-endian).
Byte 4+: UTF-16-BE encoded characters (2 bytes each).
Args:
bytearray_: buffer to write to.
byte_index: byte index to start writing from.
value: string to write.
max_size: maximum number of characters allowed (default 16382).
Raises:
TypeError: if the value is not a string.
ValueError: if the string is too long or max_size exceeds 16382.
Examples:
>>> data = bytearray(26)
>>> set_wstring(data, 0, "hello", 10)
"""
if not isinstance(value, str):
raise TypeError(f"Value value:{value} is not from Type string")
if max_size > 16382:
raise ValueError(f"max_size: {max_size} > max. allowed 16382 chars")
size = len(value)
if size > max_size:
raise ValueError(f"size {size} > max_size {max_size}")
# set max string size (2 bytes, big-endian)
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", max_size)
# set current length (2 bytes, big-endian)
bytearray_[byte_index + 2 : byte_index + 4] = struct.pack(">H", size)
# encode and write UTF-16-BE characters
encoded = value.encode("utf-16-be")
bytearray_[byte_index + 4 : byte_index + 4 + len(encoded)] = encoded
[docs]
def set_tod(bytearray_: bytearray, byte_index: int, tod: timedelta) -> bytearray:
"""Set TIME_OF_DAY value in bytearray.
Notes:
Datatype `TIME_OF_DAY` is stored as milliseconds since midnight in 4 bytes.
Range: 0 to 86399999 ms (00:00:00.000 to 23:59:59.999).
Args:
bytearray_: buffer to write to.
byte_index: byte index from where to start writing.
tod: timedelta representing the time of day.
Returns:
Buffer with the written value.
Examples:
>>> from datetime import timedelta
>>> data = bytearray(4)
>>> set_tod(data, 0, timedelta(hours=12, minutes=30, seconds=15, milliseconds=500))
"""
if tod.days >= 1 or tod < timedelta(0):
raise ValueError("TIME_OF_DAY must be between 00:00:00.000 and 23:59:59.999")
ms = int(tod.total_seconds() * 1000)
bytearray_[byte_index : byte_index + 4] = ms.to_bytes(4, byteorder="big")
return bytearray_
[docs]
def set_dtl(bytearray_: bytearray, byte_index: int, dt_: datetime) -> bytearray:
"""Set DTL (Date and Time Long) value in bytearray.
Notes:
Datatype `DTL` consists of 12 bytes in the PLC:
- Bytes 0-1: Year (uint16, big-endian)
- Byte 2: Month (1-12)
- Byte 3: Day (1-31)
- Byte 4: Weekday (1=Sunday, 7=Saturday)
- Byte 5: Hour (0-23)
- Byte 6: Minute (0-59)
- Byte 7: Second (0-59)
- Bytes 8-11: Nanoseconds (uint32, big-endian)
Args:
bytearray_: buffer to write to.
byte_index: byte index from where to start writing.
dt_: datetime object to write.
Returns:
Buffer with the written value.
Examples:
>>> from datetime import datetime
>>> data = bytearray(12)
>>> set_dtl(data, 0, datetime(2024, 3, 27, 14, 30, 0))
"""
if dt_ > datetime(2554, 12, 31, 23, 59, 59):
raise ValueError("date_val is higher than specification allows.")
# Year as 2-byte big-endian
bytearray_[byte_index : byte_index + 2] = struct.pack(">H", dt_.year)
bytearray_[byte_index + 2] = dt_.month
bytearray_[byte_index + 3] = dt_.day
# Weekday: isoweekday() returns 1=Monday..7=Sunday, S7 uses 1=Sunday..7=Saturday
bytearray_[byte_index + 4] = (dt_.isoweekday() % 7) + 1
bytearray_[byte_index + 5] = dt_.hour
bytearray_[byte_index + 6] = dt_.minute
bytearray_[byte_index + 7] = dt_.second
# Nanoseconds from microseconds
nanoseconds = dt_.microsecond * 1000
bytearray_[byte_index + 8 : byte_index + 12] = struct.pack(">I", nanoseconds)
return bytearray_
[docs]
def set_dt(bytearray_: bytearray, byte_index: int, dt_: datetime) -> bytearray:
"""Set DATE_AND_TIME value in bytearray.
Notes:
Datatype `DATE_AND_TIME` consists of 8 bytes in BCD encoding:
- Byte 0: Year (BCD, 0-99, 90-99 = 1990-1999, 0-89 = 2000-2089)
- Byte 1: Month (BCD)
- Byte 2: Day (BCD)
- Byte 3: Hour (BCD)
- Byte 4: Minute (BCD)
- Byte 5: Second (BCD)
- Byte 6-7: Milliseconds (BCD) + weekday
Args:
bytearray_: buffer to write to.
byte_index: byte index from where to start writing.
dt_: datetime object to write.
Returns:
Buffer with the written value.
Examples:
>>> from datetime import datetime
>>> data = bytearray(8)
>>> set_dt(data, 0, datetime(2020, 7, 12, 17, 32, 2, 854000))
"""
def byte_to_bcd(val: int) -> int:
return ((val // 10) << 4) | (val % 10)
year = dt_.year
if year < 1990 or year > 2089:
raise ValueError("DATE_AND_TIME year must be between 1990 and 2089")
year_bcd = year - 2000 if year >= 2000 else year - 1900
bytearray_[byte_index] = byte_to_bcd(year_bcd)
bytearray_[byte_index + 1] = byte_to_bcd(dt_.month)
bytearray_[byte_index + 2] = byte_to_bcd(dt_.day)
bytearray_[byte_index + 3] = byte_to_bcd(dt_.hour)
bytearray_[byte_index + 4] = byte_to_bcd(dt_.minute)
bytearray_[byte_index + 5] = byte_to_bcd(dt_.second)
# Milliseconds: 3 BCD digits in byte 6 and upper nibble of byte 7
ms = dt_.microsecond // 1000
ms_hundreds = ms // 100
ms_tens = (ms % 100) // 10
ms_ones = ms % 10
bytearray_[byte_index + 6] = byte_to_bcd(ms_hundreds * 10 + ms_tens)
# Lower nibble of byte 7: weekday (1=Sunday..7=Saturday)
weekday = (dt_.isoweekday() % 7) + 1
bytearray_[byte_index + 7] = (ms_ones << 4) | weekday
return bytearray_