dronecot 2.1.4__tar.gz → 2.2.0__tar.gz

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.
Files changed (33) hide show
  1. {dronecot-2.1.4 → dronecot-2.2.0}/CHANGELOG.md +12 -0
  2. {dronecot-2.1.4/dronecot.egg-info → dronecot-2.2.0}/PKG-INFO +2 -2
  3. {dronecot-2.1.4 → dronecot-2.2.0/dronecot.egg-info}/PKG-INFO +2 -2
  4. {dronecot-2.1.4 → dronecot-2.2.0}/dronecot.egg-info/requires.txt +1 -1
  5. {dronecot-2.1.4 → dronecot-2.2.0}/setup.cfg +1 -1
  6. dronecot-2.2.0/src/dronecot/VERSION +1 -0
  7. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/__init__.py +7 -0
  8. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/classes.py +54 -0
  9. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/constants.py +6 -0
  10. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/functions.py +54 -0
  11. dronecot-2.1.4/src/dronecot/VERSION +0 -1
  12. {dronecot-2.1.4 → dronecot-2.2.0}/LICENSE +0 -0
  13. {dronecot-2.1.4 → dronecot-2.2.0}/MANIFEST.in +0 -0
  14. {dronecot-2.1.4 → dronecot-2.2.0}/README.md +0 -0
  15. {dronecot-2.1.4 → dronecot-2.2.0}/dronecot.egg-info/SOURCES.txt +0 -0
  16. {dronecot-2.1.4 → dronecot-2.2.0}/dronecot.egg-info/dependency_links.txt +0 -0
  17. {dronecot-2.1.4 → dronecot-2.2.0}/dronecot.egg-info/entry_points.txt +0 -0
  18. {dronecot-2.1.4 → dronecot-2.2.0}/dronecot.egg-info/top_level.txt +0 -0
  19. {dronecot-2.1.4 → dronecot-2.2.0}/setup.py +0 -0
  20. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/ble_capture.py +0 -0
  21. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/ble_parse.py +0 -0
  22. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/commands.py +0 -0
  23. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/dji_exceptions.py +0 -0
  24. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/dji_functions.py +0 -0
  25. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/dji_text_parser.py +0 -0
  26. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/odid.py +0 -0
  27. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/open_drone_id.py +0 -0
  28. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/rid_normalize.py +0 -0
  29. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/udp_rid.py +0 -0
  30. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/wifi_capture.py +0 -0
  31. {dronecot-2.1.4 → dronecot-2.2.0}/src/dronecot/wifi_parse.py +0 -0
  32. {dronecot-2.1.4 → dronecot-2.2.0}/tests/test_functions.py +0 -0
  33. {dronecot-2.1.4 → dronecot-2.2.0}/tests/test_rid_normalize.py +0 -0
@@ -1,3 +1,15 @@
1
+ ## DroneCOT 2.2.0
2
+
3
+ - Add `SensorWorker`: periodic `a-f-G-E-S-E` sensor CoT heartbeat, emitted every `SENSOR_KEEPALIVE_PERIOD` seconds (default 30).
4
+ - Position sourced from system gpsd if present (mode 2/3 fix); falls back to static `SENSOR_LAT`/`SENSOR_LON`/`SENSOR_HAE` config; falls back to null island (0, 0, 0).
5
+ - Add `gen_sensor_cot()`: reusable CoT generator for sensor beacon events, used by `SensorWorker`.
6
+ - New constants: `DEFAULT_SENSOR_KEEPALIVE_PERIOD = 30`, `DEFAULT_SENSOR_LAT/LON/HAE = 0.0`.
7
+ - Add `gpsd-py3` as optional runtime dependency (soft import, gracefully absent).
8
+
9
+ ## DroneCOT 2.1.5
10
+
11
+ - Add `takproto` to postinst pip install; enables TAK Protocol v1 protobuf encoding over WebSocket when connected to TAK Server via `wss://`.
12
+
1
13
  ## DroneCOT 2.1.4
2
14
 
3
15
  - Fix packaging: `postinst` now installs `aiomqtt` and `pymavlink` via pip for Debian systems where these packages are not yet available in the distro repos.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dronecot
3
- Version: 2.1.4
3
+ Version: 2.2.0
4
4
  Summary: Display Drones in Team Awareness Kit (TAK).
5
5
  Home-page: https://github.com/snstac/dronecot
6
6
  Maintainer: Greg Albrecht <oss@undef.net>
@@ -22,7 +22,7 @@ Classifier: Operating System :: OS Independent
22
22
  Requires-Python: <4,>=3.9
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: pytak>=5.4.0
25
+ Requires-Dist: pytak>=7.3.0
26
26
  Requires-Dist: paho-mqtt<2.0.0
27
27
  Requires-Dist: bitstruct
28
28
  Requires-Dist: pymavlink
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dronecot
3
- Version: 2.1.4
3
+ Version: 2.2.0
4
4
  Summary: Display Drones in Team Awareness Kit (TAK).
5
5
  Home-page: https://github.com/snstac/dronecot
6
6
  Maintainer: Greg Albrecht <oss@undef.net>
@@ -22,7 +22,7 @@ Classifier: Operating System :: OS Independent
22
22
  Requires-Python: <4,>=3.9
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: pytak>=5.4.0
25
+ Requires-Dist: pytak>=7.3.0
26
26
  Requires-Dist: paho-mqtt<2.0.0
27
27
  Requires-Dist: bitstruct
28
28
  Requires-Dist: pymavlink
@@ -1,4 +1,4 @@
1
- pytak>=5.4.0
1
+ pytak>=7.3.0
2
2
  paho-mqtt<2.0.0
3
3
  bitstruct
4
4
  pymavlink
@@ -48,7 +48,7 @@ package_dir =
48
48
  dronecot = src/dronecot
49
49
  python_requires = >=3.9, <4
50
50
  install_requires =
51
- pytak >= 5.4.0
51
+ pytak >= 7.3.0
52
52
  paho-mqtt < 2.0.0
53
53
  bitstruct
54
54
  pymavlink
@@ -0,0 +1 @@
1
+ 2.2.0
@@ -54,6 +54,11 @@ from .constants import ( # NOQA
54
54
  DEFAULT_DJI_SENSOR_NAME,
55
55
  DEFAULT_DJI_SENSOR_TYPE,
56
56
  DEFAULT_DJI_SENSOR_COT_TYPE,
57
+ # Sensor keep-alive constants
58
+ DEFAULT_SENSOR_KEEPALIVE_PERIOD,
59
+ DEFAULT_SENSOR_LAT,
60
+ DEFAULT_SENSOR_LON,
61
+ DEFAULT_SENSOR_HAE,
57
62
  )
58
63
 
59
64
  from .functions import ( # NOQA
@@ -69,6 +74,7 @@ from .functions import ( # NOQA
69
74
  dji_handle_frame,
70
75
  dji_handle_text_line,
71
76
  dji_handle_parsed_data,
77
+ gen_sensor_cot,
72
78
  )
73
79
 
74
80
  from .udp_rid import ( # NOQA
@@ -97,6 +103,7 @@ from .classes import ( # NOQA
97
103
  TextNetWorker,
98
104
  FileReplayWorker,
99
105
  TCPListenerWorker,
106
+ SensorWorker,
100
107
  )
101
108
 
102
109
  from .dji_exceptions import ( # NOQA
@@ -31,6 +31,7 @@ from typing import Optional, Union
31
31
  from urllib.parse import urlparse
32
32
 
33
33
  import lzma
34
+ import xml.etree.ElementTree as ET
34
35
 
35
36
  try:
36
37
  import aiomqtt
@@ -804,3 +805,56 @@ class UDPRIDWorker(pytak.QueueWorker):
804
805
  raise
805
806
  finally:
806
807
  transport.close()
808
+
809
+
810
+ try:
811
+ import gpsd as _gpsd
812
+ except ImportError:
813
+ _gpsd = None # type: ignore[assignment]
814
+
815
+
816
+ class SensorWorker(pytak.QueueWorker):
817
+ """Periodic sensor CoT heartbeat. Sources position from gpsd, config, or null island."""
818
+
819
+ async def run(self, _=-1) -> None:
820
+ period = int(self.config.get(
821
+ "SENSOR_KEEPALIVE_PERIOD", dronecot.DEFAULT_SENSOR_KEEPALIVE_PERIOD))
822
+ self._logger.info(
823
+ "Running SensorWorker (period=%ds, gpsd=%s)", period, _gpsd is not None)
824
+ while True:
825
+ lat, lon, hae, ce, le = await self._get_position()
826
+ cot = dronecot.gen_sensor_cot(self.config, lat, lon, hae, ce, le)
827
+ if cot is not None:
828
+ await self.put_queue(ET.tostring(cot))
829
+ await asyncio.sleep(period)
830
+
831
+ async def _get_position(self):
832
+ if _gpsd is not None:
833
+ try:
834
+ result = await asyncio.to_thread(self._poll_gpsd)
835
+ if result is not None:
836
+ return result
837
+ except Exception as exc:
838
+ self._logger.debug("gpsd unavailable: %s", exc)
839
+ lat = float(self.config.get("SENSOR_LAT") or dronecot.DEFAULT_SENSOR_LAT)
840
+ lon = float(self.config.get("SENSOR_LON") or dronecot.DEFAULT_SENSOR_LON)
841
+ hae = float(self.config.get("SENSOR_HAE") or dronecot.DEFAULT_SENSOR_HAE)
842
+ return lat, lon, hae, "9999999.0", "9999999.0"
843
+
844
+ @staticmethod
845
+ def _poll_gpsd():
846
+ _gpsd.connect()
847
+ packet = _gpsd.get_current()
848
+ if packet.mode < 2:
849
+ return None
850
+ try:
851
+ lat, lon = packet.position()
852
+ except Exception:
853
+ return None
854
+ try:
855
+ hae = packet.altitude()
856
+ except Exception:
857
+ hae = 0.0
858
+ ce = str(getattr(packet, "error", {}).get("x", "9999999.0") or "9999999.0")
859
+ le = str(getattr(packet, "error", {}).get("v", "9999999.0") or "9999999.0")
860
+ return lat, lon, hae, ce, le
@@ -46,6 +46,12 @@ DEFAULT_OP_COT_TYPE: str = "a-n-G"
46
46
  DEFAULT_UAS_COT_TYPE: str = "a-n-A-M-H-Q"
47
47
  DEFAULT_HOME_COT_TYPE: str = "a-n-G"
48
48
 
49
+ # Sensor keep-alive / heartbeat
50
+ DEFAULT_SENSOR_KEEPALIVE_PERIOD: int = 30 # seconds between a-f-G-E-S-E CoT beacons
51
+ DEFAULT_SENSOR_LAT: float = 0.0
52
+ DEFAULT_SENSOR_LON: float = 0.0
53
+ DEFAULT_SENSOR_HAE: float = 0.0
54
+
49
55
  # Pre-decoded Remote ID JSON over UDP constants
50
56
  DEFAULT_UDP_RID_PORT: int = 9999
51
57
  DEFAULT_UDP_RID_HOST: str = "0.0.0.0"
@@ -133,6 +133,8 @@ def create_tasks(config: SectionProxy, clitool: pytak.CLITool) -> Set[pytak.Work
133
133
  if config.get("ENABLE_RX_MOCK", "0") == "1":
134
134
  tasks.add(dronecot.RXMockWorker(clitool.rx_queue, config))
135
135
 
136
+ tasks.add(dronecot.SensorWorker(clitool.tx_queue, config))
137
+
136
138
  return tasks
137
139
 
138
140
 
@@ -843,3 +845,55 @@ def dji_handle_frame(
843
845
  _DJI_Logger.warning("Error parsing DJI data: %s", exc)
844
846
  return []
845
847
  return dji_handle_parsed_data(parsed_data, config)
848
+
849
+
850
+ def gen_sensor_cot(
851
+ config: Union[SectionProxy, dict, None] = None,
852
+ lat: float = 0.0,
853
+ lon: float = 0.0,
854
+ hae: float = 0.0,
855
+ ce: str = "9999999.0",
856
+ le: str = "9999999.0",
857
+ ) -> Optional[ET.Element]:
858
+ """Generate a periodic sensor beacon CoT event (a-f-G-E-S-E).
859
+
860
+ Position sourced from caller: gpsd, static config, or null island.
861
+ ce/le are '9999999.0' when accuracy is unknown.
862
+ """
863
+ config = config or {}
864
+ sensor_id = config.get("SENSOR_ID", dronecot.DEFAULT_SENSOR_ID)
865
+ cot_type = config.get("SENSOR_COT_TYPE", dronecot.DEFAULT_SENSOR_COT_TYPE)
866
+ cot_stale = int(config.get("COT_STALE", pytak.DEFAULT_COT_STALE))
867
+ callsign = config.get("SENSOR_CALLSIGN", sensor_id)
868
+ payload_type = config.get("SENSOR_PAYLOAD_TYPE", dronecot.DEFAULT_SENSOR_PAYLOAD_TYPE)
869
+
870
+ contact = ET.Element("contact")
871
+ contact.set("callsign", callsign)
872
+
873
+ cuas = ET.Element("__cuas")
874
+ cuas.set("sensor_id", sensor_id)
875
+ cuas.set("type", payload_type)
876
+
877
+ detail = ET.Element("detail")
878
+ detail.append(contact)
879
+ detail.append(cuas)
880
+
881
+ cot = pytak.gen_cot_xml(
882
+ lat=str(lat),
883
+ lon=str(lon),
884
+ hae=str(hae),
885
+ ce=ce,
886
+ le=le,
887
+ uid=f"SENSOR.{sensor_id}",
888
+ cot_type=cot_type,
889
+ stale=cot_stale,
890
+ )
891
+ cot.set("how", "m-g")
892
+ cot.set("access", config.get("COT_ACCESS", pytak.DEFAULT_COT_ACCESS))
893
+
894
+ _detail = cot.find("detail")
895
+ if _detail is not None:
896
+ cot.remove(_detail)
897
+ cot.append(detail)
898
+
899
+ return cot
@@ -1 +0,0 @@
1
- 2.1.4
File without changes
File without changes
File without changes
File without changes
File without changes