@@ -25,20 +25,30 @@ class Parameter:
2525 2. ARINC 717 (word-based indexing):
2626 - subframe, word, bit_offset: locate parameter in frame structure
2727 - decode_from_frame(frame_bytes, ...) -> (decoded_value, valid)
28+
29+ Note on start_bit:
30+ start_bit is intentionally Optional. 717-style parameters should leave
31+ it as None and rely on subframe/word/bit_offset; decode_from_frame()
32+ only falls back to computing the position from those fields when
33+ start_bit is None. Setting start_bit=0 on a 717 parameter (instead of
34+ leaving it None) silently forces decoding from bit 0 of the frame
35+ regardless of subframe/word/bit_offset -- do not do that.
2836 """
2937
3038 name : str
3139 """Parameter identifier/name."""
3240
33- start_bit : int
34- """Absolute bit position (0-based, MSB-first) for ARINC 767."""
35-
3641 bit_length : int
3742 """Number of bits occupied by parameter (1-32)."""
3843
3944 data_type : str
4045 """Data type: 'BNR', 'BCD', 'DISCRETE', 'CHAR', 'ASCII', 'UTC', 'PACKED', 'COB'."""
4146
47+ start_bit : int | None = None
48+ """Absolute bit position (0-based, MSB-first). Required for ARINC 767
49+ (decode_raw_from_bytes). For ARINC 717, leave as None so
50+ decode_from_frame() computes the position from subframe/word/bit_offset."""
51+
4252 scale : float | None = None
4353 """Scale factor for BNR decoding (multiplier)."""
4454
@@ -73,10 +83,85 @@ class Parameter:
7383
7484 cob_formula : str | None = None
7585 """Optional formula for COB (Computed On Board) parameters.
76-
86+
7787 Evaluated with restricted namespace: {"raw": <bit_value>, "scale": <scale>, "offset": <offset>}
7888 Example: "raw * 0.00390625" for Mach computation.
7989 """
90+ # ----------------------------------------------------------------------
91+ # Factory constructors
92+ # ----------------------------------------------------------------------
93+
94+ @classmethod
95+ def from_717 (
96+ cls ,
97+ name : str ,
98+ bit_length : int ,
99+ data_type : str ,
100+ * ,
101+ subframe : int ,
102+ word : int ,
103+ bit_offset : int ,
104+ rate : float = 1.0 ,
105+ scale : float | None = None ,
106+ offset : float | None = None ,
107+ signed : bool = False ,
108+ superframe : int | None = None ,
109+ superframe_group_count : int = 4 ,
110+ ) -> "Parameter" :
111+ """
112+ Construct an ARINC 717 parameter.
113+
114+ start_bit is intentionally left as None so decode_from_frame()
115+ computes the absolute bit position from subframe/word/bit_offset.
116+ """
117+ return cls (
118+ name = name ,
119+ bit_length = bit_length ,
120+ data_type = data_type ,
121+ start_bit = None , # critical: 717 parameters must NOT set start_bit
122+ scale = scale ,
123+ offset = offset ,
124+ signed = signed ,
125+ rate = rate ,
126+ subframe = subframe ,
127+ word = word ,
128+ bit_offset = bit_offset ,
129+ superframe = superframe ,
130+ superframe_group_count = superframe_group_count ,
131+ )
132+
133+ @classmethod
134+ def from_767 (
135+ cls ,
136+ name : str ,
137+ bit_length : int ,
138+ data_type : str ,
139+ * ,
140+ start_bit : int ,
141+ frame_id_767 : int | None = None ,
142+ scale : float | None = None ,
143+ offset : float | None = None ,
144+ signed : bool = False ,
145+ rate : float = 1.0 ,
146+ cob_formula : str | None = None ,
147+ ) -> "Parameter" :
148+ """
149+ Construct an ARINC 767 parameter.
150+
151+ Requires an absolute start_bit (0-based MSB-first).
152+ """
153+ return cls (
154+ name = name ,
155+ bit_length = bit_length ,
156+ data_type = data_type ,
157+ start_bit = start_bit ,
158+ frame_id_767 = frame_id_767 ,
159+ scale = scale ,
160+ offset = offset ,
161+ signed = signed ,
162+ rate = rate ,
163+ cob_formula = cob_formula ,
164+ )
80165
81166 def decode_raw_from_bytes (self , data : bytes ) -> Any :
82167 """Decode from frame data using absolute start_bit (ARINC 767 style).
@@ -89,7 +174,18 @@ def decode_raw_from_bytes(self, data: bytes) -> Any:
89174
90175 Returns:
91176 Decoded value (float, int, str, bool, etc. depending on data_type)
177+
178+ Raises:
179+ ValueError: if start_bit is not set, or extraction is out of bounds.
92180 """
181+ if self .start_bit is None :
182+ raise ValueError (
183+ f"Parameter { self .name } : start_bit is not set; "
184+ "decode_raw_from_bytes() requires an absolute start_bit "
185+ "(ARINC 767 style). Use decode_from_frame() for "
186+ "subframe/word/bit_offset-based (ARINC 717) parameters."
187+ )
188+
93189 total_bits = len (data ) * 8
94190 if self .start_bit < 0 or (self .start_bit + self .bit_length ) > total_bits :
95191 raise ValueError (
@@ -290,12 +386,13 @@ def _decode_char(self, raw_bits: int) -> str:
290386 def _decode_utc (self , raw_bits : int ) -> str :
291387 """Decode UTC time format.
292388
293- UTC is typically encoded as BCD HH:MM:SS (6 decimal digits).
389+ UTC is typically encoded as BCD HH:MM:SS (6 decimal digits) packed
390+ directly into nibbles, e.g. 23:59:59 -> raw_bits == 0x235959.
294391
295392 Returns:
296393 str in format "HH:MM:SS"
297394 """
298- s = str ( self ._bcd_from_int (raw_bits ) ).rjust (6 , "0" )
395+ s = self ._decode_bcd (raw_bits ).rjust (6 , "0" )
299396 hh = int (s [0 :2 ])
300397 mm = int (s [2 :4 ])
301398 ss = int (s [4 :6 ])
@@ -322,6 +419,10 @@ def _decode_cob(self, raw_bits: int) -> Any:
322419
323420 Safety:
324421 Uses restricted eval() with no __builtins__ access.
422+ NOTE: this is not a true sandbox -- attribute-chain sandbox
423+ escapes are possible in plain Python eval(). Do not load
424+ cob_formula values from untrusted sources without further
425+ hardening (e.g. an AST allowlist).
325426 """
326427 if self .cob_formula :
327428 ctx = {
0 commit comments