blob: ffb990e4d86628c33c958f947af79829cb385fc0 [file] [log] [blame]
# SPDX-License-Identifier: MPL-2.0
# Copyright (c) 2022 Philipp Le <philipp@philipple.de>.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from __future__ import annotations
from tkinter import ttk
from pydantic import confloat
from dcs.config import ConfigObject, ui_create, ConfigControlFrame
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
import numpy as np
from typing import Optional, Type, Callable, Tuple
from .signal import Signal
from .chain import RfBandGenerator, TxBasebandGenerator, RxCorrelationGenerator
from .filter import Filter, NoFilter
from .iq_mixer import IqTxBasebandGenerator
from .qam_modulation import ModulationMethod, QamBasebandModulator, QamBasebandGenerator
from .symbols import Symbols
#####################################################################
# Configuration Items
class SpreadingMethod(str, Enum):
DSSS_ASYNC = 'Asynchronous CDMA / DSSS'
def get_spreader(self) -> Type[Spreader]:
return {
self.DSSS_ASYNC: AsyncDsssSpreader
}[self]
def get_despreader(self) -> Type[Despreader]:
return {
self.DSSS_ASYNC: AsyncDsssDespreader
}[self]
class SpreadingCode(ABC):
@abstractmethod
def get_code(self) -> CodeSequence:
...
def get_spreading_factor(self) -> int:
return self.get_code().sequence.bits_per_symbol
def get_hopping_length(self) -> int:
return len(self.get_code().sequence)
class AsyncSpreadingCode(SpreadingCode, ABC):
pass
class AsyncDsssCode(str, Enum):
IEEE80211B = 'IEEE 802.11b'
def make_code(self) -> SpreadingCode:
code = self
class _Code(AsyncSpreadingCode):
def get_code(self) -> CodeSequence:
return {
AsyncDsssCode.IEEE80211B: CodeSequence(
sequence=Symbols(
# symbols=[1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0b01001000111],
symbols=[0b01001000111],
bits_per_symbol=11
)
),
}[code]
return _Code()
@ui_create
class SpreadingConfigBase(ConfigObject, ABC):
method: SpreadingMethod = SpreadingMethod.DSSS_ASYNC
async_dsss_code: AsyncDsssCode = AsyncDsssCode.IEEE80211B
chip_rate: confloat(ge=0) = 10.0
@abstractmethod
def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
...
@abstractmethod
def _get_chip_rate(self) -> float:
...
def make_title(self):
if self.method == SpreadingMethod.DSSS_ASYNC:
return f'DSSS Code: {self.async_dsss_code.value}'
else:
raise ValueError(f'Invalid spreading method {self.method}')
def _get_code(self) -> SpreadingCode:
if self.method == SpreadingMethod.DSSS_ASYNC:
return self.async_dsss_code.make_code()
else:
raise ValueError(f'Invalid spreading method {self.method}')
def make_spreader(self, modulation: ModulationMethod, baseband_filter: Filter):
cls = self.method.get_spreader()
return cls(
code=self._get_code(),
chip_rate=self._get_chip_rate(),
modulation=modulation,
baseband_filter=baseband_filter
)
def make_despreader(self, modulation: ModulationMethod, baseband_filter: Filter):
cls = self.method.get_despreader()
return cls(
code=self._get_code(),
chip_rate=self._get_chip_rate(),
modulation=modulation,
baseband_filter=baseband_filter
)
def create_spreading_config_class(sample_rate: int, global_chip_rate: Optional[Callable[[], float]] = None):
@ui_create
class _SpreadingConfig(SpreadingConfigBase):
chip_rate: confloat(ge=0, lt=sample_rate/4, multiple_of=(4.0/sample_rate)) = 10.0
def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
frm = ConfigControlFrame(parent)
ttk.Label(frm, text='Spreading Method:').grid(row=0, column=0)
w = self.ui_create_method_dropdown(frm)
frm.add_widget(w)
w.grid(row=0, column=1)
ttk.Label(frm, text='DSSS Code:').grid(row=1, column=0)
w = self.ui_create_async_dsss_code_dropdown(frm)
frm.add_widget(w)
w.grid(row=1, column=1)
# ttk.Label(frm, text='DSSS Code:').grid(row=2, column=0)
# w = self.ui_create_sync_dsss_code_dropdown(frm)
# frm.add_widget(w)
# w.grid(row=2, column=1)
# ttk.Label(frm, text='FHSS Code:').grid(row=3, column=0)
# w = self.ui_create_fhss_code_dropdown(frm)
# frm.add_widget(w)
# w.grid(row=3, column=1)
# ttk.Label(frm, text='THSS Code:').grid(row=4, column=0)
# w = self.ui_create_thss_code_dropdown(frm)
# frm.add_widget(w)
# w.grid(row=4, column=1)
if global_chip_rate is None:
ttk.Label(frm, text='Chip Rate:').grid(row=5, column=0)
w = self.ui_create_chip_rate(frm)
frm.add_widget(w)
w.grid(row=5, column=1)
ttk.Label(frm, text='Hz').grid(row=4, column=2)
return frm
def _get_chip_rate(self) -> float:
if global_chip_rate is None:
return self.chip_rate
else:
return global_chip_rate()
_SpreadingConfig.update_forward_refs(sample_rate=sample_rate)
return _SpreadingConfig
#####################################################################
# Spectrum Plotting Helper
class SpreadedBasebandSpectrum3D:
def __init__(self, gen: RfBandGenerator, symbol_rate: float, samples_per_sym: int):
self.gen = gen
self.symbol_rate = symbol_rate
self.samples_per_sym = samples_per_sym
def make_plot_data(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
sample_rate = self.symbol_rate * self.samples_per_sym
base_sig = self.gen.generate_rf_signal(sample_rate)
symbols_sigs = base_sig.split(self.samples_per_sym)
x = np.zeros(0)
y = np.zeros(0)
z = np.zeros(0)
for idx, sig in enumerate(symbols_sigs):
f, fd = sig.fft()
x = np.concatenate((x, f))
y = np.concatenate((y, np.ones(len(f)) * idx))
z = np.concatenate((z, fd))
return np.real(x), np.real(y), np.real(z)
#####################################################################
# Code
@dataclass
class CodeSequence:
sequence: Symbols
def make_qam_bandband_generator(self, chip_rate: float, baseband_filter: Filter) -> QamBasebandGenerator:
return QamBasebandGenerator(
self.sequence,
QamBasebandModulator(
symbol_rate=chip_rate,
method=ModulationMethod.BPSK,
baseband_filter=baseband_filter
)
)
def calc_signal(self, chip_rate: float, sample_rate: float, baseband_filter: Filter = NoFilter()) -> Signal:
return self.make_qam_bandband_generator(
chip_rate,
baseband_filter=baseband_filter
).generate_tx_baseband_signal(sample_rate)
#####################################################################
# Spreader
class Spreader(ABC):
def __init__(self, code: SpreadingCode, chip_rate: float, modulation: ModulationMethod, baseband_filter: Filter):
self.code = code
self.chip_rate = chip_rate
self.modulation = modulation
self.baseband_filter = baseband_filter
@abstractmethod
def make_tx_baseband_generator(self, message: Symbols) -> IqTxBasebandGenerator:
...
def calc_tx_baseband(self, message: Symbols, sample_rate: float) -> Signal:
gen = self.make_tx_baseband_generator(message)
return gen.generate_tx_baseband_signal(sample_rate)
@abstractmethod
def get_symbol_rate(self) -> float:
...
class AsyncDsssSpreader(Spreader):
def make_tx_baseband_generator(self, message: Symbols) -> IqTxBasebandGenerator:
assert isinstance(self.code, AsyncSpreadingCode), f'Code must be AsyncSpreadingCode'
spread_sequence = []
for msg_sym in message.reencode(1).symbols:
for code_sym in self.code.get_code().sequence.reencode(1).symbols:
spread_sequence.append(msg_sym ^ code_sym)
spread_sym = Symbols(symbols=spread_sequence, bits_per_symbol=1)
return QamBasebandGenerator(
symbols=spread_sym,
qam_mod=QamBasebandModulator(
method=self.modulation,
symbol_rate=self.chip_rate,
baseband_filter=self.baseband_filter
)
)
def get_symbol_rate(self) -> float:
return self.chip_rate / self.code.get_spreading_factor()
#####################################################################
# Depreader
class CrossCorrelationGenerator(RxCorrelationGenerator):
def __init__(self, rf_gen: RfBandGenerator, code_signal_gen: TxBasebandGenerator):
self.rf_gen = rf_gen
self.code_signal_gen = code_signal_gen
def generate_rx_correlation(self, sample_rate: float) -> Signal:
rf = self.rf_gen.generate_rf_signal(sample_rate)
code = self.code_signal_gen.generate_tx_baseband_signal(sample_rate)
corr = np.correlate(rf.signal, code.signal, mode='same')
return Signal(
t=rf.t,
signal=corr,
sample_rate=sample_rate
)
class Despreader(ABC):
def __init__(self, code: SpreadingCode, chip_rate: float, modulation: ModulationMethod, baseband_filter: Filter):
self.code = code
self.chip_rate = chip_rate
self.modulation = modulation
self.baseband_filter = baseband_filter
@abstractmethod
def make_rx_correlation_generator(self, rf_gen: RfBandGenerator) -> RxCorrelationGenerator:
...
def calc_rx_correlation(self, rf_gen: RfBandGenerator, sample_rate: float) -> Signal:
gen = self.make_rx_correlation_generator(rf_gen)
return gen.generate_rx_correlation(sample_rate)
class AsyncDsssDespreader(Despreader):
def make_code_signal_generator(self) -> TxBasebandGenerator:
assert isinstance(self.code, AsyncSpreadingCode), f'Code must be AsyncSpreadingCode'
return self.code.get_code().make_qam_bandband_generator(self.chip_rate, self.baseband_filter)
def calc_code_signal(self, sample_rate: float) -> Signal:
gen = self.make_code_signal_generator()
return gen.generate_tx_baseband_signal(sample_rate)
def make_rx_correlation_generator(self, rf_gen: RfBandGenerator) -> RxCorrelationGenerator:
return CrossCorrelationGenerator(rf_gen, self.make_code_signal_generator())