Change in ...osmocom-bb[master]: trx_toolkit/data_msg.py: implement header version 0x01 coding

fixeria gerrit-no-reply at lists.osmocom.org
Mon Jun 24 03:22:18 UTC 2019


fixeria 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.html>


More information about the gerrit-log mailing list