dronecot 2.3.8__tar.gz → 2.3.10__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.
- {dronecot-2.3.8 → dronecot-2.3.10}/CHANGELOG.md +5 -0
- {dronecot-2.3.8/dronecot.egg-info → dronecot-2.3.10}/PKG-INFO +1 -1
- {dronecot-2.3.8 → dronecot-2.3.10/dronecot.egg-info}/PKG-INFO +1 -1
- dronecot-2.3.10/src/dronecot/VERSION +1 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/classes.py +14 -5
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/functions.py +8 -1
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_functions.py +30 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_status.py +23 -1
- dronecot-2.3.8/src/dronecot/VERSION +0 -1
- {dronecot-2.3.8 → dronecot-2.3.10}/LICENSE +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/MANIFEST.in +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/README.md +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/dronecot.egg-info/SOURCES.txt +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/dronecot.egg-info/dependency_links.txt +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/dronecot.egg-info/entry_points.txt +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/dronecot.egg-info/requires.txt +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/dronecot.egg-info/top_level.txt +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/setup.cfg +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/setup.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/__init__.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/ble_capture.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/ble_parse.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/bluez_capture.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/commands.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/constants.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/dji_exceptions.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/dji_functions.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/dji_text_parser.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/odid.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/open_drone_id.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/rid_normalize.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/rid_track.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/udp_rid.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/wifi_capture.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/src/dronecot/wifi_parse.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_adsb_vehicle.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_bluez_capture.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_dji_listener.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_mavlink_identity.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_rid_normalize.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_rid_track.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_sensor_detail.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_serial_crlf.py +0 -0
- {dronecot-2.3.8 → dronecot-2.3.10}/tests/test_wifi_parse.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.10
|
|
@@ -118,11 +118,18 @@ class _CRLFNormalizingReceiver:
|
|
|
118
118
|
return self._normalizer.feed(self._receiver(size))
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
def make_status(app_name: str, version: str):
|
|
121
|
+
def make_status(app_name: str, version: str, path: Optional[str] = None):
|
|
122
122
|
"""Return a status writer, or a no-op if this pytak has none."""
|
|
123
123
|
if _StatusWriter is None:
|
|
124
124
|
return _NoStatus()
|
|
125
|
-
return _StatusWriter(app_name, version=version)
|
|
125
|
+
return _StatusWriter(app_name, path=path, version=version)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def make_worker_status(config):
|
|
129
|
+
"""Build the status writer for one independently managed instance."""
|
|
130
|
+
app_name = str(config.get("STATUS_APP", "dronecot") or "dronecot")
|
|
131
|
+
path = str(config.get("STATUS_PATH", "") or "") or None
|
|
132
|
+
return make_status(app_name, dronecot.__version__, path=path)
|
|
126
133
|
|
|
127
134
|
|
|
128
135
|
class SerialWorker(pytak.QueueWorker):
|
|
@@ -832,7 +839,7 @@ class RIDWorker(pytak.QueueWorker):
|
|
|
832
839
|
# every RF source (Wi-Fi, BLE, BlueZ, serial MAVLink, MQTT, UDP) funnels
|
|
833
840
|
# through here. Instrumenting the capture workers instead would give N
|
|
834
841
|
# writers racing on one path, and each would see only its own slice.
|
|
835
|
-
self.status =
|
|
842
|
+
self.status = make_worker_status(config)
|
|
836
843
|
|
|
837
844
|
def _tracked(self) -> int:
|
|
838
845
|
"""Number of transmitters the aggregator is currently holding."""
|
|
@@ -990,10 +997,12 @@ class RXMockWorker(pytak.RXWorker):
|
|
|
990
997
|
class DJIWorker(pytak.QueueWorker):
|
|
991
998
|
"""Process DJI Drone ID data from a net queue and emit CoT events."""
|
|
992
999
|
|
|
993
|
-
def __init__(
|
|
1000
|
+
def __init__(
|
|
1001
|
+
self, tx_queue: asyncio.Queue, config, net_queue: asyncio.Queue
|
|
1002
|
+
) -> None:
|
|
994
1003
|
super().__init__(tx_queue, config)
|
|
995
1004
|
self.net_queue = net_queue
|
|
996
|
-
self.status =
|
|
1005
|
+
self.status = make_worker_status(config)
|
|
997
1006
|
|
|
998
1007
|
async def handle_data(self, data) -> None:
|
|
999
1008
|
if not data:
|
|
@@ -65,6 +65,12 @@ _DJI_Logger = logging.getLogger(__name__)
|
|
|
65
65
|
APP_NAME = "dronecot"
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
def sensor_beacon_enabled(config: Union[dict, SectionProxy]) -> bool:
|
|
69
|
+
"""Return whether the periodic receiver beacon is enabled."""
|
|
70
|
+
value = config.get("SENSOR_BEACON", "1")
|
|
71
|
+
return str(value).strip().lower() not in {"0", "false", "no", "off"}
|
|
72
|
+
|
|
73
|
+
|
|
68
74
|
def _pacific_timestamp() -> str:
|
|
69
75
|
"""Return a human-readable last-seen timestamp, falling back if tzdata is absent."""
|
|
70
76
|
try:
|
|
@@ -160,7 +166,8 @@ def create_tasks(config: SectionProxy, clitool: pytak.CLITool) -> Set[pytak.Work
|
|
|
160
166
|
if config.get("ENABLE_RX_MOCK", "0") == "1":
|
|
161
167
|
tasks.add(dronecot.RXMockWorker(clitool.rx_queue, config))
|
|
162
168
|
|
|
163
|
-
|
|
169
|
+
if sensor_beacon_enabled(config):
|
|
170
|
+
tasks.add(dronecot.SensorWorker(clitool.tx_queue, config))
|
|
164
171
|
|
|
165
172
|
return tasks
|
|
166
173
|
|
|
@@ -16,9 +16,12 @@
|
|
|
16
16
|
|
|
17
17
|
"""DroneCOT Function Tests."""
|
|
18
18
|
|
|
19
|
+
import asyncio
|
|
19
20
|
import json
|
|
20
21
|
import os
|
|
21
22
|
import unittest
|
|
23
|
+
from types import SimpleNamespace
|
|
24
|
+
from unittest.mock import patch
|
|
22
25
|
|
|
23
26
|
import xml.etree.ElementTree as ET
|
|
24
27
|
|
|
@@ -45,6 +48,33 @@ def load_sample_data(file_path, line=0):
|
|
|
45
48
|
|
|
46
49
|
|
|
47
50
|
class FunctionsTestCase(unittest.TestCase):
|
|
51
|
+
def test_sensor_beacon_switch(self):
|
|
52
|
+
"""Receiver beacons default on and accept common false values."""
|
|
53
|
+
enabled = dronecot.functions.sensor_beacon_enabled
|
|
54
|
+
self.assertTrue(enabled({}))
|
|
55
|
+
for value in ("0", "false", "False", "no", "off"):
|
|
56
|
+
with self.subTest(value=value):
|
|
57
|
+
self.assertFalse(enabled({"SENSOR_BEACON": value}))
|
|
58
|
+
|
|
59
|
+
def test_create_tasks_can_omit_sensor_beacon(self):
|
|
60
|
+
"""Disabling the receiver beacon keeps drone processing enabled."""
|
|
61
|
+
clitool = SimpleNamespace(tx_queue=object())
|
|
62
|
+
config = {"FEED_URL": "serial:///dev/ttyACM0", "SENSOR_BEACON": "0"}
|
|
63
|
+
loop = asyncio.new_event_loop()
|
|
64
|
+
try:
|
|
65
|
+
asyncio.set_event_loop(loop)
|
|
66
|
+
with patch.object(dronecot, "SerialWorker", return_value="serial"):
|
|
67
|
+
with patch.object(dronecot, "RIDWorker", return_value="tracks"):
|
|
68
|
+
with patch.object(
|
|
69
|
+
dronecot, "SensorWorker", return_value="receiver"
|
|
70
|
+
) as sensor_worker:
|
|
71
|
+
tasks = dronecot.functions.create_tasks(config, clitool)
|
|
72
|
+
finally:
|
|
73
|
+
asyncio.set_event_loop(None)
|
|
74
|
+
loop.close()
|
|
75
|
+
self.assertEqual(tasks, {"serial", "tracks"})
|
|
76
|
+
sensor_worker.assert_not_called()
|
|
77
|
+
|
|
48
78
|
"""
|
|
49
79
|
Test class for functions... functions.
|
|
50
80
|
"""
|
|
@@ -216,7 +216,10 @@ class TestDJIStatusSurface:
|
|
|
216
216
|
def _worker(self, tmp_path):
|
|
217
217
|
worker = classes.DJIWorker.__new__(classes.DJIWorker)
|
|
218
218
|
worker.config = {"FEED_URL": "tcp://172.31.100.2:52002"}
|
|
219
|
-
|
|
219
|
+
# handle_data does not consume net_queue. Keeping this synthetic worker
|
|
220
|
+
# loop-free also works on Python 3.9 after an earlier asyncio.run()
|
|
221
|
+
# closes and clears the process-wide default loop.
|
|
222
|
+
worker.net_queue = None
|
|
220
223
|
worker._logger = logging.getLogger("test")
|
|
221
224
|
worker.status = pytak.StatusWriter(
|
|
222
225
|
"dronecot-test", path=str(tmp_path / "status.json")
|
|
@@ -330,3 +333,22 @@ class TestStatusDegradesVisibly:
|
|
|
330
333
|
if classes._StatusWriter is None:
|
|
331
334
|
pytest.skip("installed pytak has no StatusWriter")
|
|
332
335
|
assert not isinstance(classes.make_status("x", "0"), classes._NoStatus)
|
|
336
|
+
|
|
337
|
+
def test_instance_status_uses_configured_app_and_path(self, monkeypatch, tmp_path):
|
|
338
|
+
captured = {}
|
|
339
|
+
|
|
340
|
+
def fake_writer(app_name, *, path=None, version=None):
|
|
341
|
+
captured.update(app_name=app_name, path=path, version=version)
|
|
342
|
+
return object()
|
|
343
|
+
|
|
344
|
+
monkeypatch.setattr(classes, "_StatusWriter", fake_writer)
|
|
345
|
+
path = str(tmp_path / "status.json")
|
|
346
|
+
classes.make_worker_status(
|
|
347
|
+
{"STATUS_APP": "dronecot-dronescout", "STATUS_PATH": path}
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
assert captured == {
|
|
351
|
+
"app_name": "dronecot-dronescout",
|
|
352
|
+
"path": path,
|
|
353
|
+
"version": classes.dronecot.__version__,
|
|
354
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.3.8
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|