Source code for secsgem.secs.variables.base

#####################################################################
# base.py
#
# (c) Copyright 2021, Benjamin Parzella. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#####################################################################
"""SECS variable base type."""
import typing


[docs]class Base: """ Base class for SECS variables. Due to the python types, wrapper classes for variables are required. If constructor is called with Base or subclass only the value is copied. """ format_code = -1 preferred_types: typing.Optional[typing.List[typing.Type]] def __init__(self, value=None): """Initialize a secs variable.""" self.value = value
[docs] def set(self, value): """ Set the internal value to the provided value. :param value: new value :type value: various """ raise NotImplementedError("Function set not implemented on " + self.__class__.__name__)
[docs] def encode_item_header(self, length): """ Encode item header depending on the number of length bytes required. :param length: number of bytes in data :type length: integer :returns: encoded item header bytes :rtype: string """ if length < 0: raise ValueError(f"Encoding {self.__class__.__name__} not possible, data length too small {length}") if length > 0xFFFFFF: raise ValueError(f"Encoding {self.__class__.__name__} not possible, data length too big {length}") if length > 0xFFFF: length_bytes = 3 format_byte = (self.format_code << 2) | length_bytes return bytes(bytearray((format_byte, (length & 0xFF0000) >> 16, (length & 0x00FF00) >> 8, (length & 0x0000FF)))) if length > 0xFF: length_bytes = 2 format_byte = (self.format_code << 2) | length_bytes return bytes(bytearray((format_byte, (length & 0x00FF00) >> 8, (length & 0x0000FF)))) length_bytes = 1 format_byte = (self.format_code << 2) | length_bytes return bytes(bytearray((format_byte, (length & 0x0000FF))))
[docs] def decode_item_header(self, data, text_pos=0): """ Encode item header depending on the number of length bytes required. :param data: encoded data :type data: string :param text_pos: start of item header in data :type text_pos: integer :returns: start position for next item, format code, length item of data :rtype: (integer, integer, integer) """ if len(data) == 0: raise ValueError(f"Decoding for {self.__class__.__name__} without any text") # parse format byte format_byte = bytearray(data)[text_pos] format_code = (format_byte & 0b11111100) >> 2 length_bytes = format_byte & 0b00000011 text_pos += 1 # read 1-3 length bytes length = 0 for _ in range(length_bytes): length <<= 8 length += bytearray(data)[text_pos] text_pos += 1 if 0 <= self.format_code != format_code: raise ValueError( f"Decoding data for {self.__class__.__name__} ({self.format_code}) has invalid format {format_code}") return text_pos, format_code, length
@property def is_dynamic(self) -> bool: """Check if this instance is Dynamic or derived.""" return False @property def preferred_type(self): """Get the preferred type for this variable.""" return self.preferred_types[0]