blob: ffb990e4d86628c33c958f947af79829cb385fc0 [file] [log] [blame]
Philipp Lef65de0e2022-06-02 20:08:43 +02001# SPDX-License-Identifier: MPL-2.0
2# Copyright (c) 2022 Philipp Le <philipp@philipple.de>.
3# This Source Code Form is subject to the terms of the Mozilla Public
4# License, v. 2.0. If a copy of the MPL was not distributed with this
5# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
7from __future__ import annotations
8
9from tkinter import ttk
10from pydantic import confloat
11from dcs.config import ConfigObject, ui_create, ConfigControlFrame
12from abc import ABC, abstractmethod
13from dataclasses import dataclass
14from enum import Enum
15import numpy as np
16from typing import Optional, Type, Callable, Tuple
17from .signal import Signal
18from .chain import RfBandGenerator, TxBasebandGenerator, RxCorrelationGenerator
19from .filter import Filter, NoFilter
20from .iq_mixer import IqTxBasebandGenerator
21from .qam_modulation import ModulationMethod, QamBasebandModulator, QamBasebandGenerator
22from .symbols import Symbols
23
24
25#####################################################################
26# Configuration Items
27
28
29class SpreadingMethod(str, Enum):
30 DSSS_ASYNC = 'Asynchronous CDMA / DSSS'
31
32 def get_spreader(self) -> Type[Spreader]:
33 return {
34 self.DSSS_ASYNC: AsyncDsssSpreader
35 }[self]
36
37 def get_despreader(self) -> Type[Despreader]:
38 return {
39 self.DSSS_ASYNC: AsyncDsssDespreader
40 }[self]
41
42
43class SpreadingCode(ABC):
44 @abstractmethod
45 def get_code(self) -> CodeSequence:
46 ...
47
48 def get_spreading_factor(self) -> int:
49 return self.get_code().sequence.bits_per_symbol
50
51 def get_hopping_length(self) -> int:
52 return len(self.get_code().sequence)
53
54
55class AsyncSpreadingCode(SpreadingCode, ABC):
56 pass
57
58
59class AsyncDsssCode(str, Enum):
60 IEEE80211B = 'IEEE 802.11b'
61
62 def make_code(self) -> SpreadingCode:
63 code = self
64
65 class _Code(AsyncSpreadingCode):
66 def get_code(self) -> CodeSequence:
67 return {
68 AsyncDsssCode.IEEE80211B: CodeSequence(
69 sequence=Symbols(
70 # symbols=[1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0b01001000111],
71 symbols=[0b01001000111],
72 bits_per_symbol=11
73 )
74 ),
75 }[code]
76
77 return _Code()
78
79
80@ui_create
81class SpreadingConfigBase(ConfigObject, ABC):
82 method: SpreadingMethod = SpreadingMethod.DSSS_ASYNC
83 async_dsss_code: AsyncDsssCode = AsyncDsssCode.IEEE80211B
84 chip_rate: confloat(ge=0) = 10.0
85
86 @abstractmethod
87 def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
88 ...
89
90 @abstractmethod
91 def _get_chip_rate(self) -> float:
92 ...
93
94 def make_title(self):
95 if self.method == SpreadingMethod.DSSS_ASYNC:
96 return f'DSSS Code: {self.async_dsss_code.value}'
97 else:
98 raise ValueError(f'Invalid spreading method {self.method}')
99
100 def _get_code(self) -> SpreadingCode:
101 if self.method == SpreadingMethod.DSSS_ASYNC:
102 return self.async_dsss_code.make_code()
103 else:
104 raise ValueError(f'Invalid spreading method {self.method}')
105
106 def make_spreader(self, modulation: ModulationMethod, baseband_filter: Filter):
107 cls = self.method.get_spreader()
108 return cls(
109 code=self._get_code(),
110 chip_rate=self._get_chip_rate(),
111 modulation=modulation,
112 baseband_filter=baseband_filter
113 )
114
115 def make_despreader(self, modulation: ModulationMethod, baseband_filter: Filter):
116 cls = self.method.get_despreader()
117 return cls(
118 code=self._get_code(),
119 chip_rate=self._get_chip_rate(),
120 modulation=modulation,
121 baseband_filter=baseband_filter
122 )
123
124
125def create_spreading_config_class(sample_rate: int, global_chip_rate: Optional[Callable[[], float]] = None):
126 @ui_create
127 class _SpreadingConfig(SpreadingConfigBase):
128 chip_rate: confloat(ge=0, lt=sample_rate/4, multiple_of=(4.0/sample_rate)) = 10.0
129
130 def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
131 frm = ConfigControlFrame(parent)
132
133 ttk.Label(frm, text='Spreading Method:').grid(row=0, column=0)
134 w = self.ui_create_method_dropdown(frm)
135 frm.add_widget(w)
136 w.grid(row=0, column=1)
137
138 ttk.Label(frm, text='DSSS Code:').grid(row=1, column=0)
139 w = self.ui_create_async_dsss_code_dropdown(frm)
140 frm.add_widget(w)
141 w.grid(row=1, column=1)
142
143 # ttk.Label(frm, text='DSSS Code:').grid(row=2, column=0)
144 # w = self.ui_create_sync_dsss_code_dropdown(frm)
145 # frm.add_widget(w)
146 # w.grid(row=2, column=1)
147
148 # ttk.Label(frm, text='FHSS Code:').grid(row=3, column=0)
149 # w = self.ui_create_fhss_code_dropdown(frm)
150 # frm.add_widget(w)
151 # w.grid(row=3, column=1)
152
153 # ttk.Label(frm, text='THSS Code:').grid(row=4, column=0)
154 # w = self.ui_create_thss_code_dropdown(frm)
155 # frm.add_widget(w)
156 # w.grid(row=4, column=1)
157
158 if global_chip_rate is None:
159 ttk.Label(frm, text='Chip Rate:').grid(row=5, column=0)
160 w = self.ui_create_chip_rate(frm)
161 frm.add_widget(w)
162 w.grid(row=5, column=1)
163 ttk.Label(frm, text='Hz').grid(row=4, column=2)
164
165 return frm
166
167 def _get_chip_rate(self) -> float:
168 if global_chip_rate is None:
169 return self.chip_rate
170 else:
171 return global_chip_rate()
172
173 _SpreadingConfig.update_forward_refs(sample_rate=sample_rate)
174
175 return _SpreadingConfig
176
177
178#####################################################################
179# Spectrum Plotting Helper
180
181
182class SpreadedBasebandSpectrum3D:
183 def __init__(self, gen: RfBandGenerator, symbol_rate: float, samples_per_sym: int):
184 self.gen = gen
185 self.symbol_rate = symbol_rate
186 self.samples_per_sym = samples_per_sym
187
188 def make_plot_data(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
189 sample_rate = self.symbol_rate * self.samples_per_sym
190 base_sig = self.gen.generate_rf_signal(sample_rate)
191
192 symbols_sigs = base_sig.split(self.samples_per_sym)
193
194 x = np.zeros(0)
195 y = np.zeros(0)
196 z = np.zeros(0)
197 for idx, sig in enumerate(symbols_sigs):
198 f, fd = sig.fft()
199 x = np.concatenate((x, f))
200 y = np.concatenate((y, np.ones(len(f)) * idx))
201 z = np.concatenate((z, fd))
202
203 return np.real(x), np.real(y), np.real(z)
204
205
206#####################################################################
207# Code
208
209
210@dataclass
211class CodeSequence:
212 sequence: Symbols
213
214 def make_qam_bandband_generator(self, chip_rate: float, baseband_filter: Filter) -> QamBasebandGenerator:
215 return QamBasebandGenerator(
216 self.sequence,
217 QamBasebandModulator(
218 symbol_rate=chip_rate,
219 method=ModulationMethod.BPSK,
220 baseband_filter=baseband_filter
221 )
222 )
223
224 def calc_signal(self, chip_rate: float, sample_rate: float, baseband_filter: Filter = NoFilter()) -> Signal:
225 return self.make_qam_bandband_generator(
226 chip_rate,
227 baseband_filter=baseband_filter
228 ).generate_tx_baseband_signal(sample_rate)
229
230
231#####################################################################
232# Spreader
233
234
235class Spreader(ABC):
236 def __init__(self, code: SpreadingCode, chip_rate: float, modulation: ModulationMethod, baseband_filter: Filter):
237 self.code = code
238 self.chip_rate = chip_rate
239 self.modulation = modulation
240 self.baseband_filter = baseband_filter
241
242 @abstractmethod
243 def make_tx_baseband_generator(self, message: Symbols) -> IqTxBasebandGenerator:
244 ...
245
246 def calc_tx_baseband(self, message: Symbols, sample_rate: float) -> Signal:
247 gen = self.make_tx_baseband_generator(message)
248 return gen.generate_tx_baseband_signal(sample_rate)
249
250 @abstractmethod
251 def get_symbol_rate(self) -> float:
252 ...
253
254
255class AsyncDsssSpreader(Spreader):
256 def make_tx_baseband_generator(self, message: Symbols) -> IqTxBasebandGenerator:
257 assert isinstance(self.code, AsyncSpreadingCode), f'Code must be AsyncSpreadingCode'
258 spread_sequence = []
259 for msg_sym in message.reencode(1).symbols:
260 for code_sym in self.code.get_code().sequence.reencode(1).symbols:
261 spread_sequence.append(msg_sym ^ code_sym)
262 spread_sym = Symbols(symbols=spread_sequence, bits_per_symbol=1)
263 return QamBasebandGenerator(
264 symbols=spread_sym,
265 qam_mod=QamBasebandModulator(
266 method=self.modulation,
267 symbol_rate=self.chip_rate,
268 baseband_filter=self.baseband_filter
269 )
270 )
271
272 def get_symbol_rate(self) -> float:
273 return self.chip_rate / self.code.get_spreading_factor()
274
275
276#####################################################################
277# Depreader
278
279
280class CrossCorrelationGenerator(RxCorrelationGenerator):
281 def __init__(self, rf_gen: RfBandGenerator, code_signal_gen: TxBasebandGenerator):
282 self.rf_gen = rf_gen
283 self.code_signal_gen = code_signal_gen
284
285 def generate_rx_correlation(self, sample_rate: float) -> Signal:
286 rf = self.rf_gen.generate_rf_signal(sample_rate)
287 code = self.code_signal_gen.generate_tx_baseband_signal(sample_rate)
288 corr = np.correlate(rf.signal, code.signal, mode='same')
289 return Signal(
290 t=rf.t,
291 signal=corr,
292 sample_rate=sample_rate
293 )
294
295
296class Despreader(ABC):
297 def __init__(self, code: SpreadingCode, chip_rate: float, modulation: ModulationMethod, baseband_filter: Filter):
298 self.code = code
299 self.chip_rate = chip_rate
300 self.modulation = modulation
301 self.baseband_filter = baseband_filter
302
303 @abstractmethod
304 def make_rx_correlation_generator(self, rf_gen: RfBandGenerator) -> RxCorrelationGenerator:
305 ...
306
307 def calc_rx_correlation(self, rf_gen: RfBandGenerator, sample_rate: float) -> Signal:
308 gen = self.make_rx_correlation_generator(rf_gen)
309 return gen.generate_rx_correlation(sample_rate)
310
311
312class AsyncDsssDespreader(Despreader):
313 def make_code_signal_generator(self) -> TxBasebandGenerator:
314 assert isinstance(self.code, AsyncSpreadingCode), f'Code must be AsyncSpreadingCode'
315 return self.code.get_code().make_qam_bandband_generator(self.chip_rate, self.baseband_filter)
316
317 def calc_code_signal(self, sample_rate: float) -> Signal:
318 gen = self.make_code_signal_generator()
319 return gen.generate_tx_baseband_signal(sample_rate)
320
321 def make_rx_correlation_generator(self, rf_gen: RfBandGenerator) -> RxCorrelationGenerator:
322 return CrossCorrelationGenerator(rf_gen, self.make_code_signal_generator())