not1mm 25.6.10__py3-none-any.whl → 25.6.11.1__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 +45 -2
- not1mm/data/main.ui +12 -0
- not1mm/data/map3.png +0 -0
- not1mm/data/rotator.ui +144 -0
- not1mm/lib/rot_interface.py +101 -0
- not1mm/lib/version.py +1 -1
- not1mm/rotator.py +323 -0
- {not1mm-25.6.10.dist-info → not1mm-25.6.11.1.dist-info}/METADATA +17 -3
- {not1mm-25.6.10.dist-info → not1mm-25.6.11.1.dist-info}/RECORD +13 -9
- {not1mm-25.6.10.dist-info → not1mm-25.6.11.1.dist-info}/WHEEL +0 -0
- {not1mm-25.6.10.dist-info → not1mm-25.6.11.1.dist-info}/entry_points.txt +0 -0
- {not1mm-25.6.10.dist-info → not1mm-25.6.11.1.dist-info}/licenses/LICENSE +0 -0
- {not1mm-25.6.10.dist-info → not1mm-25.6.11.1.dist-info}/top_level.txt +0 -0
not1mm/__main__.py
CHANGED
@@ -69,6 +69,7 @@ import not1mm.fsutils as fsutils
|
|
69
69
|
from not1mm.logwindow import LogWindow
|
70
70
|
from not1mm.checkwindow import CheckWindow
|
71
71
|
from not1mm.dxcc_tracker import DXCCWindow
|
72
|
+
from not1mm.rotator import RotatorWindow
|
72
73
|
from not1mm.bandmap import BandMapWindow
|
73
74
|
from not1mm.vfo import VfoWindow
|
74
75
|
from not1mm.ratewindow import RateWindow
|
@@ -187,6 +188,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
187
188
|
rate_window = None
|
188
189
|
statistics_window = None
|
189
190
|
dxcc_window = None
|
191
|
+
rotator_window = None
|
192
|
+
voice_window = None
|
190
193
|
settings = None
|
191
194
|
lookup_service = None
|
192
195
|
fldigi_util = None
|
@@ -311,6 +314,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
311
314
|
self.actionStatistics.triggered.connect(self.launch_stats_window)
|
312
315
|
self.actionVFO.triggered.connect(self.launch_vfo)
|
313
316
|
self.actionDXCC.triggered.connect(self.launch_dxcc_window)
|
317
|
+
self.actionRotator.triggered.connect(self.launch_rotator_window)
|
314
318
|
self.actionRecalculate_Mults.triggered.connect(self.recalculate_mults)
|
315
319
|
self.actionLoad_Call_History_File.triggered.connect(self.load_call_history)
|
316
320
|
|
@@ -679,6 +683,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
679
683
|
self.station = self.database.fetch_station()
|
680
684
|
if self.station is None:
|
681
685
|
self.station = {}
|
686
|
+
if self.rotator_window is not None:
|
687
|
+
self.rotator_window.set_mygrid(self.station.get("GridSquare", ""))
|
682
688
|
self.contact = self.database.empty_contact.copy()
|
683
689
|
self.current_op = self.station.get("Call", "")
|
684
690
|
self.voice_process.current_op = self.current_op
|
@@ -750,6 +756,16 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
750
756
|
self.dxcc_window.hide()
|
751
757
|
self.dxcc_window.message.connect(self.dockwidget_message)
|
752
758
|
|
759
|
+
self.show_splash_msg("Setting up RotatorWindow.")
|
760
|
+
self.rotator_window = RotatorWindow()
|
761
|
+
self.rotator_window.setObjectName("rotator-window")
|
762
|
+
if os.environ.get("WAYLAND_DISPLAY") and old_Qt is True:
|
763
|
+
self.rotator_window.setFeatures(dockfeatures)
|
764
|
+
self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.rotator_window)
|
765
|
+
self.rotator_window.hide()
|
766
|
+
self.rotator_window.message.connect(self.dockwidget_message)
|
767
|
+
self.rotator_window.set_mygrid(self.station.get("GridSquare", ""))
|
768
|
+
|
753
769
|
self.show_splash_msg("Setting up VFOWindow.")
|
754
770
|
self.vfo_window = VfoWindow()
|
755
771
|
self.vfo_window.setObjectName("vfo-window")
|
@@ -823,11 +839,18 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
823
839
|
if self.actionDXCC.isChecked():
|
824
840
|
self.dxcc_window.show()
|
825
841
|
self.dxcc_window.setActive(True)
|
826
|
-
# self.dxcc_window.get_run_and_total_qs()
|
827
842
|
else:
|
828
843
|
self.dxcc_window.hide()
|
829
844
|
self.dxcc_window.setActive(False)
|
830
845
|
|
846
|
+
self.actionRotator.setChecked(self.pref.get("rotatorwindow", False))
|
847
|
+
if self.actionRotator.isChecked():
|
848
|
+
self.rotator_window.show()
|
849
|
+
self.rotator_window.setActive(True)
|
850
|
+
else:
|
851
|
+
self.rotator_window.hide()
|
852
|
+
self.rotator_window.setActive(False)
|
853
|
+
|
831
854
|
self.actionVFO.setChecked(self.pref.get("vfowindow", False))
|
832
855
|
if self.actionVFO.isChecked():
|
833
856
|
self.vfo_window.show()
|
@@ -1236,7 +1259,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
1236
1259
|
self.heading_distance.setText(
|
1237
1260
|
f"{grid} Hdg {heading}° LP {reciprocol(heading)}° / "
|
1238
1261
|
f"distance {int(kilometers*0.621371)}mi {kilometers}km"
|
1262
|
+
f" {msg.get('result', {}).get('name_fmt', '')}"
|
1239
1263
|
)
|
1264
|
+
self.rotator_window.set_requested_azimuth(float(heading))
|
1240
1265
|
|
1241
1266
|
def cluster_expire_updated(self, number):
|
1242
1267
|
"""signal from bandmap"""
|
@@ -1631,6 +1656,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
1631
1656
|
self.station = self.database.fetch_station()
|
1632
1657
|
if self.station is None:
|
1633
1658
|
self.station = {}
|
1659
|
+
if self.rotator_window is not None:
|
1660
|
+
self.rotator_window.set_mygrid(self.station.get("GridSquare", ""))
|
1634
1661
|
self.current_op = self.station.get("Call", "")
|
1635
1662
|
self.voice_process.current_op = self.current_op
|
1636
1663
|
self.make_op_dir()
|
@@ -1673,6 +1700,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
1673
1700
|
self.station = self.database.fetch_station()
|
1674
1701
|
if self.station is None:
|
1675
1702
|
self.station = {}
|
1703
|
+
if self.rotator_window is not None:
|
1704
|
+
self.rotator_window.set_mygrid(self.station.get("GridSquare", ""))
|
1676
1705
|
if self.station.get("Call", "") == "":
|
1677
1706
|
self.edit_station_settings()
|
1678
1707
|
self.current_op = self.station.get("Call", "")
|
@@ -2245,11 +2274,21 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
2245
2274
|
if self.actionDXCC.isChecked():
|
2246
2275
|
self.dxcc_window.show()
|
2247
2276
|
self.dxcc_window.setActive(True)
|
2248
|
-
# self.dxcc_window.get_run_and_total_qs()
|
2249
2277
|
else:
|
2250
2278
|
self.dxcc_window.hide()
|
2251
2279
|
self.dxcc_window.setActive(False)
|
2252
2280
|
|
2281
|
+
def launch_rotator_window(self) -> None:
|
2282
|
+
"""Launch the rotator window"""
|
2283
|
+
self.pref["rotatorwindow"] = self.actionRotator.isChecked()
|
2284
|
+
self.write_preference()
|
2285
|
+
if self.actionRotator.isChecked():
|
2286
|
+
self.rotator_window.show()
|
2287
|
+
self.rotator_window.setActive(True)
|
2288
|
+
else:
|
2289
|
+
self.rotator_window.hide()
|
2290
|
+
self.rotator_window.setActive(False)
|
2291
|
+
|
2253
2292
|
def launch_vfo(self) -> None:
|
2254
2293
|
"""Launch the VFO window"""
|
2255
2294
|
self.pref["vfowindow"] = self.actionVFO.isChecked()
|
@@ -3099,6 +3138,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
3099
3138
|
self.station["Club"] = self.settings_dialog.Club.text()
|
3100
3139
|
self.station["Email"] = self.settings_dialog.Email.text()
|
3101
3140
|
self.database.add_station(self.station)
|
3141
|
+
if self.rotator_window is not None:
|
3142
|
+
self.rotator_window.set_mygrid(self.settings_dialog.GridSquare.text())
|
3102
3143
|
self.settings_dialog.close()
|
3103
3144
|
if self.current_op == "":
|
3104
3145
|
self.current_op = self.station.get("Call", "")
|
@@ -4060,6 +4101,8 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
4060
4101
|
f"Regional Hdg {heading}° LP {reciprocol(heading)}° / "
|
4061
4102
|
f"distance {int(kilometers*0.621371)}mi {kilometers}km"
|
4062
4103
|
)
|
4104
|
+
if self.rotator_window is not None:
|
4105
|
+
self.rotator_window.set_requested_azimuth(float(heading))
|
4063
4106
|
self.contact["CountryPrefix"] = primary_pfx
|
4064
4107
|
self.contact["ZN"] = int(cq)
|
4065
4108
|
if self.contest:
|
not1mm/data/main.ui
CHANGED
@@ -1573,6 +1573,7 @@
|
|
1573
1573
|
<addaction name="actionStatistics"/>
|
1574
1574
|
<addaction name="actionVFO"/>
|
1575
1575
|
<addaction name="actionDXCC"/>
|
1576
|
+
<addaction name="actionRotator"/>
|
1576
1577
|
</widget>
|
1577
1578
|
<widget class="QMenu" name="menuOther">
|
1578
1579
|
<property name="title">
|
@@ -2144,6 +2145,17 @@
|
|
2144
2145
|
<string>Alt+D</string>
|
2145
2146
|
</property>
|
2146
2147
|
</action>
|
2148
|
+
<action name="actionRotator">
|
2149
|
+
<property name="checkable">
|
2150
|
+
<bool>true</bool>
|
2151
|
+
</property>
|
2152
|
+
<property name="text">
|
2153
|
+
<string>Rotator</string>
|
2154
|
+
</property>
|
2155
|
+
<property name="shortcut">
|
2156
|
+
<string>Alt+P</string>
|
2157
|
+
</property>
|
2158
|
+
</action>
|
2147
2159
|
</widget>
|
2148
2160
|
<resources/>
|
2149
2161
|
<connections/>
|
not1mm/data/map3.png
ADDED
Binary file
|
not1mm/data/rotator.ui
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<ui version="4.0">
|
3
|
+
<class>RotatorWidget</class>
|
4
|
+
<widget class="QDockWidget" name="RotatorWidget">
|
5
|
+
<property name="geometry">
|
6
|
+
<rect>
|
7
|
+
<x>0</x>
|
8
|
+
<y>0</y>
|
9
|
+
<width>352</width>
|
10
|
+
<height>248</height>
|
11
|
+
</rect>
|
12
|
+
</property>
|
13
|
+
<property name="windowTitle">
|
14
|
+
<string>Rotator</string>
|
15
|
+
</property>
|
16
|
+
<widget class="QWidget" name="centralwidget">
|
17
|
+
<layout class="QVBoxLayout" name="verticalLayout">
|
18
|
+
<property name="leftMargin">
|
19
|
+
<number>0</number>
|
20
|
+
</property>
|
21
|
+
<property name="topMargin">
|
22
|
+
<number>0</number>
|
23
|
+
</property>
|
24
|
+
<property name="rightMargin">
|
25
|
+
<number>0</number>
|
26
|
+
</property>
|
27
|
+
<property name="bottomMargin">
|
28
|
+
<number>0</number>
|
29
|
+
</property>
|
30
|
+
<item>
|
31
|
+
<layout class="QHBoxLayout" name="horizontalLayout">
|
32
|
+
<item>
|
33
|
+
<widget class="QPushButton" name="move_button">
|
34
|
+
<property name="focusPolicy">
|
35
|
+
<enum>Qt::FocusPolicy::NoFocus</enum>
|
36
|
+
</property>
|
37
|
+
<property name="text">
|
38
|
+
<string>Move</string>
|
39
|
+
</property>
|
40
|
+
</widget>
|
41
|
+
</item>
|
42
|
+
</layout>
|
43
|
+
</item>
|
44
|
+
<item>
|
45
|
+
<widget class="QGraphicsView" name="compassView">
|
46
|
+
<property name="focusPolicy">
|
47
|
+
<enum>Qt::FocusPolicy::ClickFocus</enum>
|
48
|
+
</property>
|
49
|
+
<property name="frameShape">
|
50
|
+
<enum>QFrame::Shape::NoFrame</enum>
|
51
|
+
</property>
|
52
|
+
<property name="frameShadow">
|
53
|
+
<enum>QFrame::Shadow::Plain</enum>
|
54
|
+
</property>
|
55
|
+
<property name="renderHints">
|
56
|
+
<set>QPainter::RenderHint::Antialiasing|QPainter::RenderHint::SmoothPixmapTransform|QPainter::RenderHint::TextAntialiasing</set>
|
57
|
+
</property>
|
58
|
+
</widget>
|
59
|
+
</item>
|
60
|
+
<item>
|
61
|
+
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
62
|
+
<item>
|
63
|
+
<spacer name="horizontalSpacer">
|
64
|
+
<property name="orientation">
|
65
|
+
<enum>Qt::Orientation::Horizontal</enum>
|
66
|
+
</property>
|
67
|
+
<property name="sizeHint" stdset="0">
|
68
|
+
<size>
|
69
|
+
<width>40</width>
|
70
|
+
<height>20</height>
|
71
|
+
</size>
|
72
|
+
</property>
|
73
|
+
</spacer>
|
74
|
+
</item>
|
75
|
+
<item>
|
76
|
+
<widget class="QPushButton" name="north_button">
|
77
|
+
<property name="focusPolicy">
|
78
|
+
<enum>Qt::FocusPolicy::NoFocus</enum>
|
79
|
+
</property>
|
80
|
+
<property name="text">
|
81
|
+
<string>N</string>
|
82
|
+
</property>
|
83
|
+
<property name="iconSize">
|
84
|
+
<size>
|
85
|
+
<width>16</width>
|
86
|
+
<height>16</height>
|
87
|
+
</size>
|
88
|
+
</property>
|
89
|
+
<property name="flat">
|
90
|
+
<bool>false</bool>
|
91
|
+
</property>
|
92
|
+
</widget>
|
93
|
+
</item>
|
94
|
+
<item>
|
95
|
+
<widget class="QPushButton" name="south_button">
|
96
|
+
<property name="focusPolicy">
|
97
|
+
<enum>Qt::FocusPolicy::NoFocus</enum>
|
98
|
+
</property>
|
99
|
+
<property name="text">
|
100
|
+
<string>S</string>
|
101
|
+
</property>
|
102
|
+
</widget>
|
103
|
+
</item>
|
104
|
+
<item>
|
105
|
+
<widget class="QPushButton" name="west_button">
|
106
|
+
<property name="focusPolicy">
|
107
|
+
<enum>Qt::FocusPolicy::NoFocus</enum>
|
108
|
+
</property>
|
109
|
+
<property name="text">
|
110
|
+
<string>W</string>
|
111
|
+
</property>
|
112
|
+
</widget>
|
113
|
+
</item>
|
114
|
+
<item>
|
115
|
+
<widget class="QPushButton" name="east_button">
|
116
|
+
<property name="focusPolicy">
|
117
|
+
<enum>Qt::FocusPolicy::NoFocus</enum>
|
118
|
+
</property>
|
119
|
+
<property name="text">
|
120
|
+
<string>E</string>
|
121
|
+
</property>
|
122
|
+
</widget>
|
123
|
+
</item>
|
124
|
+
<item>
|
125
|
+
<spacer name="horizontalSpacer_2">
|
126
|
+
<property name="orientation">
|
127
|
+
<enum>Qt::Orientation::Horizontal</enum>
|
128
|
+
</property>
|
129
|
+
<property name="sizeHint" stdset="0">
|
130
|
+
<size>
|
131
|
+
<width>40</width>
|
132
|
+
<height>20</height>
|
133
|
+
</size>
|
134
|
+
</property>
|
135
|
+
</spacer>
|
136
|
+
</item>
|
137
|
+
</layout>
|
138
|
+
</item>
|
139
|
+
</layout>
|
140
|
+
</widget>
|
141
|
+
</widget>
|
142
|
+
<resources/>
|
143
|
+
<connections/>
|
144
|
+
</ui>
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import socket
|
2
|
+
import logging
|
3
|
+
|
4
|
+
if __name__ == "__main__":
|
5
|
+
print("I'm not the program you are looking for.")
|
6
|
+
|
7
|
+
logger = logging.getLogger("rot_interface")
|
8
|
+
|
9
|
+
|
10
|
+
class RotatorInterface:
|
11
|
+
"""
|
12
|
+
A class to interface with a rotator control program (like rotctld).
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, host="127.0.0.1", port=4533):
|
16
|
+
self.host = host
|
17
|
+
self.port = port
|
18
|
+
self.socket = None
|
19
|
+
self.connected = False
|
20
|
+
self.connect()
|
21
|
+
|
22
|
+
def connect(self):
|
23
|
+
"""Connect to the rotator control program."""
|
24
|
+
try:
|
25
|
+
self.socket = socket.create_connection((self.host, self.port), timeout=1)
|
26
|
+
self.connected = True
|
27
|
+
logger.info(f"Connected to rotator at {self.host}:{self.port}")
|
28
|
+
except (socket.timeout, ConnectionRefusedError, OSError) as e:
|
29
|
+
self.connected = False
|
30
|
+
logger.warning(
|
31
|
+
f"Failed to connect to rotator at {self.host}:{self.port}: {e}"
|
32
|
+
)
|
33
|
+
self.socket = None
|
34
|
+
|
35
|
+
def disconnect(self):
|
36
|
+
"""Disconnect from the rotator control program."""
|
37
|
+
if self.socket:
|
38
|
+
try:
|
39
|
+
self.socket.close()
|
40
|
+
logger.info("Disconnected from rotator")
|
41
|
+
except OSError as e:
|
42
|
+
logger.warning(f"Error closing rotator socket: {e}")
|
43
|
+
self.socket = None
|
44
|
+
self.connected = False
|
45
|
+
|
46
|
+
def send_command(self, command):
|
47
|
+
"""Send a command to the rotator control program and return the response."""
|
48
|
+
if not self.connected or not self.socket:
|
49
|
+
self.connect()
|
50
|
+
if not self.connected or not self.socket:
|
51
|
+
logger.warning("Not connected to rotator. Command not sent.")
|
52
|
+
return None
|
53
|
+
|
54
|
+
try:
|
55
|
+
self.socket.sendall((command + "\n").encode())
|
56
|
+
response = self.socket.recv(1024).decode().strip()
|
57
|
+
logger.debug(f"Sent: {command}, Received: {response}")
|
58
|
+
return response
|
59
|
+
except OSError as e:
|
60
|
+
logger.warning(f"Error sending command to rotator: {e}")
|
61
|
+
self.disconnect()
|
62
|
+
return None
|
63
|
+
|
64
|
+
def get_position(self):
|
65
|
+
"""Get the current azimuth and elevation from the rotator."""
|
66
|
+
response = self.send_command("p")
|
67
|
+
logger.debug(f"get_position response: {response}")
|
68
|
+
if response:
|
69
|
+
if response == "RPRT -1":
|
70
|
+
return None, None
|
71
|
+
try:
|
72
|
+
azimuth, elevation = map(float, response.split("\n"))
|
73
|
+
return azimuth, elevation
|
74
|
+
except ValueError:
|
75
|
+
logger.warning(f"Invalid response from rotator: {response}")
|
76
|
+
return None, None
|
77
|
+
|
78
|
+
def set_position(self, azimuth, elevation=0.0) -> bool:
|
79
|
+
"""Set the azimuth and elevation on the rotator."""
|
80
|
+
response = self.send_command(f"P {azimuth} {elevation}")
|
81
|
+
return response is not None
|
82
|
+
|
83
|
+
def park_rotator(self) -> bool:
|
84
|
+
"""Park the rotator."""
|
85
|
+
response = self.send_command("K")
|
86
|
+
return response is not None
|
87
|
+
|
88
|
+
def reset_rotator(self) -> bool:
|
89
|
+
"""Reset the rotator."""
|
90
|
+
response = self.send_command("R")
|
91
|
+
return response is not None
|
92
|
+
|
93
|
+
def move_rotator(self, direction, speed) -> bool:
|
94
|
+
"""Move the rotator in the specified direction at the specified speed."""
|
95
|
+
response = self.send_command(f"M {direction} {speed}")
|
96
|
+
return response is not None
|
97
|
+
|
98
|
+
def stop_rotator(self) -> bool:
|
99
|
+
"""Stop the rotator."""
|
100
|
+
response = self.send_command("S")
|
101
|
+
return response is not None
|
not1mm/lib/version.py
CHANGED
not1mm/rotator.py
ADDED
@@ -0,0 +1,323 @@
|
|
1
|
+
from PyQt6.QtWidgets import QDockWidget, QGraphicsScene
|
2
|
+
|
3
|
+
from PyQt6.QtGui import (
|
4
|
+
QImage,
|
5
|
+
QColor,
|
6
|
+
QPixmap,
|
7
|
+
QPen,
|
8
|
+
QBrush,
|
9
|
+
QPainterPath,
|
10
|
+
QShowEvent,
|
11
|
+
QResizeEvent,
|
12
|
+
)
|
13
|
+
|
14
|
+
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
15
|
+
from PyQt6 import uic
|
16
|
+
|
17
|
+
from not1mm.lib.rot_interface import RotatorInterface
|
18
|
+
import not1mm.fsutils as fsutils
|
19
|
+
import math
|
20
|
+
import logging
|
21
|
+
import os
|
22
|
+
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
class RotatorWindow(QDockWidget):
|
27
|
+
message = pyqtSignal(dict)
|
28
|
+
pref = {}
|
29
|
+
MAP_RESOLUTION = 600
|
30
|
+
GLOBE_RADIUS = 100.0
|
31
|
+
AZIMUTH_DEAD_BAND = 2
|
32
|
+
requestedAzimuthNeedle = None
|
33
|
+
antennaNeedle = None
|
34
|
+
|
35
|
+
def __init__(self):
|
36
|
+
super().__init__()
|
37
|
+
self.active = False
|
38
|
+
self.compassScene = None
|
39
|
+
self.mygrid = "DM13at"
|
40
|
+
self.requestedAzimuth = None
|
41
|
+
self.antennaAzimuth = None
|
42
|
+
uic.loadUi(fsutils.APP_DATA_PATH / "rotator.ui", self)
|
43
|
+
self.north_button.clicked.connect(self.set_north_azimuth)
|
44
|
+
self.south_button.clicked.connect(self.set_south_azimuth)
|
45
|
+
self.east_button.clicked.connect(self.set_east_azimuth)
|
46
|
+
self.west_button.clicked.connect(self.set_west_azimuth)
|
47
|
+
self.move_button.clicked.connect(self.the_eye_of_sauron)
|
48
|
+
self.redrawMap()
|
49
|
+
self.rotator = RotatorInterface()
|
50
|
+
self.antennaAzimuth, _ = self.rotator.get_position()
|
51
|
+
self.set_antenna_azimuth(self.antennaAzimuth)
|
52
|
+
self.watch_timer = QTimer()
|
53
|
+
self.watch_timer.timeout.connect(self.check_rotator)
|
54
|
+
self.watch_timer.start(1000)
|
55
|
+
|
56
|
+
def msg_from_main(self, msg: dict) -> None:
|
57
|
+
""""""
|
58
|
+
if self.active is True and isinstance(msg, dict):
|
59
|
+
if msg.get("cmd", "") in ("UPDATELOG", "CONTACTCHANGED", "DELETED"):
|
60
|
+
...
|
61
|
+
if msg.get("cmd", "") == "NEWDB":
|
62
|
+
...
|
63
|
+
|
64
|
+
def set_mygrid(self, mygrid: str) -> None:
|
65
|
+
""""""
|
66
|
+
if isinstance(mygrid, str):
|
67
|
+
self.mygrid = mygrid
|
68
|
+
self.redrawMap()
|
69
|
+
|
70
|
+
def setActive(self, active: bool) -> None:
|
71
|
+
""""""
|
72
|
+
if isinstance(active, bool):
|
73
|
+
self.active = active
|
74
|
+
|
75
|
+
def set_requested_azimuth(self, azimuth: float) -> None:
|
76
|
+
if isinstance(azimuth, float):
|
77
|
+
self.requestedAzimuth = azimuth
|
78
|
+
self.requestedAzimuthNeedle.setRotation(self.requestedAzimuth)
|
79
|
+
self.requestedAzimuthNeedle.show()
|
80
|
+
else:
|
81
|
+
self.requestedAzimuthNeedle.hide()
|
82
|
+
|
83
|
+
def set_antenna_azimuth(self, azimuth: float) -> None:
|
84
|
+
if isinstance(azimuth, float):
|
85
|
+
self.antennaAzimuth = azimuth
|
86
|
+
self.antennaNeedle.setRotation(self.antennaAzimuth)
|
87
|
+
self.antennaNeedle.show()
|
88
|
+
else:
|
89
|
+
self.antennaNeedle.hide()
|
90
|
+
|
91
|
+
def set_north_azimuth(self) -> None:
|
92
|
+
"""Point the antenna North."""
|
93
|
+
if self.rotator.connected:
|
94
|
+
self.rotator.set_position(0.0)
|
95
|
+
|
96
|
+
def set_south_azimuth(self) -> None:
|
97
|
+
"""Point the antenna South."""
|
98
|
+
if self.rotator.connected:
|
99
|
+
self.rotator.set_position(180.0)
|
100
|
+
|
101
|
+
def set_east_azimuth(self) -> None:
|
102
|
+
"""Point the antenna East."""
|
103
|
+
if self.rotator.connected:
|
104
|
+
self.rotator.set_position(90.0)
|
105
|
+
|
106
|
+
def set_west_azimuth(self) -> None:
|
107
|
+
"""Point the antenna West."""
|
108
|
+
if self.rotator.connected:
|
109
|
+
self.rotator.set_position(270.0)
|
110
|
+
|
111
|
+
def the_eye_of_sauron(self) -> None:
|
112
|
+
"""Move the antennas azimuth to match the contacts."""
|
113
|
+
if self.rotator.connected:
|
114
|
+
self.rotator.set_position(self.requestedAzimuth)
|
115
|
+
|
116
|
+
def redrawMap(self) -> None:
|
117
|
+
""""""
|
118
|
+
self.compassScene = QGraphicsScene()
|
119
|
+
self.compassView.setScene(self.compassScene)
|
120
|
+
self.compassView.setStyleSheet("background-color: transparent;")
|
121
|
+
file = fsutils.APP_DATA_PATH / "map3.png"
|
122
|
+
source = QImage()
|
123
|
+
source.load(str(file))
|
124
|
+
the_map = QImage(
|
125
|
+
self.MAP_RESOLUTION, self.MAP_RESOLUTION, QImage.Format.Format_ARGB32
|
126
|
+
)
|
127
|
+
the_map.fill(QColor(0, 0, 0, 0))
|
128
|
+
lat, lon = self.gridtolatlon(self.mygrid)
|
129
|
+
|
130
|
+
if os.path.exists(f"{fsutils.USER_DATA_PATH}/{self.mygrid}v2.png"):
|
131
|
+
the_map.load(f"{fsutils.USER_DATA_PATH}/{self.mygrid}v2.png")
|
132
|
+
else:
|
133
|
+
the_map = self.equirectangular_to_azimuthal_equidistant(
|
134
|
+
source, lat, lon, output_size=self.MAP_RESOLUTION
|
135
|
+
)
|
136
|
+
|
137
|
+
pixMapItem = self.compassScene.addPixmap(QPixmap.fromImage(the_map))
|
138
|
+
pixMapItem.moveBy(-self.MAP_RESOLUTION / 2, -self.MAP_RESOLUTION / 2)
|
139
|
+
pixMapItem.setTransformOriginPoint(
|
140
|
+
self.MAP_RESOLUTION / 2, self.MAP_RESOLUTION / 2
|
141
|
+
)
|
142
|
+
pixMapItem.setScale(self.GLOBE_RADIUS * 2 / self.MAP_RESOLUTION)
|
143
|
+
self.compassScene.addEllipse(
|
144
|
+
-100,
|
145
|
+
-100,
|
146
|
+
self.GLOBE_RADIUS * 2,
|
147
|
+
self.GLOBE_RADIUS * 2,
|
148
|
+
QPen(QColor(100, 100, 100)),
|
149
|
+
QBrush(QColor(0, 0, 0), Qt.BrushStyle.NoBrush),
|
150
|
+
)
|
151
|
+
self.compassScene.addEllipse(
|
152
|
+
-1,
|
153
|
+
-1,
|
154
|
+
2,
|
155
|
+
2,
|
156
|
+
QPen(Qt.PenStyle.NoPen),
|
157
|
+
QBrush(QColor(0, 0, 0), Qt.BrushStyle.SolidPattern),
|
158
|
+
)
|
159
|
+
path = QPainterPath()
|
160
|
+
path.lineTo(-4, 0)
|
161
|
+
path.lineTo(0, -90)
|
162
|
+
path.lineTo(4, 0)
|
163
|
+
path.closeSubpath()
|
164
|
+
|
165
|
+
path2 = QPainterPath()
|
166
|
+
path2.lineTo(-1, 0)
|
167
|
+
path2.lineTo(0, -90)
|
168
|
+
path2.lineTo(1, 0)
|
169
|
+
path2.closeSubpath()
|
170
|
+
|
171
|
+
self.requestedAzimuthNeedle = self.compassScene.addPath(
|
172
|
+
path,
|
173
|
+
QPen(QColor(0, 0, 0, 150)),
|
174
|
+
QBrush(QColor(0, 0, 255), Qt.BrushStyle.SolidPattern),
|
175
|
+
)
|
176
|
+
|
177
|
+
if isinstance(self.requestedAzimuth, float):
|
178
|
+
self.requestedAzimuthNeedle.setRotation(self.requestedAzimuth)
|
179
|
+
self.requestedAzimuthNeedle.show()
|
180
|
+
else:
|
181
|
+
self.requestedAzimuthNeedle.hide()
|
182
|
+
|
183
|
+
self.antennaNeedle = self.compassScene.addPath(
|
184
|
+
path,
|
185
|
+
QPen(QColor(0, 0, 0, 150)),
|
186
|
+
QBrush(QColor(255, 191, 0), Qt.BrushStyle.SolidPattern),
|
187
|
+
)
|
188
|
+
if isinstance(self.antennaAzimuth, float):
|
189
|
+
self.antennaNeedle.setRotation(self.antennaAzimuth)
|
190
|
+
self.antennaNeedle.show()
|
191
|
+
else:
|
192
|
+
self.antennaNeedle.hide()
|
193
|
+
|
194
|
+
def gridtolatlon(self, maiden: str) -> tuple[float, float]:
|
195
|
+
"""
|
196
|
+
Converts a maidenhead gridsquare to a latitude longitude pair.
|
197
|
+
"""
|
198
|
+
try:
|
199
|
+
maiden = str(maiden).strip().upper()
|
200
|
+
|
201
|
+
chars_in_grid_square = len(maiden)
|
202
|
+
if not 8 >= chars_in_grid_square >= 2 and chars_in_grid_square % 2 == 0:
|
203
|
+
return 0, 0
|
204
|
+
lon = (ord(maiden[0]) - 65) * 20 - 180
|
205
|
+
lat = (ord(maiden[1]) - 65) * 10 - 90
|
206
|
+
if chars_in_grid_square >= 4:
|
207
|
+
lon += (ord(maiden[2]) - 48) * 2
|
208
|
+
lat += (ord(maiden[3]) - 48) * 1
|
209
|
+
if chars_in_grid_square >= 6:
|
210
|
+
lon += (ord(maiden[4]) - 65) * (5.0 / 60.0)
|
211
|
+
lat += (ord(maiden[5]) - 65) * (2.5 / 60.0)
|
212
|
+
if chars_in_grid_square >= 8:
|
213
|
+
lon += (ord(maiden[6]) - 48) * (30.0 / 3600.0)
|
214
|
+
lat += (ord(maiden[7]) - 48) * (15.0 / 3600.0)
|
215
|
+
lon += 15.0 / 3600.0
|
216
|
+
lat += 7.5 / 3600.0
|
217
|
+
else:
|
218
|
+
lon += 2.5 / 60.0
|
219
|
+
lat += 1.25 / 60.0
|
220
|
+
else:
|
221
|
+
lon += 1
|
222
|
+
lat += 0.5
|
223
|
+
else:
|
224
|
+
lon += 10
|
225
|
+
lat += 5
|
226
|
+
|
227
|
+
return lat, lon
|
228
|
+
except IndexError:
|
229
|
+
return 0.0, 0.0
|
230
|
+
|
231
|
+
def equirectangular_to_azimuthal_equidistant(
|
232
|
+
self,
|
233
|
+
source_img: QImage,
|
234
|
+
center_lat_deg: float,
|
235
|
+
center_lon_deg: float,
|
236
|
+
output_size: int = 600,
|
237
|
+
) -> QImage:
|
238
|
+
"""This does some super magic"""
|
239
|
+
|
240
|
+
width, height = source_img.width(), source_img.height()
|
241
|
+
dest_img = QImage(output_size, output_size, QImage.Format.Format_ARGB32)
|
242
|
+
|
243
|
+
# Convert center point to radians
|
244
|
+
lat0 = math.radians(center_lat_deg)
|
245
|
+
lon0 = math.radians(center_lon_deg)
|
246
|
+
|
247
|
+
sin_lat = math.sin(lat0)
|
248
|
+
cos_lat = math.cos(lat0)
|
249
|
+
|
250
|
+
R = output_size / 2
|
251
|
+
for y in range(output_size):
|
252
|
+
for x in range(output_size):
|
253
|
+
# Convert pixel coordinate to normalized cartesian coordinate in range [-1, 1]
|
254
|
+
dx = (x - R) / R
|
255
|
+
dy = (R - y) / R # Invert Y axis to have +Y upwards
|
256
|
+
|
257
|
+
rho = math.sqrt(dx * dx + dy * dy)
|
258
|
+
if rho > 1.0:
|
259
|
+
# Outside the projection circle
|
260
|
+
dest_img.setPixelColor(x, y, QColor(0, 0, 0, 0))
|
261
|
+
continue
|
262
|
+
|
263
|
+
c = (
|
264
|
+
rho * math.pi
|
265
|
+
) # scale rho to angular distance (c = great-circle distance)
|
266
|
+
|
267
|
+
if rho == 0:
|
268
|
+
lat = lat0
|
269
|
+
lon = lon0
|
270
|
+
else:
|
271
|
+
sin_c = math.sin(c)
|
272
|
+
cos_c = math.cos(c)
|
273
|
+
|
274
|
+
lat = math.asin(cos_c * sin_lat + (dy * sin_c * cos_lat / rho))
|
275
|
+
|
276
|
+
lon = lon0 + math.atan2(
|
277
|
+
dx * sin_c,
|
278
|
+
rho * cos_lat * cos_c - dy * sin_lat * sin_c,
|
279
|
+
)
|
280
|
+
|
281
|
+
# Map lat/lon to source image pixel coordinates (equirectangular)
|
282
|
+
lon_deg = math.degrees(lon)
|
283
|
+
lat_deg = math.degrees(lat)
|
284
|
+
|
285
|
+
# Wrap longitude to [-180, 180]
|
286
|
+
if lon_deg < -180:
|
287
|
+
lon_deg += 360
|
288
|
+
elif lon_deg > 180:
|
289
|
+
lon_deg -= 360
|
290
|
+
|
291
|
+
# Map to source image coordinates
|
292
|
+
src_x = int(((lon_deg + 180.0) / 360.0) * width)
|
293
|
+
src_y = int(((90 - lat_deg) / 180.0) * height)
|
294
|
+
|
295
|
+
# Clamp coordinates
|
296
|
+
src_x = max(0, min(width - 1, src_x))
|
297
|
+
src_y = max(0, min(height - 1, src_y))
|
298
|
+
|
299
|
+
color = source_img.pixelColor(src_x, src_y)
|
300
|
+
dest_img.setPixelColor(x, y, color)
|
301
|
+
|
302
|
+
dest_img.save(f"{fsutils.USER_DATA_PATH}/{self.mygrid}v2.png", "PNG")
|
303
|
+
return dest_img
|
304
|
+
|
305
|
+
def showEvent(self, event: QShowEvent) -> None:
|
306
|
+
"""Make the globe fit in the widget when widget is shown."""
|
307
|
+
self.compassView.fitInView(
|
308
|
+
self.compassScene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio
|
309
|
+
)
|
310
|
+
|
311
|
+
def resizeEvent(self, event: QResizeEvent) -> None:
|
312
|
+
"""Make globe fit in widget when widget is resized."""
|
313
|
+
self.compassView.fitInView(
|
314
|
+
self.compassScene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio
|
315
|
+
)
|
316
|
+
|
317
|
+
def check_rotator(self) -> None:
|
318
|
+
"""Check the rotator"""
|
319
|
+
if self.rotator.connected:
|
320
|
+
self.antennaAzimuth, _ = self.rotator.get_position()
|
321
|
+
self.set_antenna_azimuth(self.antennaAzimuth)
|
322
|
+
else:
|
323
|
+
self.rotator.connect()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: not1mm
|
3
|
-
Version: 25.6.
|
3
|
+
Version: 25.6.11.1
|
4
4
|
Summary: NOT1MM Logger
|
5
5
|
Author-email: Michael Bridak <michael.bridak@gmail.com>
|
6
6
|
License: GPL-3.0-or-later
|
@@ -108,6 +108,7 @@ Dynamic: license-file
|
|
108
108
|
- [The Check Partial Window](#the-check-partial-window)
|
109
109
|
- [The Rate Window](#the-rate-window)
|
110
110
|
- [The DXCC window](#the-dxcc-window)
|
111
|
+
- [The Rotator Window (Work In Progress)](#the-rotator-window-work-in-progress)
|
111
112
|
- [The Remote VFO Window](#the-remote-vfo-window)
|
112
113
|
- [Cabrillo](#cabrillo)
|
113
114
|
- [ADIF](#adif)
|
@@ -245,6 +246,7 @@ generated, 'cause I'm lazy, list of those who've submitted PR's.
|
|
245
246
|
|
246
247
|
## Recent Changes
|
247
248
|
|
249
|
+
- [25-6-11] Added a rotator control widget.
|
248
250
|
- [25-6-10] Merged PR from @dj1yfk correcting WPX prefix calculation.
|
249
251
|
- [25-6-8] Revmoved SQLite WAL mode.
|
250
252
|
- Rewrote DXCC tracker.
|
@@ -385,8 +387,8 @@ Under the `CW` tab, there are three options: i) `cwdaemon` that normally uses IP
|
|
385
387
|
iii) `CAT` that sends Morse characters via rigctld if your radio supports it.
|
386
388
|
|
387
389
|
For contests that require a serial number as part of the exchange, there is an option to pad it with leading zeroes,
|
388
|
-
typically represented by the cut number "T". For example, serial number "001" can be sent as "TT1". The user can
|
389
|
-
configure the `CW Sent Nr Padding` character (default: T) and `CW Sent Nr Padding
|
390
|
+
typically represented by the cut number "T". For example, serial number "001" can be sent as "TT1". The user can
|
391
|
+
configure the `CW Sent Nr Padding` character (default: T) and `CW Sent Nr Padding
|
390
392
|
Length` (default: 3) or specify no padding by entering length "0".
|
391
393
|
|
392
394
|
### Cluster
|
@@ -696,6 +698,18 @@ This window shows you a grid of DXCC entities you've aquired and on what bands.
|
|
696
698
|
|
697
699
|

|
698
700
|
|
701
|
+
### The Rotator Window (Work In Progress)
|
702
|
+
|
703
|
+
`Window`>`Rotator`
|
704
|
+
|
705
|
+
The Rotator window is a work in progress. Currently it blindly connects to a rigctld
|
706
|
+
instance on it's default port of 4533.
|
707
|
+
|
708
|
+

|
709
|
+
|
710
|
+
I myself don't have a rotator to test with. I'm a QRP wires in the trees, if only I
|
711
|
+
had a tree, kind of guy. So we're kind of hoping this works. If not, don't use it.
|
712
|
+
|
699
713
|
### The Remote VFO Window
|
700
714
|
|
701
715
|
You can control the VFO on a remote rig by following the directions listed in
|
@@ -1,5 +1,5 @@
|
|
1
1
|
not1mm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
not1mm/__main__.py,sha256=
|
2
|
+
not1mm/__main__.py,sha256=MYfdudrNuWI_EqNQK3lrFEP-1WQv8pUyJI4Ul6LFY1M,174761
|
3
3
|
not1mm/bandmap.py,sha256=0JmZ32UvkaPjXs2xTgowX1GLvZo5zHU_Zo9y_GL-On4,31139
|
4
4
|
not1mm/checkwindow.py,sha256=zEHlw40j6Wr3rvKbCQf2lcezCoiZqaBqEvBjQU5aKW0,7630
|
5
5
|
not1mm/dxcc_tracker.py,sha256=LUTt538mBjOr590WMby-hmRz47XG8xUCkU2x0j4oB4w,4306
|
@@ -8,6 +8,7 @@ not1mm/logwindow.py,sha256=O2dMaT_BYWsXA_dxsEHN92JwN-qVGy9nmH0MCMaG9gY,42830
|
|
8
8
|
not1mm/lookupservice.py,sha256=GkY_qHZfrW6XHf8upIoaG4hCFqm0fg6Ganu9ConGrIc,2628
|
9
9
|
not1mm/radio.py,sha256=4Lysf9BY3vdtYCHwKfzO5WN7IGyh4_lKSVuQ6F4Z08g,5536
|
10
10
|
not1mm/ratewindow.py,sha256=iBjqdOetIEX0wSwdGM89Ibt4gVlFdE-K8HQPnkVPVOg,6965
|
11
|
+
not1mm/rotator.py,sha256=9HjhrlbeYFhNOjvCPmmoWMfRtbQ6ReYHMDJm6EPKrJo,11200
|
11
12
|
not1mm/rtc_service.py,sha256=axAwnCBuTr-QL0YwXtWvg9tjwhcFsiiEZFgFjOofX6k,2816
|
12
13
|
not1mm/statistics.py,sha256=eOmUvbbYdbbIYHmaEhtBGab1IxAf2RQYV1q9MItpqEM,7969
|
13
14
|
not1mm/test.py,sha256=WhL0DLlJTD15aON8Dkf2q_tlP_X1juxKZJh0jEC99tU,154
|
@@ -37,7 +38,8 @@ not1mm/data/k6gte.not1mm-32.png,sha256=XdTsCa3xqwTfn26Ga7RwO_Vlbg_77RKkSc8bMxVcC
|
|
37
38
|
not1mm/data/k6gte.not1mm-64.png,sha256=6ku45Gq1g5ezh04F07osoKRtanb3e4kbx5XdIEh3N90,2925
|
38
39
|
not1mm/data/logwindow.ui,sha256=vfkNdzJgFs3tTOBKLDavF2zVMvNHWOZ82fAErRi6pQY,1436
|
39
40
|
not1mm/data/logwindowx.ui,sha256=9FzDJtLRpagvAWcDjFdB9NnvNZ4bVxdTNHy1Jit2ido,1610
|
40
|
-
not1mm/data/main.ui,sha256=
|
41
|
+
not1mm/data/main.ui,sha256=2cGrhepmcTylTWJ2LwisW6q9nZsReOH38yThvUn_31g,65717
|
42
|
+
not1mm/data/map3.png,sha256=EJqHeFp0ys6Vb1oKkjL4m0v8i3uWXtIgRrxzi0ETUHM,40704
|
41
43
|
not1mm/data/new_contest.ui,sha256=i-WThxa9IBstyAMCRtNMB4NVPpV3fekUIX1YDYPyuOQ,25521
|
42
44
|
not1mm/data/not1mm.html,sha256=c9-mfjMwDt4f5pySUruz2gREW33CQ2_rCddM2z5CZQo,23273
|
43
45
|
not1mm/data/opon.ui,sha256=mC4OhoVIfR1H9IqHAKXliPMm8VOVmxSEadpsFQ7XnS4,2247
|
@@ -47,6 +49,7 @@ not1mm/data/radio_grey.png,sha256=9eOtMHDpQvRYY29D7_vPeacWbwotRXZTMm8EiHE9TW0,12
|
|
47
49
|
not1mm/data/radio_red.png,sha256=QvkMk7thd_hCEIyK5xGAG4xVVXivl39nwOfD8USDI20,957
|
48
50
|
not1mm/data/ratewindow.ui,sha256=c0gikcZQYWuGwWdFE1PGcRbeJ9nTUPIRkOozWo2FQw8,11541
|
49
51
|
not1mm/data/reddot.png,sha256=M33jEMoU8W4rQ4_MVyzzKxDPDte1ypKBch5VnUMNLKE,565
|
52
|
+
not1mm/data/rotator.ui,sha256=GcyEsb1TOQwp4g02jfCxsQRr8WkC7L4Bm5TTyvkk7hE,3935
|
50
53
|
not1mm/data/rttymacros.txt,sha256=FQ2BnAChXF5w-tzmMnBOE8IgvviAEsd3cmmz4b8GOPk,467
|
51
54
|
not1mm/data/settings.ui,sha256=0nAdg4hDv37x0RL5G0Cc1L8p-F95YtkOjIGqlCGu0Fs,40073
|
52
55
|
not1mm/data/splash.png,sha256=85_BQotR1q24uCthrKm4SB_6ZOMwRjR-Jdp1XBHSTyg,5368
|
@@ -119,10 +122,11 @@ not1mm/lib/multicast.py,sha256=KJcruI-bOuHfHXPjl3SGQhL6I9sKrygy-sdFSvxffUM,3255
|
|
119
122
|
not1mm/lib/n1mm.py,sha256=H54mpgJF0GAmKavM-nb5OAq2SJFWYkux4eMWWiSRxJc,6288
|
120
123
|
not1mm/lib/new_contest.py,sha256=IznTDMq7yXHB6zBoGUEC_WDYPCPpsSZW4wwMJi16zK0,816
|
121
124
|
not1mm/lib/plugin_common.py,sha256=nqiUq11T9Wz8RDrRen4Zvp-KXVWUYcIp5JPZwqmu2Oo,13913
|
125
|
+
not1mm/lib/rot_interface.py,sha256=kGhpRsxPa6MYDaHd7gLJfzgsJKiJ-qHyx8tJ2tWwwlk,3589
|
122
126
|
not1mm/lib/select_contest.py,sha256=WsptLuwkouIHeocJL3oZ6-eUfEnhpwdc-x7eMZ_TIVM,359
|
123
127
|
not1mm/lib/settings.py,sha256=5xnsagH48qGeCDhfxPWW9yaXtv8wT13yoIVvYt8h_Qs,16023
|
124
128
|
not1mm/lib/super_check_partial.py,sha256=jX7DjHesEV4KNVQbddJui0wAsYHerikH7W0iPv7PXQw,3110
|
125
|
-
not1mm/lib/version.py,sha256=
|
129
|
+
not1mm/lib/version.py,sha256=gFvmA0SCQlEBGGGX5yLybz7A77WPaHb3AItzX4Lv2ck,50
|
126
130
|
not1mm/lib/versiontest.py,sha256=8vDNptuBBunn-1IGkjNaquehqBYUJyjrPSF8Igmd4_Y,1286
|
127
131
|
not1mm/plugins/10_10_fall_cw.py,sha256=oJh3JKqjOpnWElSlZpiQ631UnaOd8qra5s9bl_QoInk,14783
|
128
132
|
not1mm/plugins/10_10_spring_cw.py,sha256=p7dSDtbFK0e6Xouw2V6swYn3VFVgHKyx4IfRWyBjMZY,14786
|
@@ -186,9 +190,9 @@ not1mm/plugins/ukeidx.py,sha256=ZsIFXgOSwjuKNmN4W_C0TAgGqgnabJGNLMHwGkl3_bk,1910
|
|
186
190
|
not1mm/plugins/vhf_sprint.py,sha256=a9QFTpv8XUbZ_GLjdVCh7svykFa-gXOWwKFZ6MD3uQM,19289
|
187
191
|
not1mm/plugins/weekly_rtty.py,sha256=C8Xs3Q5UgSYx-mFFar8BVARWtmqlyrbeC98Ubzb4UN8,20128
|
188
192
|
not1mm/plugins/winter_field_day.py,sha256=hmAMgkdqIXtnCNyUp8J9Bb8liN8wj10wps6ROuG-Bok,15284
|
189
|
-
not1mm-25.6.
|
190
|
-
not1mm-25.6.
|
191
|
-
not1mm-25.6.
|
192
|
-
not1mm-25.6.
|
193
|
-
not1mm-25.6.
|
194
|
-
not1mm-25.6.
|
193
|
+
not1mm-25.6.11.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
194
|
+
not1mm-25.6.11.1.dist-info/METADATA,sha256=qkyEgV2Ba4TyrISLl1aqFEvP0ZmxYPft7m2yyzXyhAY,35680
|
195
|
+
not1mm-25.6.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
196
|
+
not1mm-25.6.11.1.dist-info/entry_points.txt,sha256=pMcZk_0dxFgLkcUkF0Q874ojpwOmF3OL6EKw9LgvocM,47
|
197
|
+
not1mm-25.6.11.1.dist-info/top_level.txt,sha256=0YmTxEcDzQlzXub-lXASvoLpg_mt1c2thb5cVkDf5J4,7
|
198
|
+
not1mm-25.6.11.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|