not1mm 24.8.27__py3-none-any.whl → 24.9.5__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 CHANGED
@@ -23,6 +23,7 @@ import threading
23
23
  import uuid
24
24
 
25
25
  from json import dumps, loads
26
+ from json.decoder import JSONDecodeError
26
27
  from pathlib import Path
27
28
  from shutil import copyfile
28
29
 
@@ -66,6 +67,7 @@ from not1mm.lib.settings import Settings
66
67
  from not1mm.lib.version import __version__
67
68
  from not1mm.lib.versiontest import VersionTest
68
69
  from not1mm.lib.ft8_watcher import FT8Watcher
70
+ from not1mm.lib.fldigi_watcher import FlDigiWatcher
69
71
 
70
72
  import not1mm.fsutils as fsutils
71
73
  from not1mm.logwindow import LogWindow
@@ -162,7 +164,9 @@ class MainWindow(QtWidgets.QMainWindow):
162
164
 
163
165
  radio_thread = QThread()
164
166
  voice_thread = QThread()
167
+ fldigi_thread = QThread()
165
168
 
169
+ fldigi_watcher = None
166
170
  rig_control = None
167
171
  log_window = None
168
172
  check_window = None
@@ -186,14 +190,13 @@ class MainWindow(QtWidgets.QMainWindow):
186
190
  )
187
191
  self.setCorner(Qt.Corner.TopLeftCorner, Qt.DockWidgetArea.LeftDockWidgetArea)
188
192
  self.setCorner(Qt.Corner.BottomLeftCorner, Qt.DockWidgetArea.LeftDockWidgetArea)
189
- data_path = fsutils.APP_DATA_PATH / "main.ui"
190
- uic.loadUi(data_path, self)
193
+ uic.loadUi(fsutils.APP_DATA_PATH / "main.ui", self)
191
194
  self.cw_entry.hide()
192
195
  self.leftdot.hide()
193
196
  self.rightdot.hide()
194
197
  self.n1mm = N1MM()
195
198
  self.ft8 = FT8Watcher()
196
- self.ft8.set_callback(self.ft8_result)
199
+ self.ft8.set_callback(None)
197
200
  self.mscp = SCP(fsutils.APP_DATA_PATH)
198
201
  self.next_field = self.other_2
199
202
  self.dupe_indicator.hide()
@@ -459,8 +462,15 @@ class MainWindow(QtWidgets.QMainWindow):
459
462
  self.setWindowIcon(
460
463
  QtGui.QIcon(str(fsutils.APP_DATA_PATH / "k6gte.not1mm-32.png"))
461
464
  )
462
- with open(fsutils.APP_DATA_PATH / "cty.json", "rt", encoding="utf-8") as c_file:
463
- self.ctyfile = loads(c_file.read())
465
+
466
+ try:
467
+ with open(
468
+ fsutils.APP_DATA_PATH / "cty.json", "rt", encoding="utf-8"
469
+ ) as c_file:
470
+ self.ctyfile = loads(c_file.read())
471
+ except (IOError, JSONDecodeError, TypeError):
472
+ logging.CRITICAL("There was an error parsing the BigCity file.")
473
+
464
474
  self.readpreferences()
465
475
 
466
476
  self.voice_process = Voice()
@@ -491,9 +501,7 @@ class MainWindow(QtWidgets.QMainWindow):
491
501
  self.make_op_dir()
492
502
 
493
503
  self.clearinputs()
494
-
495
- if self.pref.get("contest"):
496
- self.load_contest()
504
+ self.load_contest()
497
505
  self.read_cw_macros()
498
506
 
499
507
  # Featureset for wayland
@@ -502,6 +510,13 @@ class MainWindow(QtWidgets.QMainWindow):
502
510
  | QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable
503
511
  )
504
512
 
513
+ self.fldigi_watcher = FlDigiWatcher()
514
+ self.fldigi_watcher.moveToThread(self.fldigi_thread)
515
+ self.fldigi_thread.started.connect(self.fldigi_watcher.run)
516
+ self.fldigi_thread.finished.connect(self.fldigi_watcher.deleteLater)
517
+ self.fldigi_watcher.poll_callback.connect(self.fldigi_qso)
518
+ self.fldigi_thread.start()
519
+
505
520
  self.log_window = LogWindow()
506
521
  self.log_window.setObjectName("log-window")
507
522
  if os.environ.get("WAYLAND_DISPLAY"):
@@ -561,32 +576,50 @@ class MainWindow(QtWidgets.QMainWindow):
561
576
  "You can udate to the current version by using:\npip install -U not1mm"
562
577
  )
563
578
 
564
- def ft8_result(self, result: dict):
579
+ def fldigi_qso(self, result: str):
565
580
  """
566
- Callback for ft8 watcher
581
+ gets called when there is a new fldigi qso logged.
567
582
 
568
583
  {
569
- 'CALL': 'KE0OG',
570
- 'GRIDSQUARE': 'DM10AT',
571
- 'MODE': 'FT8',
572
- 'RST_SENT': '',
573
- 'RST_RCVD': '',
574
- 'QSO_DATE': '20210329',
575
- 'TIME_ON': '183213',
576
- 'QSO_DATE_OFF': '20210329',
577
- 'TIME_OFF': '183213',
578
- 'BAND': '20M',
579
- 'FREQ': '14.074754',
584
+ 'FREQ': '7.029500',
585
+ 'CALL': 'DL2DSL',
586
+ 'MODE': 'RTTY',
587
+ 'NAME': 'BOB',
588
+ 'QSO_DATE': '20240904',
589
+ 'QSO_DATE_OFF': '20240904',
590
+ 'TIME_OFF': '212825',
591
+ 'TIME_ON': '212800',
592
+ 'RST_RCVD': '599',
593
+ 'RST_SENT': '599',
594
+ 'BAND': '40M',
595
+ 'COUNTRY': 'FED. REP. OF GERMANY',
596
+ 'CQZ': '14',
597
+ 'STX': '000',
598
+ 'STX_STRING': '1D ORG',
599
+ 'CLASS': '1D',
600
+ 'ARRL_SECT': 'DX',
601
+ 'TX_PWR': '0',
602
+ 'OPERATOR': 'K6GTE',
580
603
  'STATION_CALLSIGN': 'K6GTE',
581
604
  'MY_GRIDSQUARE': 'DM13AT',
582
- 'CONTEST_ID': 'ARRL-FIELD-DAY',
583
- 'SRX_STRING': '1D UT',
584
- 'CLASS': '1D',
585
- 'ARRL_SECT': 'UT'
605
+ 'MY_CITY': 'ANAHEIM, CA',
606
+ 'MY_STATE': 'CA'
586
607
  }
587
608
 
588
609
  """
589
- print(f"{result=}")
610
+
611
+ datadict = {}
612
+ splitdata = result.upper().strip().split("<")
613
+ for data in splitdata:
614
+ if data:
615
+ tag = data.split(":")
616
+ if tag == ["EOR>"]:
617
+ break
618
+ datadict[tag[0]] = tag[1].split(">")[1].strip()
619
+ logger.debug(f"{datadict=}")
620
+ if hasattr(self.contest, "ft8_handler"):
621
+ self.contest.set_self(self)
622
+ self.contest.ft8_handler(datadict)
590
623
 
591
624
  def setDarkMode(self, setdarkmode=False) -> None:
592
625
  """Forces a darkmode palette."""
@@ -1287,10 +1320,15 @@ class MainWindow(QtWidgets.QMainWindow):
1287
1320
  if updated:
1288
1321
  cty.dump(fsutils.APP_DATA_PATH / "cty.json")
1289
1322
  self.show_message_box("cty file updated.")
1290
- with open(
1291
- fsutils.APP_DATA_PATH / "cty.json", "rt", encoding="utf-8"
1292
- ) as ctyfile:
1293
- self.ctyfile = loads(ctyfile.read())
1323
+ try:
1324
+ with open(
1325
+ fsutils.APP_DATA_PATH / "cty.json", "rt", encoding="utf-8"
1326
+ ) as ctyfile:
1327
+ self.ctyfile = loads(ctyfile.read())
1328
+ except (IOError, JSONDecodeError, TypeError) as err:
1329
+ logging.CRITICAL(
1330
+ f"There was an error {err} parsing the BigCity file."
1331
+ )
1294
1332
  else:
1295
1333
  self.show_message_box("An Error occured updating file.")
1296
1334
  else:
@@ -1898,11 +1936,14 @@ class MainWindow(QtWidgets.QMainWindow):
1898
1936
  " "
1899
1937
  )[:19]
1900
1938
  self.contact["Call"] = self.callsign.text()
1901
- self.contact["Freq"] = round(float(self.radio_state.get("vfoa", 0.0)) / 1000, 2)
1902
- self.contact["QSXFreq"] = round(
1903
- float(self.radio_state.get("vfoa", 0.0)) / 1000, 2
1904
- )
1905
- self.contact["Mode"] = self.radio_state.get("mode", "")
1939
+ if self.contact.get("Mode") not in ("FT8", "FT4"):
1940
+ self.contact["Freq"] = round(
1941
+ float(self.radio_state.get("vfoa", 0.0)) / 1000, 2
1942
+ )
1943
+ self.contact["QSXFreq"] = round(
1944
+ float(self.radio_state.get("vfoa", 0.0)) / 1000, 2
1945
+ )
1946
+ self.contact["Mode"] = self.radio_state.get("mode", "")
1906
1947
  self.contact["ContestName"] = self.contest.cabrillo_name
1907
1948
  self.contact["ContestNR"] = self.pref.get("contest", "0")
1908
1949
  self.contact["StationPrefix"] = self.station.get("Call", "")
@@ -2093,7 +2134,7 @@ class MainWindow(QtWidgets.QMainWindow):
2093
2134
 
2094
2135
  def save_settings(self) -> None:
2095
2136
  """
2096
- Save settings to database.
2137
+ Save Station settings to database.
2097
2138
 
2098
2139
  Parameters
2099
2140
  ----------
@@ -2322,7 +2363,7 @@ class MainWindow(QtWidgets.QMainWindow):
2322
2363
  with open(fsutils.CONFIG_FILE, "wt", encoding="utf-8") as file_descriptor:
2323
2364
  file_descriptor.write(dumps(self.pref, indent=4))
2324
2365
  # logger.info("writing: %s", self.pref)
2325
- except IOError as exception:
2366
+ except (IOError, TypeError, ValueError) as exception:
2326
2367
  logger.critical("writepreferences: %s", exception)
2327
2368
 
2328
2369
  def readpreferences(self) -> None:
@@ -2344,7 +2385,12 @@ class MainWindow(QtWidgets.QMainWindow):
2344
2385
  with open(
2345
2386
  fsutils.CONFIG_FILE, "rt", encoding="utf-8"
2346
2387
  ) as file_descriptor:
2347
- self.pref = loads(file_descriptor.read())
2388
+ try:
2389
+ self.pref = loads(file_descriptor.read())
2390
+ except (JSONDecodeError, TypeError):
2391
+ logging.CRITICAL(
2392
+ "There was an error parsing the preference file."
2393
+ )
2348
2394
  logger.info("%s", self.pref)
2349
2395
  else:
2350
2396
  logger.info("No preference file. Writing preference.")
@@ -2354,7 +2400,7 @@ class MainWindow(QtWidgets.QMainWindow):
2354
2400
  self.pref = self.pref_ref.copy()
2355
2401
  file_descriptor.write(dumps(self.pref, indent=4))
2356
2402
  logger.info("%s", self.pref)
2357
- except IOError as exception:
2403
+ except (IOError, TypeError, ValueError) as exception:
2358
2404
  logger.critical("Error: %s", exception)
2359
2405
 
2360
2406
  self.look_up = None
@@ -3197,10 +3243,14 @@ class MainWindow(QtWidgets.QMainWindow):
3197
3243
  else:
3198
3244
  macro_file = "ssbmacros.txt"
3199
3245
  if not (fsutils.USER_DATA_PATH / macro_file).exists():
3200
- logger.debug("read_cw_macros: copying default macro file.")
3201
- copyfile(
3202
- fsutils.APP_DATA_PATH / macro_file, fsutils.USER_DATA_PATH / macro_file
3203
- )
3246
+ logger.debug("copying default macro file.")
3247
+ try:
3248
+ copyfile(
3249
+ fsutils.APP_DATA_PATH / macro_file,
3250
+ fsutils.USER_DATA_PATH / macro_file,
3251
+ )
3252
+ except IOError as err:
3253
+ logger.critical(f"Error {err} copying macro file.")
3204
3254
  try:
3205
3255
  fsutils.openFileWithOS(fsutils.USER_DATA_PATH / macro_file)
3206
3256
  except FileNotFoundError | PermissionError | OSError as err:
@@ -3221,22 +3271,26 @@ class MainWindow(QtWidgets.QMainWindow):
3221
3271
  macro_file = "ssbmacros.txt"
3222
3272
 
3223
3273
  if not (fsutils.USER_DATA_PATH / macro_file).exists():
3224
- logger.debug("read_cw_macros: copying default macro file.")
3225
- copyfile(
3226
- fsutils.APP_DATA_PATH / macro_file, fsutils.USER_DATA_PATH / macro_file
3227
- )
3228
- with open(
3229
- fsutils.USER_DATA_PATH / macro_file, "r", encoding="utf-8"
3230
- ) as file_descriptor:
3231
- for line in file_descriptor:
3232
- try:
3274
+ logger.debug("copying default macro file.")
3275
+ try:
3276
+ copyfile(
3277
+ fsutils.APP_DATA_PATH / macro_file,
3278
+ fsutils.USER_DATA_PATH / macro_file,
3279
+ )
3280
+ except IOError as err:
3281
+ logger.critical(f"Error {err} copying macro file.")
3282
+ try:
3283
+ with open(
3284
+ fsutils.USER_DATA_PATH / macro_file, "r", encoding="utf-8"
3285
+ ) as file_descriptor:
3286
+ for line in file_descriptor:
3233
3287
  mode, fkey, buttonname, cwtext = line.split("|")
3234
3288
  if mode.strip().upper() == "R" and self.pref.get("run_state"):
3235
3289
  self.fkeys[fkey.strip()] = (buttonname.strip(), cwtext.strip())
3236
3290
  if mode.strip().upper() != "R" and not self.pref.get("run_state"):
3237
3291
  self.fkeys[fkey.strip()] = (buttonname.strip(), cwtext.strip())
3238
- except ValueError as err:
3239
- logger.info("read_cw_macros: %s", err)
3292
+ except (IOError, ValueError) as err:
3293
+ logger.info("read_cw_macros: %s", err)
3240
3294
  keys = self.fkeys.keys()
3241
3295
  if "F1" in keys:
3242
3296
  self.F1.setText(f"F1: {self.fkeys['F1'][0]}")
not1mm/lib/database.py CHANGED
@@ -886,28 +886,14 @@ class DataBase:
886
886
  logger.debug("%s", exception)
887
887
  return {}
888
888
 
889
- def fetch_mult1_count(self) -> dict:
889
+ def fetch_mult_count(self, mult: int) -> dict:
890
890
  """return QSO count"""
891
891
  try:
892
892
  with sqlite3.connect(self.database) as conn:
893
893
  conn.row_factory = self.row_factory
894
894
  cursor = conn.cursor()
895
895
  cursor.execute(
896
- f"select count(*) as count from dxlog where IsMultiplier1 = 1 and ContestNR = {self.current_contest};"
897
- )
898
- return cursor.fetchone()
899
- except sqlite3.OperationalError as exception:
900
- logger.debug("%s", exception)
901
- return {}
902
-
903
- def fetch_mult2_count(self) -> dict:
904
- """return QSO count"""
905
- try:
906
- with sqlite3.connect(self.database) as conn:
907
- conn.row_factory = self.row_factory
908
- cursor = conn.cursor()
909
- cursor.execute(
910
- f"select count(*) as count from dxlog where IsMultiplier2 = 1 and ContestNR = {self.current_contest};"
896
+ f"select count(*) as count from dxlog where IsMultiplier{mult} = 1 and ContestNR = {self.current_contest};"
911
897
  )
912
898
  return cursor.fetchone()
913
899
  except sqlite3.OperationalError as exception:
@@ -0,0 +1,32 @@
1
+ import xmlrpc.client
2
+ from PyQt6.QtCore import QObject, pyqtSignal, QThread, QEventLoop
3
+
4
+
5
+ class FlDigiWatcher(QObject):
6
+ """fldigi watcher"""
7
+
8
+ poll_callback = pyqtSignal(str)
9
+ time_to_quit = False
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ ...
14
+
15
+ self.target = "http://127.0.0.1:7362"
16
+ self.payload = ""
17
+ self.response = ""
18
+
19
+ def run(self):
20
+ while not self.time_to_quit:
21
+ try:
22
+ server = xmlrpc.client.ServerProxy(self.target)
23
+ self.response = server.logbook.last_record()
24
+ except OSError:
25
+ continue
26
+ if self.payload != self.response:
27
+ self.payload = self.response
28
+ try:
29
+ self.poll_callback.emit(self.payload)
30
+ except QEventLoop:
31
+ ...
32
+ QThread.msleep(100)
not1mm/lib/ft8_watcher.py CHANGED
@@ -6,10 +6,14 @@ https://github.com/mbridak/not1mm
6
6
  GPL V3
7
7
  """
8
8
 
9
+ import logging
10
+
9
11
  from PyQt6 import QtNetwork
10
12
 
11
13
  import struct
12
14
 
15
+ logger = logging.getLogger(__name__)
16
+
13
17
 
14
18
  class FT8Watcher:
15
19
  """Main Window"""
@@ -64,7 +68,7 @@ class FT8Watcher:
64
68
  datagram, sender_host, sender_port_number = self.udp_socket.readDatagram(
65
69
  self.udp_socket.pendingDatagramSize()
66
70
  )
67
- # print("%s %s %s", sender_host, sender_port_number, datagram)
71
+ logger.debug(f"{datagram=}")
68
72
 
69
73
  if datagram[0:4] != b"\xad\xbc\xcb\xda":
70
74
  return # bail if no wsjt-x magic number
not1mm/lib/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """It's the version"""
2
2
 
3
- __version__ = "24.8.27"
3
+ __version__ = "24.9.5"
@@ -8,6 +8,7 @@ import logging
8
8
  from pathlib import Path
9
9
  from PyQt6 import QtWidgets
10
10
 
11
+ from not1mm.lib.ham_utility import get_logged_band
11
12
  from not1mm.lib.plugin_common import gen_adif, get_points
12
13
  from not1mm.lib.version import __version__
13
14
 
@@ -114,7 +115,7 @@ def points(self):
114
115
  _mode = self.contact.get("Mode", "")
115
116
  if _mode in "SSB, USB, LSB, FM, AM":
116
117
  return 1
117
- if _mode in "CW, RTTY":
118
+ if _mode in "CW, RTTY, FT8":
118
119
  return 2
119
120
  return 0
120
121
 
@@ -295,8 +296,10 @@ def cabrillo(self):
295
296
  for contact in log:
296
297
  the_date_and_time = contact.get("TS", "")
297
298
  themode = contact.get("Mode", "")
298
- if themode == "LSB" or themode == "USB":
299
+ if themode in ("LSB", "USB", "FM"):
299
300
  themode = "PH"
301
+ if themode in ("FT8", "FT4", "RTTY"):
302
+ themode = "DG"
300
303
  frequency = str(int(contact.get("Freq", "0"))).rjust(5)
301
304
 
302
305
  loggeddate = the_date_and_time[:10]
@@ -330,7 +333,7 @@ def set_self(the_outie):
330
333
 
331
334
  def ft8_handler(the_packet: dict):
332
335
  """Process FT8 QSO packets
333
-
336
+ FT8
334
337
  {
335
338
  'CALL': 'KE0OG',
336
339
  'GRIDSQUARE': 'DM10AT',
@@ -350,19 +353,51 @@ def ft8_handler(the_packet: dict):
350
353
  'CLASS': '1D',
351
354
  'ARRL_SECT': 'UT'
352
355
  }
356
+ FlDigi
357
+ {
358
+ 'FREQ': '7.029500',
359
+ 'CALL': 'DL2DSL',
360
+ 'MODE': 'RTTY',
361
+ 'NAME': 'BOB',
362
+ 'QSO_DATE': '20240904',
363
+ 'QSO_DATE_OFF': '20240904',
364
+ 'TIME_OFF': '212825',
365
+ 'TIME_ON': '212800',
366
+ 'RST_RCVD': '599',
367
+ 'RST_SENT': '599',
368
+ 'BAND': '40M',
369
+ 'COUNTRY': 'FED. REP. OF GERMANY',
370
+ 'CQZ': '14',
371
+ 'STX': '000',
372
+ 'STX_STRING': '1D ORG',
373
+ 'CLASS': '1D',
374
+ 'ARRL_SECT': 'DX',
375
+ 'TX_PWR': '0',
376
+ 'OPERATOR': 'K6GTE',
377
+ 'STATION_CALLSIGN': 'K6GTE',
378
+ 'MY_GRIDSQUARE': 'DM13AT',
379
+ 'MY_CITY': 'ANAHEIM, CA',
380
+ 'MY_STATE': 'CA'
381
+ }
353
382
 
354
383
  """
355
- print(f"{the_packet=}")
356
-
384
+ logger.debug(f"{the_packet=}")
357
385
  if ALTEREGO is not None:
358
-
359
386
  ALTEREGO.callsign.setText(the_packet.get("CALL"))
360
387
  ALTEREGO.contact["Call"] = the_packet.get("CALL", "")
361
388
  ALTEREGO.contact["SNT"] = ALTEREGO.sent.text()
362
389
  ALTEREGO.contact["RCV"] = ALTEREGO.receive.text()
363
390
  ALTEREGO.contact["Exchange1"] = the_packet.get("CLASS", "ERR")
364
391
  ALTEREGO.contact["Sect"] = the_packet.get("ARRL_SECT", "ERR")
365
- ALTEREGO.contact["Mode"] = "FT8"
392
+ ALTEREGO.contact["Mode"] = the_packet.get("MODE", "ERR")
393
+ ALTEREGO.contact["Freq"] = round(float(the_packet.get("FREQ", "0.0")) * 1000, 2)
394
+ ALTEREGO.contact["QSXFreq"] = round(
395
+ float(the_packet.get("FREQ", "0.0")) * 1000, 2
396
+ )
397
+ ALTEREGO.contact["Band"] = get_logged_band(
398
+ str(int(float(the_packet.get("FREQ", "0.0")) * 1000000))
399
+ )
400
+ logger.debug(f"{ALTEREGO.contact=}")
366
401
  ALTEREGO.other_1.setText(the_packet.get("CLASS", "ERR"))
367
402
  ALTEREGO.other_2.setText(the_packet.get("ARRL_SECT", "ERR"))
368
- print(f"\n{ALTEREGO.contact=}\n")
403
+ ALTEREGO.save_contact()
@@ -177,7 +177,7 @@ def points(self):
177
177
 
178
178
  def show_mults(self):
179
179
  """Return display string for mults"""
180
- result = self.database.fetch_mult1_count()
180
+ result = self.database.fetch_mult_count(1)
181
181
  count = result.get("count", 0)
182
182
  return count
183
183
 
@@ -198,7 +198,7 @@ def calc_score(self):
198
198
  if score is None:
199
199
  score = "0"
200
200
  contest_points = int(score)
201
- result = self.database.fetch_mult1_count()
201
+ result = self.database.fetch_mult_count(1)
202
202
  mults = int(result.get("count", 0))
203
203
  return contest_points * mults
204
204
  return 0
@@ -177,7 +177,7 @@ def points(self):
177
177
 
178
178
  def show_mults(self):
179
179
  """Return display string for mults"""
180
- result = self.database.fetch_mult1_count()
180
+ result = self.database.fetch_mult_count(1)
181
181
  count = result.get("count", 0)
182
182
  return count
183
183
 
@@ -198,7 +198,7 @@ def calc_score(self):
198
198
  if score is None:
199
199
  score = "0"
200
200
  contest_points = int(score)
201
- result = self.database.fetch_mult1_count()
201
+ result = self.database.fetch_mult_count(1)
202
202
  mults = int(result.get("count", 0))
203
203
  return contest_points * mults
204
204
  return 0
@@ -250,8 +250,8 @@ def points(self):
250
250
 
251
251
  def show_mults(self):
252
252
  """Return display string for mults"""
253
- return int(self.database.fetch_mult1_count().get("count", 0)) + int(
254
- self.database.fetch_mult2_count().get("count", 0)
253
+ return int(self.database.fetch_mult_count(1).get("count", 0)) + int(
254
+ self.database.fetch_mult_count(2).get("count", 0)
255
255
  )
256
256
 
257
257
 
@@ -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 CHANGED
@@ -1,13 +1,6 @@
1
- #!/usr/bin/env python3
2
- """
3
- NOT1MM Logger
4
- Purpose: test alternative sound playing interface
5
- """
6
- # pylint: disable=unused-import, c-extension-no-member, no-member, invalid-name, too-many-lines, no-name-in-module
7
- # pylint: disable=logging-fstring-interpolation, logging-not-lazy, line-too-long, bare-except
1
+ import xmlrpc.client
8
2
 
9
- from not1mm.lib.ham_utility import parse_udc
10
-
11
- filename = "./testing/K1USNSSTOP.udc"
12
-
13
- print(f"{parse_udc(filename)}")
3
+ target = "http://127.0.0.1:8421"
4
+ server = xmlrpc.client.ServerProxy(target)
5
+ adif = "<QSO_DATE:8>20150721<QSO_DATE_OFF:8>20150721<TIME_ON:4>1333<TIME_OFF:6>133436<CALL:5>N3FJP<FREQ:8>3.081500<MODE:0><RST_SENT:0><RST_RCVD:0><TX_PWR:0><NAME:5>Glenn<QTH:7>Bel Air<STATE:2>MD<VE_PROV:0><COUNTRY:13>United States<GRIDSQUARE:6>FM19tm<STX:0><SRX:0><SRX_STRING:0><STX_STRING:0><NOTES:0><IOTA:0><DXCC:0><QSL_VIA:0><QSLRDATE:0><QSLSDATE:0><eor>"
6
+ response = server.log.add_record(adif)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: not1mm
3
- Version: 24.8.27
3
+ Version: 24.9.5
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
@@ -100,6 +100,7 @@ Requires-Dist: Levenshtein
100
100
  - [Cluster](#cluster)
101
101
  - [N1MM Packets](#n1mm-packets)
102
102
  - [Bands](#bands)
103
+ - [Logging WSJT-X FT8/FT4 contacts](#logging-wsjt-x-ft8ft4-contacts)
103
104
  - [Sending CW](#sending-cw)
104
105
  - [Editing macro keys](#editing-macro-keys)
105
106
  - [Macro substitutions](#macro-substitutions)
@@ -225,11 +226,8 @@ generated, 'cause I'm lazy, list of those who've submitted PR's.
225
226
 
226
227
  ## Recent Changes
227
228
 
228
- - [24-8-27] Added Helvetia contest.
229
- - [24-8-22] Add loading splash screen.
230
- - [24-8-20] Added K1USN Slow Speed Test
231
- - [24-8-17-1] Did an oops. Fixed the oops.
232
- - [24-8-17] Removed some cruft. Made dockable widgets not floatable since Wayland breaks this.
229
+ - [24-9-5] Added FlDigi support for Field Day.
230
+ - [24-9-3] Added WSJT-X FT8 mode contacts to ARRL Field Day.
233
231
 
234
232
  See [CHANGELOG.md](CHANGELOG.md) for prior changes.
235
233
 
@@ -250,7 +248,7 @@ not1mm requires:
250
248
  - PyQt6
251
249
  - libportaudio2
252
250
  - libxcb-cursor0 (maybe... Depends on the distro)
253
-
251
+
254
252
  You should install these through your distribution's package manager before continuing.
255
253
 
256
254
  ### Common installation recipes for Ubuntu and Fedora
@@ -337,9 +335,7 @@ pamac build not1mm-git
337
335
  #### Mint
338
336
 
339
337
  ```bash
340
- sudo apt install python3-pip
341
- sudo apt install pipx
342
- sudo apt install libxcb-cursor0
338
+ sudo apt install python3-pip pipx libxcb-cursor0
343
339
  pipx install not1mm
344
340
  pipx ensurepath
345
341
  ```
@@ -608,6 +604,13 @@ appear. Those without will not.
608
604
 
609
605
  ![Bands Configuration Screen](https://github.com/mbridak/not1mm/raw/master/pic/configure_bands.png)
610
606
 
607
+ ## Logging WSJT-X FT8/FT4 contacts
608
+
609
+ **Currently only working for ARRL Field Day.**
610
+
611
+ not1mm listens for WSJT-X UDP traffic on the default localhost:2237. No setup is
612
+ needed to be done on not1mm's side.
613
+
611
614
  ## Sending CW
612
615
 
613
616
  Other than sending CW by hand, you can also send predefined CW text messages by
@@ -1,11 +1,11 @@
1
1
  not1mm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- not1mm/__main__.py,sha256=QJRnfWEcHEQ40ESeIeEtPB6lOwMySP8r6Ao-4TBqLoc,121832
2
+ not1mm/__main__.py,sha256=C9DOp1FLdRj1Oako2gW9Y6rlfHniSp379RIhIKiYgEw,124001
3
3
  not1mm/bandmap.py,sha256=1b5tXCfGTnpqqn6hPNt7zRA8SmuwSXzSwNHZXhCRt70,31434
4
4
  not1mm/checkwindow.py,sha256=aI-nr8OF90IWV7R_XRdmitvBJ9M85evCs72HoU3Jnvc,10374
5
5
  not1mm/fsutils.py,sha256=ukHKxKTeNKxKwqRaJjtzRShL4X5Xl0jRBbADyy3Ifp8,1701
6
6
  not1mm/logwindow.py,sha256=pwhiwolmGnW01LF4sjlu3ywLsgfxL6KuGuKuYKYmgeY,44403
7
7
  not1mm/radio.py,sha256=eiB04LPMPBeMrBRI021Z7VXth66EOYb0Ujh11T9877c,3362
8
- not1mm/test.py,sha256=ev_YgRFBurFw7_W1SXn509XyUkC6tWchLF5w_c8Z-ow,422
8
+ not1mm/test.py,sha256=QE9lemU13glwB2yuBAuXXQHzADTW1vSX90HHu0F-Akg,496
9
9
  not1mm/vfo.py,sha256=IvmUQYMIPzLJw_BHQGis4J_IEW-vlBtdfxZLXPh7OzI,12335
10
10
  not1mm/voice_keying.py,sha256=sA3gw5_k7kShTg2qhG7HkKDM5M6KheJVRkAc_C7mxDk,3006
11
11
  not1mm/data/JetBrainsMono-Regular.ttf,sha256=UOHctAKY_PzCGh7zy-6f6egnCcSK0wzmF0csBqO9lDY,203952
@@ -94,12 +94,13 @@ not1mm/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
94
  not1mm/lib/about.py,sha256=sWycfGcruN3SaEe4JmaJ61K6D8Itq0WxpUYT-lEcmYM,416
95
95
  not1mm/lib/cat_interface.py,sha256=aazvNTSeZAROq3KL8gPx-I95iVez2IiIOSk22qeqVCQ,19502
96
96
  not1mm/lib/cwinterface.py,sha256=Q8p3pScOHczZ8ptICfH1Yu6rCEwQJLgrNwYMN76B2i8,3389
97
- not1mm/lib/database.py,sha256=rX1wOoSiSuX7CCFghvD_yc4q8E0FHwZp1k7Z2iVNYv8,43680
97
+ not1mm/lib/database.py,sha256=0dx6SX40OiPFent2ePAGsFj_XmOVMNBHaqd0QtAiewY,43129
98
98
  not1mm/lib/edit_contact.py,sha256=Ki9bGPpqyQQBB1cU8VIBDCal3lbXeQ6qxhzklmhE2_w,353
99
99
  not1mm/lib/edit_macro.py,sha256=raKWBwsHInj5EUKmvyLQ6gqc3ZFDlstsD3xqoM4PC8E,517
100
100
  not1mm/lib/edit_opon.py,sha256=j3qJ1aBsQoIOnQ9yiBl3lyeISvKTP0I_rtBYBPAfgeI,359
101
101
  not1mm/lib/edit_station.py,sha256=doL21Hs6jzIE43ohAopdFt_iqnRJZHFcqzcnCS0-iio,1965
102
- not1mm/lib/ft8_watcher.py,sha256=ISfXjs-Mgbz_lE5SThEnFoCe8apNLElgSuECAMCH18I,4080
102
+ not1mm/lib/fldigi_watcher.py,sha256=k_fAaJLdt4mwyKKnpiZrV-rSFtRbi2C8y1Dg3eyILIU,883
103
+ not1mm/lib/ft8_watcher.py,sha256=M9R5OUs3i99u3IfKnv1KYIMfljPViSbk-VeyjsVQLp4,4100
103
104
  not1mm/lib/ham_utility.py,sha256=uRErxCxZr8dfxzekPyett0e_BABDVOCvSUUTzXq6ctE,11790
104
105
  not1mm/lib/lookup.py,sha256=F2fl5QkMxaGSxl1XMWnLUub3T9Mt7LhCX4acOlAsks4,13952
105
106
  not1mm/lib/multicast.py,sha256=bnFUNHyy82GmIb3_88EPBVVssj7-HzkJPaH671cK8Qw,3249
@@ -110,7 +111,7 @@ not1mm/lib/plugin_common.py,sha256=wuG7B0OJx9zYc5Gew3fdt_lNyan8Ul9KNlPQ7PDKGsU,9
110
111
  not1mm/lib/select_contest.py,sha256=WsptLuwkouIHeocJL3oZ6-eUfEnhpwdc-x7eMZ_TIVM,359
111
112
  not1mm/lib/settings.py,sha256=MWiKXbasaFbzeHTjfzTaTqbCBrIijudP_-0a5jNmUAA,9265
112
113
  not1mm/lib/super_check_partial.py,sha256=p5l3u2ZOCBtlWgbvskC50FpuoaIpR07tfC6zTdRWbh4,2334
113
- not1mm/lib/version.py,sha256=1KiLcfpXjS8-V6c6tXOR_NoOKat-5IpaUjb1wrD5Q8g,48
114
+ not1mm/lib/version.py,sha256=F98YdDdv2B3tvIDuoqpiagUBxJ2s48EdRr4FvAbwOnc,47
114
115
  not1mm/lib/versiontest.py,sha256=8vDNptuBBunn-1IGkjNaquehqBYUJyjrPSF8Igmd4_Y,1286
115
116
  not1mm/plugins/10_10_fall_cw.py,sha256=IttjX1yy4nDdACGsiYlPteFG8eVseX_WtoFio6bqHE8,10953
116
117
  not1mm/plugins/10_10_spring_cw.py,sha256=ThCptdM3dX4ywhoy2JRcOEyHSqcJolFaT7O_PYzM1Mg,10958
@@ -120,7 +121,7 @@ not1mm/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
121
  not1mm/plugins/arrl_10m.py,sha256=EyNRb3Sm0Qb-GVm0p05EnadlHisVh7y-sKTBP2ddMLo,13768
121
122
  not1mm/plugins/arrl_dx_cw.py,sha256=LVnYDNFEUiIpQ1TlhOCcRK7JNwH5XPO5WzUoApSUMTY,13802
122
123
  not1mm/plugins/arrl_dx_ssb.py,sha256=fUFzuNbCAfA5sQSYm8ISV3P9Z_2xnuKeOdO6E66zn1k,13805
123
- not1mm/plugins/arrl_field_day.py,sha256=bMBrww0-zBnkrLY3JsG1tXumeWGDWgLi1QZiPtriFqE,11387
124
+ not1mm/plugins/arrl_field_day.py,sha256=pivtEK5j9sLpta12_wuUQYvs7MuiP3JfhlO-AOIDJug,12707
124
125
  not1mm/plugins/arrl_rtty_ru.py,sha256=hKUS4isjdXo3EYxQrsqsDupPp2chW8fpoWj0T1pTgJ4,7994
125
126
  not1mm/plugins/arrl_ss_cw.py,sha256=4yN68xZMuJRaSUfdiY4hafC07A3lifl5q6DEUZ-oYPQ,13080
126
127
  not1mm/plugins/arrl_ss_phone.py,sha256=Yzc5sNjrY8TlnozbYF6k8hbEamyDuUAD_3-BNqHgXqY,13068
@@ -128,15 +129,15 @@ not1mm/plugins/arrl_vhf_jan.py,sha256=jkX9v2HHsjoawtTKiy8X0Td_HtfGQVT3MapJwpmM1q
128
129
  not1mm/plugins/arrl_vhf_jun.py,sha256=_9h6joxVCRCoFuNnyTOlnonafvrMv7H0eBERhYCoGug,11591
129
130
  not1mm/plugins/arrl_vhf_sep.py,sha256=XbS1OSAfGaYXFaTAP2HRyVvVeRD_Z6cefPc1_mOLJKw,11591
130
131
  not1mm/plugins/canada_day.py,sha256=OVpcCl1Chse_zLHf6PayTrgawWM4W-pmrTw40Al-o9s,11998
131
- not1mm/plugins/cq_160_cw.py,sha256=e9ajqIzDRteSI7zQaFpXUJ_6SNvBlIuNq213wadReUs,14139
132
- not1mm/plugins/cq_160_ssb.py,sha256=7MuMC_AVlj9ds_ohvEU4Tts-z9eTvnsSIENSMFKQCBc,14182
132
+ not1mm/plugins/cq_160_cw.py,sha256=5s6rIZdJEnmWe1SI06BEyz7p5vP0N2n9mI4l_mZ0icw,14139
133
+ not1mm/plugins/cq_160_ssb.py,sha256=zIwSMAjHSt6W2edrDzVbyTf860JowHoFkU9BKO8Enag,14182
133
134
  not1mm/plugins/cq_wpx_cw.py,sha256=9aNzAR-KhznIwUlxUFjAi_hbiw_6RrCMwUBk9I2f6Hs,14037
134
135
  not1mm/plugins/cq_wpx_ssb.py,sha256=-hGRovqHR9rfOUnG4LPOoABTb4heH8VAX6rYdJbCqsw,12687
135
136
  not1mm/plugins/cq_ww_cw.py,sha256=ltXFnSXabCOuW70s-WOydgghZTNpztX8TKLpVIV50B4,11194
136
137
  not1mm/plugins/cq_ww_ssb.py,sha256=kt-EQofmCbynX1iXFm9ehffi_TMW25ke8Qi9MiR69ZQ,11199
137
138
  not1mm/plugins/cwt.py,sha256=4xdXN6ZJM5k-6gn0hJzNheWfFlGiqquC2p0ZMEe516M,12818
138
139
  not1mm/plugins/general_logging.py,sha256=t02xtJs601qRICGdrvLs3G9y4GCG9H4AgQNkgA18CYs,3467
139
- not1mm/plugins/helvetia.py,sha256=OtMTOw3-SavrhTNRb_lulTX9BEaNbQdK5lLufowKihY,15432
140
+ not1mm/plugins/helvetia.py,sha256=6aOO4uiLzFFgHA-A3xz6IRdCJpqPOAm0egKxP5Y_Ie0,15432
140
141
  not1mm/plugins/iaru_hf.py,sha256=-ROUo2gBkw3xB89t8bd-4f7_1hROw2VXZXVHLFdB62s,11541
141
142
  not1mm/plugins/icwc_mst.py,sha256=BaUP2kzrT2D27un_WLGT4HCTTi1e7CNYC4NHcC_9r74,11842
142
143
  not1mm/plugins/jidx_cw.py,sha256=9oV4hDxMiGXa9wuYUNYOCsr-mz8LYB-4WMHBN8u2dFk,12153
@@ -145,11 +146,12 @@ not1mm/plugins/k1usn_sst.py,sha256=2Nu7SRiQeUG3mL9CLKReRLh8vKsNbWcizMgv9MTLkrg,1
145
146
  not1mm/plugins/naqp_cw.py,sha256=c0MuKqfkIxiYFvv2z7vqrBz3m9FSnSYkPK3f-DdkTIA,12632
146
147
  not1mm/plugins/naqp_ssb.py,sha256=VLWVrSzI0UP1AhSXYn61eZ7or1rz6a_pS_xCKfgS4Jw,11595
147
148
  not1mm/plugins/phone_weekly_test.py,sha256=fLpMe03WB9_KgRl6vMgQQt_aktFdqfNt2Sw81CTRAUs,12325
149
+ not1mm/plugins/ref_cw.py,sha256=aWjHHkqIKutjRUtzh09y5haFfnZK9poRQDWRQMDRxxU,16326
148
150
  not1mm/plugins/stew_perry_topband.py,sha256=CKBQbYl4ETxhXJd2dma4fg_C5pag_s7Nf61SCztZtqE,10668
149
151
  not1mm/plugins/winter_field_day.py,sha256=4rcfRtobwjHO6BNL3WOTHzBmyyeuX79BNGBG8PfjrI8,10238
150
- not1mm-24.8.27.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
151
- not1mm-24.8.27.dist-info/METADATA,sha256=PC9MtwYLjp66VOkyLGbCkMNw4Eb1Z5PoF3AkrKQCh6I,29688
152
- not1mm-24.8.27.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
153
- not1mm-24.8.27.dist-info/entry_points.txt,sha256=pMcZk_0dxFgLkcUkF0Q874ojpwOmF3OL6EKw9LgvocM,47
154
- not1mm-24.8.27.dist-info/top_level.txt,sha256=0YmTxEcDzQlzXub-lXASvoLpg_mt1c2thb5cVkDf5J4,7
155
- not1mm-24.8.27.dist-info/RECORD,,
152
+ not1mm-24.9.5.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
153
+ not1mm-24.9.5.dist-info/METADATA,sha256=ARDMBkO0MbKxknvDqFFkeCqup5PWor3Ag0hZtqDWl_8,29781
154
+ not1mm-24.9.5.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
155
+ not1mm-24.9.5.dist-info/entry_points.txt,sha256=pMcZk_0dxFgLkcUkF0Q874ojpwOmF3OL6EKw9LgvocM,47
156
+ not1mm-24.9.5.dist-info/top_level.txt,sha256=0YmTxEcDzQlzXub-lXASvoLpg_mt1c2thb5cVkDf5J4,7
157
+ not1mm-24.9.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.0.0)
2
+ Generator: setuptools (74.1.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5