Skip to content

Commit

Permalink
refactor almost everything
Browse files Browse the repository at this point in the history
  • Loading branch information
gasparka committed Mar 4, 2019
1 parent 05fa187 commit 57cb723
Show file tree
Hide file tree
Showing 25 changed files with 1,569 additions and 411 deletions.
File renamed without changes.
Binary file added data/ieee_full_packet_test_case.npy
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1,101 changes: 1,101 additions & 0 deletions doc/tx_yed.graphml

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions wifi/bits.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import List, Optional

from typing import List
import numpy as np
from wifi.util import flip_byte_endian


class bits:
Expand Down
12 changes: 12 additions & 0 deletions wifi/channel_impairments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import numpy as np
from wifi.preambler import long_training_symbol


def undo(long_training, carriers):
long_training = np.array(long_training[32:])
avg_training = (long_training[:64] + long_training[64:128]) / 2
channel_estimate = np.fft.fft(avg_training) / long_training_symbol()
equalizer = 1 / channel_estimate

result = np.array(carriers) * equalizer
return result.tolist()
5 changes: 5 additions & 0 deletions wifi/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from dataclasses import dataclass

"""
c) Calculate from RATE field of the TXVECTOR the number of data bits per OFDM symbol (N DBPS ),
the coding rate (R), the number of bits in each OFDM subcarrier (N BPSC ), and the number of coded
bits per OFDM symbol (N CBPS ). Refer to 17.3.2.3 for details.
"""

@dataclass
class Config:
Expand Down
9 changes: 3 additions & 6 deletions wifi/convolutional_coder.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import logging
from loguru import logger
import numpy as np
from hypothesis import given, assume, settings
from hypothesis._strategies import binary, sampled_from
from hypothesis._strategies import binary
from numba import njit

from wifi import puncturer, bits
from wifi.util import xor_reduce_poly, is_divisible

logger = logging.getLogger(__name__)

# config
K = 7
STATES = 2 ** (K - 1)
Expand Down Expand Up @@ -109,7 +106,7 @@ def undo(data: bits) -> bits:
data = [LUT[state_transition] for state_transition in data.split(2)]

out, error_score = trellis_kernel(data)
logger.info(f'Decoded {len(out)} bits, error_score={int(error_score)}')
logger.debug(f'{len(out)//8}B, error_score={int(error_score)}')
return bits(out)


Expand Down
21 changes: 21 additions & 0 deletions wifi/guard_interval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import List
import numpy as np
from hypothesis import given
from hypothesis._strategies import lists, complex_numbers
from wifi.to_time_domain import OFDMFrame

SIZE = 16


def do(frames: List[OFDMFrame]) -> List[OFDMFrame]:
return [frame[-SIZE:] + frame for frame in frames]


def undo(frames: List[OFDMFrame]) -> List[OFDMFrame]:
return [frame[SIZE:] for frame in frames]


@given(lists(lists(complex_numbers(), min_size=64, max_size=64), min_size=1, max_size=32))
def test_hypothesis(data):
un = undo(do(data))
np.testing.assert_equal(data, un)
31 changes: 26 additions & 5 deletions wifi/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,24 @@
RATE RESERVED LENGTH PARITY SIGNAL_TAIL
(4 bits) (1 bit) (12 bits) (1 bit) (6 bits)
b) Produce the PHY header field from the RATE, LENGTH fields. In order to facilitate a reliable and timely
detection of the RATE and LENGTH fields, 6 zero tail bits are inserted into the PHY header.
The encoding of the SIGNAL field into an OFDM symbol follows the same steps for convolutional
encoding, interleaving, BPSK modulation, pilot insertion, Fourier transform, and prepending a GI as
described subsequently for data transmission with BPSK-OFDM modulated at coding rate 1/2. The
contents of the SIGNAL field are not scrambled. Refer to 17.3.4 for details.
"""

from typing import Tuple
from typing import Tuple, List
from hypothesis import given
from hypothesis._strategies import integers, sampled_from

from wifi import convolutional_coder, interleaver, modulator
from wifi.bits import bits
from wifi.modulator import Symbol
from wifi.subcarrier_mapping import Carriers
from wifi.util import reverse

"""
Expand All @@ -40,7 +52,7 @@
54: '0011'}


def do(data_rate: int, length_bytes: int) -> bits:
def do(data_rate: int, length_bytes: int) -> List[Symbol]:
if length_bytes > (2 ** 12) - 1:
raise Exception(f'Maximum bytes in a packet is {(2**12)-1}, you require {length_bytes}')

Expand All @@ -59,10 +71,19 @@ def do(data_rate: int, length_bytes: int) -> bits:
# In order to facilitate a reliable and timely detection of the RATE and LENGTH fields, 6 zero tail bits are
# inserted into the PHY header.
signal += '000000'

signal = convolutional_coder.do(signal)
signal = interleaver.do(signal, coded_bits_ofdm_symbol=48, coded_bits_subcarrier=1)
signal = modulator.do(signal, bits_per_symbol=1)
return signal


def undo(data: bits) -> Tuple[int, int]:
def undo(carriers: Carriers) -> Tuple[int, int]:
data = modulator.undo(carriers, bits_per_symbol=1)
data = interleaver.undo(data, coded_bits_ofdm_symbol=48, coded_bits_subcarrier=1)
data = convolutional_coder.undo(data)


parity = data[:17].count('1') & 1
assert parity == data[17]

Expand All @@ -78,11 +99,11 @@ def test_signal_field():
"""

# IEEE Std 802.11-2016: Table I-7—Bit assignment for SIGNAL field
expect = '101100010011000000000000'
# expect = '101100010011000000000000'
data_rate = 36
length_bytes = 100
output = do(data_rate, length_bytes)
assert output == expect
# assert output == expect

# test decode
dec_data_rate, dec_length_bytes = undo(output)
Expand Down
42 changes: 39 additions & 3 deletions wifi/interleaver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,25 @@
“interleaving” (reordering) of the bits according to a rule corresponding to the TXVECTOR
parameter RATE. Refer to 17.3.5.7 for details.
"""
from functools import lru_cache
from typing import List
import numpy as np
from hypothesis import given, assume
from hypothesis._strategies import composite, integers, sampled_from, binary

from wifi.bits import bits
from wifi.util import is_divisible


@lru_cache()
def first_permute(coded_bits_ofdm_symbol: int) -> List[int]:
""" The first permutation causes adjacent coded bits to be mapped onto nonadjacent subcarriers. """
lut = [int((coded_bits_ofdm_symbol / 16) * (k % 16) + np.floor(k / 16))
for k in range(coded_bits_ofdm_symbol)]
return lut


@lru_cache()
def second_permute(coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> List[int]:
""" The second permutation causes adjacent coded bits to be mapped alternately onto less and more significant bits of the
constellation and, thereby, long runs of low reliability (LSB) bits are avoided. """
Expand All @@ -41,8 +48,7 @@ def inverse_permute(x: List[int]) -> List[int]:
return result.tolist()


def do(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> bits:

def do_one(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> bits:
table = first_permute(coded_bits_ofdm_symbol)
table = inverse_permute(table)
first_result = data[table]
Expand All @@ -53,7 +59,7 @@ def do(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> b
return second_result


def undo(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> bits:
def undo_one(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> bits:
table = second_permute(coded_bits_ofdm_symbol, coded_bits_subcarrier)
first_result = data[table]

Expand All @@ -62,6 +68,20 @@ def undo(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) ->
return second_result


def do(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> bits:
assume(is_divisible(data, coded_bits_ofdm_symbol))
result = [do_one(interleaving_group, coded_bits_ofdm_symbol, coded_bits_subcarrier)
for interleaving_group in data.split(coded_bits_ofdm_symbol)]
return bits(result)


def undo(data: bits, coded_bits_ofdm_symbol: int, coded_bits_subcarrier: int) -> bits:
assume(is_divisible(data, coded_bits_ofdm_symbol))
result = [undo_one(group, coded_bits_ofdm_symbol, coded_bits_subcarrier)
for group in data.split(coded_bits_ofdm_symbol)]
return bits(result)


def test_first_permutation_table():
result = first_permute(192)

Expand Down Expand Up @@ -127,3 +147,19 @@ def test_i162():
assert result == input


@composite
def combaination(draw):
from wifi.config import Config
packets = draw(integers(min_value=0, max_value=64))
rate = draw(sampled_from([6, 9, 12, 18, 24, 36, 48, 54]))
conf = Config.from_data_rate(rate)
lim = packets * conf.coded_bits_per_ofdm_symbol
data = draw(binary(min_size=lim, max_size=lim))
data = bits(data)
return data, conf.coded_bits_per_ofdm_symbol, conf.coded_bits_per_carrier_symbol


@given(combaination())
def test_hypothesis(data):
data, coded_bits_per_ofdm_symbol, coded_bits_per_carrier_symbol = data
assert undo(do(data, coded_bits_per_ofdm_symbol, coded_bits_per_carrier_symbol), coded_bits_per_ofdm_symbol, coded_bits_per_carrier_symbol) == data
48 changes: 48 additions & 0 deletions wifi/merger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from hypothesis import given
from hypothesis._strategies import lists, complex_numbers
from more_itertools import flatten, chunked

from wifi import guard_interval


def do(train_short, train_long, frames):
parts = [train_short] + [train_long] + frames

for i in range(1, len(parts)):
parts[i][0] = (parts[i - 1][-64] + parts[i][0]) / 2

result = list(flatten(parts))

# smoother power on/off
result[0] /= 2
result.append(result[-64] / 2)

return result


# not a perfect reconstuction!
def undo(iq):
short = iq[:160]
long = iq[160:320]

frame_size = guard_interval.SIZE + 64
frames = list(chunked(iq[320:], frame_size))
if len(frames[-1]) != frame_size:
frames = frames[:-1]

return short, long, frames


@given(lists(lists(complex_numbers(), min_size=80, max_size=80), min_size=1, max_size=32))
def test_hypothesis(frames):
from wifi import preambler
short = preambler.short_training_sequence()
long = preambler.long_training_sequence()

res_short, res_long, res_frames = undo(do(short, long, frames))

# 'do' contaminates the first sample of each symbol, cannot be restored with 'undo'
assert res_short[1:] == short[1:]
assert res_long[1:] == long[1:]
for frame, res_frame in zip(frames, res_frames):
assert frame[1:] == res_frame[1:]
1 change: 1 addition & 0 deletions wifi/ofdm.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def do(ofdm_symbol: OFDMSymbol, index_in_package: int) -> OFDMFrame:
carriers[30] = 0
carriers[31] = 0


ifft = np.fft.ifft(carriers)
result = np.concatenate([ifft[-16:], ifft]) # add 16 samples of GI (guard interval)
return list(result)
Expand Down
6 changes: 3 additions & 3 deletions wifi/padder.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import numpy as np


def do(data: bits, data_bits_per_ofdm_symbol: int) -> Tuple[bits, int, int]:
def do(data: bits, data_bits_per_ofdm_symbol: int) -> Tuple[bits, int]:
service = '0' * 16
tail = '0' * 6
data = service + data + tail
Expand All @@ -35,7 +35,7 @@ def do(data: bits, data_bits_per_ofdm_symbol: int) -> Tuple[bits, int, int]:
pad = '0' * n_pad

data = data + pad
return data, n_symbols, n_pad
return data, n_pad


def undo(data: bits, length_bytes: int) -> bits:
Expand All @@ -46,5 +46,5 @@ def undo(data: bits, length_bytes: int) -> bits:
def test_hypothesis(data, data_bits_per_ofdm_symbol):
data = bits(data)

done_data, n_symbols, n_pad = do(data, data_bits_per_ofdm_symbol)
done_data, n_pad = do(data, data_bits_per_ofdm_symbol)
assert undo(done_data, len(data) // 8) == data
51 changes: 51 additions & 0 deletions wifi/pilots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import List
import numpy as np
from hypothesis import given
from hypothesis._strategies import lists, complex_numbers
from wifi.subcarrier_mapping import Carriers

PILOT_POLARITY = [1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1,
-1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1,
-1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, 1, 1,
1, -1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, 1,
1, 1, -1, -1, -1, -1, -1, -1, -1]


def do_one(carriers: Carriers, index_in_package: int) -> Carriers:
pilots = np.array([1, 1, 1, -1], dtype=complex) * PILOT_POLARITY[index_in_package % 127]
carriers[-21] = pilots[0]
carriers[-7] = pilots[1]
carriers[7] = pilots[2]
carriers[21] = pilots[3]

return carriers


def undo_one(carriers: Carriers, index_in_package: int) -> Carriers:

pilots = np.empty(4, dtype=complex)
pilots[0] = carriers[-21]
pilots[1] = carriers[-7]
pilots[2] = carriers[7]
pilots[3] = carriers[21]

# remove latent frequency offset by using pilot symbols
pilots *= PILOT_POLARITY[index_in_package % 127]
mean_phase_offset = np.angle(np.mean(pilots))
carriers = np.array(carriers) * np.exp(-1j * mean_phase_offset)

return carriers.tolist()


def do(carriers: List[Carriers]) -> List[Carriers]:
return [do_one(carrier, index) for index, carrier in enumerate(carriers)]


def undo(carriers: List[Carriers]) -> List[Carriers]:
return [undo_one(carrier, index) for index, carrier in enumerate(carriers)]


@given(lists(lists(complex_numbers(allow_nan=False, allow_infinity=False), min_size=64, max_size=64), min_size=1, max_size=32))
def test_hypothesis(data):
un = undo(do(data))
np.testing.assert_allclose(data, un, rtol=1e-16, atol=1e-16)

0 comments on commit 57cb723

Please sign in to comment.