-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathencoding.py
More file actions
1626 lines (1328 loc) · 59.9 KB
/
Copy pathencoding.py
File metadata and controls
1626 lines (1328 loc) · 59.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from __future__ import annotations
import sys
from enum import Enum
from itertools import repeat
from typing import Optional, MutableSequence, Sequence, TypeVar, Tuple, Callable, List, Iterator
T = TypeVar("T")
U = TypeVar("U")
def _to_mutable(seq: Sequence[T], copy: bool = True) -> MutableSequence[T]:
"""
Convert a sequence to a mutable sequence type.
Args:
seq: Input sequence to convert (can be bytes, bytearray, or other sequence)
copy: If True, creates a copy of the input; if False, returns the original when possible
Returns:
MutableSequence: bytearray for bytes input, bytearray for bytearray input,
or list for other sequence types
"""
# 1. Handle bytes input by converting to bytearray
if isinstance(seq, bytes):
return bytearray(seq)
# 2. Handle bytearray input - return copy or original based on copy parameter
if isinstance(seq, bytearray):
return bytearray(seq) if copy else seq
# 3. Handle all other sequence types by converting to list
return list(seq)
def _set_or_extend(current: MutableSequence[T] | None,
to_add: Sequence[T] | None, copy: bool = True) -> MutableSequence[T] | None:
"""
Set or extend a mutable sequence with additional data.
Args:
current: Current mutable sequence or None (if None, will be initialized)
to_add: Sequence to add to current sequence or None
copy: If True, creates copies when initializing new sequences
Returns:
MutableSequence or None: Updated sequence or None if both inputs are None
"""
# 1. If current is None, initialize it with to_add data
if current is None:
current = _to_mutable(to_add, copy) if to_add is not None else None
elif to_add is not None:
# 2. If current exists and to_add is provided, extend current with to_add
current.extend(to_add)
# 3. Return the (possibly modified) current sequence
return current
#
# Processors
#
class Processor(object):
"""
Base processor class for handling sequential data processing with buffering capabilities.
Provides framework for processing data in chunks while maintaining state between calls.
"""
class Status(Enum):
"""
Enumeration of processor status values indicating processing state.
"""
CONTINUE = 0xAABBCCDD # Continue processing normally
OUT_OF_DATA = 0xBADCAFE # No more data available
RESET = 0xDEADBEEF # Reset processing state
def __init__(self, throw=True, verbose=False):
"""
Initialize the processor with configuration options.
Args:
throw: If True, raises exceptions on errors; if False, prints errors and continues
verbose: If True, enables verbose logging output
"""
# 1. Store configuration parameters
self._throw = throw
self._verbose = verbose
# 2. Initialize processing state variables
self._offset = 0
self._buffered_data = None
self.reset()
def info(self, info):
"""
Log informational message if verbose mode is enabled.
Args:
info: Informational message to log
"""
if self._verbose:
print(f"{self} - {info}")
def error(self, error):
"""
Handle error conditions based on throw configuration.
Args:
error: Error message to handle
"""
# 1. If throw is enabled, raise exception; otherwise print to stderr
if self._throw:
raise Exception(error)
else:
print(f"Process error: {error}", file=sys.stderr)
def process(self, value: Sequence[T] | None) -> Tuple[int, Optional[Sequence[U]], Processor.Status]:
"""
Process incoming data and return results with status information.
Args:
value: Input data sequence to process or None to signal end of data
Returns:
Tuple containing:
- int: Number of input items consumed
- Optional[Sequence[U]]: Processed output data
- Processor.Status: Current processing status
"""
# 1. Handle None input by returning OUT_OF_DATA status
if value is None:
return 0, None, Processor.Status.OUT_OF_DATA
# 2. Combine buffered data with new input data
in_data = _set_or_extend(self._buffered_data, value, True)
# 3. Process the combined data
consumed, out_data, s = self.data(in_data)
# 4. Update offset counter with consumed data
self._offset += consumed
# 5. Handle reset status - if not reset, update buffered data for next iteration
if s != Processor.Status.RESET:
self._buffered_data = in_data[consumed:]
consumed = len(value)
return consumed, out_data, s
def data(self, in_data: Sequence[T]) -> Tuple[int, Optional[Sequence[U]], Processor.Status]:
"""
Abstract method to be implemented by subclasses for actual data processing.
Args:
in_data: Input data sequence to process
Returns:
Tuple containing:
- int: Number of input items consumed
- Optional[Sequence[U]]: Processed output data
- Processor.Status: Processing status
"""
raise NotImplementedError()
def reset(self):
"""
Reset processor state and clear any buffered data.
"""
# 1. If there's buffered data, add its length to offset and clear buffer
if self._buffered_data is not None:
self._offset += len(self._buffered_data)
self._buffered_data = None
class Bitstream(object):
"""
Bitstream class that handles bit-level encoding and decoding operations.
Provides functionality for converting between byte sequences and bit streams,
supporting both big-endian and little-endian bit ordering.
"""
class Decoder(Processor):
"""
Decoder class for converting bit streams back to byte sequences.
Handles bit reordering based on endianness configuration.
"""
def __init__(self, be=True, *args, **kwargs):
"""
Initialize the bitstream decoder.
Args:
be: If True, uses big-endian bit ordering; if False, uses little-endian
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
self._be = be
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming bit stream data and convert to byte sequences.
Args:
in_data: Input bytes representing bit stream data
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Processed output bytes
- Processor.Status: Processing status
"""
# 1. Initialize output data and bit accumulator
out_data = None
b = 0
idx = 0
# 2. Process input data in 8-byte chunks
while idx < int(len(in_data) / 8) * 8:
# 3. Extract least significant bit from current byte
d = in_data[idx] & 0x1
# 4. Shift and insert bit based on endianness
if self._be:
# Big-endian: shift left and insert bit at LSB
b = b << 1 | d << 0
else:
# Little-endian: shift right and insert bit at MSB
b = b >> 1 | d << 7
idx += 1
# 5. When 8 bits are processed, add to output and reset accumulator
if idx % 8 == 0:
out_data = _set_or_extend(out_data, bytearray([b]))
b = 0
return idx, out_data, Processor.Status.CONTINUE
class Encoder(Processor):
"""
Encoder class for converting byte sequences to bit streams.
Handles bit reordering based on endianness configuration.
"""
def __init__(self, be=True, *args, **kwargs):
"""
Initialize the bitstream encoder.
Args:
be: If True, uses big-endian bit ordering; if False, uses little-endian
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
self._be = be
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming byte data and convert to bit stream.
Args:
in_data: Input bytes to encode as bit stream
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Processed output bit stream
- Processor.Status: Processing status
"""
# 1. Initialize output data
out_data = None
idx = 0
# 2. Process each byte in input data
while idx < len(in_data):
d = in_data[idx]
b = bytearray()
# 3. Extract each bit and append to output based on endianness
for c in range(8):
if self._be:
# Big-endian: extract bits from MSB to LSB
bit = (d >> (7 - c)) & 0x1
else:
# Little-endian: extract bits from LSB to MSB
bit = (d >> c) & 0x1
b.append(bit)
# 4. Extend output with processed bits
out_data = _set_or_extend(out_data, b)
idx += 1
return idx, out_data, Processor.Status.CONTINUE
class OOK(object):
"""
On-Off Keying (OOK) modulation processor for digital communication.
Handles encoding and decoding of OOK signals with pulse width detection.
"""
class Decoder(Processor):
"""
Decoder class for OOK signal decoding.
Converts pulse width modulated signals back to original bit sequences.
"""
UNDEFINED = 2
def __init__(self, sample_rate, symbol_rate, error=0.3, *args, **kwargs):
"""
Initialize the OOK decoder.
Args:
sample_rate: Sampling rate of the input signal
symbol_rate: Rate at which symbols are transmitted
error: Error tolerance for pulse width detection (as fraction)
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
# 1. Calculate threshold based on sample rate and symbol rate
self._threshold = sample_rate / symbol_rate
# 2. Calculate error threshold for pulse width validation
self._error_threshold = self._threshold * error
# 3. Initialize decoder state variables
self._bit = self.UNDEFINED
self._count = None
def reset(self):
"""
Reset decoder state to initial values.
"""
super().reset()
self._bit = self.UNDEFINED
self._count = None
def find_pulse_width(self, count):
"""
Determine pulse width based on threshold and error tolerance.
Args:
count: Measured pulse duration
Returns:
int: Detected pulse width (number of symbols) or None if invalid
"""
# 1. Check if pulse width matches expected thresholds (1x, 2x, 3x)
for i in range(1, 3):
if (self._threshold - self._error_threshold) * i < count < (
self._threshold + self._error_threshold) * i:
return i
return None
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming OOK signal data and decode pulse widths to bits.
Args:
in_data: Input bytes representing OOK signal
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Decoded output bytes
- Processor.Status: Processing status
"""
# 1. Initialize output data and processing state
out_data = None
idx = 0
s = Processor.Status.CONTINUE
# 2. Process input data while still in valid state
while idx < len(in_data) and s == Processor.Status.CONTINUE:
d = in_data[idx]
# 3. Handle undefined bit state (first bit)
if self._bit == self.UNDEFINED:
self._bit = d
self._count = 1
idx += 1
else:
# 4. Find next transition (bit change)
try:
end = in_data.index(1 - self._bit, idx)
# 5. Accumulate count of current pulse
self._count += (end - idx)
idx += (end - idx)
d = self._bit
self._bit = self.UNDEFINED
# 6. Determine pulse width
width = self.find_pulse_width(self._count)
if width is None:
# 7. Invalid pulse width - error condition
self.error(f"Invalid pulse \"{d}\" " +
f"at offset {self._offset + idx - self._count} " +
f"of size {self._count}")
s = Processor.Status.RESET
else:
# 8. Valid pulse width - add to output
self.info(
f"Pulse \"{d}\" at offset {self._offset + idx - self._count} of size {self._count}")
out_data = _set_or_extend(out_data, bytearray(repeat(d, width)))
except ValueError:
# 9. No more transitions found - end of data
s = Processor.Status.OUT_OF_DATA
return idx, out_data, s
class Encoder(Processor):
"""
Encoder class for OOK signal encoding.
Converts bit sequences to pulse width modulated signals.
"""
def __init__(self, sample_rate, symbol_rate, *args, **kwargs):
"""
Initialize the OOK encoder.
Args:
sample_rate: Sampling rate of the output signal
symbol_rate: Rate at which symbols are transmitted
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
# 1. Calculate threshold based on sample rate and symbol rate
self._threshold = sample_rate / symbol_rate
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming bit data and encode as OOK pulse width modulated signal.
Args:
in_data: Input bytes representing bit sequence to encode
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Encoded output signal
- Processor.Status: Processing status
"""
# 1. Initialize output data
out_data = None
idx = 0
# 2. Process each bit in input data
while idx < len(in_data):
d = in_data[idx]
# 3. Repeat each bit for the threshold duration
out_data = _set_or_extend(out_data, bytearray(repeat(d, self._threshold)))
idx += 1
return idx, out_data, Processor.Status.CONTINUE
class Manchester(object):
"""
Manchester encoding/decoding processor for digital communication.
Implements Manchester encoding where each bit is represented by a transition
at the center of the bit period.
Encoding:
- 0 bit: 01 transition (low to high)
- 1 bit: 10 transition (high to low)
_ __ _ _...
..._| |_| |_| |__|
0 0 1 1 0
"""
# Manchester encoding patterns
zero_pulse = bytes([0, 1]) # Low-to-high transition for 0 bit
one_pulse = bytes([1, 0]) # High-to-low transition for 1 bit
class Encoder(Processor):
"""
Encoder class for Manchester encoding.
Converts bit sequences to Manchester encoded bit streams.
"""
def __init__(self, initial: bytes, *args, **kwargs):
"""
Initialize the Manchester encoder.
Args:
initial: Initial bytes to prepend to output
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
self._initial = initial or bytes()
self._initialized = False
def reset(self):
"""
Reset encoder state to initial values.
"""
super().reset()
self._initialized = False
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming bit data and encode as Manchester bit stream.
Args:
in_data: Input bytes representing bit sequence to encode
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Encoded Manchester bit stream
- Processor.Status: Processing status
"""
# 1. Initialize output data
out_data = None
# 2. Handle initial bytes (only once)
if not self._initialized:
self._initialized = True
out_data = _to_mutable(self._initial, True)
# 3. Initialize processing state
s = Processor.Status.CONTINUE
idx = 0
# 4. Process each bit in input data
while idx < len(in_data):
d = in_data[idx]
if d == 0:
# 5. Encode 0 bit as 01 transition
out_data = _set_or_extend(out_data, Manchester.zero_pulse)
elif d == 1:
# 6. Encode 1 bit as 10 transition
out_data = _set_or_extend(out_data, Manchester.one_pulse)
else:
# 7. Invalid bit value - error condition
self.error(f"Invalid value: {d} at offset {self._offset + idx}")
s = Processor.Status.RESET
idx += 1
return idx, out_data, s
class Decoder(Processor):
"""
Decoder class for Manchester decoding.
Converts Manchester encoded bit streams back to original bit sequences.
"""
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming Manchester bit stream and decode to original bits.
Args:
in_data: Input bytes representing Manchester encoded bit stream
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Decoded original bit sequence
- Processor.Status: Processing status
"""
# 1. Initialize output data and processing state
out_data = None
s = Processor.Status.CONTINUE
idx = 0
# 2. Process input data in pairs (2 bytes per bit)
while idx + 2 <= len(in_data) and s == Processor.Status.CONTINUE:
part = in_data[idx:idx + 2]
if part == Manchester.zero_pulse:
# 3. Decode 01 transition as 0 bit
out_data = _set_or_extend(out_data, bytearray([0]))
elif part == Manchester.one_pulse:
# 4. Decode 10 transition as 1 bit
out_data = _set_or_extend(out_data, bytearray([1]))
else:
# 5. Invalid Manchester pattern - error condition
self.error(f"Invalid value \"{part}\" at offset {self._offset + idx}")
s = Processor.Status.RESET
idx += 2
return idx, out_data, s
class BiphaseMark(object):
"""
Biphase Mark encoding/decoding processor for digital communication.
Implements Biphase Mark encoding where each bit is represented by transitions
at both the beginning and center of the bit period.
Encoding:
- 0 bit: 00 or 11 transition (no transition at bit center)
- 1 bit: 01 or 10 transition (transition at bit center)
__ _ __ _
...| |__| |_| |_| |_...
0 0 1 0 1
"""
# Biphase Mark encoding patterns
zero_pulses = [bytes([0, 0]), bytes([1, 1])] # No transition at bit center
one_pulses = [bytes([0, 1]), bytes([1, 0])] # Transition at bit center
class Encoder(Processor):
"""
Encoder class for Biphase Mark encoding.
Converts bit sequences to Biphase Mark encoded bit streams.
"""
def __init__(self, *args, **kwargs):
"""
Initialize the Biphase Mark encoder.
Args:
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
self._flip = 1
def reset(self):
"""
Reset encoder state to initial values.
"""
super().reset()
self._flip = 1
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming bit data and encode as Biphase Mark bit stream.
Args:
in_data: Input bytes representing bit sequence to encode
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Encoded Biphase Mark bit stream
- Processor.Status: Processing status
"""
# 1. Initialize output data and processing state
out_data = None
idx = 0
s = Processor.Status.CONTINUE
# 2. Process each bit in input data
while idx < len(in_data) and s == Processor.Status.CONTINUE:
d = in_data[idx]
if d == 0:
# 3. Encode 0 bit using current flip state
out_data = _set_or_extend(out_data, BiphaseMark.zero_pulses[1 - self._flip])
self._flip = out_data[-1] # Update flip state based on last bit
elif d == 1:
# 4. Encode 1 bit using current flip state
out_data = _set_or_extend(out_data, BiphaseMark.one_pulses[1 - self._flip])
self._flip = out_data[-1] # Update flip state based on last bit
else:
# 5. Invalid bit value - error condition
self.error(f"Invalid value \"{d}\" at offset {self._offset + idx}")
s = Processor.Status.RESET
idx += 1
return idx, out_data, s
class Decoder(Processor):
"""
Decoder class for Biphase Mark decoding.
Converts Biphase Mark encoded bit streams back to original bit sequences.
"""
def data(self, in_data: bytes) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process incoming Biphase Mark bit stream and decode to original bits.
Args:
in_data: Input bytes representing Biphase Mark encoded bit stream
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[bytes]: Decoded original bit sequence
- Processor.Status: Processing status
"""
# 1. Initialize output data and processing state
out_data = None
idx = 0
# 2. Process input data in pairs (2 bytes per bit)
while idx + 2 <= len(in_data):
chunk = in_data[idx:idx + 2]
if chunk in BiphaseMark.zero_pulses:
# 3. Decode 00 or 11 pattern as 0 bit
out_data = _set_or_extend(out_data, bytearray([0]))
elif chunk in BiphaseMark.one_pulses:
# 4. Decode 01 or 10 pattern as 1 bit
out_data = _set_or_extend(out_data, bytearray([1]))
self.info(f"Detect \"{out_data[-1]}\" at offset {self._offset + idx}")
idx += 2
return idx, out_data, Processor.Status.CONTINUE
class Packetizer(object):
"""
Packetizer for extracting packets from bitstream data.
Detects silent periods and extracts packets based on preamble and syncword patterns.
"""
class Decoder(Processor):
"""
Decoder class for packet extraction from bitstream.
"""
UNDEFINED = 2
def __init__(self, sample_rate, symbol_rate, silent_length=10, preamble=None, syncword=None, *args, **kwargs):
"""
Initialize the Packetizer decoder.
Args:
sample_rate: Sampling rate of the input data
symbol_rate: Rate at which symbols are transmitted
silent_length: Length of silent period to detect (in symbol periods)
preamble: Expected preamble pattern for packet detection
syncword: Expected syncword pattern for packet detection
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
# Number of samples representing one symbol
self._bit_count = sample_rate // symbol_rate
# Number of samples for silent period (silent_length * _bit_count)
self._silent_count = int(self._bit_count * silent_length)
self._preamble = preamble
self._syncword = syncword
# Initialize output data and processing state
self._bit = self.UNDEFINED
self._current = None
self._count = None
def reset(self):
"""
Reset packetizer state to initial values.
"""
super().reset()
self._bit = self.UNDEFINED
self._current = None
self._count = None
def data(self, in_data: bytes) -> Tuple[int, Optional[Sequence[bytes]], Processor.Status]:
"""
Process incoming bitstream data and extract packets.
Args:
in_data: Input bytes representing bitstream data
Returns:
Tuple containing:
- int: Number of input bytes consumed
- Optional[Sequence[bytes]]: List of extracted packets
- Processor.Status: Processing status
"""
# 1. Initialize output data and processing state
out_data = None
idx = 0
s = Processor.Status.CONTINUE
# 2. Process each byte in input data
while idx < len(in_data) and s == Processor.Status.CONTINUE:
d = in_data[idx]
if self._bit == self.UNDEFINED:
# 3. Initialize bit detection
self._bit = d
self._count = 1
idx += 1
else:
# 4. Find next transition (bit change)
try:
end = in_data.index(1 - self._bit, idx)
except ValueError:
end = len(in_data)
# 5. Accumulate count of consecutive bits
self._count += (end - idx)
# 6. Check if silent period detected
if self._count >= self._silent_count:
idx += (end - idx)
# 7. Process silent period only if there is data
if end != len(in_data) or self._current is not None:
d = self._bit
self._bit = self.UNDEFINED
self.info(
f"Silent detected \"{d}\" at offset {self._offset + idx - self._count} of size {self._count}")
# 8. Process current packet if exists
if self._current is not None:
forward = True
# 9. Add missing bits to complete byte alignment
extend_length = ((((len(self._current) - 1) // 8) + 1) * 8) - len(self._current)
_set_or_extend(self._current, bytearray(repeat(d, extend_length)))
# 10. Decode bitstream to bytes
length, data, f = Bitstream.Decoder(True).process(self._current)
# 11. Validate preamble
if forward and self._preamble is not None:
preamble = data[0:len(self._preamble)]
if preamble != self._preamble:
forward = False
self.info(
f"Preamble \"{preamble}\" doesn't match \"{self._preamble}\" exclude the packet")
else:
data = data[len(self._preamble):]
# 12. Validate syncword
if forward and self._syncword is not None:
syncword = data[0:len(self._syncword)]
if syncword != self._syncword:
forward = False
self.info(
f"Syncword \"{syncword}\" doesn't match \"{self._syncword}\" exclude the packet")
else:
data = data[len(self._syncword):]
# 13. Add packet to output if valid
if forward:
self.info(f"New packet")
out_data = _set_or_extend(out_data, [data])
self._current = None
else:
# 14. Only handle the data if this not the end of the data
if end != len(in_data):
idx += (end - idx)
d = self._bit
self._bit = self.UNDEFINED
width = round(self._count / self._bit_count)
if width < 1:
self.info(
f"Glitch \"{d}\" at offset {self._offset + idx - self._count} of size {self._count}")
else:
self.info(
f"Pulse \"{d}\" at offset {self._offset + idx - self._count} of size {self._count} ({width} symbols)")
self._current = _set_or_extend(self._current, bytearray(repeat(d, width)))
else:
# 15. Keep data for next processing round
self._count -= (end - idx)
s = Processor.Status.OUT_OF_DATA
return idx, out_data, s
class Encoder(Processor):
"""
Encoder class for packet assembly into bitstream.
"""
def __init__(self, sample_rate, symbol_rate, silent_length=10, preamble=None, syncword=None, *args, **kwargs):
"""
Initialize the Packetizer encoder.
Args:
sample_rate: Sampling rate of the output data
symbol_rate: Rate at which symbols are transmitted
silent_length: Length of silent period to insert (in symbol periods)
preamble: Preamble pattern to insert
syncword: Syncword pattern to insert
*args: Additional arguments passed to parent Processor class
**kwargs: Additional keyword arguments passed to parent Processor class
"""
super().__init__(*args, **kwargs)
# Number of samples representing one symbol
self._bit_count = sample_rate // symbol_rate
# Number of samples for silent period (silent_length * _bit_count)
self._silent_count = int(self._bit_count * silent_length)
self._preamble = preamble
self._syncword = syncword
@classmethod
def repeat_bits(cls, out_data: Optional[bytearray], data: Sequence[int], count: int) -> bytearray:
"""
Repeat each bit a specified number of times.
Args:
out_data: Output data to append to
data: Data to repeat
count: Number of times to repeat
Returns:
Updated output data
"""
for b in data:
out_data = _set_or_extend(out_data, bytearray(repeat(b, count)))
return out_data
def data(self, in_data: Sequence[bytes]) -> Tuple[int, Optional[bytes], Processor.Status]:
"""
Process input packets and encode them into bitstream.
Args:
in_data: Input packets (sequence of bytes)
Returns:
Tuple containing:
- int: Number of input packets consumed
- Optional[bytes]: Encoded bitstream data
- Processor.Status: Processing status
"""
# 1. Initialize output data
out_data = None
idx = 0
# 2. Process each packet in input data
while idx < len(in_data):
d = in_data[idx]
# 3. Encode packet to bitstream (array of bytes to array of bits)
length, data, f = Bitstream.Encoder(True).process(d)
# 4. Insert silent period before packet
out_data = self.repeat_bits(out_data, [0], self._silent_count)
# 5. Insert preamble
if self._preamble is not None:
preamble_length, preamble_data, preamble_f = Bitstream.Encoder(True).process(self._preamble)
out_data = self.repeat_bits(out_data, preamble_data, self._bit_count)
# 6. Insert syncword
if self._syncword is not None:
syncword_length, syncword_data, syncword_f = Bitstream.Encoder(True).process(self._syncword)
out_data = self.repeat_bits(out_data, syncword_data, self._bit_count)
# 7. Insert packet data
out_data = self.repeat_bits(out_data, data, self._bit_count)
# 8. Insert silent period after packet
out_data = self.repeat_bits(out_data, [0], self._silent_count)
idx += 1
return idx, out_data, Processor.Status.CONTINUE
class CcittWhitening(object):
@classmethod
def reverse_bits(cls, x):
"""
Reverse the bit order of an 8-bit value.
This function performs bit reversal using bitwise operations:
- First swaps nibbles (4-bit groups)
- Then swaps 2-bit groups
- Finally swaps individual bits
Args:
x: 8-bit integer to reverse
Returns:
Integer with reversed bit order
"""
x = ((x & 0xF0) >> 4) | ((x & 0x0F) << 4)
x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2)
x = ((x & 0xAA) >> 1) | ((x & 0x55) << 1)
return x
@classmethod
def ccitt_whitening_cstyle(cls, data: bytes):
"""
Apply CCITT whitening algorithm to input data.
CCITT whitening is a pseudo-random bit sequence generator used for
data scrambling. This implementation follows the CCITT standard
(ITU-T Recommendation V.42) for error detection and prevention.
Args:
data: Input bytes to be whitened
Returns:
Bytes with CCITT whitening applied
"""
key_msb = 0x01
key_lsb = 0xFF
out = _to_mutable(data)
for i in range(len(out)):
whitening_byte = cls.reverse_bits(key_lsb)
out[i] ^= whitening_byte