|
| 1 | +from typing import Dict, Tuple |
| 2 | + |
| 3 | +import numpy |
| 4 | +from bitarray import bitarray |
| 5 | +from okdmr.dmrlib.etsi.fec.hamming_16_11_4 import Hamming16114 |
| 6 | + |
| 7 | + |
| 8 | +class VBPTC3211: |
| 9 | + """ |
| 10 | + ETSI TS 102 361-1 V2.5.1 (2017-10) - B.2.2 Single Burst Variable length BPTC |
| 11 | + """ |
| 12 | + |
| 13 | + # disable formatter for this whole table, as manual formatting is applied |
| 14 | + # fmt: off |
| 15 | + # @formatter:off |
| 16 | + INTERLEAVING_INDICES: Dict[int, Tuple[int, int, int, bool, bool]] = { |
| 17 | + # (key) index => (value) interleave index, row, column, is row hamming, is parity check bit |
| 18 | + # rows are numbered from 1 to match the documentation/specification |
| 19 | + |
| 20 | + # Row 1 of table, starts with SB(10)/RC(10) |
| 21 | + 0: (0, 1, 0, False, False), |
| 22 | + 1: (2, 1, 1, False, False), |
| 23 | + 2: (4, 1, 2, False, False), |
| 24 | + 3: (6, 1, 3, False, False), |
| 25 | + 4: (8, 1, 4, False, False), |
| 26 | + 5: (10, 1, 5, False, False), |
| 27 | + 6: (12, 1, 6, False, False), |
| 28 | + 7: (14, 1, 7, False, False), |
| 29 | + 8: (16, 1, 8, False, False), |
| 30 | + 9: (18, 1, 9, False, False), |
| 31 | + 10: (20, 1, 10, False, False), |
| 32 | + # Row 1 hamming bits, starts with H1(4) |
| 33 | + 11: (22, 1, 11, True, False), |
| 34 | + 12: (24, 1, 12, True, False), |
| 35 | + 13: (26, 1, 13, True, False), |
| 36 | + 14: (28, 1, 14, True, False), |
| 37 | + 15: (30, 1, 15, True, False), |
| 38 | + |
| 39 | + # Row 4 of table, starts with PC(15) |
| 40 | + 16: (17, 2, 0, False, True), |
| 41 | + 17: (19, 2, 1, False, True), |
| 42 | + 18: (21, 2, 2, False, True), |
| 43 | + 19: (23, 2, 3, False, True), |
| 44 | + 20: (25, 2, 4, False, True), |
| 45 | + 21: (27, 2, 5, False, True), |
| 46 | + 22: (29, 2, 6, False, True), |
| 47 | + 23: (31, 2, 7, False, True), |
| 48 | + 24: (1, 2, 8, False, True), |
| 49 | + 25: (3, 2, 9, False, True), |
| 50 | + 26: (5, 2, 10, False, True), |
| 51 | + 27: (7, 2, 11, False, True), |
| 52 | + 28: (9, 2, 12, False, True), |
| 53 | + 29: (11, 2, 13, False, True), |
| 54 | + 30: (13, 2, 14, False, True), |
| 55 | + 31: (15, 2, 15, False, True), |
| 56 | + } |
| 57 | + """Interleave table as key(index) => value(interleave index, row, column, is reserved, is hamming)""" |
| 58 | + # @formatter:on |
| 59 | + # fmt: on |
| 60 | + |
| 61 | + FULL_INTERLEAVING_MAP: Dict[int, int] = dict( |
| 62 | + (k, v[0]) for k, v in INTERLEAVING_INDICES.items() |
| 63 | + ) |
| 64 | + """Extract only (table index -> interleave index)""" |
| 65 | + FULL_DEINTERLEAVING_MAP: Dict[int, int] = dict( |
| 66 | + (v[0], k) for k, v in INTERLEAVING_INDICES.items() |
| 67 | + ) |
| 68 | + """Extract only (interleave index -> index)""" |
| 69 | + DEINTERLEAVE_INFO_BITS_ONLY_MAP: Dict[int, int] = dict( |
| 70 | + (i, l) |
| 71 | + for i, l in enumerate( |
| 72 | + dict( |
| 73 | + (idx, interleave_idx) |
| 74 | + for idx, ( |
| 75 | + interleave_idx, |
| 76 | + row, |
| 77 | + col, |
| 78 | + is_hamming, |
| 79 | + is_parity, |
| 80 | + ) in INTERLEAVING_INDICES.items() |
| 81 | + if not is_hamming and not is_parity # not parity bits or hamming |
| 82 | + ).values() |
| 83 | + ) |
| 84 | + ) |
| 85 | + """Extract only (interleave index -> index) where it's not reserved or hamming bit""" |
| 86 | + INTERLEAVE_INFO_BITS_ONLY_MAP: Dict[int, int] = dict( |
| 87 | + (i, l) |
| 88 | + for i, l in enumerate( |
| 89 | + dict( |
| 90 | + (interleave_idx, idx) |
| 91 | + for idx, ( |
| 92 | + interleave_idx, |
| 93 | + row, |
| 94 | + col, |
| 95 | + is_hamming, |
| 96 | + is_parity, |
| 97 | + ) in INTERLEAVING_INDICES.items() |
| 98 | + if not is_hamming and not is_parity |
| 99 | + ).values() |
| 100 | + ) |
| 101 | + ) |
| 102 | + """Extract only (index -> interleave index) where it's not reserved or hamming bit""" |
| 103 | + |
| 104 | + @staticmethod |
| 105 | + def deinterleave_all_bits(bits: bitarray) -> bitarray: |
| 106 | + """ |
| 107 | + Will take BPTC interleaved (and FEC protected) bits and return 11 bits of deinterleaved bits |
| 108 | + :param bits: 32 bits of on-air payload |
| 109 | + :return: |
| 110 | + """ |
| 111 | + assert ( |
| 112 | + len(bits) == 32 |
| 113 | + ), f"VBPTC 31,11 deinterleave_all_bits requires 32 bits, got {len(bits)}" |
| 114 | + mapping = VBPTC3211.FULL_DEINTERLEAVING_MAP |
| 115 | + |
| 116 | + out = bitarray([0] * len(mapping), endian="big") |
| 117 | + for i, n in mapping.items(): |
| 118 | + out[i] = bits[n] |
| 119 | + |
| 120 | + return out |
| 121 | + |
| 122 | + @staticmethod |
| 123 | + def deinterleave_data_bits(bits: bitarray) -> bitarray: |
| 124 | + """ |
| 125 | + Will take BPTC interleaved (and FEC protected) bits and return 11bits of data |
| 126 | + :param bits: 32 bits of on-air payload |
| 127 | + :return: bitarray with 11 (data bits) |
| 128 | + """ |
| 129 | + assert len(bits) == 32, f"VBPTC 32,11 decode requires 32 bits, got {len(bits)}" |
| 130 | + mapping = VBPTC3211.DEINTERLEAVE_INFO_BITS_ONLY_MAP |
| 131 | + |
| 132 | + out = bitarray([0] * len(mapping.keys()), endian="big") |
| 133 | + for i, n in mapping.items(): |
| 134 | + out[i] = bits[n] |
| 135 | + |
| 136 | + return out |
| 137 | + |
| 138 | + @staticmethod |
| 139 | + def make_encoding_table() -> numpy.ndarray: |
| 140 | + # create table 4 rows, 17 columns, for FEC encoding |
| 141 | + table: numpy.ndarray = numpy.ndarray(shape=(2, 16), dtype=int) |
| 142 | + table.fill(0) |
| 143 | + |
| 144 | + return table |
| 145 | + |
| 146 | + @staticmethod |
| 147 | + def fill_encoding_table( |
| 148 | + table: numpy.ndarray, bits_deinterleaved: bitarray |
| 149 | + ) -> numpy.ndarray: |
| 150 | + assert ( |
| 151 | + len(bits_deinterleaved) == 11 or len(bits_deinterleaved) == 32 |
| 152 | + ), f"Can fill encoding table only with data bits (len 11) or full bits (len 32), got {len(bits_deinterleaved)}" |
| 153 | + |
| 154 | + # make bitarray of size 32, fill with provided bits |
| 155 | + mapping = ( |
| 156 | + VBPTC3211.DEINTERLEAVE_INFO_BITS_ONLY_MAP |
| 157 | + if len(bits_deinterleaved) == 11 |
| 158 | + else VBPTC3211.FULL_DEINTERLEAVING_MAP |
| 159 | + ) |
| 160 | + bits_interleaved: bitarray = bitarray([0] * 32, endian="big") |
| 161 | + |
| 162 | + for index, interleave_index in mapping.items(): |
| 163 | + bits_interleaved[interleave_index] = bits_deinterleaved[index] |
| 164 | + |
| 165 | + for data_index, ( |
| 166 | + interleave_idx, |
| 167 | + row_no, |
| 168 | + col_no, |
| 169 | + is_hamming, |
| 170 | + is_parity, |
| 171 | + ) in VBPTC3211.INTERLEAVING_INDICES.items(): |
| 172 | + table[row_no - 1][col_no] = bits_interleaved[interleave_idx] |
| 173 | + |
| 174 | + return table |
| 175 | + |
| 176 | + @staticmethod |
| 177 | + def encode(bits_deinterleaved: bitarray) -> bitarray: |
| 178 | + """ |
| 179 | + Takes 11 bits of data (info bits) and return interleaved and FEC protected 32 bits |
| 180 | + :param bits_deinterleaved: |
| 181 | + :return: |
| 182 | + """ |
| 183 | + if len(bits_deinterleaved) == 32: |
| 184 | + # full deinterleaved data including hamming and parity |
| 185 | + # interleave again and deinterleave only data bits |
| 186 | + interleaved: bitarray = bitarray([0] * 32) |
| 187 | + for data_index, ( |
| 188 | + interleave_index, |
| 189 | + _, |
| 190 | + _, |
| 191 | + _, |
| 192 | + _, |
| 193 | + ) in VBPTC3211.INTERLEAVING_INDICES.items(): |
| 194 | + interleaved[data_index] = bits_deinterleaved[interleave_index] |
| 195 | + bits_deinterleaved = VBPTC3211.deinterleave_data_bits(interleaved) |
| 196 | + |
| 197 | + assert ( |
| 198 | + len(bits_deinterleaved) == 11 |
| 199 | + ), f"Unexpected number of bits fed to VBPTC3211.encode, expected 11 or 32, got {len(bits_deinterleaved)}" |
| 200 | + |
| 201 | + table: numpy.ndarray = VBPTC3211.make_encoding_table() |
| 202 | + table = VBPTC3211.fill_encoding_table( |
| 203 | + table=table, bits_deinterleaved=bits_deinterleaved |
| 204 | + ) |
| 205 | + |
| 206 | + # fill row 0 with hamming |
| 207 | + for row in range(0, 1): |
| 208 | + table[row] = Hamming16114.generate(table[row][:11]) |
| 209 | + |
| 210 | + # fill columns with parity bit |
| 211 | + for column in range(0, 16): |
| 212 | + table[:, column] = VBPTC3211.set_parity(table[:, column]) |
| 213 | + |
| 214 | + out: bitarray = bitarray([0] * 32) |
| 215 | + for index, ( |
| 216 | + interleave_index, |
| 217 | + row, |
| 218 | + col, |
| 219 | + is_hamming, |
| 220 | + is_parity, |
| 221 | + ) in VBPTC3211.INTERLEAVING_INDICES.items(): |
| 222 | + out[interleave_index] = table[row - 1][col] |
| 223 | + |
| 224 | + return out |
| 225 | + |
| 226 | + @staticmethod |
| 227 | + def set_parity(column: numpy.ndarray) -> numpy.ndarray: |
| 228 | + assert len(column) in (1, 2) |
| 229 | + if len(column) == 1: |
| 230 | + column = numpy.append(column, [0]) |
| 231 | + column[1] = column[0] |
| 232 | + return column |
0 commit comments