not1mm 24.12.3__py3-none-any.whl → 24.12.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
@@ -77,6 +77,7 @@ from not1mm.vfo import VfoWindow
77
77
  from not1mm.radio import Radio
78
78
  from not1mm.voice_keying import Voice
79
79
  from not1mm.lookupservice import LookupService
80
+ from not1mm.rtc_service import RTCService
80
81
 
81
82
  poll_time = datetime.datetime.now()
82
83
 
@@ -102,6 +103,11 @@ class MainWindow(QtWidgets.QMainWindow):
102
103
  "multicast_group": "239.1.1.1",
103
104
  "multicast_port": 2239,
104
105
  "interface_ip": "0.0.0.0",
106
+ "send_rtc_scores": False,
107
+ "rtc_url": "",
108
+ "rtc_user": "",
109
+ "rtc_pass": "",
110
+ "rtc_interval": 2,
105
111
  "send_n1mm_packets": False,
106
112
  "n1mm_station_name": "20M CW Tent",
107
113
  "n1mm_operator": "Bernie",
@@ -156,7 +162,6 @@ class MainWindow(QtWidgets.QMainWindow):
156
162
  opon_dialog = None
157
163
  dbname = fsutils.USER_DATA_PATH, "/ham.db"
158
164
  radio_state = {}
159
- rig_control = None
160
165
  worked_list = {}
161
166
  cw_entry_visible = False
162
167
  last_focus = None
@@ -170,6 +175,7 @@ class MainWindow(QtWidgets.QMainWindow):
170
175
  radio_thread = QThread()
171
176
  voice_thread = QThread()
172
177
  fldigi_thread = QThread()
178
+ rtc_thread = QThread()
173
179
 
174
180
  fldigi_watcher = None
175
181
  rig_control = None
@@ -179,6 +185,7 @@ class MainWindow(QtWidgets.QMainWindow):
179
185
  vfo_window = None
180
186
  lookup_service = None
181
187
  fldigi_util = None
188
+ rtc_service = None
182
189
 
183
190
  current_widget = None
184
191
 
@@ -712,7 +719,7 @@ class MainWindow(QtWidgets.QMainWindow):
712
719
  )
713
720
 
714
721
  def load_call_history(self) -> None:
715
- """"""
722
+ """Display filepicker and load chosen call history file."""
716
723
  filename = self.filepicker("other")
717
724
  if filename:
718
725
  self.database.create_callhistory_table()
@@ -755,13 +762,13 @@ class MainWindow(QtWidgets.QMainWindow):
755
762
  self.show_message_box(f"{err}")
756
763
 
757
764
  def on_focus_changed(self, new):
758
- """"""
765
+ """Called when text entry focus has changed."""
759
766
  if self.use_esm:
760
767
  if hasattr(self.contest, "process_esm"):
761
768
  self.contest.process_esm(self, new_focused_widget=new)
762
769
 
763
770
  def make_button_green(self, the_button: QtWidgets.QPushButton) -> None:
764
- """Turn the_button green."""
771
+ """Takes supplied QPushButton object and turns it green."""
765
772
  if the_button is not None:
766
773
  pal = QPalette()
767
774
  pal.isCopyOf(self.current_palette)
@@ -2457,7 +2464,10 @@ class MainWindow(QtWidgets.QMainWindow):
2457
2464
  self.worked_list = self.database.get_calls_and_bands()
2458
2465
  self.send_worked_list()
2459
2466
  self.clearinputs()
2460
-
2467
+ if self.pref.get("send_rtc_scores", False):
2468
+ if hasattr(self.contest, "online_score_xml"):
2469
+ if self.rtc_service is not None:
2470
+ self.rtc_service.xml = self.contest.online_score_xml(self)
2461
2471
  cmd = {}
2462
2472
  cmd["cmd"] = "UPDATELOG"
2463
2473
  if self.log_window:
@@ -2898,6 +2908,25 @@ class MainWindow(QtWidgets.QMainWindow):
2898
2908
  self.setDarkMode(False)
2899
2909
  self.actionDark_Mode_2.setChecked(False)
2900
2910
 
2911
+ try:
2912
+ if self.rtc_thread.isRunning():
2913
+ self.rtc_service.time_to_quit = True
2914
+ self.rtc_thread.quit()
2915
+ self.rtc_thread.wait(1000)
2916
+
2917
+ except (RuntimeError, AttributeError):
2918
+ ...
2919
+
2920
+ self.rtc_service = None
2921
+
2922
+ if self.pref.get("send_rtc_scores", False):
2923
+ self.rtc_service = RTCService()
2924
+ self.rtc_service.moveToThread(self.rtc_thread)
2925
+ self.rtc_thread.started.connect(self.rtc_service.run)
2926
+ self.rtc_thread.finished.connect(self.rtc_service.deleteLater)
2927
+ # self.rtc_service.poll_callback.connect(self.rtc_result)
2928
+ self.rtc_thread.start()
2929
+
2901
2930
  try:
2902
2931
  if self.radio_thread.isRunning():
2903
2932
  self.rig_control.time_to_quit = True
@@ -3046,6 +3075,12 @@ class MainWindow(QtWidgets.QMainWindow):
3046
3075
  self.esm_dict["MYCALL"] = fkey_dict.get(self.pref.get("esm_mycall", "DISABLED"))
3047
3076
  self.esm_dict["QSOB4"] = fkey_dict.get(self.pref.get("esm_qsob4", "DISABLED"))
3048
3077
 
3078
+ self.send_rtc_scores = self.pref.get("send_rtc_scores", False)
3079
+ self.rtc_url = self.pref.get("rtc_url", "")
3080
+ self.rtc_user = self.pref.get("rtc_user", "")
3081
+ self.rtc_pass = self.pref.get("rtc_pass", "")
3082
+ self.rtc_interval = self.pref.get("rtc_interval", 2)
3083
+
3049
3084
  def dark_mode_state_changed(self) -> None:
3050
3085
  """Called when the Dark Mode menu state is changed."""
3051
3086
  self.pref["darkmode"] = self.actionDark_Mode_2.isChecked()
@@ -2173,6 +2173,76 @@
2173
2173
  </property>
2174
2174
  </widget>
2175
2175
  </item>
2176
+ <item>
2177
+ <widget class="Line" name="line">
2178
+ <property name="orientation">
2179
+ <enum>Qt::Horizontal</enum>
2180
+ </property>
2181
+ </widget>
2182
+ </item>
2183
+ <item>
2184
+ <widget class="QCheckBox" name="send_rtc_scores">
2185
+ <property name="text">
2186
+ <string>Use RTC score reporting</string>
2187
+ </property>
2188
+ </widget>
2189
+ </item>
2190
+ <item>
2191
+ <widget class="QComboBox" name="rtc_url">
2192
+ <item>
2193
+ <property name="text">
2194
+ <string>https://hamscore.com/postxml/</string>
2195
+ </property>
2196
+ </item>
2197
+ <item>
2198
+ <property name="text">
2199
+ <string>https://contestonlinescore.com/post/</string>
2200
+ </property>
2201
+ </item>
2202
+ <item>
2203
+ <property name="text">
2204
+ <string>http://contest.run</string>
2205
+ </property>
2206
+ </item>
2207
+ </widget>
2208
+ </item>
2209
+ <item>
2210
+ <widget class="QLineEdit" name="rtc_user">
2211
+ <property name="placeholderText">
2212
+ <string>username</string>
2213
+ </property>
2214
+ </widget>
2215
+ </item>
2216
+ <item>
2217
+ <widget class="QLineEdit" name="rtc_pass">
2218
+ <property name="echoMode">
2219
+ <enum>QLineEdit::EchoMode::Password</enum>
2220
+ </property>
2221
+ <property name="placeholderText">
2222
+ <string>password</string>
2223
+ </property>
2224
+ </widget>
2225
+ </item>
2226
+ <item>
2227
+ <widget class="QLabel" name="label_28">
2228
+ <property name="text">
2229
+ <string>Score posting interval (minutes)</string>
2230
+ </property>
2231
+ <property name="alignment">
2232
+ <set>Qt::AlignCenter</set>
2233
+ </property>
2234
+ </widget>
2235
+ </item>
2236
+ <item>
2237
+ <widget class="QLineEdit" name="rtc_interval">
2238
+ <property name="text">
2239
+ <string>2</string>
2240
+ </property>
2241
+ <property name="alignment">
2242
+ <set>Qt::AlignCenter</set>
2243
+ </property>
2244
+ </widget>
2245
+ </item>
2176
2246
  <item>
2177
2247
  <spacer name="verticalSpacer_8">
2178
2248
  <property name="orientation">
@@ -217,6 +217,11 @@
217
217
  <string>ARRL 10M</string>
218
218
  </property>
219
219
  </item>
220
+ <item>
221
+ <property name="text">
222
+ <string>ARRL 160M</string>
223
+ </property>
224
+ </item>
220
225
  <item>
221
226
  <property name="text">
222
227
  <string>ARRL DX CW</string>
not1mm/lib/database.py CHANGED
@@ -695,6 +695,23 @@ class DataBase:
695
695
  logger.debug("%s", exception)
696
696
  return {}
697
697
 
698
+ def fetch_exchange1_unique_count(self) -> dict:
699
+ """
700
+ Fetch count of unique countries
701
+ {exch1_count: count}
702
+ """
703
+ try:
704
+ with sqlite3.connect(self.database) as conn:
705
+ conn.row_factory = self.row_factory
706
+ cursor = conn.cursor()
707
+ cursor.execute(
708
+ f"select count(DISTINCT(Exchange1)) as exch1_count from dxlog where Exchange1 != '' and ContestNR = {self.current_contest};"
709
+ )
710
+ return cursor.fetchone()
711
+ except sqlite3.OperationalError as exception:
712
+ logger.debug("%s", exception)
713
+ return {}
714
+
698
715
  def fetch_arrldx_country_band_count(self) -> dict:
699
716
  """
700
717
  returns dict with count of unique NR.
@@ -4,6 +4,58 @@ import datetime
4
4
  from decimal import Decimal
5
5
  from pathlib import Path
6
6
  from not1mm.lib.ham_utility import get_adif_band
7
+ from not1mm.lib.version import __version__
8
+
9
+
10
+ def online_score_xml(self):
11
+ """generate online xml"""
12
+
13
+ mults = self.contest.get_mults(self)
14
+ the_mults = ""
15
+ for thing in mults:
16
+ the_mults += (
17
+ f'<mult band="total" mode="ALL" type="{thing}">{mults.get(thing,0)}</mult>'
18
+ )
19
+
20
+ the_points = self.contest.just_points(self)
21
+
22
+ the_date_time = datetime.datetime.now(datetime.timezone.utc).isoformat(" ")[:19]
23
+ assisted = self.contest_settings.get("AssistedCategory", "")
24
+ bands = self.contest_settings.get("BandCategory", "")
25
+ modes = self.contest_settings.get("ModeCategory", "")
26
+ xmiter = self.contest_settings.get("TransmitterCategory", "")
27
+ ops = self.contest_settings.get("OperatorCategory", "")
28
+ overlay = self.contest_settings.get("OverlayCategory", "")
29
+ power = self.contest_settings.get("PowerCategory", "")
30
+
31
+ the_xml = (
32
+ '<?xml version="1.0"?>'
33
+ "<dynamicresults>"
34
+ f"<contest>{self.contest.cabrillo_name}</contest>"
35
+ f'<call>{self.station.get("Call", "")}</call>'
36
+ # <ops>NR9Q</ops>
37
+ f'<class power="{power}" assisted = "{assisted}" transmitter="{xmiter}" ops="{ops}" bands="{bands}" mode="{modes}" overlay="{overlay}"></class>'
38
+ f"<club>{self.station.get('Club', '').upper()}</club>"
39
+ "<soft>Not1MM</soft>"
40
+ f"<version>{__version__}</version>"
41
+ "<qth>"
42
+ # <dxcccountry>K</dxcccountry>
43
+ f"<cqzone>{self.station.get('CQZone','')}</cqzone>"
44
+ f"<iaruzone>{self.station.get('IARUZone','')}</iaruzone>"
45
+ f"<arrlsection>{self.station.get('ARRLSection', '')}</arrlsection>"
46
+ f"<stprvoth>{self.station.get('State','')}</stprvoth>"
47
+ f"<grid6>{self.station.get('GridSquare','')}</grid6>"
48
+ "</qth>"
49
+ "<breakdown>"
50
+ f'<qso band="total" mode="ALL">{self.contest.show_qso(self)}</qso>'
51
+ f"{the_mults}"
52
+ f'<point band="total" mode="ALL">{the_points}</point>'
53
+ "</breakdown>"
54
+ f"<score>{self.contest.calc_score(self)}</score>"
55
+ f"<timestamp>{the_date_time}</timestamp>"
56
+ "</dynamicresults>"
57
+ )
58
+ return the_xml
7
59
 
8
60
 
9
61
  def get_points(self):
@@ -21,10 +73,11 @@ def gen_adif(self, cabrillo_name: str, contest_id=""):
21
73
  """
22
74
  now = datetime.datetime.now()
23
75
  date_time = now.strftime("%Y-%m-%d_%H-%M-%S")
76
+ station_callsign = self.station.get('Call', '').upper()
24
77
  filename = (
25
78
  str(Path.home())
26
79
  + "/"
27
- + f"{self.station.get('Call', '').upper()}_{cabrillo_name}_{date_time}.adi"
80
+ + f"{station_callsign}_{cabrillo_name}_{date_time}.adi"
28
81
  )
29
82
  log = self.database.fetch_all_contacts_asc()
30
83
  try:
@@ -71,6 +124,15 @@ def gen_adif(self, cabrillo_name: str, contest_id=""):
71
124
  except TypeError:
72
125
  ...
73
126
 
127
+ try:
128
+ print(
129
+ f"<STATION_CALLSIGN:{len(station_callsign)}>{station_callsign}",
130
+ end="\r\n",
131
+ file=file_descriptor,
132
+ )
133
+ except TypeError:
134
+ ...
135
+
74
136
  try:
75
137
  print(
76
138
  f"<CALL:{len(hiscall)}>{hiscall.upper()}",
not1mm/lib/settings.py CHANGED
@@ -41,6 +41,19 @@ class Settings(QtWidgets.QDialog):
41
41
  def setup(self):
42
42
  """setup dialog"""
43
43
 
44
+ self.send_rtc_scores.setChecked(
45
+ bool(self.preference.get("send_rtc_scores", False))
46
+ )
47
+
48
+ value = self.preference.get("rtc_url", "")
49
+ index = self.rtc_url.findText(value)
50
+ if index != -1:
51
+ self.rtc_url.setCurrentIndex(index)
52
+
53
+ self.rtc_user.setText(str(self.preference.get("rtc_user", "")))
54
+ self.rtc_pass.setText(str(self.preference.get("rtc_pass", "")))
55
+ self.rtc_interval.setText(str(self.preference.get("rtc_interval", "2")))
56
+
44
57
  self.use_call_history.setChecked(
45
58
  bool(self.preference.get("use_call_history", False))
46
59
  )
@@ -195,6 +208,15 @@ class Settings(QtWidgets.QDialog):
195
208
  """
196
209
  Write preferences to json file.
197
210
  """
211
+ self.preference["send_rtc_scores"] = self.send_rtc_scores.isChecked()
212
+ self.preference["rtc_url"] = self.rtc_url.currentText()
213
+ self.preference["rtc_user"] = self.rtc_user.text()
214
+ self.preference["rtc_pass"] = self.rtc_pass.text()
215
+ try:
216
+ self.preference["rtc_interval"] = int(self.rtc_interval.text())
217
+ except ValueError:
218
+ self.preference["rtc_interval"] = 2
219
+
198
220
  self.preference["use_call_history"] = self.use_call_history.isChecked()
199
221
  self.preference["use_esm"] = self.use_esm.isChecked()
200
222
  self.preference["esm_cq"] = self.esm_cq.currentText()
not1mm/lib/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """It's the version"""
2
2
 
3
- __version__ = "24.12.3"
3
+ __version__ = "24.12.5"
not1mm/lookupservice.py CHANGED
@@ -3,8 +3,8 @@
3
3
  not1mm Contest logger
4
4
  Email: michael.bridak@gmail.com
5
5
  GPL V3
6
- Class: BandMapWindow
7
- Purpose: Onscreen widget to show realtime spots from an AR cluster.
6
+ Class: LookupService
7
+ Purpose: Lookup callsigns with online services.
8
8
  """
9
9
 
10
10
  # pylint: disable=unused-import, c-extension-no-member, no-member, invalid-name, too-many-lines
@@ -0,0 +1,543 @@
1
+ """ARRL 160 CW plugin"""
2
+
3
+ # pylint: disable=invalid-name, c-extension-no-member, unused-import, line-too-long
4
+
5
+ import datetime
6
+ import logging
7
+ import platform
8
+
9
+ from pathlib import Path
10
+
11
+ from PyQt6 import QtWidgets
12
+
13
+ from not1mm.lib.plugin_common import gen_adif
14
+ from not1mm.lib.version import __version__
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ EXCHANGE_HINT = "ST/Prov or DX CQ Zone"
19
+
20
+ name = "ARRL 160-Meter"
21
+ cabrillo_name = "ARRL-160"
22
+ mode = "CW" # CW SSB BOTH RTTY
23
+
24
+ columns = [
25
+ "YYYY-MM-DD HH:MM:SS",
26
+ "Call",
27
+ "Freq",
28
+ "Snt",
29
+ "Rcv",
30
+ "PFX",
31
+ "Exchange1",
32
+ "PTS",
33
+ ]
34
+
35
+ advance_on_space = [True, True, True, True, True]
36
+
37
+ # 1 once per contest, 2 work each band, 3 each band/mode, 4 no dupe checking
38
+ dupe_type = 1
39
+
40
+
41
+ def init_contest(self):
42
+ """setup plugin"""
43
+ set_tab_next(self)
44
+ set_tab_prev(self)
45
+ interface(self)
46
+ self.next_field = self.other_2
47
+
48
+
49
+ def interface(self):
50
+ """Setup user interface"""
51
+ self.field1.show()
52
+ self.field2.show()
53
+ self.field3.hide()
54
+ self.field4.show()
55
+ self.snt_label.setText("SNT")
56
+ self.field1.setAccessibleName("RST Sent")
57
+ self.exch_label.setText("ARRL/RAC Section")
58
+ self.field4.setAccessibleName("Received Exchange")
59
+
60
+
61
+ def reset_label(self): # pylint: disable=unused-argument
62
+ """reset label after field cleared"""
63
+
64
+
65
+ def set_tab_next(self):
66
+ """Set TAB Advances"""
67
+ self.tab_next = {
68
+ self.callsign: self.sent,
69
+ self.sent: self.receive,
70
+ self.receive: self.other_2,
71
+ self.other_1: self.other_2,
72
+ self.other_2: self.callsign,
73
+ }
74
+
75
+
76
+ def set_tab_prev(self):
77
+ """Set TAB Advances"""
78
+ self.tab_prev = {
79
+ self.callsign: self.other_2,
80
+ self.sent: self.callsign,
81
+ self.receive: self.sent,
82
+ self.other_1: self.receive,
83
+ self.other_2: self.receive,
84
+ }
85
+
86
+
87
+ def set_contact_vars(self):
88
+ """Contest Specific"""
89
+ self.contact["SNT"] = self.sent.text()
90
+ self.contact["RCV"] = self.receive.text()
91
+ self.contact["SentNr"] = self.contest_settings.get("SentExchange", 0)
92
+ self.contact["Exchange1"] = self.other_2.text()
93
+
94
+
95
+ def predupe(self): # pylint: disable=unused-argument
96
+ """called after callsign entered"""
97
+
98
+
99
+ def prefill(self):
100
+ """Fill SentNR"""
101
+
102
+
103
+ def points(self):
104
+ """Calc point"""
105
+ # Each contact between W/VE stations counts for two (2) QSO points. Each contact with a DX station counts five (5) QSO points
106
+ call = self.contact.get("Call", "")
107
+ dupe_check = self.database.check_dupe(call)
108
+ if dupe_check.get("isdupe", 0) > 0:
109
+ return 0
110
+ result = self.cty_lookup(self.station.get("Call", ""))
111
+ if result:
112
+ for item in result.items():
113
+ mypfx = item[1].get("primary_pfx", "")
114
+ # mycountry = item[1].get("entity", "")
115
+ # mycontinent = item[1].get("continent", "")
116
+
117
+ result = self.cty_lookup(self.contact.get("Call", ""))
118
+ if result:
119
+ for item in result.items():
120
+ pfx = item[1].get("primary_pfx", "")
121
+ # entity = item[1].get("entity", "")
122
+ # continent = item[1].get("continent", "")
123
+
124
+ # Both in same country
125
+
126
+ if mypfx in ["K", "VE"] and pfx in ["K", "VE"]:
127
+ return 2
128
+
129
+ if mypfx.upper() != pfx.upper():
130
+ return 5
131
+
132
+ return 0
133
+
134
+
135
+ def show_mults(self):
136
+ """Return display string for mults"""
137
+ result = self.database.fetch_country_count()
138
+ mults = int(result.get("dxcc_count", 0))
139
+
140
+ result = self.database.fetch_exchange1_unique_count()
141
+ mults2 = int(result.get("exch1_count", 0))
142
+
143
+ return mults + mults2
144
+
145
+
146
+ def show_qso(self):
147
+ """Return qso count"""
148
+ result = self.database.fetch_qso_count()
149
+ if result:
150
+ return int(result.get("qsos", 0))
151
+ return 0
152
+
153
+
154
+ def calc_score(self):
155
+ """Return calculated score"""
156
+ result = self.database.fetch_points()
157
+ if result is not None:
158
+ score = result.get("Points", "0")
159
+ if score is None:
160
+ score = "0"
161
+ contest_points = int(score)
162
+
163
+ result = self.database.fetch_country_count()
164
+ mults = int(result.get("dxcc_count", 0))
165
+
166
+ result = self.database.fetch_exchange1_unique_count()
167
+ mults2 = int(result.get("exch1_count", 0))
168
+ return contest_points * (mults + mults2)
169
+ return 0
170
+
171
+
172
+ def adif(self):
173
+ """Call the generate ADIF function"""
174
+ gen_adif(self, cabrillo_name, "ARRL 160-Meter")
175
+
176
+
177
+ def output_cabrillo_line(line_to_output, ending, file_descriptor, file_encoding):
178
+ """"""
179
+ print(
180
+ line_to_output.encode(file_encoding, errors="ignore").decode(),
181
+ end=ending,
182
+ file=file_descriptor,
183
+ )
184
+
185
+
186
+ def cabrillo(self, file_encoding):
187
+ """Generates Cabrillo file. Maybe."""
188
+ # https://www.cw160.com/cabrillo.htm
189
+ logger.debug("******Cabrillo*****")
190
+ logger.debug("Station: %s", f"{self.station}")
191
+ logger.debug("Contest: %s", f"{self.contest_settings}")
192
+ now = datetime.datetime.now()
193
+ date_time = now.strftime("%Y-%m-%d_%H-%M-%S")
194
+ filename = (
195
+ str(Path.home())
196
+ + "/"
197
+ + f"{self.station.get('Call', '').upper()}_{cabrillo_name}_{date_time}.log"
198
+ )
199
+ logger.debug("%s", filename)
200
+ log = self.database.fetch_all_contacts_asc()
201
+ try:
202
+ with open(filename, "w", encoding=file_encoding) as file_descriptor:
203
+ output_cabrillo_line(
204
+ "START-OF-LOG: 3.0",
205
+ "\r\n",
206
+ file_descriptor,
207
+ file_encoding,
208
+ )
209
+ output_cabrillo_line(
210
+ f"CREATED-BY: Not1MM v{__version__}",
211
+ "\r\n",
212
+ file_descriptor,
213
+ file_encoding,
214
+ )
215
+ output_cabrillo_line(
216
+ f"CONTEST: {cabrillo_name}",
217
+ "\r\n",
218
+ file_descriptor,
219
+ file_encoding,
220
+ )
221
+ if self.station.get("Club", ""):
222
+ output_cabrillo_line(
223
+ f"CLUB: {self.station.get('Club', '').upper()}",
224
+ "\r\n",
225
+ file_descriptor,
226
+ file_encoding,
227
+ )
228
+ output_cabrillo_line(
229
+ f"CALLSIGN: {self.station.get('Call','')}",
230
+ "\r\n",
231
+ file_descriptor,
232
+ file_encoding,
233
+ )
234
+ output_cabrillo_line(
235
+ f"LOCATION: {self.station.get('ARRLSection', '')}",
236
+ "\r\n",
237
+ file_descriptor,
238
+ file_encoding,
239
+ )
240
+ output_cabrillo_line(
241
+ f"CATEGORY-OPERATOR: {self.contest_settings.get('OperatorCategory','')}",
242
+ "\r\n",
243
+ file_descriptor,
244
+ file_encoding,
245
+ )
246
+ output_cabrillo_line(
247
+ f"CATEGORY-ASSISTED: {self.contest_settings.get('AssistedCategory','')}",
248
+ "\r\n",
249
+ file_descriptor,
250
+ file_encoding,
251
+ )
252
+ output_cabrillo_line(
253
+ f"CATEGORY-BAND: {self.contest_settings.get('BandCategory','')}",
254
+ "\r\n",
255
+ file_descriptor,
256
+ file_encoding,
257
+ )
258
+ output_cabrillo_line(
259
+ f"CATEGORY-MODE: {self.contest_settings.get('ModeCategory','')}",
260
+ "\r\n",
261
+ file_descriptor,
262
+ file_encoding,
263
+ )
264
+ output_cabrillo_line(
265
+ f"CATEGORY-TRANSMITTER: {self.contest_settings.get('TransmitterCategory','')}",
266
+ "\r\n",
267
+ file_descriptor,
268
+ file_encoding,
269
+ )
270
+ if self.contest_settings.get("OverlayCategory", "") != "N/A":
271
+ output_cabrillo_line(
272
+ f"CATEGORY-OVERLAY: {self.contest_settings.get('OverlayCategory','')}",
273
+ "\r\n",
274
+ file_descriptor,
275
+ file_encoding,
276
+ )
277
+ output_cabrillo_line(
278
+ f"GRID-LOCATOR: {self.station.get('GridSquare','')}",
279
+ "\r\n",
280
+ file_descriptor,
281
+ file_encoding,
282
+ )
283
+ output_cabrillo_line(
284
+ f"CATEGORY-POWER: {self.contest_settings.get('PowerCategory','')}",
285
+ "\r\n",
286
+ file_descriptor,
287
+ file_encoding,
288
+ )
289
+
290
+ output_cabrillo_line(
291
+ f"CLAIMED-SCORE: {calc_score(self)}",
292
+ "\r\n",
293
+ file_descriptor,
294
+ file_encoding,
295
+ )
296
+ ops = f"@{self.station.get('Call','')}"
297
+ list_of_ops = self.database.get_ops()
298
+ for op in list_of_ops:
299
+ ops += f", {op.get('Operator', '')}"
300
+ output_cabrillo_line(
301
+ f"OPERATORS: {ops}",
302
+ "\r\n",
303
+ file_descriptor,
304
+ file_encoding,
305
+ )
306
+ output_cabrillo_line(
307
+ f"NAME: {self.station.get('Name', '')}",
308
+ "\r\n",
309
+ file_descriptor,
310
+ file_encoding,
311
+ )
312
+ output_cabrillo_line(
313
+ f"ADDRESS: {self.station.get('Street1', '')}",
314
+ "\r\n",
315
+ file_descriptor,
316
+ file_encoding,
317
+ )
318
+ output_cabrillo_line(
319
+ f"ADDRESS-CITY: {self.station.get('City', '')}",
320
+ "\r\n",
321
+ file_descriptor,
322
+ file_encoding,
323
+ )
324
+ output_cabrillo_line(
325
+ f"ADDRESS-STATE-PROVINCE: {self.station.get('State', '')}",
326
+ "\r\n",
327
+ file_descriptor,
328
+ file_encoding,
329
+ )
330
+ output_cabrillo_line(
331
+ f"ADDRESS-POSTALCODE: {self.station.get('Zip', '')}",
332
+ "\r\n",
333
+ file_descriptor,
334
+ file_encoding,
335
+ )
336
+ output_cabrillo_line(
337
+ f"ADDRESS-COUNTRY: {self.station.get('Country', '')}",
338
+ "\r\n",
339
+ file_descriptor,
340
+ file_encoding,
341
+ )
342
+ output_cabrillo_line(
343
+ f"EMAIL: {self.station.get('Email', '')}",
344
+ "\r\n",
345
+ file_descriptor,
346
+ file_encoding,
347
+ )
348
+ for contact in log:
349
+ the_date_and_time = contact.get("TS", "")
350
+ themode = contact.get("Mode", "")
351
+ if themode == "LSB" or themode == "USB":
352
+ themode = "PH"
353
+ frequency = str(int(contact.get("Freq", "0"))).rjust(5)
354
+
355
+ loggeddate = the_date_and_time[:10]
356
+ loggedtime = the_date_and_time[11:13] + the_date_and_time[14:16]
357
+ thesentnr = contact.get("SentNr", "---")
358
+ if thesentnr == "":
359
+ thesentnr = "---"
360
+ theexch = contact.get("Exchange1", "---")
361
+ if theexch == "":
362
+ theexch = "---"
363
+
364
+ output_cabrillo_line(
365
+ f"QSO: {frequency} {themode} {loggeddate} {loggedtime} "
366
+ f"{contact.get('StationPrefix', '').ljust(13)} "
367
+ f"{str(contact.get('SNT', '')).ljust(3)} "
368
+ f"{str(thesentnr).ljust(6)} "
369
+ f"{contact.get('Call', '').ljust(13)} "
370
+ f"{str(contact.get('RCV', '')).ljust(3)} "
371
+ f"{str(theexch).ljust(6)}",
372
+ "\r\n",
373
+ file_descriptor,
374
+ file_encoding,
375
+ )
376
+ output_cabrillo_line("END-OF-LOG:", "\r\n", file_descriptor, file_encoding)
377
+ self.show_message_box(f"Cabrillo saved to: {filename}")
378
+ except IOError as exception:
379
+ logger.critical("cabrillo: IO error: %s, writing to %s", exception, filename)
380
+ self.show_message_box(f"Error saving Cabrillo: {exception} {filename}")
381
+ return
382
+
383
+
384
+ # def trigger_update(self):
385
+ # """Triggers the log window to update."""
386
+ # cmd = {}
387
+ # cmd["cmd"] = "UPDATELOG"
388
+ # cmd["station"] = platform.node()
389
+ # self.multicast_interface.send_as_json(cmd)
390
+
391
+
392
+ def recalculate_mults(self):
393
+ """Recalculates multipliers after change in logged qso."""
394
+ # all_contacts = self.database.fetch_all_contacts_asc()
395
+ # for contact in all_contacts:
396
+ # time_stamp = contact.get("TS", "")
397
+ # if contact.get("CountryPrefix", "") == "K":
398
+ # query = f"select count(*) as count from dxlog where TS < '{time_stamp}' and CountryPrefix = 'K' and Exchange1 = '{contact.get('Exchange1', '')}' and ContestNR = '{self.pref.get('contest', '0')}'"
399
+ # result = self.database.exec_sql(query)
400
+ # if result.get("count", 0) == 0:
401
+ # contact["IsMultiplier1"] = 1
402
+ # else:
403
+ # contact["IsMultiplier1"] = 0
404
+ # self.database.change_contact(contact)
405
+ # continue
406
+ # if contact.get("CountryPrefix", "") == "VE":
407
+ # query = f"select count(*) as count from dxlog where TS < '{time_stamp}' and CountryPrefix = 'VE' and Exchange1 = '{contact.get('Exchange1', '')}' and ContestNR = '{self.pref.get('contest', '0')}'"
408
+ # result = self.database.exec_sql(query)
409
+ # if result.get("count", 0) == 0:
410
+ # contact["IsMultiplier1"] = 1
411
+ # else:
412
+ # contact["IsMultiplier1"] = 0
413
+ # self.database.change_contact(contact)
414
+ # continue
415
+ # query = f"select count(*) as count from dxlog where TS < '{time_stamp}' and CountryPrefix = '{contact.get('CountryPrefix', '')}' and ContestNR = '{self.pref.get('contest', '0')}'"
416
+ # result = self.database.exec_sql(query)
417
+ # if result.get("count", 0) == 0:
418
+ # contact["IsMultiplier1"] = 1
419
+ # else:
420
+ # contact["IsMultiplier1"] = 0
421
+ # self.database.change_contact(contact)
422
+ # trigger_update(self)
423
+
424
+
425
+ def process_esm(self, new_focused_widget=None, with_enter=False):
426
+ """ESM State Machine"""
427
+
428
+ # self.pref["run_state"]
429
+
430
+ # -----===== Assigned F-Keys =====-----
431
+ # self.esm_dict["CQ"]
432
+ # self.esm_dict["EXCH"]
433
+ # self.esm_dict["QRZ"]
434
+ # self.esm_dict["AGN"]
435
+ # self.esm_dict["HISCALL"]
436
+ # self.esm_dict["MYCALL"]
437
+ # self.esm_dict["QSOB4"]
438
+
439
+ # ----==== text fields ====----
440
+ # self.callsign
441
+ # self.sent
442
+ # self.receive
443
+ # self.other_1
444
+ # self.other_2
445
+
446
+ if new_focused_widget is not None:
447
+ self.current_widget = self.inputs_dict.get(new_focused_widget)
448
+
449
+ # print(f"checking esm {self.current_widget=} {with_enter=} {self.pref.get("run_state")=}")
450
+
451
+ for a_button in [
452
+ self.esm_dict["CQ"],
453
+ self.esm_dict["EXCH"],
454
+ self.esm_dict["QRZ"],
455
+ self.esm_dict["AGN"],
456
+ self.esm_dict["HISCALL"],
457
+ self.esm_dict["MYCALL"],
458
+ self.esm_dict["QSOB4"],
459
+ ]:
460
+ if a_button is not None:
461
+ self.restore_button_color(a_button)
462
+
463
+ buttons_to_send = []
464
+
465
+ if self.pref.get("run_state"):
466
+ if self.current_widget == "callsign":
467
+ if len(self.callsign.text()) < 3:
468
+ self.make_button_green(self.esm_dict["CQ"])
469
+ buttons_to_send.append(self.esm_dict["CQ"])
470
+ elif len(self.callsign.text()) > 2:
471
+ self.make_button_green(self.esm_dict["HISCALL"])
472
+ self.make_button_green(self.esm_dict["EXCH"])
473
+ buttons_to_send.append(self.esm_dict["HISCALL"])
474
+ buttons_to_send.append(self.esm_dict["EXCH"])
475
+
476
+ elif self.current_widget in ["other_2"]:
477
+ if self.contact.get("CountryPrefix", "") in ["K", "VE"]:
478
+ if self.other_2.text() == "":
479
+ self.make_button_green(self.esm_dict["AGN"])
480
+ buttons_to_send.append(self.esm_dict["AGN"])
481
+ else:
482
+ self.make_button_green(self.esm_dict["QRZ"])
483
+ buttons_to_send.append(self.esm_dict["QRZ"])
484
+ buttons_to_send.append("LOGIT")
485
+ else:
486
+ self.make_button_green(self.esm_dict["QRZ"])
487
+ buttons_to_send.append(self.esm_dict["QRZ"])
488
+ buttons_to_send.append("LOGIT")
489
+
490
+ if with_enter is True and bool(len(buttons_to_send)):
491
+ for button in buttons_to_send:
492
+ if button:
493
+ if button == "LOGIT":
494
+ self.save_contact()
495
+ continue
496
+ self.process_function_key(button)
497
+ else:
498
+ if self.current_widget == "callsign":
499
+ if len(self.callsign.text()) > 2:
500
+ self.make_button_green(self.esm_dict["MYCALL"])
501
+ buttons_to_send.append(self.esm_dict["MYCALL"])
502
+
503
+ elif self.current_widget in ["other_2"]:
504
+ if self.contact.get("CountryPrefix", "") in ["K", "VE"]:
505
+ if self.other_2.text() == "":
506
+ self.make_button_green(self.esm_dict["AGN"])
507
+ buttons_to_send.append(self.esm_dict["AGN"])
508
+ else:
509
+ self.make_button_green(self.esm_dict["EXCH"])
510
+ buttons_to_send.append(self.esm_dict["EXCH"])
511
+ buttons_to_send.append("LOGIT")
512
+
513
+ else:
514
+ self.make_button_green(self.esm_dict["EXCH"])
515
+ buttons_to_send.append(self.esm_dict["EXCH"])
516
+ buttons_to_send.append("LOGIT")
517
+
518
+ if with_enter is True and bool(len(buttons_to_send)):
519
+ for button in buttons_to_send:
520
+ if button:
521
+ if button == "LOGIT":
522
+ self.save_contact()
523
+ continue
524
+ self.process_function_key(button)
525
+
526
+
527
+ def populate_history_info_line(self):
528
+ result = self.database.fetch_call_history(self.callsign.text())
529
+ if result:
530
+ self.history_info.setText(
531
+ f"{result.get('Call', '')}, {result.get('Name', '')}, {result.get('Exch1', '')}, {result.get('UserText','...')}"
532
+ )
533
+ else:
534
+ self.history_info.setText("")
535
+
536
+
537
+ def check_call_history(self):
538
+ """"""
539
+ result = self.database.fetch_call_history(self.callsign.text())
540
+ if result:
541
+ self.history_info.setText(f"{result.get('UserText','')}")
542
+ if self.other_2.text() == "":
543
+ self.other_2.setText(f"{result.get('Exch1', '')}")
@@ -43,7 +43,7 @@ from pathlib import Path
43
43
 
44
44
  from PyQt6 import QtWidgets
45
45
 
46
- from not1mm.lib.plugin_common import gen_adif, get_points
46
+ from not1mm.lib.plugin_common import gen_adif, get_points, online_score_xml
47
47
  from not1mm.lib.version import __version__
48
48
  from not1mm.lib.ham_utility import get_logged_band
49
49
 
@@ -177,6 +177,19 @@ def points(self):
177
177
  return 0
178
178
 
179
179
 
180
+ def get_mults(self):
181
+ """"""
182
+ mults = {}
183
+ mults["zone"] = self.database.fetch_zn_band_count().get("zb_count", 0)
184
+ mults["country"] = self.database.fetch_country_band_count().get("cb_count", 0)
185
+ return mults
186
+
187
+
188
+ def just_points(self):
189
+ """"""
190
+ return self.database.fetch_points().get("Points", "0")
191
+
192
+
180
193
  def show_mults(self):
181
194
  """Return display string for mults"""
182
195
  result1 = self.database.fetch_zn_band_count()
not1mm/plugins/cwt.py CHANGED
@@ -33,7 +33,7 @@ from pathlib import Path
33
33
 
34
34
  from PyQt6 import QtWidgets
35
35
 
36
- from not1mm.lib.plugin_common import gen_adif, get_points
36
+ from not1mm.lib.plugin_common import gen_adif, get_points, online_score_xml
37
37
  from not1mm.lib.version import __version__
38
38
 
39
39
  logger = logging.getLogger(__name__)
@@ -519,3 +519,23 @@ def check_call_history(self):
519
519
  self.other_1.setText(f"{result.get('Name', '')}")
520
520
  if self.other_2.text() == "":
521
521
  self.other_2.setText(f"{result.get('Exch1', '')}")
522
+
523
+
524
+ # --------RTC Stuff-----------
525
+ def get_mults(self):
526
+ """"""
527
+
528
+ mults = {}
529
+ mults["state"] = show_mults(self)
530
+ return mults
531
+
532
+
533
+ def just_points(self):
534
+ """"""
535
+ result = self.database.fetch_points()
536
+ if result is not None:
537
+ score = result.get("Points", "0")
538
+ if score is None:
539
+ score = "0"
540
+ return int(score)
541
+ return 0
@@ -34,7 +34,7 @@ from pathlib import Path
34
34
 
35
35
  from PyQt6 import QtWidgets
36
36
 
37
- from not1mm.lib.plugin_common import gen_adif, get_points
37
+ from not1mm.lib.plugin_common import gen_adif, get_points, online_score_xml
38
38
  from not1mm.lib.version import __version__
39
39
 
40
40
  logger = logging.getLogger(__name__)
@@ -493,3 +493,23 @@ def check_call_history(self):
493
493
  self.history_info.setText(f"{result.get('UserText','')}")
494
494
  if self.other_2.text() == "":
495
495
  self.other_2.setText(f"{result.get('Name', '')}")
496
+
497
+
498
+ # --------RTC Stuff-----------
499
+ def get_mults(self):
500
+ """"""
501
+
502
+ mults = {}
503
+ mults["state"] = show_mults(self)
504
+ return mults
505
+
506
+
507
+ def just_points(self):
508
+ """"""
509
+ result = self.database.fetch_points()
510
+ if result is not None:
511
+ score = result.get("Points", "0")
512
+ if score is None:
513
+ score = "0"
514
+ return int(score)
515
+ return 0
not1mm/rtc_service.py ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ not1mm Contest logger
4
+ Email: michael.bridak@gmail.com
5
+ GPL V3
6
+ Class: RTCService
7
+ Purpose: Service to post 'real time' scores.
8
+ """
9
+
10
+ # pylint: disable=unused-import, c-extension-no-member, no-member, invalid-name, too-many-lines
11
+ # pylint: disable=logging-fstring-interpolation, line-too-long, no-name-in-module
12
+
13
+ import datetime
14
+ import logging
15
+ import os
16
+ from json import loads
17
+
18
+ import requests
19
+ from requests.auth import HTTPBasicAuth
20
+
21
+ from PyQt6.QtCore import QObject, pyqtSignal, QThread, QEventLoop
22
+
23
+ import not1mm.fsutils as fsutils
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class RTCService(QObject):
29
+ """The RTC Service class."""
30
+
31
+ poll_callback = pyqtSignal(dict)
32
+ delta = 2 # two minutes
33
+ poll_time = datetime.datetime.now() + datetime.timedelta(minutes=delta)
34
+ time_to_quit = False
35
+ xml = ""
36
+
37
+ def __init__(self):
38
+ super().__init__()
39
+ self.pref = self.get_settings()
40
+ self.delta = self.pref.get("rtc_interval", 2)
41
+
42
+ def run(self) -> None:
43
+ """Send score xml object to rtc scoring site."""
44
+ while not self.time_to_quit:
45
+ # if self.pref.get("send_rtc_scores", False) is True:
46
+ if datetime.datetime.now() > self.poll_time:
47
+ self.poll_time = datetime.datetime.now() + datetime.timedelta(
48
+ minutes=self.delta
49
+ )
50
+ if len(self.xml):
51
+ headers = {"Content-Type": "text/xml"}
52
+ try:
53
+ result = requests.post(
54
+ self.pref.get("rtc_url", ""),
55
+ data=self.xml,
56
+ headers=headers,
57
+ auth=HTTPBasicAuth(
58
+ self.pref.get("rtc_user", ""),
59
+ self.pref.get("rtc_pass", ""),
60
+ ),
61
+ timeout=30,
62
+ )
63
+ print(f"{self.xml=}\n{result=}\n{result.text}")
64
+ except requests.exceptions.Timeout:
65
+ print("RTC post timeout.")
66
+ except requests.exceptions.RequestException as e:
67
+ print(f"An RTC post error occurred: {e}")
68
+ else:
69
+ print("No XML data")
70
+ try:
71
+ self.poll_callback.emit({"success": True})
72
+ except QEventLoop:
73
+ ...
74
+ QThread.msleep(1)
75
+
76
+ def get_settings(self) -> dict:
77
+ """Get the settings."""
78
+ if os.path.exists(fsutils.CONFIG_FILE):
79
+ with open(fsutils.CONFIG_FILE, "rt", encoding="utf-8") as file_descriptor:
80
+ return loads(file_descriptor.read())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: not1mm
3
- Version: 24.12.3
3
+ Version: 24.12.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
@@ -238,6 +238,9 @@ generated, 'cause I'm lazy, list of those who've submitted PR's.
238
238
 
239
239
  ## Recent Changes (Polishing the Turd)
240
240
 
241
+ - [24-12-5] Add 'real time' score posting to external sites.
242
+ - [24-12-4] Merged PR from @alduhoo Add STATION_CALLSIGN field to ADIF output
243
+ - [24-12-3-1] Adding ARRL 160
241
244
  - [24-12-3] Add button to bandmap to delete marked spots.
242
245
 
243
246
  See [CHANGELOG.md](CHANGELOG.md) for prior changes.
@@ -531,7 +534,7 @@ You can fill. You can fill. Everyone look at your keys.
531
534
 
532
535
  ### Changing station information
533
536
 
534
- Station information can be changed any time by going toawandahl
537
+ Station information can be changed any time by going to
535
538
  `File` > `Station Settings` and editing the information.
536
539
 
537
540
  ## Selecting a contest (It's REQUIRED Russ)
@@ -584,10 +587,7 @@ onscreen icon for CAT status. Green good, Red bad, Grey neither.
584
587
 
585
588
  Under the `CW` TAB, There are three options. `cwdaemon`, which normally uses IP
586
589
  `127.0.0.1`port `6789`. `pywinkeyer` which normally uses IP `127.0.0.1` port `8000` and
587
- `CAT` which if your radio supports it, sends Morse characters via rigctld. As far
588
- as I can tell rigctld does not support setting the radios internal keyer speed. So
589
- the CW speed control widget will not be functional and you'd need to control the
590
- keyer speed thru the radios interface.
590
+ `CAT` which if your radio supports it, sends Morse characters via rigctld.
591
591
 
592
592
  ### Cluster
593
593
 
@@ -615,27 +615,21 @@ appear. Those without will not.
615
615
 
616
616
  ### Options
617
617
 
618
- On the Options TAB you can select to use Enter Sends Message (ESM), configure its function keys.
619
- Select whether or not to use Call History info.
618
+ On the Options TAB you can:
620
619
 
621
- ![Bands Options Screen](https://github.com/mbridak/not1mm/blob/master/pic/configuration_options.png?raw=true)
620
+ - Select to use Enter Sends Message (ESM), and configure it's function keys.
621
+ - Select whether or not to use Call History info.
622
622
 
623
- ## Logging WSJT-X FT8/FT4/ETC and FLDIGI RTTY contacts
624
-
625
- **Digital modes only working for:**
623
+ ![Options Screen](https://github.com/mbridak/not1mm/blob/master/pic/configuration_options.png?raw=true)
626
624
 
627
- - ARRL Field Day
628
- - ARRL VHF
629
- - Weekly RTTY
630
- - CQ WW DX RTTY
631
- - CQ WPX RTTY
625
+ ## Logging WSJT-X FT8/FT4/ETC and FLDIGI RTTY contacts
632
626
 
633
627
  not1mm listens for WSJT-X UDP traffic on the Multicast address 224.0.0.1:2237.
634
628
  No setup is needed to be done on not1mm's side. That's good because I'm lazy.
635
629
 
636
630
  not1mm polls for fldigi QSOs via it's XMLRPC interface. It does this in a rather stupid
637
631
  way. It just keeps asking what was the last QSO and compares it to the previous response.
638
- If it's different, it's new. I've added the Weekly RTTY Test so this can be tested.
632
+ If it's different, it's new.
639
633
 
640
634
  The F1-F12 function keys be sent to fldigi via XMLRPC. Fldigi will be placed into TX
641
635
  mode, the message will be sent and a ^r will be tacked onto the end to place it back into
@@ -648,18 +642,20 @@ Generic Contest. Make sure the Text Capture Order field says CALL EXCHANGE.
648
642
  ## Sending CW
649
643
 
650
644
  Other than sending CW by hand, you can also send predefined CW text messages by
651
- pressing F1 - F12. See next section on Editing macro keys. If you need to send
652
- something freeform, you can press CTRL-SHIFT-K, this will expose an entry field
653
- at the bottom of the window which you can type directly into. When you're done
654
- you can either press CTRL-SHIFT-K again, or press the Enter Key to close the
655
- field.
645
+ pressing F1 - F12. See next section on Editing macro keys.
646
+
647
+ If you need to send something freeform, you can press `CTRL-SHIFT-K`, this will
648
+ expose an entry field at the bottom of the window which you can type directly into.
649
+ When you're done you can either press CTRL-SHIFT-K again, or press the Enter Key to
650
+ close the field.
656
651
 
657
652
  ## Editing macro keys
658
653
 
659
654
  To edit the macros, choose `File` > `Edit Macros`. This will open your systems
660
655
  registered text editor with current macros loaded. When your done just save the
661
656
  file and close the editor. The file loaded to edit, CW, SSB or RTTY, will be
662
- determined by your current operating mode.
657
+ determined by your current operating mode and contest. Each contest gets it's own
658
+ copy of the macros.
663
659
 
664
660
  After editing and saving the macro file. You can force the logger to reload the
665
661
  macro file by toggeling between `Run` and `S&P` states.
@@ -1,11 +1,12 @@
1
1
  not1mm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- not1mm/__main__.py,sha256=SOPb_t3oI7vk8v1MhgTTeU-axyfU464n-pTDciNWp3U,142786
2
+ not1mm/__main__.py,sha256=4NazDz0wMfre9nXHwdtAZZtkQkEbCUeo4IiquZeuxWg,144335
3
3
  not1mm/bandmap.py,sha256=zD3aUf36NVQCy0plAcZLNxYhSEM9xZ8J1Cu9vrcFPYA,31136
4
4
  not1mm/checkwindow.py,sha256=VFAcKYTcoWhmIf91chwY6tyao9FQMWPiUkgDDkkWaog,9670
5
5
  not1mm/fsutils.py,sha256=ukHKxKTeNKxKwqRaJjtzRShL4X5Xl0jRBbADyy3Ifp8,1701
6
6
  not1mm/logwindow.py,sha256=TvpzQTNB92hISlUO3iWBqtlPmlebdhOkAArx0DNGcOs,43966
7
- not1mm/lookupservice.py,sha256=4c36x_1G3Sy69gQfJ6El7vHLIKTjLGH67ziPPoeYweM,2648
7
+ not1mm/lookupservice.py,sha256=GkY_qHZfrW6XHf8upIoaG4hCFqm0fg6Ganu9ConGrIc,2628
8
8
  not1mm/radio.py,sha256=c4m7Ci38uKGKxB0JUT5uOKalI_Mm8Vmixu5D_roN5z4,5400
9
+ not1mm/rtc_service.py,sha256=Fu_Ru0GR2wcTfmbnt8cIAC5BJfPQEb4kuSTyQohZoi8,2756
9
10
  not1mm/test.py,sha256=RN71m2S9MPIOJMaoCi0wZhwEhpEZunvtosZxaKahRB4,101
10
11
  not1mm/vfo.py,sha256=ggPyWtxMbdSE5RwdK0nDRwDNqOxdpb_pvnzZdbzZVQE,11136
11
12
  not1mm/voice_keying.py,sha256=sA3gw5_k7kShTg2qhG7HkKDM5M6KheJVRkAc_C7mxDk,3006
@@ -16,7 +17,7 @@ not1mm/data/alpha bravo charlie delta.txt,sha256=d5QMmSWEUAe4Rj1XbNjTPLa_5Be4Se6
16
17
  not1mm/data/bandmap.ui,sha256=krmUYxGPRt2EeqSGrEkY2cXG79ujel3T2_2ymrrbgzg,7942
17
18
  not1mm/data/check.png,sha256=UvFOLr8V-79qnjW8wUaGItXk_OSP8m8hqPevs8NDlFY,387
18
19
  not1mm/data/checkwindow.ui,sha256=PRD98K0julJ9EfWqoE89dT8UPuPKQzGiWBk_efAko3o,5141
19
- not1mm/data/configuration.ui,sha256=nDiLlttOOCnejEMxOFkPNpXHJErD92NdGA1llXUtzL0,70329
20
+ not1mm/data/configuration.ui,sha256=VJv_uJp53mPK6YyVXOu0aRRtgrgFvIha_wX_7P4M9t0,72554
20
21
  not1mm/data/contests.sql,sha256=4hmJCDvrbxnA_Y5S4T5o52TZieeFk6QUwFerwlFePNA,89307
21
22
  not1mm/data/cty.json,sha256=dPG9K1Pm4Rxd4uJom_gQ8y-sbqiZfILpl4kBAFnOveU,4877142
22
23
  not1mm/data/cwmacros.txt,sha256=NztufsX6R52gAO7VyJ2AHr7wOh41pJTwHKh5Lcs32ds,468
@@ -31,7 +32,7 @@ not1mm/data/k6gte.not1mm-64.png,sha256=6ku45Gq1g5ezh04F07osoKRtanb3e4kbx5XdIEh3N
31
32
  not1mm/data/logwindow.ui,sha256=f7vULj96tHIQuR1nJMyvPHHcmVgzkhv9D1isyojsnFU,1458
32
33
  not1mm/data/logwindowx.ui,sha256=CwpI-h7cI1yqyldH9quKftsdHL5lTyL9ABOcf80nfqc,1632
33
34
  not1mm/data/main.ui,sha256=pI-70TYESe85ENkRH8l1DXnKDOkwYqKXUdMk6KYaN50,63193
34
- not1mm/data/new_contest.ui,sha256=SiKF_zwnfHopEN_x9R1ftC5f4IO9qLOkIFnoXhoe8vQ,23738
35
+ not1mm/data/new_contest.ui,sha256=KXLQixsrKPnS7siN12CznMxtJtwVInf8Aqdw7o4Q2kA,23844
35
36
  not1mm/data/not1mm.html,sha256=c9-mfjMwDt4f5pySUruz2gREW33CQ2_rCddM2z5CZQo,23273
36
37
  not1mm/data/opon.ui,sha256=QDicqAk2lORG2UWsHa6jHlsGn6uzrrI2R4HSAocpPes,2258
37
38
  not1mm/data/pickcontest.ui,sha256=4hPBszCglObThx_eIWtmK9CEcbr7WBjbB1rKZdI-o3I,1707
@@ -97,7 +98,7 @@ not1mm/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
98
  not1mm/lib/about.py,sha256=sWycfGcruN3SaEe4JmaJ61K6D8Itq0WxpUYT-lEcmYM,416
98
99
  not1mm/lib/cat_interface.py,sha256=0R9ZiEZ0u0RzPrw_hrFNIdS4ybUBgAKtD74x2jdCVIU,25094
99
100
  not1mm/lib/cwinterface.py,sha256=yQL8Dif9oOIynaRItHgvcmu4mYv1TnTpqCHPtkeb09o,4472
100
- not1mm/lib/database.py,sha256=nqWp2eJ7JfUTqaQ9AVbx3XjgtlRnYY9ruTQCv2YRreY,48310
101
+ not1mm/lib/database.py,sha256=mm3vJgKmAQcUn4Ej--feR0yc_RhWTN01JIwMelI1EdI,48971
101
102
  not1mm/lib/edit_contact.py,sha256=Ki9bGPpqyQQBB1cU8VIBDCal3lbXeQ6qxhzklmhE2_w,353
102
103
  not1mm/lib/edit_macro.py,sha256=raKWBwsHInj5EUKmvyLQ6gqc3ZFDlstsD3xqoM4PC8E,517
103
104
  not1mm/lib/edit_opon.py,sha256=j3qJ1aBsQoIOnQ9yiBl3lyeISvKTP0I_rtBYBPAfgeI,359
@@ -110,11 +111,11 @@ not1mm/lib/lookup.py,sha256=KECMDi9tflRDzgTLeDfDl7HGWWRHvW3HCjNHyyjoWaY,10835
110
111
  not1mm/lib/multicast.py,sha256=KJcruI-bOuHfHXPjl3SGQhL6I9sKrygy-sdFSvxffUM,3255
111
112
  not1mm/lib/n1mm.py,sha256=H54mpgJF0GAmKavM-nb5OAq2SJFWYkux4eMWWiSRxJc,6288
112
113
  not1mm/lib/new_contest.py,sha256=IznTDMq7yXHB6zBoGUEC_WDYPCPpsSZW4wwMJi16zK0,816
113
- not1mm/lib/plugin_common.py,sha256=AABdx9DoTT8Znrup7AkfmKGC22hshMsEypiMqV0iKw0,10671
114
+ not1mm/lib/plugin_common.py,sha256=-aduWHc_YEQKAFFYXblC7UGq9ymcxhEPL_LKgwDD7_w,13157
114
115
  not1mm/lib/select_contest.py,sha256=WsptLuwkouIHeocJL3oZ6-eUfEnhpwdc-x7eMZ_TIVM,359
115
- not1mm/lib/settings.py,sha256=pyCa6EUhDs97g1KTmWS8RLXCSbrB9ruv7LV48_nuEAk,13781
116
+ not1mm/lib/settings.py,sha256=j5lIMLHJ-eqIaVr_QhI82gkbOl17_C-5suRkWbHYET8,14717
116
117
  not1mm/lib/super_check_partial.py,sha256=hwT2NRwobu0PLDyw6ltmbmcAtGBD02CKGFbgGWjXMqA,2334
117
- not1mm/lib/version.py,sha256=GO-war2hnAKCX5G2VG4VUL4397VZxAsACqOxGXlhe3Q,48
118
+ not1mm/lib/version.py,sha256=Gq_FT5obxRUq85-MUXiLa6wBTa2vTHKIXlLWiMe9oRY,48
118
119
  not1mm/lib/versiontest.py,sha256=8vDNptuBBunn-1IGkjNaquehqBYUJyjrPSF8Igmd4_Y,1286
119
120
  not1mm/plugins/10_10_fall_cw.py,sha256=AsvB2VUd6Qb2_FzZkSBkSd1_qeP8Dt-B-exF1Pzb9tk,14469
120
121
  not1mm/plugins/10_10_spring_cw.py,sha256=nA4v0oqlp-ivvKqNPakb19I-wE_ElhvH5bCzDRx00JU,14474
@@ -122,6 +123,7 @@ not1mm/plugins/10_10_summer_phone.py,sha256=FNcTQoyZCeAW2i3SKYYDZWuJS1vmk1CO4XO1
122
123
  not1mm/plugins/10_10_winter_phone.py,sha256=NRAKgu4oYzrpUtjUKWgCfZQf3b85sdVe9oyl-yD6kJo,14486
123
124
  not1mm/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
125
  not1mm/plugins/arrl_10m.py,sha256=-stuBD3DCdlWctxE9bWEeznZrgIfNw7VcMIE1ZmsfMo,17942
126
+ not1mm/plugins/arrl_160m.py,sha256=azm0SV-bTDHe2LDK1TjVgRLajlv2vnKZrpN7wgTv7ho,18441
125
127
  not1mm/plugins/arrl_dx_cw.py,sha256=1epTIf9TjeUCjYlpRDU54Ig3lJdXa2e5JZI9SCGE080,17400
126
128
  not1mm/plugins/arrl_dx_ssb.py,sha256=IEZ6tlP9stW3Mdr5_qTBS77hjAUU43IpagyPjr0eqaQ,17403
127
129
  not1mm/plugins/arrl_field_day.py,sha256=YEyXr1Ytllq12sCj54erOba0wrblwb0_bq51gj75OIQ,16478
@@ -137,17 +139,17 @@ not1mm/plugins/cq_160_ssb.py,sha256=_0bkvq0KvSdyTBhkqJ35VqXuaWIaDNmBGE1aQ-cxQz8,
137
139
  not1mm/plugins/cq_wpx_cw.py,sha256=H1H4xL-hx0sqU_8fSQYnNzO0ZcOLV-KSOQT9wPvIva0,17843
138
140
  not1mm/plugins/cq_wpx_rtty.py,sha256=nT2lMdAM1pRu2jNKI4FpkGei9kEGX0XcF_24FkL0lnY,20662
139
141
  not1mm/plugins/cq_wpx_ssb.py,sha256=Zjga12w_ISh4aZjCYZbpwN0x0032Prc8p7aIGI7HJFQ,16410
140
- not1mm/plugins/cq_ww_cw.py,sha256=POh_H6FlZrFeayt4x0aGn-xytcPZ_Ww8w8mFStoLyOE,17893
142
+ not1mm/plugins/cq_ww_cw.py,sha256=53N-q1mTKF4-GH-cyh-a43b4BQ2TP_PNsWWpZchxieI,18230
141
143
  not1mm/plugins/cq_ww_rtty.py,sha256=Pfpr8xWJwp2NOci-WQMTUZaMpAtsUGq1jrIIUv6lQ2Y,21971
142
144
  not1mm/plugins/cq_ww_ssb.py,sha256=IyPmEImq_eb5YSFuhHxiJU4EFAPB4D5dg2xED6Nu97k,17491
143
- not1mm/plugins/cwt.py,sha256=3gA1DqiXxj5NARdG5i0PyFmuq3XSXn6LisZxD5jFs4M,17034
145
+ not1mm/plugins/cwt.py,sha256=WlZu8urd0J_4sEOKtgf-GrgHPukYT2E13GRXal8i794,17424
144
146
  not1mm/plugins/darc_xmas.py,sha256=GdtAQVCLogKGzZaexJfzsZms5SbLLlO1YweFPjgvYWw,18458
145
147
  not1mm/plugins/general_logging.py,sha256=NV_FCgpAEEQrVRxMDD7nQ2krJgPrhtopizxrGndtUNk,6686
146
148
  not1mm/plugins/helvetia.py,sha256=SRKn7jflfYPUNrvmErDM44af5YWUe57h7JkIwFSbT0Q,19609
147
149
  not1mm/plugins/iaru_fieldday_r1_cw.py,sha256=oWeFuKxvY15vRiUh2vW3z3o7mxJMae7vfpKx4OFU_yA,16816
148
150
  not1mm/plugins/iaru_fieldday_r1_ssb.py,sha256=HylTAcNs0DSii5EDzMQlocjs4k7rQ579YvLrwn6sqIQ,16821
149
151
  not1mm/plugins/iaru_hf.py,sha256=RcVf0UFaHX0eSpUZMMGHC0HTsOy_SwTH9Yi9SeJNQUA,15715
150
- not1mm/plugins/icwc_mst.py,sha256=K1tgNXiknGbnvxG4sEZQCgZnjI6x3OYRI_4Djmr8E2Q,15976
152
+ not1mm/plugins/icwc_mst.py,sha256=iFV7iHdI8BLnag-gkQ2q0S4h9n7jXoZ0oTJxtTG14OU,16366
151
153
  not1mm/plugins/jidx_cw.py,sha256=KJOE3fU0KVMqD5IqvnN3YDHPEwrMx3yJZBmCtAIP7WQ,15650
152
154
  not1mm/plugins/jidx_ph.py,sha256=1l92EmDZJFRGZjR1VrISgFc8KoHVfmJvLsaVsuufIMs,14599
153
155
  not1mm/plugins/k1usn_sst.py,sha256=71uO6nUf86J77qSQIiYl_H9EKa2iwyHijcMOk61q6uQ,16653
@@ -162,9 +164,9 @@ not1mm/plugins/ref_ssb.py,sha256=G2Gz4kApchmOZQVnBexEokSEvdb-mZWJAfyJ1D6JDGY,204
162
164
  not1mm/plugins/stew_perry_topband.py,sha256=Gy_vv6tgkR-3vmvsUVO0pVfHMkUJSxpt7G4secn0RH8,15084
163
165
  not1mm/plugins/weekly_rtty.py,sha256=PI0_AtEdZZKGAuKnP-b2EYn9xwCN1Ablk38trbNP3Rc,19603
164
166
  not1mm/plugins/winter_field_day.py,sha256=9w3tDL9ZWiENSTERc3vzDbBktvI7pnyNvlH6fDjAi08,14841
165
- not1mm-24.12.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
166
- not1mm-24.12.3.dist-info/METADATA,sha256=Ij0CAlyXU7wET1KQoq2pLushvUl2JY0iKB0BRfKliVI,35655
167
- not1mm-24.12.3.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
168
- not1mm-24.12.3.dist-info/entry_points.txt,sha256=pMcZk_0dxFgLkcUkF0Q874ojpwOmF3OL6EKw9LgvocM,47
169
- not1mm-24.12.3.dist-info/top_level.txt,sha256=0YmTxEcDzQlzXub-lXASvoLpg_mt1c2thb5cVkDf5J4,7
170
- not1mm-24.12.3.dist-info/RECORD,,
167
+ not1mm-24.12.5.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
168
+ not1mm-24.12.5.dist-info/METADATA,sha256=Ar9RMBUq1lqLo-121C91gleMPhRr1xnRINb7-l50MfE,35509
169
+ not1mm-24.12.5.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
170
+ not1mm-24.12.5.dist-info/entry_points.txt,sha256=pMcZk_0dxFgLkcUkF0Q874ojpwOmF3OL6EKw9LgvocM,47
171
+ not1mm-24.12.5.dist-info/top_level.txt,sha256=0YmTxEcDzQlzXub-lXASvoLpg_mt1c2thb5cVkDf5J4,7
172
+ not1mm-24.12.5.dist-info/RECORD,,