def choose_mapping(normalize=params.NORMALIZE_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) elif params.MAPPING == "psk": chosen_mapping = psk_map(params.M) elif params.MAPPING == "pam": chosen_mapping = pam_map(params.M) else: raise ValueError('No modulation of this type is defined') if normalize: chosen_mapping = chosen_mapping / np.sqrt( np.mean(np.abs(chosen_mapping)**2)) if params.logs: print("Choosing the mapping...") print("Mapping: {}".format(chosen_mapping)) print("Normalized = {}".format(params.NORMALIZE_MAPPING)) print("--------------------------------------------------------") if params.plots: plot_helper.plot_complex_symbols( chosen_mapping, title="Chosen mapping, normalized={}".format(normalize), color="red", annotate=True) return chosen_mapping
def generate_preamble_to_transmit(len_data_symbols): """ Generate preamble symbols that will be used to synchronize our signal at the receiver :param len_data_symbols: The number of symbols to transmit (useful in case of a random preamble) :return: The preamble symbols that were generated """ if params.logs: print("Generating the preamble...") preambles.generate_preamble_symbols(len_data_symbols) preamble_symbols = read_write.read_preamble_symbols() if params.plots: plot_helper.plot_complex_symbols(preamble_symbols, "Preamble symbols") if params.logs: print("Preamble symbols:\n{}".format(preamble_symbols)) print("--------------------------------------------------------") return preamble_symbols
def concatenate_symbols(preamble_symbols, data_symbols): """ Construct the symbols to send by concatenating the data symbols with the preamble symbols at the beginning and at the end :param preamble_symbols: The preamble symbols to stick to the data symbols :param data_symbols: The symbols that carry the data to send :return: The symbols that result from the concatenation """ if params.logs: print( "Concatenating everything together (preamble-data-flipped preamble)..." ) if params.MOD == 1 or params.MOD == 2: p_data_p_symbols = np.concatenate( (preamble_symbols, data_symbols[0], preamble_symbols[::-1])) if params.logs: print("Total symbols: {}".format(p_data_p_symbols)) print("Number of symbols in total: {}".format( np.shape(p_data_p_symbols))) elif params.MOD == 3: p_data_p_symbols = [] for i in range(len(data_symbols)): p_data_p_symbols.append( np.concatenate((preamble_symbols, data_symbols[i], preamble_symbols[::-1]))) if params.logs: print("Shape of the total symbols: {}".format( np.shape(p_data_p_symbols))) if params.plots: for i in range(len(p_data_p_symbols)): plot_helper.plot_complex_symbols(p_data_p_symbols[i], "Symbols {}".format(i)) else: raise ValueError("This mapping type does not exist yet... He he he") if params.logs: print("--------------------------------------------------------") return p_data_p_symbols
def downsample(data_samples): """ Down-sample the data samples to obtain the received symbols :param data_samples: The data samples :return: The data symbols received """ if params.logs: print("Down-sampling...") if params.MOD == 1 or params.MOD == 2: data_symbols = data_samples[::params.USF] if params.logs: print("Number of symbols received: {}".format(len(data_symbols))) if params.plots: plot_helper.plot_complex_function(data_symbols, "y without preamble") plot_helper.plot_complex_symbols(data_symbols, "Symbols received", annotate=False) elif params.MOD == 3: data_symbols = [] for i in range(len(data_samples)): data_symbols.append(data_samples[i][::params.USF]) if params.logs: print("Shape of the received symbols: {}".format( np.shape(data_symbols))) if params.plots: for i in range(len(data_symbols)): plot_helper.plot_complex_function(data_symbols[i], "Samples without preamble") plot_helper.plot_complex_symbols(data_symbols[i], "Symbols received", annotate=False) else: raise ValueError("This modulation type does not exist yet... He he he") if params.logs: print("--------------------------------------------------------") return data_symbols
def grouped_bytes_to_symbols(grouped_bytes): """ Associate the message bits to an array of corresponding symbols that depend on the chosen mapping :param grouped_bytes: The bits corresponding to the message as an array of 0 and 1 :return: The corresponding symbols to the given message as an array """ if params.logs: print("Mapping message bytes to the symbols from our mapping...") if params.MOD == 1 or params.MOD == 2: # New structure with bits_per_symbol bits by row grouped_bytes = [ grouped_bytes[i:i + params.BITS_PER_SYMBOL] for i in range(0, len(grouped_bytes), params.BITS_PER_SYMBOL) ] # Convert this new bits sequence to an integer sequence ints = [[int(b, 2) for b in grouped_bytes]] if params.logs: print("Cropped and re-structured bits:\n{}".format(grouped_bytes)) print("Equivalent integers (indices for our mapping):\n{}".format( ints)) elif params.MOD == 3: # Choose the number of bit streams (depends on the number of frequency ranges) n_bit_streams = len(params.FREQ_RANGES) # Choose the length of our bit streams len_bit_streams = int(np.ceil( len(grouped_bytes) / (n_bit_streams - 1))) # Make it even while len_bit_streams % params.BITS_PER_SYMBOL != 0: len_bit_streams += 1 # Construct the bit streams array with zeros bit_streams = np.zeros((n_bit_streams, len_bit_streams), dtype=int) # Fill the bit streams arrays for i in range(len(grouped_bytes)): bit_streams[i % (n_bit_streams - 1)][int( np.floor(i / (n_bit_streams - 1)))] = grouped_bytes[i] # Construct the parity check bit stream and insert it in the bit streams array pc_bit_stream = np.sum(bit_streams[:n_bit_streams - 1], axis=0) for i in range(len_bit_streams): pc_bit_stream[i] = 0 if pc_bit_stream[i] % 2 == 0 else 1 bit_streams[n_bit_streams - 1] = pc_bit_stream if params.logs: print("Bit streams: {}".format(np.shape(bit_streams))) for i in range(len(bit_streams)): print("{}".format(bit_streams[i])) print() # Group them by groups of BITS_PER_SYMBOL bits ints = np.zeros( (n_bit_streams, int(len_bit_streams / params.BITS_PER_SYMBOL)), dtype=str) for i in range(n_bit_streams): for j in range(int(len_bit_streams / params.BITS_PER_SYMBOL)): index = j * params.BITS_PER_SYMBOL grouped_bits = ''.join( map(str, bit_streams[i][index:index + params.BITS_PER_SYMBOL])) mapping_index = int(grouped_bits, base=2) ints[i][j] = mapping_index if params.logs: print("Ints bits stream {}:\n{}".format(ints.shape, ints)) else: raise ValueError("This modulation type does not exist yet... He he he") corresponding_symbols = np.zeros(np.shape(ints), dtype=complex) if params.logs: print("--------------------------------------------------------") mapping = mappings.choose_mapping() for i in range(len(ints)): corresponding_symbols[i] = [mapping[int(j)] for j in ints[i]] if params.logs: print("Symbols/n-tuples to be sent: {}\n{}".format( np.shape(corresponding_symbols), corresponding_symbols)) if params.plots: plot_helper.plot_complex_symbols( corresponding_symbols, "{} data symbols to send".format(np.shape(corresponding_symbols)), "blue") if params.logs: print("--------------------------------------------------------") return corresponding_symbols
def local_test(): """ Test the design locally with modulation and demodulation :return: None """ 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(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, data_symbols[0], preamble_symbols[::-1])) # Shape the signal with the pulse h total_samples = upfirdn(h, total_symbols, params.USF) print("Shaping the preamble...") print("Number of symbols for the preamble: {}".format( len(preamble_symbols))) print("Number of samples for the preamble: {}".format( len(preamble_samples))) print("--------------------------------------------------------") plot_helper.plot_complex_function( preamble_samples, "Synchronization sequence shaped, in Time domain") plot_helper.fft_plot( preamble_samples, "Synchronization sequence shaped, in Frequency domain", shift=True) print("Shaping the preamble-data-preamble...") print("Up-sampling factor: {}".format(params.USF)) print("Number of samples: {}".format(len(total_samples))) print("Minimum sample: {}".format(min(total_samples))) print("Maximum sample: {}".format(max(total_samples))) print("--------------------------------------------------------") plot_helper.plot_complex_function(total_samples, "Total samples in Time domain") plot_helper.fft_plot(total_samples, "Total samples in Frequency domain", shift=True) # Modulate the total_samples if params.MOD == 1: samples = fourier_helper.modulate_complex_samples( total_samples, params.np.mean(params.FREQ_RANGES, axis=1)) elif params.MOD == 2: samples = fourier_helper.modulate_complex_samples( total_samples, [params.FREQ_RANGES[0][1], params.FREQ_RANGES[2][1]]) else: raise ValueError('This modulation type does not exist yet... He he he') print("Modulation of the signal...") print("Number of samples: {}".format(len(samples))) print("Minimum sample after modulation: {}".format(min(samples))) print("Maximum sample after modulation: {}".format(max(samples))) print("--------------------------------------------------------") plot_helper.plot_complex_function( samples, "Input samples after modulation, in Time domain") plot_helper.fft_plot(samples, "Input samples after modulation, in Frequency domain", shift=True) # Scale the signal to the range [-1, 1] (with a bit of uncertainty margin, according to params.ABS_SAMPLE_RANGE) samples = (samples / (max(abs(samples))) * params.ABS_SAMPLE_RANGE) print("Scaling the signal...") print("Minimum sample after scaling: {}".format(min(samples))) print("Maximum sample after scaling: {}".format(max(samples))) print("--------------------------------------------------------") # read_write.write_samples(samples) # ---------------------------------------------------------------------------------------------------------------- # Channel simulation --------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------- # samples = server_simulation(samples, filter_freq=False) samples = np.loadtxt(params.input_sample_file_path) # ---------------------------------------------------------------------------------------------------------------- # Channel simulation's end --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------- # Supposed to retrieve the preamble symbols and samples from the appropriate files, but here we got it above plot_helper.plot_complex_function( samples, "Samples received from the simulated channel, time domain") plot_helper.fft_plot( samples, "Samples received from the simulated channel, frequency domain") # Find the frequency range that has been removed range_indices, removed_freq_range = fourier_helper.find_removed_freq_range( samples) print("Removed frequency range: {} (range {})".format( removed_freq_range, removed_freq_range + 1)) # Choose a frequency for demodulation 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.MOD == 2: if removed_freq_range == 0 or removed_freq_range == 1: fc = 7000 else: fc = 3000 else: raise ValueError('This modulation type does not exist yet... He he he') demodulated_samples = fourier_helper.demodulate(samples, fc) plot_helper.plot_complex_function(demodulated_samples, "Demodulated samples in Time domain") plot_helper.fft_plot(demodulated_samples, "Demodulated samples in Frequency domain", shift=True) # Match filter (i.e Low-pass) h_matched = np.conjugate(h[::-1]) y = np.convolve(demodulated_samples, h_matched) plot_helper.plot_complex_function(y, "y in Time domain") plot_helper.fft_plot(y, "y in Frequency domain", shift=True) # Find the delay 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[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") print("Number of samples for the actual preamble: {}".format( len_preamble_samples)) print("Number of samples for the received preamble: {}".format( len(preamble_samples_received))) # Compute the phase offset # We remove the rrc-equivalent-tail because there is data on the tail otherwise # TODO: why dot works and not vdot (supposed to conjugate the first term in the formula) dot_product = np.dot( preamble_samples[:len_preamble_samples - half_span_h], preamble_samples_received[:len(preamble_samples_received) - half_span_h]) print("Dot product: {}".format(dot_product)) preamble_energy = 0 for i in range(len_preamble_samples - half_span_h): preamble_energy += np.absolute(preamble_samples[i])**2 print("Energy of the preamble: {}".format(preamble_energy)) frequency_offset_estim = np.angle(dot_product) print("Frequency offset: {}".format(frequency_offset_estim)) scaling_factor = abs(dot_product) / preamble_energy print("Scaling factor: {}".format(scaling_factor)) # Crop the samples (remove the delay, the preamble, and the ramp-up) 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]) print("Second preamble index: {} samples".format(second_preamble_index)) print("--------------------------------------------------------") # Crop the samples (remove the preamble, and the garbage at the end) data_samples = data_samples[:second_preamble_index + half_span_h - params.USF + 1] plot_helper.plot_complex_function( data_samples, "y after removing the delay, the preamble, and the ramp-up") # TODO: why frequency_offset - pi/2 works ? data_samples = data_samples * np.exp(-1j * (frequency_offset_estim - np.pi / 2)) # Down-sample the samples to obtain the symbols data_symbols = data_samples[::params.USF] print("Number of symbols received: {}".format(len(data_symbols))) plot_helper.plot_complex_function(data_symbols, "y without preamble") plot_helper.plot_complex_symbols(data_symbols, "Symbols received", annotate=False) # Decode the symbols 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)