Skip to content

Commit

Permalink
Merge pull request #1 from rayandaod/PC_receiver
Browse files Browse the repository at this point in the history
Pc receiver
  • Loading branch information
rayandaod committed May 31, 2019
2 parents ef3045d + a838534 commit 42858c9
Show file tree
Hide file tree
Showing 19 changed files with 1,328 additions and 558 deletions.
Binary file added Project report.pdf
Binary file not shown.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,4 @@
## Professor: Emre Telatar
### Teaching assistants: Sepand Kashani, Reka Inovan, Arda Atalik

**Deadline: 31/05/2019**

Introduction

**Deadline: 31/05/2019**
2 changes: 1 addition & 1 deletion data/input_text.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient.
35 changes: 19 additions & 16 deletions src/fourier_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def dft_shift(X):
obtain a symmetric vector also for even-length signals. Here is a function that does that.
(credits: Prandoni P. - Signal processing for Communication course at EPFL)
:param X: the fourier transform of our signal x
:return: a shifted version of the fourier transform (to be around 0) for even and odd length signals
:param X: The fourier transform of our signal x
:return: A shifted version of the fourier transform (to be around 0) for even and odd length signals
:
"""
N = len(X)
Expand All @@ -34,10 +34,10 @@ def dft_map(X, Fs=params.Fs, shift=True):
namely k(F_s/N). Let's remap the DFT coefficients using the sampling rate.
(credits: Prandoni P. - Signal processing for Communication course at EPFL)
:param X: the fourier transform of our signal x
:param Fs: the sampling frequency
:param shift: rather we want to shift the fft or not
:return: a real-world-frequency DFT
:param X: The fourier transform of our signal x
:param Fs: The sampling frequency
:param shift: Rather we want to shift the fft or not
:return: A real-world-frequency DFT
"""
resolution = float(Fs) / len(X)
if shift:
Expand All @@ -52,9 +52,10 @@ def dft_map(X, Fs=params.Fs, shift=True):
# TODO awful code, change that ASAP
def find_removed_freq_range(samples):
"""
Checks which range of frequencies has been removed by the channel (among 1-3kHz, 3-5kHz, 5-7kHz, 7-9kHz)
:param samples: the samples received from the server
:return: the index in params.FREQ_RANGES corresponding to the removed frequency range
Checks which range of frequencies has been removed by the channel (among 1-3kHz, 3-5kHz, 5-7kHz, 7-9kHz)
:param samples: The samples received from the server
:return: The index in params.FREQ_RANGES corresponding to the removed frequency range
"""
X = np.fft.fft(samples)
f, Y = dft_map(X)
Expand All @@ -77,9 +78,10 @@ def find_removed_freq_range(samples):
def modulate_complex_samples(samples, frequencies):
"""
Modulate the signal by shifting duplicates of it in the given frequencies
:param samples: the signal to modulate
:param frequencies: the frequencies we want the signal to be duplicated and shifted in
:return: the modulated signals
:param samples: The signal to modulate
:param frequencies: The frequencies we want the signal to be duplicated and shifted in
:return: The modulated signals
"""
n_sample = len(samples)
time_indices = np.arange(n_sample) / params.Fs
Expand All @@ -95,10 +97,11 @@ def modulate_complex_samples(samples, frequencies):

def demodulate(samples, f):
"""
Demodulate the signal
:param samples: the signal to demodulate
:param f: the frequency we want the signal to be modulated with
:return: the demodulated signal
Demodulate the signal from the given frequency, i.e go from pass-band to base-band
:param samples: The signal to demodulate
:param f: The frequency we want the signal to be modulated with
:return: The demodulated signal
"""
n_sample = len(samples)
time_indices = np.arange(n_sample) / params.Fs
Expand Down
26 changes: 13 additions & 13 deletions src/helper.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
def string2bits(s=''):
"""
:param s: the string to be converted
:return: the corresponding array of bits
Converts a string to an array of strings of bytes
:param s: The string to be converted
:return: The corresponding array of bits
"""
return [bin(ord(x))[2:].zfill(8) for x in s]


def bits2string(b=None):
"""
:param b: array of bits to be converted
:return: the corresponding string
Convert an array of strings of bytes to a single string
:param b: The array of bits to be converted
:return: The corresponding string
"""
return ''.join([chr(int(x, 2)) for x in b])


def compare_messages(message_sent, message):
"""
Compare 2 messages and print the differences
:param message_sent: the first message to compare
:param message: the second message to compare
:return: None
:param message_sent: The first message to compare
:param message: The second message to compare
:return: None
"""
same_length = len(message_sent) == len(message)
n_errors = 0
Expand All @@ -29,16 +34,11 @@ def compare_messages(message_sent, message):
comparison += " "
else:
n_errors += 1
comparison += '-'
comparison += '!'

print("Errors: {}".format(comparison))
if not same_length:
print("/!\\ MESSAGE DO NOT HAVE THE SAME LENGTH /!\\")
else:
print("({} error(s))".format(n_errors))
return None


# Intended for testing (to run the program, run main.py)
if __name__ == "__main__":
print("helper.py")
36 changes: 17 additions & 19 deletions src/local_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def butter_bandpass_filter(data, low_cut_freq, high_cut_freq, Fs=params.Fs, orde
def server_simulation(samples, clip=True, filter_freq=True, delay_start=True, delay_end=True, noise=True, scale=True):
"""
Simulate a server that clips the data to [-1, 1] adds delay, AWGN(0, params.NOISE_VAR), and some garbage at the end
:param scale: rather we scale the samples or not
:param noise: rather we noise the signal or not
:param delay_end: rather we add delay at the end or not
Expand Down Expand Up @@ -141,26 +142,25 @@ def server_simulation(samples, clip=True, filter_freq=True, delay_start=True, de
def local_test():
"""
Test the design locally with modulation and demodulation
:return: None
"""
mapping = mappings.choose_mapping()
ints = transmitter.message_to_ints()
symbols = transmitter.encoder(ints, mapping)
data_symbols = transmitter.encoder(mappings.choose_mapping())

# Generate the pulse h
_, h = pulses.root_raised_cosine()
half_span_h = int(params.SPAN / 2)

# Generate the preamble symbols and read it from the corresponding file
preambles.generate_preamble_symbols(len(symbols))
preambles.generate_preamble_symbols(len(data_symbols))
preamble_symbols = read_write.read_preamble_symbols()

# Generate the preamble samples
preamble_samples = upfirdn(h, preamble_symbols, params.USF)
len_preamble_samples = len(preamble_samples)

# Concatenate the preamble symbols with the data symbols
total_symbols = np.concatenate((preamble_symbols, symbols, preamble_symbols[::-1]))
total_symbols = np.concatenate((preamble_symbols, data_symbols[0], preamble_symbols[::-1]))

# Shape the signal with the pulse h
total_samples = upfirdn(h, total_symbols, params.USF)
Expand All @@ -182,9 +182,9 @@ def local_test():
plot_helper.fft_plot(total_samples, "Total samples in Frequency domain", shift=True)

# Modulate the total_samples
if params.MODULATION_TYPE == 1:
if params.MOD == 1:
samples = fourier_helper.modulate_complex_samples(total_samples, params.np.mean(params.FREQ_RANGES, axis=1))
elif params.MODULATION_TYPE == 2:
elif params.MOD == 2:
samples = fourier_helper.modulate_complex_samples(total_samples,
[params.FREQ_RANGES[0][1], params.FREQ_RANGES[2][1]])
else:
Expand All @@ -205,10 +205,13 @@ def local_test():
print("Maximum sample after scaling: {}".format(max(samples)))
print("--------------------------------------------------------")

# read_write.write_samples(samples)

# ----------------------------------------------------------------------------------------------------------------
# Channel simulation ---------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------
samples = server_simulation(samples, filter_freq=False)
# samples = server_simulation(samples, filter_freq=False)
samples = np.loadtxt(params.input_sample_file_path)
# ----------------------------------------------------------------------------------------------------------------
# Channel simulation's end ---------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------------
Expand All @@ -222,12 +225,12 @@ def local_test():
print("Removed frequency range: {} (range {})".format(removed_freq_range, removed_freq_range + 1))

# Choose a frequency for demodulation
if params.MODULATION_TYPE == 1:
if params.MOD == 1:
if removed_freq_range == 0:
fc = np.mean(params.FREQ_RANGES[1])
else:
fc = np.mean(params.FREQ_RANGES[0])
elif params.MODULATION_TYPE == 2:
elif params.MOD == 2:
if removed_freq_range == 0 or removed_freq_range == 1:
fc = 7000
else:
Expand All @@ -246,12 +249,12 @@ def local_test():
plot_helper.fft_plot(y, "y in Frequency domain", shift=True)

# Find the delay
delay = parameter_estim.ML_theta_estimation(demodulated_samples, preamble_samples=preamble_samples)
delay = parameter_estim.ML_theta_estimation(y, preamble_samples=preamble_samples)
print("Delay: {} samples".format(delay))
print("--------------------------------------------------------")

# Extract the preamble samples
preamble_samples_received = y[half_span_h + delay - 1:half_span_h + delay + len_preamble_samples - 1]
preamble_samples_received = y[delay:delay + len_preamble_samples]
plot_helper.two_simple_plots(preamble_samples_received.real, preamble_samples.real,
"Comparison between preamble samples received and preamble samples sent",
"received", "expected")
Expand All @@ -277,7 +280,7 @@ def local_test():
print("Scaling factor: {}".format(scaling_factor))

# Crop the samples (remove the delay, the preamble, and the ramp-up)
data_samples = y[half_span_h + delay + len_preamble_samples - half_span_h + params.USF - 1 - 1:]
data_samples = y[delay + len_preamble_samples - half_span_h + params.USF:]

# Find the second_preamble_index
second_preamble_index = parameter_estim.ML_theta_estimation(data_samples, preamble_samples=preamble_samples[::-1])
Expand All @@ -299,14 +302,9 @@ def local_test():
plot_helper.plot_complex_symbols(data_symbols, "Symbols received", annotate=False)

# Decode the symbols
ints = receiver.decoder(data_symbols, mapping)
ints = receiver.decoder(data_symbols, mappings.choose_mapping())
message_received = receiver.ints_to_message(ints)

message_file = open(params.input_message_file_path)
message_sent = message_file.readline()
print(message_received == message_sent)


# Intended for testing (to run the program, run main.py)
if __name__ == "__main__":
local_test()
22 changes: 5 additions & 17 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
import sys
import time

import mappings
import params
import pulses
import read_write
import receiver
import transmitter

if __name__ == "__main__":
moment = time.strftime("%Y-%b-%d__%H_%M_%S", time.localtime())
log_file = open("../logs/" + moment + ".log", "w+")
if params.logs:
moment = time.strftime("%Y-%b-%d__%H_%M_%S", time.localtime())
log_file = open("../logs/" + moment + ".log", "w+")
sys.stdout = log_file

# Write the parameters in the log file
if params.logs:
params.params_log()

# Transmitter
symbols = transmitter.encoder(transmitter.message_to_ints(), mappings.choose_mapping())
_, h = pulses.root_raised_cosine()
samples_to_send = transmitter.waveform_former(h, symbols)
read_write.write_samples(samples_to_send)
samples_to_send = transmitter.waveform_former(h, transmitter.encoder())
transmitter.send_samples()

# Wait for the user to press enter (in case the server is down for example)
sys.stdout = sys.__stdout__
input("Press Enter to continue...")
if params.logs:
sys.stdout = log_file

# Receiver
receiver.received_from_server()

sys.stdout = sys.__stdout__
input("Press Enter to end the transmission!")
data_symbols, removed_freq_range = receiver.n_tuple_former()
receiver.decoder(data_symbols, removed_freq_range)
54 changes: 20 additions & 34 deletions src/mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,52 +6,40 @@

def qam_map(M):
"""
:param M: the size of our mapping, i.e the number of symbols we can send
:return: the array of symbols of the M-QAM (Quadrature Amplitude Modulation)
Compute the array of symbols that correspond to a M-QAM mapping
:param M: The size of our mapping, i.e the number of symbols we can send
:return: The array of symbols of the M-QAM (Quadrature Amplitude Modulation)
"""
log_sqrt_m = np.log2(np.sqrt(M))
if log_sqrt_m != np.ceil(log_sqrt_m):
raise ValueError('Parameter[M] is not of the form 2^2K, K a positive integer.')
raise ValueError("Parameter[M] is not of the form 2^2K, K a positive integer.")
# Implement Gray code
if M == 4:
return [1 + 1j, 1 - 1j, -1 + 1j, -1 - 1j]
elif M == 16:
raise ValueError("TODO: implement gray code for M=16")
return [-3 - 3j, -3 - 1j, -3 + 3j, -3 + 1j, -1 - 3j, -1 - 1j, -1 + 3j, -1 + 1j, 3 - 3j, 3 - 1j, 3 + 3j, 3 + 1j,
1 - 3j, 1 - 1j, 1 + 3j, 1 + 1j]
else:
N = np.sqrt(M) - 1
aux = np.arange(-N, N + 1, 2)
x, y = np.meshgrid(aux[::-1], aux[::-1])
a = (x + y * 1j).T
size_a = len(a) ** 2
b = np.zeros(size_a, dtype=complex)
c = 0
i = 0
j = 0
while c < size_a:
b[c] = a[j][i]
c += 1
if (i == len(a) - 1 and j % 2 == 0) or (i == 0 and j % 2 == 1):
j += 1
continue
if j % 2 == 1:
i -= 1
else:
i += 1
return b
raise ValueError("This mapping does not exist yet... He he he")


def psk_map(M):
"""
:param M: the size of our mapping, i.e the number of symbols we can send
:return: the array of symbols of the M-PSK (Pulse Shift Keying)
Compute the array of symbols that correspond to a M-PSK mapping
:param M: The size of our mapping, i.e the number of symbols we can send
:return: The array of symbols of the M-PSK (Pulse Shift Keying)
"""
return np.exp(1j * 2 * np.pi * np.arange(0, M) / M)


def pam_map(M):
"""
:param M: the size of our mapping, i.e the number of symbols we can send
:return: the array of symbols of the M-PAM (Pulse Amplitude Modulation)
Compute the array of symbols that correspond to a M-PAM mapping
:param M: The size of our mapping, i.e the number of symbols we can send
:return: The array of symbols of the M-PAM (Pulse Amplitude Modulation)
"""
if M % 2 != 0:
raise ValueError('Parameter[M] is not even.')
Expand All @@ -61,7 +49,10 @@ def pam_map(M):

def choose_mapping(normalize=params.NORMALIZE_MAPPING):
"""
:return: The mapping corresponding to the given mapping
Choose the mapping according to the parameters in the file params.py
:param normalize: Rather we normalize the mapping or not
:return: The corresponding array of symbols
"""
if params.MAPPING == "qam":
chosen_mapping = qam_map(params.M)
Expand All @@ -85,8 +76,3 @@ def choose_mapping(normalize=params.NORMALIZE_MAPPING):
color="red", annotate=True)

return chosen_mapping


# Intended for testing (to run the program, run main.py)
if __name__ == "__main__":
print(qam_map(16))
Loading

0 comments on commit 42858c9

Please sign in to comment.