This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
fixeria gerrit-no-reply at lists.osmocom.orgfixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/14579 Change subject: trx_toolkit/data_msg.py: implement header version 0x01 coding ...................................................................... trx_toolkit/data_msg.py: implement header version 0x01 coding The new version mostly adds new fields to the TRX2L1 message: +------+-----+-----+-----+-----+--------------------+ | RSSI | ToA | MTS | C/I | L16 | soft-bits (254..0) | +------+-----+-----+-----+-----+--------------------+ where: - MTS (1 octet) - Modulation and Training Sequence info, - C/I (2 octets) - Carrier-to-Interference ratio (big endian), - L16 (2 octets) - length of burst (big endian). In case of L12TRX, the burst bits are now also encoded as L16V: +-----+-----+--------------------+ | PWR | L16 | hard-bits (1 or 0) | +-----+-----+--------------------+ == Coding of MTS: Modulation and Training Sequence info 3GPP TS 45.002 version 15.1.0 defines several modulation types, and a few sets of training sequences for each type. The most common are GMSK and 8-PSK (which is used in EDGE). +-----------------+---------------------------------------+ | 7 6 5 4 3 2 1 0 | bit numbers (value range) | +-----------------+---------------------------------------+ | . . . . . X X X | Training Sequence Code (0..7) | +-----------------+---------------------------------------+ | . X X X X . . . | Modulation, TS set number (see below) | +-----------------+---------------------------------------+ | X . . . . . . . | IDLE / nope frame indication (0 or 1) | +-----------------+---------------------------------------+ The bit number 7 (MSB) is set to high when either nothing has been detected, or during IDLE frames, so we can deliver noise levels, and avoid clock gaps on the L1 side. Other bits are ignored, and should be set to low (0) in this case. L16 shall be set to 0x00. == Coding of modulation and TS set number GMSK has 4 sets of training sequences (see tables 5.2.3a-d), while 8-PSK (see tables 5.2.3f-g) and the others have 2 sets. Access and Synchronization bursts also have several synch. sequences. +-----------------+---------------------------------------+ | 7 6 5 4 3 2 1 0 | bit numbers (value range) | +-----------------+---------------------------------------+ | . 0 0 X X . . . | GMSK, 4 TS sets (0..3) | +-----------------+---------------------------------------+ | . 0 1 0 X . . . | 8-PSK, 2 TS sets (0..1) | +-----------------+---------------------------------------+ | . 0 1 1 X . . . | AQPSK, 2 TS sets (0..1) | +-----------------+---------------------------------------+ | . 1 0 0 X . . . | 16QAM, 2 TS sets (0..1) | +-----------------+---------------------------------------+ | . 1 0 1 X . . . | 32QAM, 2 TS sets (0..1) | +-----------------+---------------------------------------+ | . 1 1 1 X . . . | RESERVED (0) | +-----------------+---------------------------------------+ == C/I: Carrier-to-Interference ratio The C/I value is computed from the training sequence of each burst, where we can compare the "ideal" training sequence with the actual training sequence and then express that in dB. Change-Id: Ie810c5a482d1c908994e8cdd32a2ea641ae7cedd Related: OS#4006 --- M src/target/trx_toolkit/data_msg.py 1 file changed, 289 insertions(+), 32 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/79/14579/1 diff --git a/src/target/trx_toolkit/data_msg.py b/src/target/trx_toolkit/data_msg.py index 8a114f7..8d52764 100644 --- a/src/target/trx_toolkit/data_msg.py +++ b/src/target/trx_toolkit/data_msg.py @@ -25,8 +25,17 @@ import random import struct +from enum import IntEnum from gsm_shared import * +class Modulation(IntEnum): + """ Modulation types defined in 3GPP TS 45.002 """ + ModGMSK = 0b0000 + Mod8PSK = 0b0100 + ModAQPSK = 0b0110 + Mod16QAM = 0b1000 + Mod32QAM = 0b1010 + class DATAMSG: """ TRXD (DATA) message codec (common part). @@ -45,6 +54,12 @@ while the message specific headers and bit types are different. + NOTE: since version 0x01, the burst bits are encoded as L16V: + + +--------------------+------------+ + | L16 (2 octets, BE) | burst bits | + +--------------------+------------+ + The common header is represented by this class, which is the parent of both DATAMSG_L12TRX and DATAMSG_TRX2L2 (see below), and has the following fields: @@ -99,6 +114,12 @@ self.fn = fn self.tn = tn + # The common header length + @property + def CHDR_LEN(self): + # (VER + TN) + FN + return 1 + 4 + # Generates message specific header def gen_hdr(self): raise NotImplementedError @@ -227,7 +248,13 @@ buf += hdr # Generate burst - buf += self.gen_burst() + burst = self.gen_burst() + + # Starting from version 0x01, the burst bits are + # encoded as L16V (length is 2 octets, big endian). + if self.ver >= 0x01: + buf += struct.pack(">H", len(burst)) + buf += burst # This is a rudiment from (legacy) OpenBTS transceiver, # some L1 implementations still expect two dummy bytes. @@ -241,8 +268,8 @@ # Calculate message length length = len(msg) - # Check length - if length < (self.HDR_LEN + GSM_BURST_LEN): + # Make sure we have at least header + if length < self.HDR_LEN: raise ValueError("Message is to short") # Parse version and TDMA TN @@ -255,9 +282,38 @@ # Specific message part self.parse_hdr(msg) - # Copy burst, skipping header - msg_burst = msg[self.HDR_LEN:] - self.parse_burst(msg_burst) + # We're done with the header now + msg = msg[self.HDR_LEN:] + length -= self.HDR_LEN + + # Verify the burst length + if self.ver == 0x00: + # For version 0x00, we can either have as much as + # a GSM burst length, or an EDGE burst length. + if length in (GSM_BURST_LEN, EDGE_BURST_LEN): + pass + # Some ancient transceivers append 2 dummy octets + elif (length - 2) in (GSM_BURST_LEN, EDGE_BURST_LEN): + msg = msg[:-2] + length -= 2 + else: + raise ValueError("Odd burst length") + elif self.ver >= 0x01: + # Starting from version 0x01, the burst bits are + # encoded as L16V (length is 2 octets, big endian). + if length < 2: + raise ValueError("Missing burst LV") + + burst_len = struct.unpack(">H", msg[:2])[0] + if burst_len != (length - 2): + raise ValueError("Odd burst length") + + # We're done with L16 header + msg = msg[2:] + length -= 2 + + # Parse burst + self.parse_burst(msg) class DATAMSG_L12TRX(DATAMSG): """ L12TRX (L1 -> TRX) message codec. @@ -266,13 +322,24 @@ or an Uplink burst on the MS side, and has the following message specific fixed-size header preceding the burst bits: + == Version 0x00 + +-----+--------------------+ | PWR | hard-bits (1 or 0) | +-----+--------------------+ - where PWR (1 octet) is relative (to the full-scale amplitude) - transmit power level in dB. The absolute value is set on - the control interface. + == Version 0x01 + + +-----+-----+--------------------+ + | PWR | L16 | hard-bits (1 or 0) | + +-----+-----+--------------------+ + + where: + + - PWR (1 octet) - relative (to the full-scale amplitude) transmit + power level in dB. The absolute value is set + on the control interface. + - L16 (2 octets) - length of burst (big endian). Each hard-bit (1 or 0) of the burst is represented using one byte (0x01 or 0x00 respectively). @@ -280,13 +347,26 @@ """ # Constants - HDR_LEN = 6 PWR_MIN = 0x00 PWR_MAX = 0xff # Specific message fields pwr = None + # Calculates header length depending on its version + @property + def HDR_LEN(self): + # Common header length + length = self.CHDR_LEN + + # Message specific header length + if self.ver in (0x00, 0x01): + length += 1 # PWR + else: + raise IndexError("Unhandled version %u" % self.ver) + + return length + # Validates the message fields def validate(self): # Validate common fields @@ -349,13 +429,8 @@ # Parses message specific burst def parse_burst(self, burst): - length = len(burst) - - # Distinguish between GSM and EDGE - if length >= EDGE_BURST_LEN: - self.burst = list(burst[:EDGE_BURST_LEN]) - else: - self.burst = list(burst[:GSM_BURST_LEN]) + # Nothing to do, we already have hard-bits + self.burst = list(burst) # Transforms this message to TRX2L1 message def gen_trx2l1(self, ver = None): @@ -376,16 +451,79 @@ or a Downlink burst on the MS side, and has the following message specific fixed-size header preceding the burst bits: + == Version 0x00 + +------+-----+--------------------+ | RSSI | ToA | soft-bits (254..0) | +------+-----+--------------------+ + == Version 0x01 + + +------+-----+-----+-----+-----+--------------------+ + | RSSI | ToA | MTS | C/I | L16 | soft-bits (254..0) | + +------+-----+-----+-----+-----+--------------------+ + where: - RSSI (1 octet) - Received Signal Strength Indication encoded without the negative sign. - ToA (2 octets) - Timing of Arrival in units of 1/256 of symbol (big endian). + - MTS (1 octet) - Modulation and Training Sequence info. + - C/I (2 octets) - Carrier-to-Interference ratio (big endian). + - L16 (2 octets) - length of burst (big endian). + + == Coding of TS: Training Sequence and modulation + + 3GPP TS 45.002 version 15.1.0 defines several modulation types, + and a few sets of training sequences for each type. The most + common are GMSK and 8-PSK (which is used in EDGE). + + +-----------------+---------------------------------------+ + | 7 6 5 4 3 2 1 0 | bit numbers (value range) | + +-----------------+---------------------------------------+ + | . . . . . X X X | Training Sequence Code (0..7) | + +-----------------+---------------------------------------+ + | . X X X X . . . | Modulation, TS set number (see below) | + +-----------------+---------------------------------------+ + | X . . . . . . . | IDLE / nope frame indication (0 or 1) | + +-----------------+---------------------------------------+ + + The bit number 7 (MSB) is set to high when either nothing has been + detected, or during IDLE frames, so we can deliver noise levels, + and avoid clock gaps on the L1 side. Other bits are ignored, + and should be set to low (0) in this case. L16 shall be set to 0x00. + + == Coding of modulation and TS set number + + GMSK has 4 sets of training sequences (see tables 5.2.3a-d), + while 8-PSK (see tables 5.2.3f-g) and the others have 2 sets. + Access and Synchronization bursts also have several synch. + sequences. + + +-----------------+---------------------------------------+ + | 7 6 5 4 3 2 1 0 | bit numbers (value range) | + +-----------------+---------------------------------------+ + | . 0 0 X X . . . | GMSK, 4 TS sets (0..3) | + +-----------------+---------------------------------------+ + | . 0 1 0 X . . . | 8-PSK, 2 TS sets (0..1) | + +-----------------+---------------------------------------+ + | . 0 1 1 X . . . | AQPSK, 2 TS sets (0..1) | + +-----------------+---------------------------------------+ + | . 1 0 0 X . . . | 16QAM, 2 TS sets (0..1) | + +-----------------+---------------------------------------+ + | . 1 0 1 X . . . | 32QAM, 2 TS sets (0..1) | + +-----------------+---------------------------------------+ + | . 1 1 1 X . . . | RESERVED (0) | + +-----------------+---------------------------------------+ + + == C/I: Carrier-to-Interference ratio + + The C/I value can be computed from the training sequence of each + burst, where we can compare the "ideal" training sequence with + the actual training sequence and then express that in dB. + + == Coding of the burst bits Unlike to be transmitted bursts, the received bursts are designated using the soft-bits notation, so the receiver can indicate its @@ -398,9 +536,6 @@ """ - # Constants - HDR_LEN = 8 - # rxlev2dbm(0..63) gives us [-110..-47], plus -10 dbm for noise RSSI_MIN = -120 RSSI_MAX = -47 @@ -409,10 +544,42 @@ TOA256_MIN = -32768 TOA256_MAX = 32767 + # TSC (Training Sequence Code) range + TSC_RANGE = range(0, 8) + + # IDLE frame / nope detection indicator + NOPE_IND = (1 << 7) + # Specific message fields rssi = None toa256 = None + # Version 0x01 specific (default values) + mod_type = Modulation.ModGMSK + nope_ind = False + + tsc_set = None + tsc = None + ci = None + + # Calculates header length depending on its version + @property + def HDR_LEN(self): + # Common header length + length = self.CHDR_LEN + + # Message specific header length + if self.ver == 0x00: + # RSSI + ToA + length += 1 + 2 + elif self.ver == 0x01: + # RSSI + ToA + TS + C/I + length += 1 + 2 + 1 + 2 + else: + raise IndexError("Unhandled version %u" % self.ver) + + return length + # Validates the message fields def validate(self): # Validate common fields @@ -459,6 +626,17 @@ self.rssi = self.rand_rssi() self.toa256 = self.rand_toa256() + if self.ver >= 0x01: + self.mod_type = random.choice(list(Modulation)) + if self.mod_type == Modulation.ModGMSK: + self.tsc_set = random.randint(0, 3) + else: + self.tsc_set = random.randint(0, 1) + self.tsc = random.choice(self.TSC_RANGE) + + # FIXME: C/I: Carrier-to-Interference ratio + self.ci = random.choice(range(-100, 100)) + # Generates human-readable header description def desc_hdr(self): # Describe the common part @@ -470,9 +648,61 @@ if self.toa256 is not None: result += ("toa256=%d " % self.toa256) + if self.ver >= 0x01: + if not self.nope_ind: + if self.mod_type is not None: + result += ("%s " % self.mod_type) + if self.tsc_set is not None: + result += ("set=%u " % self.tsc_set) + if self.tsc is not None: + result += ("tsc=%u " % self.tsc) + if self.ci is not None: + result += ("C/I=%d " % self.ci) + else: + result += "IDLE / NOP IND" + # Strip useless whitespace and return return result.strip() + # Encodes Modulation and Training Sequence info + def gen_mts(self): + # IDLE / nope indication has no MTS info + if self.nope_ind: + return self.NOPE_IND + + # TSC: . . . . . X X X + mts = self.tsc & 0b111 + + # MTS: . X X X X . . . + mts |= self.mod_type << 3 + mts |= self.tsc_set << 3 + + return mts + + # Parses Modulation and Training Sequence info + def parse_mts(self, mts): + # IDLE / nope indication has no MTS info + self.nope_ind = (mts & self.NOPE_IND) > 0 + if self.nope_ind: + self.mod_type = None + self.tsc_set = None + self.tsc = None + return + + # TSC: . . . . . X X X + self.tsc = mts & 0b111 + + # MTS: . X X X X . . . + mts = (mts >> 3) & 0b1111 + if (mts & 0b1100) > 0: + # Mask: . . . . M M M S + self.mod_type = Modulation(mts & 0b1110) + self.tsc_set = mts & 0b1 + else: + # GMSK: . . . . 0 0 S S + self.mod_type = Modulation.ModGMSK + self.tsc_set = mts & 0b11 + # Generates message specific header part def gen_hdr(self): # Allocate an empty byte-array @@ -485,6 +715,20 @@ # Big endian, 2 bytes (int32_t) buf += struct.pack(">h", self.toa256) + if self.ver >= 0x01: + # Modulation and Training Sequence info + mts = self.gen_mts() + buf.append(mts) + + # C/I: Carrier-to-Interference ratio + # 2 octets, big endian (int16_t) + # FIXME: Dummy filling (C/I) + if not self.nope_ind: + # FIXME!!! + buf += bytearray(2) + else: + buf += bytearray(2) + return buf # Parses message specific header part @@ -495,6 +739,18 @@ # Parse ToA (Time of Arrival) self.toa256 = struct.unpack(">h", hdr[6:8])[0] + if self.ver >= 0x01: + # Modulation and Training Sequence info + self.parse_mts(hdr[8]) + + # C/I: Carrier-to-Interference ratio + # 2 octets, big endian (int16_t) + if not self.nope_ind: + # FIXME!!! + self.ci = None + else: + self.ci = None + # Generates message specific burst def gen_burst(self): # Convert soft-bits to unsigned soft-bits @@ -505,19 +761,8 @@ # Parses message specific burst def parse_burst(self, burst): - length = len(burst) - - # Distinguish between GSM and EDGE - if length >= EDGE_BURST_LEN: - burst_usbits = list(burst[:EDGE_BURST_LEN]) - else: - burst_usbits = list(burst[:GSM_BURST_LEN]) - # Convert unsigned soft-bits to soft-bits - burst_sbits = self.usbit2sbit(burst_usbits) - - # Save - self.burst = burst_sbits + self.burst = self.usbit2sbit(burst) # Transforms this message to L12TRX message def gen_l12trx(self, ver = None): @@ -682,6 +927,18 @@ log.info("Check header version %u coding: OK" % ver) + # Match version specific fields + if msg_trx2l1.ver >= 0x01: + log.info("TRX2L1: %s" % msg_trx2l1.desc_hdr()) + log.info("TRX2L1: %s" % msg_trx2l1_dec.desc_hdr()) + assert(msg_trx2l1_dec.nope_ind == msg_trx2l1.nope_ind) + assert(msg_trx2l1_dec.mod_type == msg_trx2l1.mod_type) + assert(msg_trx2l1_dec.tsc_set == msg_trx2l1.tsc_set) + assert(msg_trx2l1_dec.tsc == msg_trx2l1.tsc) + assert(msg_trx2l1_dec.ci == msg_trx2l1.ci) + + log.info("Check version %u specific header coding: OK" % ver) + msg_trx2l1_gen = msg_l12trx.gen_trx2l1() msg_l12trx_gen = msg_trx2l1.gen_l12trx() -- To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/14579 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmocom-bb Gerrit-Branch: master Gerrit-Change-Id: Ie810c5a482d1c908994e8cdd32a2ea641ae7cedd Gerrit-Change-Number: 14579 Gerrit-PatchSet: 1 Gerrit-Owner: fixeria <axilirator at gmail.com> Gerrit-MessageType: newchange -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20190624/34826362/attachment.htm>