not1mm 25.5.22__py3-none-any.whl → 25.5.26__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
@@ -17,6 +17,7 @@ import locale
17
17
  import logging
18
18
  from logging.handlers import RotatingFileHandler
19
19
  import os
20
+ import queue
20
21
  import socket
21
22
  import sys
22
23
  import uuid
@@ -40,6 +41,7 @@ from not1mm.lib.database import DataBase
40
41
  from not1mm.lib.edit_macro import EditMacro
41
42
  from not1mm.lib.edit_opon import OpOn
42
43
  from not1mm.lib.edit_station import EditStation
44
+ from not1mm.lib.multicast import Multicast
43
45
  from not1mm.lib.ham_utility import (
44
46
  bearing,
45
47
  bearing_with_latlon,
@@ -187,6 +189,10 @@ class MainWindow(QtWidgets.QMainWindow):
187
189
  lookup_service = None
188
190
  fldigi_util = None
189
191
  rtc_service = None
192
+ rtc_interval = 2
193
+ rtc_user = ""
194
+ rtc_url = ""
195
+ rtc_pass = ""
190
196
 
191
197
  current_widget = None
192
198
 
@@ -195,6 +201,8 @@ class MainWindow(QtWidgets.QMainWindow):
195
201
  auto_cq_time = datetime.datetime.now()
196
202
  auto_cq_delay = 15000
197
203
 
204
+ server_commands = []
205
+
198
206
  def __init__(self, splash):
199
207
  super().__init__()
200
208
  logger.info("MainWindow: __init__")
@@ -214,8 +222,10 @@ class MainWindow(QtWidgets.QMainWindow):
214
222
  self.setCorner(Qt.Corner.BottomLeftCorner, Qt.DockWidgetArea.LeftDockWidgetArea)
215
223
  self.fontfamily = self.load_fonts_from_dir(os.fspath(fsutils.APP_DATA_PATH))
216
224
  uic.loadUi(fsutils.APP_DATA_PATH / "main.ui", self)
225
+
217
226
  if sys.platform == "darwin":
218
- def_font_size = 12
227
+ QApplication.setStyle("Fusion")
228
+ def_font_size = 13
219
229
 
220
230
  QApplication.instance().setFont(QFont(self.fontfamily, def_font_size))
221
231
  self.F1.setFont(QFont(self.fontfamily, def_font_size))
@@ -257,7 +267,10 @@ class MainWindow(QtWidgets.QMainWindow):
257
267
  self.dark_watcher
258
268
  )
259
269
  self.dark_watcher(QApplication.instance().styleHints().colorScheme())
260
-
270
+ self.udp_fifo = queue.Queue()
271
+ self.server_message_watch_timer = QtCore.QTimer()
272
+ self.server_message_watch_timer.timeout.connect(self.check_udp_queue)
273
+ self.server_message_watch_timer.start(1000)
261
274
  self.inputs_dict = {
262
275
  self.callsign: "callsign",
263
276
  self.sent: "sent",
@@ -270,6 +283,10 @@ class MainWindow(QtWidgets.QMainWindow):
270
283
  self.cwprogressBar.hide()
271
284
  self.rightdot.hide()
272
285
  self.n1mm = N1MM()
286
+ self.server_channel = Multicast(
287
+ multicast_group="239.1.1.1", multicast_port=2239, interface_ip="0.0.0.0"
288
+ )
289
+ self.server_channel.ready_read_connect(self.server_message)
273
290
  self.ft8 = FT8Watcher()
274
291
  self.ft8.set_callback(None)
275
292
  self.mscp = SCP(fsutils.APP_DATA_PATH)
@@ -346,6 +363,8 @@ class MainWindow(QtWidgets.QMainWindow):
346
363
  icon_path = fsutils.APP_DATA_PATH
347
364
  self.greendot = QtGui.QPixmap(str(icon_path / "greendot.png"))
348
365
  self.reddot = QtGui.QPixmap(str(icon_path / "reddot.png"))
366
+ self.redserver = QtGui.QPixmap(str(icon_path / "cloud_red.png"))
367
+ self.greenserver = QtGui.QPixmap(str(icon_path / "cloud_green.png"))
349
368
  self.leftdot.setPixmap(self.greendot)
350
369
  self.rightdot.setPixmap(self.reddot)
351
370
 
@@ -621,7 +640,7 @@ class MainWindow(QtWidgets.QMainWindow):
621
640
  ) as c_file:
622
641
  self.ctyfile = loads(c_file.read())
623
642
  except (IOError, JSONDecodeError, TypeError):
624
- logging.CRITICAL("There was an error parsing the BigCity file.")
643
+ logging.critical("There was an error parsing the BigCity file.")
625
644
 
626
645
  self.show_splash_msg("Starting LookUp Service.")
627
646
 
@@ -629,6 +648,8 @@ class MainWindow(QtWidgets.QMainWindow):
629
648
  self.lookup_service.message.connect(self.dockwidget_message)
630
649
  self.lookup_service.hide()
631
650
 
651
+ self.server_seen = datetime.datetime.now()
652
+
632
653
  self.show_splash_msg("Reading preferences.")
633
654
  self.readpreferences()
634
655
 
@@ -671,7 +692,7 @@ class MainWindow(QtWidgets.QMainWindow):
671
692
  if int(x[0]) >= 6 and int(x[1]) >= 8:
672
693
  old_Qt = False
673
694
 
674
- # Featureset for wayland if pyqt is older that 6.8
695
+ # Featureset for wayland if pyqt is older than 6.8
675
696
  dockfeatures = (
676
697
  QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetClosable
677
698
  | QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable
@@ -798,7 +819,7 @@ class MainWindow(QtWidgets.QMainWindow):
798
819
  if VersionTest(__version__).test():
799
820
  self.show_message_box(
800
821
  "There is a newer version of not1mm available.\n"
801
- "You can udate to the current version by using:\n\n"
822
+ "You can update to the current version by using:\n\n"
802
823
  "pip install -U not1mm\n\tor\n"
803
824
  "pipx upgrade not1mm"
804
825
  )
@@ -809,6 +830,146 @@ class MainWindow(QtWidgets.QMainWindow):
809
830
  )
810
831
  logger.info(f"bind {b_result}")
811
832
  self.udp_socket.readyRead.connect(self.fldigi_on_udp_socket_ready_read)
833
+ self.resolve_dirty_records()
834
+
835
+ # Server stuff
836
+
837
+ def resolve_dirty_records(self):
838
+ """Go through dirty records and submit them to the server."""
839
+ if self.pref.get("useserver", False) is True and hasattr(self, "database"):
840
+ records = self.database.fetch_all_dirty_contacts()
841
+ print(f"Resolving {len(records)} unsent contacts.\n")
842
+ if records:
843
+ for contact in records:
844
+ contact["cmd"] = "POST"
845
+ stale = datetime.datetime.now() + datetime.timedelta(seconds=30)
846
+ contact["expire"] = stale.isoformat()
847
+
848
+ self.server_commands.append(contact)
849
+ self.server_channel.send_as_json(contact)
850
+
851
+ time.sleep(0.1) # Do I need this?
852
+ print(".")
853
+
854
+ def clear_dirty_flag(self, unique_id):
855
+ """clear the dirty flag on record once response is returned from server."""
856
+ self.database.clear_dirty_flag(unique_id)
857
+ # show_dirty_records()
858
+
859
+ def remove_confirmed_commands(self, data):
860
+ """Removed confirmed commands from the sent commands list."""
861
+ for index, item in enumerate(self.server_commands):
862
+ print(f"Server Commands: {item=} {data=}")
863
+ if item.get("ID") == data.get("unique_id") and item.get("cmd") == data.get(
864
+ "subject"
865
+ ):
866
+ self.server_commands.pop(index)
867
+ self.clear_dirty_flag(data.get("unique_id"))
868
+ print(f"Confirmed {data.get('subject')}")
869
+
870
+ def check_for_stale_commands(self):
871
+ """
872
+ Check through server commands to see if there has not been a reply in 30 seconds.
873
+ Resubmits those that are stale.
874
+ """
875
+ if self.pref.get("useserver", False) is True:
876
+ for index, item in enumerate(self.server_commands):
877
+ expired = datetime.datetime.strptime(
878
+ item.get("expire"), "%Y-%m-%dT%H:%M:%S.%f"
879
+ )
880
+ if datetime.datetime.now() > expired:
881
+ newexpire = datetime.datetime.now() + datetime.timedelta(seconds=30)
882
+ self.server_commands[index]["expire"] = newexpire.isoformat()
883
+ try:
884
+ self.server_channel.send_as_json(self.server_commands[index])
885
+ except OSError as err:
886
+ logging.warning("%s", err)
887
+
888
+ def server_message(self):
889
+ msg = self.server_channel.getpacket()
890
+ if msg:
891
+ self.udp_fifo.put(msg)
892
+
893
+ def check_udp_queue(self):
894
+ """checks the UDP datagram queue."""
895
+
896
+ self.check_for_stale_commands()
897
+ while not self.udp_fifo.empty():
898
+ datagram = self.udp_fifo.get()
899
+ try:
900
+ json_data = loads(datagram.decode())
901
+ except UnicodeDecodeError as err:
902
+ the_error = f"Not Unicode: {err}\n{datagram}"
903
+ logger.info(the_error)
904
+ continue
905
+ except JSONDecodeError as err:
906
+ the_error = f"Not JSON: {err}\n{datagram}"
907
+ logger.info(the_error)
908
+ continue
909
+ logger.info("%s", json_data)
910
+
911
+ if json_data.get("cmd") == "PING":
912
+ # print(f"Got {json_data.get('cmd')} {json_data=}")
913
+ # if json_data.get("station"):
914
+ # band_mode = f"{json_data.get('band')} {json_data.get('mode')}"
915
+ # if self.people.get(json_data.get("station")) != band_mode:
916
+ # self.people[json_data.get("station")] = band_mode
917
+ # self.show_people()
918
+ if json_data.get("host"):
919
+ self.server_seen = datetime.datetime.now() + datetime.timedelta(
920
+ seconds=15
921
+ )
922
+ self.server_icon.setPixmap(self.greenserver)
923
+ continue
924
+
925
+ if json_data.get("cmd") == "RESPONSE":
926
+ if json_data.get("recipient") == socket.gethostname():
927
+ if json_data.get("subject") == "HOSTINFO":
928
+ # self.groupcall = json_data.get("groupcall", "")
929
+ # self.myclassEntry.setText(str(json_data.get("groupclass", "")))
930
+ # self.mysectionEntry.setText(
931
+ # str(json_data.get("groupsection", ""))
932
+ # )
933
+ # self.group_call_indicator.setText(self.groupcall.center(14))
934
+ # self.changemyclass()
935
+ # self.changemysection()
936
+ # self.mycallEntry.hide()
937
+ # self.server_seen = datetime.now() + timedelta(seconds=30)
938
+ # self.group_call_indicator.setStyleSheet(
939
+ # "border: 1px solid green;"
940
+ # )
941
+ return
942
+ if json_data.get("subject") == "LOG":
943
+ ...
944
+ # self.infoline.setText("Server Generated Log.")
945
+
946
+ if json_data.get("subject") == "DUPE":
947
+ ...
948
+ # if json_data.get("isdupe") != 0:
949
+ # if json_data.get("contact") == self.callsign_entry.text():
950
+ # self.flash()
951
+ # self.infobox.setTextColor(QtGui.QColor(245, 121, 0))
952
+ # self.infobox.insertPlainText(
953
+ # f"{json_data.get('contact')}: " "Server DUPE\n"
954
+ # )
955
+ if json_data.get("subject") == "POST":
956
+ self.remove_confirmed_commands(json_data)
957
+ if json_data.get("subject") == "DELETE":
958
+ self.remove_confirmed_commands(json_data)
959
+ if json_data.get("subject") == "CONTACTCHANGED":
960
+ self.remove_confirmed_commands(json_data)
961
+
962
+ continue
963
+
964
+ if json_data.get("cmd") == "CHAT":
965
+ print(f"Got {json_data.get('cmd')} {json_data=}")
966
+ # self.display_chat(json_data.get("sender"), json_data.get("message"))
967
+ continue
968
+
969
+ if json_data.get("cmd") == "GROUPQUERY":
970
+ print(f"Got {json_data.get('cmd')} {json_data=}")
971
+ # if self.groupcall:
972
+ # self.send_status_udp()
812
973
 
813
974
  def fldigi_on_udp_socket_ready_read(self):
814
975
  """"""
@@ -936,9 +1097,35 @@ class MainWindow(QtWidgets.QMainWindow):
936
1097
  QCoreApplication.processEvents()
937
1098
 
938
1099
  def dockwidget_message(self, msg):
939
- """signal from bandmap"""
1100
+ """incomming signals from widgets"""
940
1101
  if msg:
1102
+ # Pass delete message from log window to server.
1103
+ if msg.get("cmd", "") == "DELETED":
1104
+ if self.pref.get("useserver", False) is True:
1105
+ stale = datetime.datetime.now() + datetime.timedelta(seconds=30)
1106
+ msg["cmd"] = "DELETE"
1107
+ msg["expire"] = stale.isoformat()
1108
+ msg["station"] = socket.gethostname()
1109
+ msg["unique_id"] = msg.get("ID")
1110
+ self.server_commands.append(msg)
1111
+ try:
1112
+ self.server_channel.send_as_json(msg)
1113
+ except OSError as err:
1114
+ logging.warning("%s", err)
1115
+
941
1116
  if msg.get("cmd", "") == "CONTACTCHANGED":
1117
+ if self.pref.get("useserver", False) is True:
1118
+ stale = datetime.datetime.now() + datetime.timedelta(seconds=30)
1119
+ msg["expire"] = stale.isoformat()
1120
+ msg["station"] = socket.gethostname()
1121
+ msg["unique_id"] = msg.get("ID")
1122
+ self.server_commands.append(msg)
1123
+ try:
1124
+ self.server_channel.send_as_json(msg)
1125
+ except OSError as err:
1126
+ logging.warning("%s", err)
1127
+
1128
+ if msg.get("cmd", "") in ["CONTACTCHANGED", "DELETE"]:
942
1129
  if self.statistics_window:
943
1130
  self.statistics_window.msg_from_main(msg)
944
1131
  if msg.get("cmd", "") == "GETCOLUMNS":
@@ -1815,11 +2002,11 @@ class MainWindow(QtWidgets.QMainWindow):
1815
2002
  ) as ctyfile:
1816
2003
  self.ctyfile = loads(ctyfile.read())
1817
2004
  except (IOError, JSONDecodeError, TypeError) as err:
1818
- logging.CRITICAL(
2005
+ logging.critical(
1819
2006
  f"There was an error {err} parsing the BigCity file."
1820
2007
  )
1821
2008
  else:
1822
- self.show_message_box("An Error occured updating file.")
2009
+ self.show_message_box("An Error occurred updating file.")
1823
2010
  else:
1824
2011
  self.show_message_box("CTY file is up to date.")
1825
2012
 
@@ -2660,6 +2847,17 @@ class MainWindow(QtWidgets.QMainWindow):
2660
2847
  self.n1mm.send_contact_info()
2661
2848
 
2662
2849
  self.database.log_contact(self.contact)
2850
+ if self.pref.get("useserver", False) is True:
2851
+ stale = datetime.datetime.now() + datetime.timedelta(seconds=30)
2852
+ self.contact["cmd"] = "POST"
2853
+ self.contact["expire"] = stale.isoformat()
2854
+ self.server_commands.append(self.contact)
2855
+ # bytesToSend = bytes(dumps(self.contact), encoding="ascii")
2856
+ try:
2857
+ self.server_channel.send_as_json(self.contact)
2858
+ # server_udp.sendto(bytesToSend, (multicast_group, int(multicast_port)))
2859
+ except OSError as err:
2860
+ logging.warning("%s", err)
2663
2861
  self.worked_list = self.database.get_calls_and_bands()
2664
2862
  self.send_worked_list()
2665
2863
  self.clearinputs()
@@ -2908,6 +3106,8 @@ class MainWindow(QtWidgets.QMainWindow):
2908
3106
  next_serial = str(result.get("serial_nr", "1"))
2909
3107
  if next_serial == "None":
2910
3108
  next_serial = "1"
3109
+ result = self.database.get_last_serial()
3110
+ prev_serial = str(result.get("serial_nr", "1")).zfill(3)
2911
3111
  macro = macro.upper()
2912
3112
  if self.radio_state.get("mode") == "CW":
2913
3113
  macro = macro.replace(
@@ -2927,6 +3127,9 @@ class MainWindow(QtWidgets.QMainWindow):
2927
3127
  macro = macro.replace("{SNT}", self.sent.text().replace("9", "n"))
2928
3128
  else:
2929
3129
  macro = macro.replace("{SNT}", self.sent.text())
3130
+ macro = macro.replace(
3131
+ "{EXCH}", self.contest_settings.get("SentExchange", "xxx")
3132
+ )
2930
3133
  if self.radio_state.get("mode") == "CW":
2931
3134
  macro = macro.replace(
2932
3135
  "{SENTNR}",
@@ -2937,11 +3140,19 @@ class MainWindow(QtWidgets.QMainWindow):
2937
3140
  self.pref.get("cwpaddingchar", "T"),
2938
3141
  ),
2939
3142
  )
3143
+ macro = macro.replace(
3144
+ "{PREVNR}",
3145
+ str(prev_serial)
3146
+ .lstrip("0")
3147
+ .rjust(
3148
+ self.pref.get("cwpaddinglength", 3),
3149
+ self.pref.get("cwpaddingchar", "T"),
3150
+ ),
3151
+ )
2940
3152
  else:
2941
3153
  macro = macro.replace("{SENTNR}", self.other_1.text())
2942
- macro = macro.replace(
2943
- "{EXCH}", self.contest_settings.get("SentExchange", "xxx")
2944
- )
3154
+ macro = macro.replace("{PREVNR}", str(prev_serial))
3155
+
2945
3156
  if "{LOGIT}" in macro:
2946
3157
  macro = macro.replace("{LOGIT}", "")
2947
3158
  self.save_contact()
@@ -3373,6 +3584,12 @@ class MainWindow(QtWidgets.QMainWindow):
3373
3584
 
3374
3585
  self.update_rtc_xml()
3375
3586
 
3587
+ if self.pref.get("useserver", False) is True:
3588
+ self.server_icon.show()
3589
+ self.server_icon.setPixmap(self.redserver)
3590
+ else:
3591
+ self.server_icon.hide()
3592
+
3376
3593
  def rtc_response(self, response):
3377
3594
  print(f"{response=}")
3378
3595
 
@@ -3948,6 +4165,8 @@ class MainWindow(QtWidgets.QMainWindow):
3948
4165
  milliseconds=self.auto_cq_delay
3949
4166
  )
3950
4167
  self.process_function_key(self.F1)
4168
+ if datetime.datetime.now() > self.server_seen:
4169
+ self.server_icon.setPixmap(self.redserver)
3951
4170
 
3952
4171
  # The following pertains to radio polling.
3953
4172
  logger.debug(f"{the_dict=}")
not1mm/bandmap.py CHANGED
@@ -19,7 +19,7 @@ from decimal import Decimal
19
19
  from json import loads
20
20
 
21
21
  from PyQt6 import QtCore, QtGui, QtWidgets, uic, QtNetwork
22
- from PyQt6.QtGui import QColorConstants, QPalette, QColor, QFont
22
+ from PyQt6.QtGui import QColorConstants, QFont, QColor, QFont
23
23
  from PyQt6.QtWidgets import QDockWidget
24
24
  from PyQt6.QtCore import Qt, pyqtSignal
25
25
 
@@ -357,7 +357,9 @@ class BandMapWindow(QDockWidget):
357
357
  self.zoomoutButton.clicked.connect(self.inc_zoom)
358
358
  self.connectButton.clicked.connect(self.connect)
359
359
  self.spots = Database()
360
+ self.font = QFont("JetBrains Mono ExtraLight", 10)
360
361
  self.bandmap_scene = QtWidgets.QGraphicsScene()
362
+ self.bandmap_scene.setFont(self.font)
361
363
  self.socket = QtNetwork.QTcpSocket()
362
364
  self.socket.readyRead.connect(self.receive)
363
365
  self.socket.connected.connect(self.maybeconnected)
@@ -563,11 +565,12 @@ class BandMapWindow(QDockWidget):
563
565
  self.clear_freq_mark(self.txMark)
564
566
  self.clear_freq_mark(self.bandwidth_mark)
565
567
  self.bandmap_scene.clear()
566
-
568
+ self.bandmap_scene.setFont(self.font)
567
569
  step, _digits = self.determine_step_digits()
568
570
  steps = int(round((self.currentBand.end - self.currentBand.start) / step))
569
571
  self.graphicsView.setFixedSize(330, steps * PIXELSPERSTEP + 30)
570
572
  self.graphicsView.setScene(self.bandmap_scene)
573
+ self.graphicsView.setFont(self.font)
571
574
  for i in range(steps): # Draw tickmarks
572
575
  length = 10
573
576
  if i % 5 == 0:
@@ -704,8 +707,27 @@ class BandMapWindow(QDockWidget):
704
707
 
705
708
  entity = ""
706
709
  if result:
710
+ # ⌾ ⦿ 🗼 ⛯ ⊕ ⊞ ⁙ ⁘ ⁕ ⌖ Ⓟ ✦ 🄿 🄿 Ⓢ 🅂 🏔
707
711
  min_y = 0.0
708
712
  for items in result:
713
+ flag = " @"
714
+ if "CW" in items.get("comment"):
715
+ flag = " ○"
716
+ if "NCDXF B" in items.get("comment"):
717
+ flag = " 🗼"
718
+ if "BCN " in items.get("comment"):
719
+ flag = " 🗼"
720
+ if "FT8" in items.get("comment"):
721
+ flag = " ⦿"
722
+ if "FT4" in items.get("comment"):
723
+ flag = " ⦿"
724
+ if "RTTY" in items.get("comment"):
725
+ flag = " ⌾"
726
+ if "POTA" in items.get("comment"):
727
+ flag += "[P]"
728
+ if "SOTA" in items.get("comment"):
729
+ flag += "[S]"
730
+
709
731
  pen_color = self.text_color
710
732
  if items.get("comment") == "MARKED":
711
733
  pen_color = QColor(254, 194, 17)
@@ -724,7 +746,7 @@ class BandMapWindow(QDockWidget):
724
746
  )
725
747
  text = self.bandmap_scene.addText(
726
748
  items.get("callsign")
727
- + " @ "
749
+ + flag
728
750
  + entity
729
751
  + " "
730
752
  + items.get("ts").split()[1][:-3]
Binary file
Binary file