not1mm 24.8.20__py3-none-any.whl → 24.9.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,502 @@
1
+ """
2
+ REF Contest, CW
3
+ Status: Active
4
+ Geographic Focus: France + overseas territories
5
+ Participation: Worldwide
6
+ Awards: Worldwide
7
+ Mode: CW
8
+ Bands: 80, 40, 20, 15, 10m
9
+ Classes: Single Op All Band
10
+ Single Op Single Band
11
+ Multi-Single
12
+ Club
13
+ SWL
14
+ Max power: HP: >100 Watts
15
+ LP: 100 Watts
16
+ QRP: 5 Watts
17
+
18
+ Exchange: French: RST + Department/Prefix
19
+ non-French: RST + Serial No.
20
+
21
+ Work stations: Once per band
22
+
23
+ QSO Points: French: 6 points per QSO with French station same continent
24
+ French: 15 points per QSO with French station on different continent
25
+ French: 1 point per QSO with non-French station same continent
26
+ French: 2 points per QSO with non-French station on different continent
27
+ non-French: 1 point per QSO with French station same continent
28
+ non-French: 3 points per QSO with French station on different continent
29
+
30
+ Multipliers: French/Corsica departments once per band
31
+ French overseas prefixes once per band
32
+ non-French DXCC countries once per band (available only to French stations)
33
+
34
+ Score Calculation: Total score = total QSO points x total mults
35
+
36
+ Upload log at: https://concours.r-e-f.org/contest/logs/upload-form/
37
+ Find rules at: https://concours.r-e-f.org/reglements/actuels/reg_cdfhfdx.pdf
38
+ Cabrillo name: REF-CW
39
+ Cabrillo name aliases: REF
40
+ """
41
+
42
+ import datetime
43
+ import logging
44
+ import platform
45
+
46
+ from pathlib import Path
47
+
48
+ from PyQt6 import QtWidgets
49
+
50
+ from not1mm.lib.plugin_common import gen_adif, get_points
51
+
52
+ from not1mm.lib.version import __version__
53
+
54
+ logger = logging.getLogger(__name__)
55
+
56
+ EXCHANGE_HINT = "Canton or #"
57
+
58
+ name = "French REF DX contest - CW"
59
+ cabrillo_name = "REF-CW"
60
+ mode = "CW" # CW SSB BOTH RTTY
61
+
62
+ columns = [
63
+ "YYYY-MM-DD HH:MM:SS",
64
+ "Call",
65
+ "Freq",
66
+ "Mode",
67
+ "Snt",
68
+ "Rcv",
69
+ "SentNr",
70
+ "RcvNr",
71
+ "M1",
72
+ "M2",
73
+ "PTS",
74
+ ]
75
+
76
+ advance_on_space = [True, True, True, True, True]
77
+
78
+ # 1 once per contest, 2 work each band, 3 each band/mode, 4 no dupe checking
79
+ dupe_type = 2
80
+
81
+
82
+ def init_contest(self):
83
+ """setup plugin"""
84
+ set_tab_next(self)
85
+ set_tab_prev(self)
86
+ interface(self)
87
+ self.next_field = self.other_2
88
+
89
+
90
+ def interface(self):
91
+ """Setup user interface"""
92
+ self.field1.show()
93
+ self.field2.show()
94
+ self.field3.show()
95
+ self.field4.show()
96
+ label = self.field3.findChild(QtWidgets.QLabel)
97
+ label.setText("Sent")
98
+ self.field3.setAccessibleName("Sent")
99
+ label = self.field4.findChild(QtWidgets.QLabel)
100
+ label.setText("Dep/Pfx/SN")
101
+ self.field4.setAccessibleName("Department, Prefix or SN")
102
+
103
+
104
+ def reset_label(self):
105
+ """reset label after field cleared"""
106
+
107
+
108
+ def set_tab_next(self):
109
+ """Set TAB Advances"""
110
+ self.tab_next = {
111
+ self.callsign: self.field3.findChild(QtWidgets.QLineEdit),
112
+ self.field1.findChild(QtWidgets.QLineEdit): self.field3.findChild(
113
+ QtWidgets.QLineEdit
114
+ ),
115
+ self.field2.findChild(QtWidgets.QLineEdit): self.field3.findChild(
116
+ QtWidgets.QLineEdit
117
+ ),
118
+ self.field3.findChild(QtWidgets.QLineEdit): self.field4.findChild(
119
+ QtWidgets.QLineEdit
120
+ ),
121
+ self.field4.findChild(QtWidgets.QLineEdit): self.callsign,
122
+ }
123
+
124
+
125
+ def set_tab_prev(self):
126
+ """Set TAB Advances"""
127
+ self.tab_prev = {
128
+ self.callsign: self.field4.findChild(QtWidgets.QLineEdit),
129
+ self.field1.findChild(QtWidgets.QLineEdit): self.callsign,
130
+ self.field2.findChild(QtWidgets.QLineEdit): self.callsign,
131
+ self.field3.findChild(QtWidgets.QLineEdit): self.callsign,
132
+ self.field4.findChild(QtWidgets.QLineEdit): self.field3.findChild(
133
+ QtWidgets.QLineEdit
134
+ ),
135
+ }
136
+
137
+
138
+ def set_contact_vars(self):
139
+ """
140
+ Contest Specific
141
+ Multipliers:
142
+ French/Corsica departments once per band
143
+ French overseas prefixes once per band
144
+ non-French DXCC countries once per band (available only to French stations)
145
+ """
146
+ self.contact["SNT"] = self.sent.text()
147
+ self.contact["RCV"] = self.receive.text()
148
+ self.contact["SentNr"] = self.other_1.text().upper()
149
+ self.contact["NR"] = self.other_2.text().upper()
150
+
151
+ self.contact["IsMultiplier1"] = 0
152
+ self.contact["IsMultiplier2"] = 0
153
+
154
+ if (
155
+ self.contact.get("CountryPrefix", "") == "F"
156
+ and self.contact.get("NR", "").isalpha()
157
+ ):
158
+ canton = self.contact.get("NR", "").upper()
159
+ band = self.contact.get("Band", "")
160
+ query = (
161
+ f"select count(*) as canton_count from dxlog where "
162
+ f"NR = '{canton}' "
163
+ f"and Band = '{band}' "
164
+ f"and ContestNR = {self.pref.get('contest', '1')};"
165
+ )
166
+ result = self.database.exec_sql(query)
167
+ count = int(result.get("canton_count", 0))
168
+ if count == 0:
169
+ self.contact["IsMultiplier1"] = 1
170
+
171
+ if self.contact.get("CountryPrefix", ""):
172
+ dxcc = self.contact.get("CountryPrefix", "")
173
+ band = self.contact.get("Band", "")
174
+ query = (
175
+ f"select count(*) as dxcc_count from dxlog where "
176
+ f"CountryPrefix = '{dxcc}' "
177
+ f"and Band = '{band}' "
178
+ f"and ContestNR = {self.pref.get('contest', '1')};"
179
+ )
180
+ result = self.database.exec_sql(query)
181
+ if not result.get("dxcc_count", ""):
182
+ self.contact["IsMultiplier2"] = 1
183
+
184
+
185
+ def predupe(self):
186
+ """called after callsign entered"""
187
+
188
+
189
+ def prefill(self):
190
+ """Fill SentNR"""
191
+ field = self.field3.findChild(QtWidgets.QLineEdit)
192
+ sent_sxchange_setting = self.contest_settings.get("SentExchange", "")
193
+ if sent_sxchange_setting.strip() == "#":
194
+ result = self.database.get_serial()
195
+ serial_nr = str(result.get("serial_nr", "1")).zfill(3)
196
+ if serial_nr == "None":
197
+ serial_nr = "001"
198
+ if len(field.text()) == 0:
199
+ field.setText(serial_nr)
200
+ else:
201
+ field.setText(sent_sxchange_setting)
202
+
203
+
204
+ def points(self):
205
+ """
206
+ Scoring:
207
+ French: 6 points per QSO with French station same continent
208
+ French: 15 points per QSO with French station on different continent
209
+ French: 1 point per QSO with non-French station same continent
210
+ French: 2 points per QSO with non-French station on different continent
211
+ non-French: 1 point per QSO with French station same continent
212
+ non-French: 3 points per QSO with French station on different continent
213
+
214
+ self.contact["CountryPrefix"]
215
+ self.contact["Continent"]
216
+ """
217
+
218
+ # Just incase the cty lookup fails
219
+ my_country = None
220
+ my_continent = None
221
+ their_continent = None
222
+ their_country = None
223
+
224
+ result = self.cty_lookup(self.station.get("Call", ""))
225
+ if result:
226
+ for item in result.items():
227
+ my_country = item[1].get("entity", "")
228
+ my_continent = item[1].get("continent", "")
229
+ result = self.cty_lookup(self.contact.get("Call", ""))
230
+ if result:
231
+ for item in result.items():
232
+ their_country = item[1].get("entity", "")
233
+ their_continent = item[1].get("continent", "")
234
+
235
+ if my_country == "France":
236
+ if their_country == "France":
237
+ if my_continent == their_continent:
238
+ return 6
239
+ else:
240
+ return 15
241
+ else:
242
+ if my_continent == their_continent:
243
+ return 1
244
+ else:
245
+ return 2
246
+ else:
247
+ if their_country == "France":
248
+ if their_continent == my_continent:
249
+ return 1
250
+ else:
251
+ return 3
252
+
253
+ return 0
254
+
255
+
256
+ def show_mults(self):
257
+ """Return display string for mults"""
258
+ return int(self.database.fetch_mult_count(1).get("count", 0)) + int(
259
+ self.database.fetch_mult_count(2).get("count", 0)
260
+ )
261
+
262
+
263
+ def show_qso(self):
264
+ """Return qso count"""
265
+ result = self.database.fetch_qso_count()
266
+ if result:
267
+ return int(result.get("qsos", 0))
268
+ return 0
269
+
270
+
271
+ def calc_score(self):
272
+ """Return calculated score"""
273
+ result = self.database.fetch_points()
274
+ if result is not None:
275
+ score = result.get("Points", "0")
276
+ if score is None:
277
+ score = "0"
278
+ contest_points = int(score)
279
+ mults = show_mults(self)
280
+ return contest_points * mults
281
+ return 0
282
+
283
+
284
+ def recalculate_mults(self):
285
+ """Recalculates multipliers after change in logged qso."""
286
+
287
+ all_contacts = self.database.fetch_all_contacts_asc()
288
+ for contact in all_contacts:
289
+
290
+ contact["IsMultiplier1"] = 0
291
+ contact["IsMultiplier2"] = 0
292
+
293
+ time_stamp = contact.get("TS", "")
294
+ canton = contact.get("NR", "")
295
+ dxcc = contact.get("CountryPrefix", "")
296
+ band = contact.get("Band", "")
297
+ if dxcc == "HB" and canton.isalpha():
298
+ query = (
299
+ f"select count(*) as canton_count from dxlog where TS < '{time_stamp}' "
300
+ f"and NR = '{canton.upper()}' "
301
+ f"and Band = '{band}' "
302
+ f"and ContestNR = {self.pref.get('contest', '1')};"
303
+ )
304
+ result = self.database.exec_sql(query)
305
+ count = int(result.get("canton_count", 0))
306
+ if count == 0:
307
+ contact["IsMultiplier1"] = 1
308
+
309
+ if dxcc:
310
+ query = (
311
+ f"select count(*) as dxcc_count from dxlog where TS < '{time_stamp}' "
312
+ f"and CountryPrefix = '{dxcc}' "
313
+ f"and Band = '{band}' "
314
+ f"and ContestNR = {self.pref.get('contest', '1')};"
315
+ )
316
+ result = self.database.exec_sql(query)
317
+ if not result.get("dxcc_count", ""):
318
+ contact["IsMultiplier2"] = 1
319
+
320
+ self.database.change_contact(contact)
321
+ cmd = {}
322
+ cmd["cmd"] = "UPDATELOG"
323
+ cmd["station"] = platform.node()
324
+ self.multicast_interface.send_as_json(cmd)
325
+
326
+
327
+ def adif(self):
328
+ """Call the generate ADIF function"""
329
+ gen_adif(self, cabrillo_name, "HELVETIA")
330
+
331
+
332
+ def cabrillo(self):
333
+ """Generates Cabrillo file. Maybe."""
334
+ # https://www.cqwpx.com/cabrillo.htm
335
+ logger.debug("******Cabrillo*****")
336
+ logger.debug("Station: %s", f"{self.station}")
337
+ logger.debug("Contest: %s", f"{self.contest_settings}")
338
+ now = datetime.datetime.now()
339
+ date_time = now.strftime("%Y-%m-%d_%H-%M-%S")
340
+ filename = (
341
+ str(Path.home())
342
+ + "/"
343
+ + f"{self.station.get('Call', '').upper()}_{cabrillo_name}_{date_time}.log"
344
+ )
345
+ logger.debug("%s", filename)
346
+ log = self.database.fetch_all_contacts_asc()
347
+ try:
348
+ with open(filename, "w", encoding="ascii") as file_descriptor:
349
+ print("START-OF-LOG: 3.0", end="\r\n", file=file_descriptor)
350
+ print(
351
+ f"CREATED-BY: Not1MM v{__version__}",
352
+ end="\r\n",
353
+ file=file_descriptor,
354
+ )
355
+ print(
356
+ f"CONTEST: {cabrillo_name}",
357
+ end="\r\n",
358
+ file=file_descriptor,
359
+ )
360
+ if self.station.get("Club", ""):
361
+ print(
362
+ f"CLUB: {self.station.get('Club', '').upper()}",
363
+ end="\r\n",
364
+ file=file_descriptor,
365
+ )
366
+ print(
367
+ f"CALLSIGN: {self.station.get('Call','')}",
368
+ end="\r\n",
369
+ file=file_descriptor,
370
+ )
371
+ print(
372
+ f"LOCATION: {self.station.get('ARRLSection', '')}",
373
+ end="\r\n",
374
+ file=file_descriptor,
375
+ )
376
+ # print(
377
+ # f"ARRL-SECTION: {self.pref.get('section', '')}",
378
+ # end="\r\n",
379
+ # file=file_descriptor,
380
+ # )
381
+ print(
382
+ f"CATEGORY-OPERATOR: {self.contest_settings.get('OperatorCategory','')}",
383
+ end="\r\n",
384
+ file=file_descriptor,
385
+ )
386
+ print(
387
+ f"CATEGORY-ASSISTED: {self.contest_settings.get('AssistedCategory','')}",
388
+ end="\r\n",
389
+ file=file_descriptor,
390
+ )
391
+ print(
392
+ f"CATEGORY-BAND: {self.contest_settings.get('BandCategory','')}",
393
+ end="\r\n",
394
+ file=file_descriptor,
395
+ )
396
+ print(
397
+ f"CATEGORY-MODE: {self.contest_settings.get('ModeCategory','')}",
398
+ end="\r\n",
399
+ file=file_descriptor,
400
+ )
401
+ print(
402
+ f"CATEGORY-TRANSMITTER: {self.contest_settings.get('TransmitterCategory','')}",
403
+ end="\r\n",
404
+ file=file_descriptor,
405
+ )
406
+ if self.contest_settings.get("OverlayCategory", "") != "N/A":
407
+ print(
408
+ f"CATEGORY-OVERLAY: {self.contest_settings.get('OverlayCategory','')}",
409
+ end="\r\n",
410
+ file=file_descriptor,
411
+ )
412
+ print(
413
+ f"GRID-LOCATOR: {self.station.get('GridSquare','')}",
414
+ end="\r\n",
415
+ file=file_descriptor,
416
+ )
417
+ # print(
418
+ # f"CATEGORY: {None}",
419
+ # end="\r\n",
420
+ # file=file_descriptor,
421
+ # )
422
+ print(
423
+ f"CATEGORY-POWER: {self.contest_settings.get('PowerCategory','')}",
424
+ end="\r\n",
425
+ file=file_descriptor,
426
+ )
427
+
428
+ print(
429
+ f"CLAIMED-SCORE: {calc_score(self)}",
430
+ end="\r\n",
431
+ file=file_descriptor,
432
+ )
433
+ ops = f"@{self.station.get('Call','')}"
434
+ list_of_ops = self.database.get_ops()
435
+ for op in list_of_ops:
436
+ ops += f", {op.get('Operator', '')}"
437
+ print(
438
+ f"OPERATORS: {ops}",
439
+ end="\r\n",
440
+ file=file_descriptor,
441
+ )
442
+ print(
443
+ f"NAME: {self.station.get('Name', '')}",
444
+ end="\r\n",
445
+ file=file_descriptor,
446
+ )
447
+ print(
448
+ f"ADDRESS: {self.station.get('Street1', '')}",
449
+ end="\r\n",
450
+ file=file_descriptor,
451
+ )
452
+ print(
453
+ f"ADDRESS-CITY: {self.station.get('City', '')}",
454
+ end="\r\n",
455
+ file=file_descriptor,
456
+ )
457
+ print(
458
+ f"ADDRESS-STATE-PROVINCE: {self.station.get('State', '')}",
459
+ end="\r\n",
460
+ file=file_descriptor,
461
+ )
462
+ print(
463
+ f"ADDRESS-POSTALCODE: {self.station.get('Zip', '')}",
464
+ end="\r\n",
465
+ file=file_descriptor,
466
+ )
467
+ print(
468
+ f"ADDRESS-COUNTRY: {self.station.get('Country', '')}",
469
+ end="\r\n",
470
+ file=file_descriptor,
471
+ )
472
+ print(
473
+ f"EMAIL: {self.station.get('Email', '')}",
474
+ end="\r\n",
475
+ file=file_descriptor,
476
+ )
477
+ for contact in log:
478
+ the_date_and_time = contact.get("TS", "")
479
+ themode = contact.get("Mode", "")
480
+ if themode == "LSB" or themode == "USB":
481
+ themode = "PH"
482
+ frequency = str(int(contact.get("Freq", "0"))).rjust(5)
483
+
484
+ loggeddate = the_date_and_time[:10]
485
+ loggedtime = the_date_and_time[11:13] + the_date_and_time[14:16]
486
+ print(
487
+ f"QSO: {frequency} {themode} {loggeddate} {loggedtime} "
488
+ f"{contact.get('StationPrefix', '').ljust(13)} "
489
+ f"{str(contact.get('SNT', '')).ljust(3)} "
490
+ f"{str(contact.get('SentNr', '')).ljust(6)} "
491
+ f"{contact.get('Call', '').ljust(13)} "
492
+ f"{str(contact.get('RCV', '')).ljust(3)} "
493
+ f"{str(contact.get('NR', '')).ljust(6)}",
494
+ end="\r\n",
495
+ file=file_descriptor,
496
+ )
497
+ print("END-OF-LOG:", end="\r\n", file=file_descriptor)
498
+ self.show_message_box(f"Cabrillo saved to: {filename}")
499
+ except IOError as exception:
500
+ logger.critical("cabrillo: IO error: %s, writing to %s", exception, filename)
501
+ self.show_message_box(f"Error saving Cabrillo: {exception} {filename}")
502
+ return
not1mm/test.py ADDED
@@ -0,0 +1,10 @@
1
+ class Somefunc:
2
+ def __init__(self):
3
+ """weee"""
4
+
5
+ def testfunc():
6
+ """wooo"""
7
+
8
+
9
+ thetest = Somefunc()
10
+ print(f"{hasattr(thetest, "ft8_catcher")}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: not1mm
3
- Version: 24.8.20
3
+ Version: 24.9.3
4
4
  Summary: NOT1MM Logger
5
5
  Author-email: Michael Bridak <michael.bridak@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/mbridak/not1mm
@@ -69,6 +69,7 @@ Requires-Dist: Levenshtein
69
69
  - [Fedora 38 \& 39](#fedora-38--39)
70
70
  - [Fedora 40](#fedora-40)
71
71
  - [Manjaro](#manjaro)
72
+ - [Mint](#mint)
72
73
  - [Python, PyPI, pip and pipx](#python-pypi-pip-and-pipx)
73
74
  - [Bootstrapping pipx](#bootstrapping-pipx)
74
75
  - [Installing with pipx](#installing-with-pipx)
@@ -99,6 +100,7 @@ Requires-Dist: Levenshtein
99
100
  - [Cluster](#cluster)
100
101
  - [N1MM Packets](#n1mm-packets)
101
102
  - [Bands](#bands)
103
+ - [Logging WSJT-X FT8/FT4 contacts](#logging-wsjt-x-ft8ft4-contacts)
102
104
  - [Sending CW](#sending-cw)
103
105
  - [Editing macro keys](#editing-macro-keys)
104
106
  - [Macro substitutions](#macro-substitutions)
@@ -209,6 +211,7 @@ generated, 'cause I'm lazy, list of those who've submitted PR's.
209
211
  - CQ World Wide CW
210
212
  - CQ World Wide SSB
211
213
  - CWOps CWT
214
+ - Helvetia
212
215
  - IARU HF
213
216
  - ICWC MST
214
217
  - Japan International DX CW
@@ -223,9 +226,7 @@ generated, 'cause I'm lazy, list of those who've submitted PR's.
223
226
 
224
227
  ## Recent Changes
225
228
 
226
- - [24-8-20] Added K1USN Slow Speed Test
227
- - [24-8-17-1] Did an oops. Fixed the oops.
228
- - [24-8-17] Removed some cruft. Made dockable widgets not floatable since Wayland breaks this.
229
+ - [24-9-3] Added WSJT-X FT8 mode contacts to ARRL Field Day.
229
230
 
230
231
  See [CHANGELOG.md](CHANGELOG.md) for prior changes.
231
232
 
@@ -240,8 +241,14 @@ clue me into the black magic needed to get it to work.
240
241
 
241
242
  ### Prerequisites
242
243
 
243
- not1mm requires Python 3.9+, PyQt6 and libportaudio2. You should install these
244
- through your distribution's package manager before continuing.
244
+ not1mm requires:
245
+
246
+ - Python 3.9+
247
+ - PyQt6
248
+ - libportaudio2
249
+ - libxcb-cursor0 (maybe... Depends on the distro)
250
+
251
+ You should install these through your distribution's package manager before continuing.
245
252
 
246
253
  ### Common installation recipes for Ubuntu and Fedora
247
254
 
@@ -318,6 +325,20 @@ pip install not1mm
318
325
  pamac build not1mm-git
319
326
  ```
320
327
 
328
+ </details>
329
+
330
+ <details>
331
+
332
+ <summary><b>Mint 22</b></summary>
333
+
334
+ #### Mint
335
+
336
+ ```bash
337
+ sudo apt install python3-pip pipx libxcb-cursor0
338
+ pipx install not1mm
339
+ pipx ensurepath
340
+ ```
341
+
321
342
  </details>
322
343
  <br>
323
344
 
@@ -429,7 +450,9 @@ qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it
429
450
  This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
430
451
  ```
431
452
 
432
- To avoid this you can export an environment variable and launch the app like this:
453
+ You can use your package manager to load libxcb-cursor0.
454
+
455
+ If that's not an option, you can export an environment variable and launch the app like this:
433
456
 
434
457
  `mbridak@vm:~$ export QT_QPA_PLATFORM=wayland; not1mm`
435
458
 
@@ -580,6 +603,13 @@ appear. Those without will not.
580
603
 
581
604
  ![Bands Configuration Screen](https://github.com/mbridak/not1mm/raw/master/pic/configure_bands.png)
582
605
 
606
+ ## Logging WSJT-X FT8/FT4 contacts
607
+
608
+ **Currently only working for ARRL Field Day.**
609
+
610
+ not1mm listens for WSJT-X UDP traffic on the default localhost:2237. No setup is
611
+ needed to be done on not1mm's side.
612
+
583
613
  ## Sending CW
584
614
 
585
615
  Other than sending CW by hand, you can also send predefined CW text messages by