"""
fields
======
A collection of built-in :code:`Field`'s for creating packets. This module also contains the
building blocks for creating custom FieldTypes as well.
"""
import ctypes
from calpack.models.utils import typed_property
__all__ = [
'Field',
'IntField', 'IntField8', 'IntField16', 'IntField32', 'IntField64',
'ArrayField',
'PacketField',
'FlagField',
'FloatField', 'DoubleField', 'LongDoubleField',
'BoolField',
]
BYTE_SIZE = 8
[docs]class Field(object):
"""
A Super class that all other fields inherit from. This class is NOT intended for direct use.
Custom Fields MUST inherit from this class.
When creating a custom field you MUST define the :code:`c_type` property with a valid
:code:`ctypes` data class.
:param default_val: the default value of the field. This is set at instantiation of the Field
"""
c_type = None
field_name = None
creation_counter = 0
def __init__(self, default_val=None):
super(Field, self).__init__()
self.default_val = default_val
self.creation_counter = Field.creation_counter
Field.creation_counter += 1
def __get__(self, instance, cls):
from calpack.models import Packet
# If being called from a parent "Packet" class, then we actually want to get the internal
# field value
if isinstance(instance, Packet):
return self.c_to_py(instance.get_c_field(self.field_name))
return self
def __set__(self, instance, val):
from calpack.models import Packet
if isinstance(instance, Packet):
c_val = self.py_to_c(val)
instance.set_c_field(self.field_name, c_val)
[docs] def py_to_c(self, val):
"""
py_to_c - A function used to convert a python object into a valid ctypes assignable object.
As a default this function simply returns :code:`val`. It's up to the other subclassesed
:code:`Field` to define this if further formatting is required in order to set the internal
structure of the packet.
:param val: the value the user is attempting to set the packet field to. This can be any
python object.
"""
return val
[docs] def c_to_py(self, c_field):
"""
c_to_py - A function used to convert the ctypes object into a python object. As a default
this function simply returns :code:`c_field` directly from the ctypes.Structure object.
It's up to the other :code:`Field`'s to define this if further formatting is required in
order to turn the ctypes value into something user friendly.
:param c_field: a ctypes object from the packet's internal :code:`ctypes.Structure` object
"""
return c_field
[docs] def create_field_c_tuple(self):
"""
create_field_c_tuple - A function used to create the required an field in the
:code:`ctypes.Structure._fields_` tuple. This must return a tuple that is acceptable for
one of the items in the :code:`_fields_` list of the :code:`ctypes.Structure`.
The first value in the tuple MUST be :code:`self.field_name` as this is used to access the
internal c structure.
"""
return (self.field_name, self.c_type)
[docs]class IntField(Field):
"""
An Integer field. This field can be configured to be signed or unsigned. It's bit length can
also be set, however the max bit length for this field is
:code:`ctypes.sizeof(ctypes.c_int) * 8`. This wraps around the :code:`ctypes.c_int` or
:code:`ctypes.c_uint` data type.
.. warning:: A word of caution when using the :code:`bit_len`. If the combination of IntFields
with the bit_len set are not byte aligned, there is the possibility of "spare" bits not
accessible but used in the overall strucuture. See :ref:`Unused Bits`
for more information
:param int bit_len: the length in bits of the integer.
:param bool signed: whether to treat the int as an signed integer or unsigned integer (default
unsigned)
:param int default_val: the default value of the field (default 0)
:raises ValueError: if the :code:`bit_len` is less than or equal to 0 or greater than
:code:`ctypes.sizeof(ctypes.c_int) * 8`
"""
signed = typed_property('signed', bool, False)
_c_types = (ctypes.c_uint, ctypes.c_int)
def __init__(self, bit_len=None, signed=False, default_val=0):
super(IntField, self).__init__(default_val)
self.signed = signed
self.c_type = self._c_types[int(self.signed)]
self.bit_len = bit_len
if bit_len is None:
self.bit_len = ctypes.sizeof(self.c_type) * BYTE_SIZE
if self.bit_len <= 0 or self.bit_len > ctypes.sizeof(self.c_type) * BYTE_SIZE:
raise ValueError("bit_len must be between 1 and {max_val}".format(
max_val=ctypes.sizeof(self.c_type) * BYTE_SIZE)
)
[docs] def py_to_c(self, val):
if not self.signed and val < 0:
raise TypeError("Signed valued cannot be set for an unsigned IntField!")
return val
[docs] def create_field_c_tuple(self):
if self.bit_len < ctypes.sizeof(self.c_type) * BYTE_SIZE:
return (self.field_name, self.c_type, self.bit_len)
return (self.field_name, self.c_type)
[docs]class IntField8(IntField):
"""
An Integer field. This field can be configured to be signed or unsigned. It's bit length can
also be set, however the max bit length for this field is 8. This wraps around the
:code:`ctypes.c_int8` or :code:`ctypes.c_uint8` data type.
.. warning:: A word of caution when using the :code:`bit_len`. If the combination of IntFields
with the bit_len set are not byte aligned, there is the possibility of "spare" bits not
accessible but used in the overall strucuture. See :ref:`Unused Bits`
for more information
:param int bit_len: the length in bits of the integer.
:param bool signed: whether to treat the int as an signed integer or unsigned integer (default
unsigned)
:param int default_val: the default value of the field (default 0)
:raises ValueError: if the :code:`bit_len` is less than or equal to 0 or greater than 8
"""
_c_types = (ctypes.c_uint8, ctypes.c_int8)
[docs]class IntField16(IntField):
"""
An Integer field. This field can be configured to be signed or unsigned. It's bit length can
also be set, however the max bit length for this field is 16. This wraps around the
:code:`ctypes.c_int16` or :code:`ctypes.c_uint16` data type.
.. warning:: A word of caution when using the :code:`bit_len`. If the combination of IntFields
with the bit_len set are not byte aligned, there is the possibility of "spare" bits not
accessible but used in the overall strucuture. See :ref:`Unused Bits`
for more information
:param int bit_len: the length in bits of the integer.
:param bool signed: whether to treat the int as an signed integer or unsigned integer (default
unsigned)
:param int default_val: the default value of the field (default 0)
:raises ValueError: if the :code:`bit_len` is less than or equal to 0 or greater than 16
"""
_c_types = (ctypes.c_uint16, ctypes.c_int16)
[docs]class IntField32(IntField):
"""
An Integer field. This field can be configured to be signed or unsigned. It's bit length can
also be set, however the max bit length for this field is 32. This wraps around the
:code:`ctypes.c_int32` or :code:`ctypes.c_uint32` data type.
.. warning:: A word of caution when using the :code:`bit_len`. If the combination of IntFields
with the bit_len set are not byte aligned, there is the possibility of "spare" bits not
accessible but used in the overall strucuture. See :ref:`Unused Bits`
for more information
:param int bit_len: the length in bits of the integer.
:param bool signed: whether to treat the int as an signed integer or unsigned integer (default
unsigned)
:param int default_val: the default value of the field (default 0)
:raises ValueError: if the :code:`bit_len` is less than or equal to 0 or greater than 32
"""
_c_types = (ctypes.c_uint32, ctypes.c_int32)
[docs]class IntField64(IntField):
"""
An Integer field. This field can be configured to be signed or unsigned. It's bit length can
also be set, however the max bit length for this field is 64. This wraps around the
:code:`ctypes.c_int64` or :code:`ctypes.c_uint64` data type.
.. warning:: A word of caution when using the :code:`bit_len`. If the combination of IntFields
with the bit_len set are not byte aligned, there is the possibility of "spare" bits not
accessible but used in the overall strucuture. See :ref:`Unused Bits`
for more information
:param int bit_len: the length in bits of the integer.
:param bool signed: whether to treat the int as an signed integer or unsigned integer (default
unsigned)
:param int default_val: the default value of the field (default 0)
:raises ValueError: if the :code:`bit_len` is less than or equal to 0 or greater than 64
"""
_c_types = (ctypes.c_uint64, ctypes.c_int64)
[docs]class PacketField(Field):
"""
A custom Field for handling another packet as a field.
:param packet_cls: A :code:`calpack.models.Packet` subclass that represents another packet
"""
packet_cls = None
def __init__(self, packet_cls):
super(PacketField, self).__init__()
self.packet_cls = packet_cls
self.packet = packet_cls()
self.c_type = self.packet._Packet__c_struct
[docs] def create_field_c_tuple(self):
return (self.field_name, self.packet_cls._Packet__c_struct)
def __setattr__(self, arg, value):
if self.packet_cls is not None and arg in self.packet_cls.fields_order:
setattr(self.packet_cls, arg, value)
else:
super(PacketField, self).__setattr__(arg, value)
[docs] def py_to_c(self, val):
if not isinstance(val, self.packet_cls):
raise TypeError("Must be of type {p}".format(p=type(self.packet_cls)))
return val.c_pkt
[docs]class ArrayField(Field):
"""
A custom field for handling an array of fields. Only tuples or other ArrayFields can be written to the
:param array_cls: a :code:`calpack.models.Field` subclass **object** that represent the Field
the array will be filled with.
:param int array_size: the length of the array.
"""
def __init__(self, array_cls, array_size, default_val=None):
super(ArrayField, self).__init__(default_val)
self.array_cls = array_cls
self.array_size = array_size
array_cls_tuple = self.array_cls.create_field_c_tuple()
if len(array_cls_tuple) == 3:
raise TypeError(
"ArrayField does not support Fields with non-byte aligned field tuples!"
)
self.c_type = (array_cls_tuple[1] * self.array_size)
[docs] def c_to_py(self, c_field):
return tuple(c_field[:])
[docs] def py_to_c(self, val):
if not isinstance(val, ArrayField) and not isinstance(val, tuple) and not isinstance(val, list):
raise TypeError("Must be of type ArrayField or list")
if len(val) != self.array_size:
raise ValueError("The length of val must be {}!".format(self.array_size))
return self.c_type(*val)
def __len__(self):
return self.array_size
[docs]class FlagField(Field):
"""
A custom field for handling single bit 'flags'.
:param bool default_val: the default value of the field (default False)
"""
c_type = ctypes.c_uint8
def __init__(self, default_val=False):
super(FlagField, self).__init__(default_val)
[docs] def c_to_py(self, c_field):
return bool(c_field)
[docs] def py_to_c(self, val):
if not isinstance(val, FlagField) and not isinstance(val, bool):
raise TypeError("Must be of type `FlagField` or `bool`")
return int(val)
[docs] def create_field_c_tuple(self):
return (self.field_name, self.c_type, 1)
[docs]class FloatField(Field):
"""
A custom field for handling floating point numbers.
"""
c_type = ctypes.c_float
def __init__(self, default_val=0.0):
super(FloatField, self).__init__(default_val)
[docs]class DoubleField(FloatField):
"""
A custom field for handling double floating point numbers
"""
c_type = ctypes.c_double
[docs]class LongDoubleField(FloatField):
"""
A custom field for handling long double floating point numbers
"""
c_type = ctypes.c_longdouble
[docs]class BoolField(Field):
"""
A custom field for handling Boolean types
"""
c_type = ctypes.c_bool
def __init__(self, default_val=False):
super(BoolField, self).__init__(default_val)
self.bit_len = ctypes.sizeof(self.c_type)
[docs] def py_to_c(self, val):
if not isinstance(val, bool) and not isinstance(val, BoolField):
raise TypeError("Must be of type `bool` or `BoolField`!")
return super(BoolField, self).py_to_c(val)