blob: f6abc8c865e1165573fe696cf5a82b4083a9ff88 [file] [log] [blame]
Philipp Led818bd52022-06-02 20:09:17 +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
9import copy
10from tkinter import ttk, LEFT, BOTH, BOTTOM
11from pydantic import confloat, conint
12from dcs.config import default_store, ConfigObject, ui_create, ConfigControlFrame
13import numpy as np
14import scipy.signal
15from dcs.frames.base import BaseFrame, Window
16from dcs.frames.groups import Ch07Group
17from dcs.utils.chain import TxBasebandGenerator, RxCorrelationGenerator, RfBandGenerator
18from dcs.utils.filter import create_lp_filter_class, NoFilter
19from dcs.utils.iq_mixer import create_iq_oscillator_class, IqUpMixer, IqDownMixer, IqRfBandGenerator, \
20 IqRxBasebandGenerator, IqTxBasebandGenerator, IqOscillatorBase
21from dcs.utils.qam_modulation import ModulationMethod, QamBasebandModulator, QamBasebandGenerator
22from dcs.utils.symbols import MessageBinarySerializer, Symbols
23from dcs.utils.spreading import create_spreading_config_class, Spreader, Despreader, SpreadedBasebandSpectrum3D
24from typing import Dict, List
25
26import matplotlib
27matplotlib.use('TkAgg')
28from matplotlib.pyplot import Circle, Text
29from matplotlib.figure import Figure
30from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
31
32
33SAMPLE_RATE = 10000
34FFT_SAMPLES_PER_SYM = 128
35
36TxBasebandFilter = create_lp_filter_class(SAMPLE_RATE, 'TX Baseband')
37
38Spreading = create_spreading_config_class(SAMPLE_RATE)
39
40
41@ui_create
42class User(ConfigObject):
43 message: str = 'DCS'
44 spreading: Spreading = Spreading()
45 modulation: ModulationMethod = ModulationMethod.BPSK
46 tx_lp: TxBasebandFilter = TxBasebandFilter()
47
48 def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
49 frm = ConfigControlFrame(parent)
50
51 frm1 = ConfigControlFrame(frm, borderwidth=1, relief='raised')
52 frm1.pack()
53 spreading_frame = self.spreading.make_config_widget(frm1)
54 spreading_frame.pack()
55
56 frm2 = ConfigControlFrame(frm, borderwidth=1, relief='raised')
57 frm2.pack()
58 ttk.Label(frm2, text='Message:').grid(row=0, column=0)
59 self.ui_create_message(frm2).grid(row=0, column=1)
60
61 ttk.Label(frm2, text='Modulation:').grid(row=1, column=0)
62 self.ui_create_modulation_dropdown(frm2).grid(row=1, column=1)
63
64 ttk.Label(frm2, text='TX Baseband Low Pass Cut-Off:').grid(row=2, column=0)
65 self.tx_lp.ui_create_cutoff(frm2).grid(row=2, column=1)
66 ttk.Label(frm2, text='Hz').grid(row=2, column=2)
67
68 return frm
69
70 def make_title(self):
71 return f'{self.message} - {self.spreading.make_title()}'
72
73 def get_symbols(self) -> Symbols:
74 return MessageBinarySerializer(self.message).to_symbols()
75
76 def make_unspreaded_signal(self) -> TxBasebandGenerator:
77 return QamBasebandGenerator(
78 self.get_symbols(),
79 QamBasebandModulator(
80 symbol_rate=self.make_spreader().get_symbol_rate(),
81 method=ModulationMethod.BPSK,
82 baseband_filter=NoFilter(),
83 )
84 )
85
86 def make_spreader(self) -> Spreader:
87 return self.spreading.make_spreader(self.modulation, self.tx_lp)
88
89 def make_despreader(self) -> Despreader:
90 return self.spreading.make_despreader(self.modulation, self.tx_lp)
91
92
93@ui_create
94class ConfigCh07Spreading(ConfigObject):
95 _KEY = 'ch07_spreading'
96
97 users: List[User] = [
98 User()
99 ]
100 #tx_osc: IqOscillator = IqOscillator(freq=100.0)
101 noise_dBc: confloat(ge=-160.0, le=40.0, multiple_of=0.1) = -60.0
102 random_seed: conint(ge=0, lt=10000) = 1000
103 #rx_osc: IqOscillator = IqOscillator(freq=100.0)
104 #rx_lp: RxBasebandFilter = RxBasebandFilter()
105
106 def _noise_frame(self, parent: ttk.Widget) -> ConfigControlFrame:
107 frm = ConfigControlFrame(parent, borderwidth=1, relief='raised')
108
109 ttk.Label(frm, text='Signal-Noise Ratio:').grid(row=0, column=0)
110 w = self.ui_create_noise_dBc(frm)
111 frm.add_widget(w)
112 w.grid(row=0, column=1)
113 ttk.Label(frm, text='dBc').grid(row=0, column=2)
114
115 ttk.Label(frm, text='Random Seed:').grid(row=1, column=0)
116 w = self.ui_create_random_seed(frm)
117 frm.add_widget(w)
118 w.grid(row=1, column=1)
119
120 return frm
121
122 def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
123 frm = ConfigControlFrame(parent, borderwidth=1, relief='raised')
124
125 user_frm = ttk.Frame(frm, borderwidth=1, relief='raised')
126 user_frm.pack()
127 ttk.Label(user_frm, text='TX Oscillator:').pack()
128 w = self.ui_create_users_list(user_frm, lambda e: e.make_title())
129 frm.add_widget(w)
130 w.pack()
131
132 noise_frm = self._noise_frame(frm)
133 noise_frm.pack()
134 for w in noise_frm.ctrl_widgets:
135 frm.add_widget(w)
136
137 return frm
138
139
140class Ch07SpreadingFrame(BaseFrame):
141 def __init__(self, *args, **kwargs):
142 super().__init__(*args, **kwargs)
143
144 self._config: ConfigCh07Spreading = default_store().get_config(ConfigCh07Spreading)
145
146 ctrl_frm = self._create_control()
147 ctrl_frm.pack(side=LEFT)
148
149 signal_frm = self._create_signal_tabs()
150 signal_frm.pack(expand=True, fill=BOTH)
151
152 def _create_control(self) -> ConfigControlFrame:
153 frm = self._config.make_config_widget(self)
154 frm.widgets_on_change(self._on_change)
155 return frm
156
157 def _on_change(self, _, __, ___):
158 default_store().save()
159 self.refresh()
160
161 def _create_signal_tabs(self) -> ttk.Widget:
162 tabs = ttk.Notebook(self)
163
164 tx_frm = ttk.Frame(tabs)
165 tabs.add(tx_frm, text='TX Baseband')
166 self._tx_fig = Figure(figsize=(12, 6), dpi=100)
167 self._tx_canvas = FigureCanvasTkAgg(self._tx_fig, tx_frm)
168 self._tx_canvas.get_tk_widget().pack(expand=True, fill=BOTH)
169 tx_tb = NavigationToolbar2Tk(self._tx_canvas, tx_frm, pack_toolbar=False)
170 tx_tb.pack(side=BOTTOM)
171
172 self._data_bytes = ttk.Label(tx_frm, text='')
173 self._data_bytes.pack(side=BOTTOM)
174 self._data_syms = ttk.Label(tx_frm, text='')
175 self._data_syms.pack(side=BOTTOM)
176
177 fd_frm = ttk.Frame(tabs)
178 tabs.add(fd_frm, text='Frequency Domain')
179 self._fd_fig = Figure(figsize=(12, 6), dpi=100)
180 self._fd_canvas = FigureCanvasTkAgg(self._fd_fig, fd_frm)
181 self._fd_canvas.get_tk_widget().pack(expand=True, fill=BOTH)
182 fd_tb = NavigationToolbar2Tk(self._fd_canvas, fd_frm, pack_toolbar=False)
183 fd_tb.pack(side=BOTTOM)
184
185 rx_frm = ttk.Frame(tabs)
186 tabs.add(rx_frm, text='RX Cross-Correlation')
187 self._rx_fig = Figure(figsize=(12, 6), dpi=100)
188 self._rx_canvas = FigureCanvasTkAgg(self._rx_fig, rx_frm)
189 self._rx_canvas.get_tk_widget().pack(expand=True, fill=BOTH)
190 rx_tb = NavigationToolbar2Tk(self._rx_canvas, rx_frm, pack_toolbar=False)
191 rx_tb.pack(side=BOTTOM)
192
193 self.refresh()
194
195 return tabs
196
197 def refresh(self):
198 if len(self._config.users) > 0:
199 user0 = self._config.users[0]
200 user0_spreader = user0.make_spreader()
201 tx_gen0 = user0_spreader.make_tx_baseband_generator(user0.get_symbols())
202
203 unspreaded_gen0 = user0.make_unspreaded_signal()
204
205 tx_gens = copy.deepcopy(tx_gen0)
206 for user in self._config.users[1:]:
207 tx_gens += user.make_spreader().make_tx_baseband_generator(user.get_symbols())
208
209 class _Osc(IqOscillatorBase):
210 def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
211 pass # Won't be called
212
213 rf_gen = IqRfBandGenerator(
214 tx_gens,
215 IqUpMixer(
216 tx_osc=_Osc(freq=0),
217 noise_dBc=self._config.noise_dBc,
218 random_seed=self._config.random_seed,
219 )
220 )
221 # rx_gen = rf_gen.make_iq_down_mixer(
222 # IqDownMixer(
223 # rx_osc=_Osc(freq=0),
224 # baseband_filter=NoFilter(),
225 # )
226 # )
227
228 rx_gen = user0.make_despreader().make_rx_correlation_generator(rf_gen)
229
230 # Draw
231 self.draw_tx(tx_gen0, unspreaded_gen0, rf_gen)
232 self.draw_fd(rf_gen, user0_spreader)
233 self.draw_rx(rx_gen, unspreaded_gen0)
234
235 def draw_tx(self, tx_gen0: TxBasebandGenerator, unspreaded_gen0: TxBasebandGenerator, rf_gen: RfBandGenerator):
236 self._tx_fig.clear()
237
238 ax_msg = self._tx_fig.add_subplot(3, 1, 1)
239 ax_msg.set_xlabel('time')
240 ax_msg.set_ylabel('value')
241 ax_msg.set_title('User 0 Message (Binary)')
242 ax_u0 = self._tx_fig.add_subplot(3, 1, 2)
243 ax_u0.set_xlabel('time')
244 ax_u0.set_ylabel('value')
245 ax_u0.set_title('User 0 TX Baseband Signal')
246 ax_tx = self._tx_fig.add_subplot(3, 1, 3)
247 ax_tx.set_xlabel('time')
248 ax_tx.set_ylabel('value')
249 ax_tx.set_title('TX Baseband Signal')
250
251 sig_msg = unspreaded_gen0.generate_tx_baseband_signal(SAMPLE_RATE)
252 ax_msg.set_xlim(np.min(sig_msg.t), np.max(sig_msg.t))
253 ax_msg.plot(sig_msg.t, np.real(sig_msg.signal), label='Message', color='blue', linewidth=1)
254
255 sig_u0 = tx_gen0.generate_tx_baseband_signal(SAMPLE_RATE)
256 ax_u0.set_xlim(np.min(sig_u0.t), np.max(sig_u0.t))
257 ax_u0.plot(sig_u0.t, np.real(sig_u0.signal), label='User 0', color='red', linewidth=1)
258
259 sig_tx = rf_gen.generate_rf_signal(SAMPLE_RATE)
260 ax_tx.set_xlim(np.min(sig_tx.t), np.max(sig_tx.t))
261 ax_tx.plot(sig_tx.t, np.real(sig_tx.signal), label='Baseband', color='brown', linewidth=1)
262
263 self._tx_fig.tight_layout()
264 self._tx_canvas.draw()
265
266 def draw_fd(self, rf_gen: RfBandGenerator, spreader: Spreader):
267 self._fd_fig.clear()
268
269 ax_fd = self._fd_fig.add_subplot(1, 1, 1, projection='3d')
270 ax_fd.set_xlabel('frequency')
271 ax_fd.set_ylabel('symbol')
272 ax_fd.set_zlabel('value (logarithmic)')
273 ax_fd.set_title('HF Signal Frequency-Domain')
274 ax_fd.set_zlim(-120, 40)
275
276 x, y, z = SpreadedBasebandSpectrum3D(
277 rf_gen,
278 spreader.get_symbol_rate(),
279 FFT_SAMPLES_PER_SYM
280 ).make_plot_data()
281
282 ax_fd.plot_trisurf(x, y, z)
283
284 self._fd_fig.tight_layout()
285 self._fd_canvas.draw()
286
287 def draw_rx(self, rx_correlation: RxCorrelationGenerator, unspreaded_gen0: TxBasebandGenerator):
288 self._rx_fig.clear()
289
290 ax_msg = self._rx_fig.add_subplot(2, 1, 1)
291 ax_msg.set_xlabel('time')
292 ax_msg.set_ylabel('value')
293 ax_msg.set_title('User 0 Message (Binary)')
294 ax_correlation = self._rx_fig.add_subplot(2, 1, 2)
295 ax_correlation.set_xlabel('time')
296 ax_correlation.set_ylabel('value')
297 ax_correlation.set_title('RX Correlation')
298
299 sig_msg = unspreaded_gen0.generate_tx_baseband_signal(SAMPLE_RATE)
300 ax_msg.set_xlim(np.min(sig_msg.t), np.max(sig_msg.t))
301 ax_msg.plot(sig_msg.t, np.real(sig_msg.signal), label='Message', color='blue', linewidth=1)
302
303 corr = rx_correlation.generate_rx_correlation(SAMPLE_RATE)
304 ax_correlation.set_xlim(np.min(corr.t), np.max(corr.t))
305 ax_correlation.plot(corr.t, np.real(corr.signal), label='Correlation', color='blue', linewidth=1)
306
307 self._rx_fig.tight_layout()
308 self._rx_canvas.draw()
309
310
311class Ch07SpreadingWindow(Window):
312 GROUP = Ch07Group
313 TITLE = 'Spread Spectrum'
314 FRAME = Ch07SpreadingFrame
315
316
317if __name__ == '__main__':
318 Ch07SpreadingWindow.main()
319