182 lines
5.9 KiB
Python
182 lines
5.9 KiB
Python
import ctypes
|
|
from enum import Enum
|
|
|
|
class Field:
|
|
"""
|
|
Base class for 1553 Data Word fields descriptors.
|
|
Handles the logic to extract specific bits from a 16-bit word
|
|
following MIL-STD-1553 convention (Bit 0 = MSB, Bit 15 = LSB).
|
|
"""
|
|
def __init__(self, word_index, start_bit, width=1):
|
|
"""
|
|
Args:
|
|
word_index (int): Index of the 16-bit word in the message (0-based).
|
|
start_bit (int): Starting bit index as per ICD (0=MSB, 15=LSB).
|
|
width (int): Number of bits occupied by the field.
|
|
"""
|
|
self.word_index = word_index
|
|
self.start_bit = start_bit
|
|
self.width = width
|
|
|
|
# Calculate shift required for Little Endian architecture
|
|
# to match 1553 Big Endian bit numbering (0=MSB).
|
|
# Example: Bit 0 (MSB) with width 1 -> Shift 15
|
|
self._shift = 16 - (start_bit + width)
|
|
self._mask = (1 << width) - 1
|
|
|
|
def _get_raw_value(self, instance):
|
|
"""Extracts the raw unsigned integer from the message data."""
|
|
# instance.data is expected to be a ctypes array or list of ints
|
|
if self.word_index >= len(instance.data):
|
|
return 0 # Fail safe for incomplete messages
|
|
|
|
word_val = instance.data[self.word_index]
|
|
return (word_val >> self._shift) & self._mask
|
|
|
|
def _set_raw_value(self, instance, value):
|
|
"""Writes the raw unsigned integer back into the message data."""
|
|
if self.word_index >= len(instance.data):
|
|
return # Fail safe
|
|
|
|
current_word = instance.data[self.word_index]
|
|
|
|
# Clear the bits
|
|
clear_mask = ~(self._mask << self._shift)
|
|
current_word &= clear_mask
|
|
|
|
# Set the new bits (masked to width)
|
|
val_to_write = (int(value) & self._mask) << self._shift
|
|
current_word |= val_to_write
|
|
|
|
instance.data[self.word_index] = current_word
|
|
|
|
|
|
class BitField(Field):
|
|
"""
|
|
Represents a generic numeric field (Unsigned).
|
|
Used for flags, counters, or raw values without scaling.
|
|
"""
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
return self._get_raw_value(instance)
|
|
|
|
def __set__(self, instance, value):
|
|
self._set_raw_value(instance, value)
|
|
|
|
|
|
class EnumField(Field):
|
|
"""
|
|
Represents a field mapped to a Python Enum.
|
|
Automatically converts between the raw integer and the Enum member.
|
|
"""
|
|
def __init__(self, word_index, start_bit, enum_cls, width=None):
|
|
"""
|
|
Args:
|
|
enum_cls (Enum): The Enum class to map to.
|
|
width (int, optional): Auto-calculated from enum length if not provided.
|
|
"""
|
|
# Calculate width based on max enum value if not provided,
|
|
# but usually ICD defines width explicitly.
|
|
if width is None:
|
|
# Minimal width to fit the max value
|
|
max_val = max([e.value for e in enum_cls])
|
|
width = max_val.bit_length()
|
|
|
|
super().__init__(word_index, start_bit, width)
|
|
self.enum_cls = enum_cls
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
raw = self._get_raw_value(instance)
|
|
try:
|
|
return self.enum_cls(raw)
|
|
except ValueError:
|
|
# Return raw value if not found in Enum (e.g. reserved values)
|
|
return raw
|
|
|
|
def __set__(self, instance, value):
|
|
if isinstance(value, self.enum_cls):
|
|
val_to_set = value.value
|
|
else:
|
|
val_to_set = int(value)
|
|
self._set_raw_value(instance, val_to_set)
|
|
|
|
|
|
class ScaledField(Field):
|
|
"""
|
|
Represents a numeric field with a scale factor (LSB).
|
|
Can be Signed (2's complement) or Unsigned.
|
|
|
|
Value = Raw * LSB
|
|
"""
|
|
def __init__(self, word_index, start_bit, width, lsb_value, signed=False):
|
|
super().__init__(word_index, start_bit, width)
|
|
self.lsb_value = lsb_value
|
|
self.signed = signed
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
|
|
raw = self._get_raw_value(instance)
|
|
|
|
# Handle 2's complement for signed fields
|
|
if self.signed:
|
|
# Check sign bit (highest bit of the field)
|
|
sign_mask = 1 << (self.width - 1)
|
|
if raw & sign_mask:
|
|
# Calculate negative value
|
|
raw = raw - (1 << self.width)
|
|
|
|
return float(raw * self.lsb_value)
|
|
|
|
def __set__(self, instance, value):
|
|
# Convert physical value to raw steps
|
|
raw = int(round(value / self.lsb_value))
|
|
|
|
if self.signed:
|
|
# Handle negative input
|
|
if raw < 0:
|
|
raw = (1 << self.width) + raw
|
|
|
|
self._set_raw_value(instance, raw)
|
|
|
|
|
|
class ASCIIField(Field):
|
|
"""
|
|
Represents 2 characters packed into a 16-bit word.
|
|
MSB (bits 0-7) = Character 1
|
|
LSB (bits 8-15) = Character 2
|
|
"""
|
|
def __init__(self, word_index):
|
|
# Takes the whole word
|
|
super().__init__(word_index, 0, 16)
|
|
|
|
def __get__(self, instance, owner):
|
|
if instance is None:
|
|
return self
|
|
|
|
raw = self._get_raw_value(instance)
|
|
# In 1553:
|
|
# High Byte (0xFF00) is first char
|
|
# Low Byte (0x00FF) is second char
|
|
char1 = (raw >> 8) & 0xFF
|
|
char2 = raw & 0xFF
|
|
|
|
# Return string, handling non-printable bytes safely
|
|
s = ""
|
|
s += chr(char1) if 32 <= char1 <= 126 else '?'
|
|
s += chr(char2) if 32 <= char2 <= 126 else '?'
|
|
return s
|
|
|
|
def __set__(self, instance, value):
|
|
if not isinstance(value, str) or len(value) != 2:
|
|
raise ValueError("ASCIIField requires a string of exactly 2 characters")
|
|
|
|
char1 = ord(value[0])
|
|
char2 = ord(value[1])
|
|
|
|
raw = (char1 << 8) | char2
|
|
self._set_raw_value(instance, raw) |