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.
- not1mm/__main__.py +77 -39
- not1mm/data/new_contest.ui +6 -1
- not1mm/data/splash.png +0 -0
- not1mm/lib/database.py +20 -3
- not1mm/lib/ham_utility.py +26 -0
- not1mm/lib/version.py +1 -1
- not1mm/plugins/arrl_field_day.py +14 -3
- not1mm/plugins/cq_160_cw.py +2 -2
- not1mm/plugins/cq_160_ssb.py +2 -2
- not1mm/plugins/helvetia.py +497 -0
- not1mm/plugins/ref_cw.py +502 -0
- not1mm/test.py +10 -0
- {not1mm-24.8.20.dist-info → not1mm-24.9.3.dist-info}/METADATA +37 -7
- {not1mm-24.8.20.dist-info → not1mm-24.9.3.dist-info}/RECORD +18 -15
- {not1mm-24.8.20.dist-info → not1mm-24.9.3.dist-info}/WHEEL +1 -1
- not1mm/playsoundtest.py +0 -15
- {not1mm-24.8.20.dist-info → not1mm-24.9.3.dist-info}/LICENSE +0 -0
- {not1mm-24.8.20.dist-info → not1mm-24.9.3.dist-info}/entry_points.txt +0 -0
- {not1mm-24.8.20.dist-info → not1mm-24.9.3.dist-info}/top_level.txt +0 -0
not1mm/plugins/ref_cw.py
ADDED
@@ -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
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: not1mm
|
3
|
-
Version: 24.
|
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-
|
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
|
244
|
-
|
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
|
-
|
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
|

|
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
|