dronecot 2.3.3__tar.gz → 2.3.5__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.3 → dronecot-2.3.5}/CHANGELOG.md +7 -0
- {dronecot-2.3.3/dronecot.egg-info → dronecot-2.3.5}/PKG-INFO +1 -1
- {dronecot-2.3.3 → dronecot-2.3.5/dronecot.egg-info}/PKG-INFO +1 -1
- {dronecot-2.3.3 → dronecot-2.3.5}/setup.cfg +3 -0
- dronecot-2.3.5/src/dronecot/VERSION +1 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/classes.py +41 -5
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_status.py +64 -0
- dronecot-2.3.3/src/dronecot/VERSION +0 -1
- {dronecot-2.3.3 → dronecot-2.3.5}/LICENSE +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/MANIFEST.in +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/README.md +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/dronecot.egg-info/SOURCES.txt +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/dronecot.egg-info/dependency_links.txt +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/dronecot.egg-info/entry_points.txt +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/dronecot.egg-info/requires.txt +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/dronecot.egg-info/top_level.txt +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/setup.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/__init__.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/ble_capture.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/ble_parse.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/bluez_capture.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/commands.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/constants.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/dji_exceptions.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/dji_functions.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/dji_text_parser.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/functions.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/odid.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/open_drone_id.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/rid_normalize.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/rid_track.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/udp_rid.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/wifi_capture.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/src/dronecot/wifi_parse.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_adsb_vehicle.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_bluez_capture.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_dji_listener.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_functions.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_mavlink_identity.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_rid_normalize.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_rid_track.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_sensor_detail.py +0 -0
- {dronecot-2.3.3 → dronecot-2.3.5}/tests/test_wifi_parse.py +0 -0
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## DroneCOT 2.3.4
|
|
2
|
+
|
|
3
|
+
- Include `dronecot/VERSION` in wheel and Debian installations. DroneCOT 2.3.3
|
|
4
|
+
omitted this runtime file and failed at import time after installation.
|
|
5
|
+
- Verify wheel and Debian archives contain the runtime version file before
|
|
6
|
+
publishing a release.
|
|
7
|
+
|
|
1
8
|
## DroneCOT 2.3.3
|
|
2
9
|
|
|
3
10
|
- Fixed the AntSDR scanner-push listener retaining active client tasks while
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.3.5
|
|
@@ -937,24 +937,60 @@ class DJIWorker(pytak.QueueWorker):
|
|
|
937
937
|
def __init__(self, tx_queue: asyncio.Queue, config, net_queue: asyncio.Queue) -> None:
|
|
938
938
|
super().__init__(tx_queue, config)
|
|
939
939
|
self.net_queue = net_queue
|
|
940
|
+
self.status = make_status("dronecot", dronecot.__version__)
|
|
940
941
|
|
|
941
942
|
async def handle_data(self, data) -> None:
|
|
943
|
+
if not data:
|
|
944
|
+
return
|
|
945
|
+
|
|
946
|
+
self.status.count("rx")
|
|
942
947
|
if isinstance(data, str):
|
|
943
948
|
events = dronecot.dji_handle_text_line(data, self.config)
|
|
949
|
+
source = "dji-text"
|
|
944
950
|
elif isinstance(data, bytes) and data.startswith(b"dji_O,"):
|
|
945
|
-
events = dronecot.dji_handle_text_line(
|
|
951
|
+
events = dronecot.dji_handle_text_line(
|
|
952
|
+
data.decode("utf-8", errors="replace"), self.config
|
|
953
|
+
)
|
|
954
|
+
source = "dji-text"
|
|
946
955
|
else:
|
|
947
956
|
events = dronecot.dji_handle_frame(data, self.config)
|
|
957
|
+
source = "dji-binary"
|
|
958
|
+
|
|
959
|
+
if events:
|
|
960
|
+
self.status.count("emitted", len(events))
|
|
961
|
+
else:
|
|
962
|
+
self.status.count("no_cot")
|
|
963
|
+
# Do not copy aircraft identifiers or the raw RF payload into the
|
|
964
|
+
# world-readable status file. Operators need to know whether the
|
|
965
|
+
# AntSDR is producing usable data, not which aircraft it heard.
|
|
966
|
+
self.status.record(source=source, placed=bool(events), events=len(events))
|
|
967
|
+
self.status.write()
|
|
968
|
+
|
|
948
969
|
for event in events:
|
|
949
970
|
await self.put_queue(event)
|
|
950
971
|
|
|
951
972
|
async def run(self, _=-1) -> None:
|
|
952
973
|
self._logger.info("Running %s", self.__class__)
|
|
974
|
+
self.status.set(
|
|
975
|
+
feed=urlparse(str(self.config.get("FEED_URL", ""))).scheme or None,
|
|
976
|
+
decoder="dji",
|
|
977
|
+
)
|
|
978
|
+
self.status.write(force=True)
|
|
979
|
+
heartbeat = asyncio.ensure_future(self._heartbeat())
|
|
980
|
+
try:
|
|
981
|
+
while True:
|
|
982
|
+
received = await self.net_queue.get()
|
|
983
|
+
if not received:
|
|
984
|
+
continue
|
|
985
|
+
await self.handle_data(received)
|
|
986
|
+
finally:
|
|
987
|
+
heartbeat.cancel()
|
|
988
|
+
|
|
989
|
+
async def _heartbeat(self, interval: float = 5.0) -> None:
|
|
990
|
+
"""Keep DJI status fresh when the RF feed is connected but quiet."""
|
|
953
991
|
while True:
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
continue
|
|
957
|
-
await self.handle_data(received)
|
|
992
|
+
await asyncio.sleep(interval)
|
|
993
|
+
self.status.write(force=True)
|
|
958
994
|
|
|
959
995
|
|
|
960
996
|
class _DJIFeedWorker(pytak.QueueWorker):
|
|
@@ -209,6 +209,70 @@ class TestStatusHeartbeat:
|
|
|
209
209
|
assert doc["tracked"] == 0
|
|
210
210
|
|
|
211
211
|
|
|
212
|
+
@needs_statuswriter
|
|
213
|
+
class TestDJIStatusSurface:
|
|
214
|
+
"""AntSDR/DJI feeds use DJIWorker rather than RIDWorker."""
|
|
215
|
+
|
|
216
|
+
def _worker(self, tmp_path):
|
|
217
|
+
worker = classes.DJIWorker.__new__(classes.DJIWorker)
|
|
218
|
+
worker.config = {"FEED_URL": "tcp://172.31.100.2:52002"}
|
|
219
|
+
worker.net_queue = asyncio.Queue()
|
|
220
|
+
worker._logger = logging.getLogger("test")
|
|
221
|
+
worker.status = pytak.StatusWriter(
|
|
222
|
+
"dronecot-test", path=str(tmp_path / "status.json")
|
|
223
|
+
)
|
|
224
|
+
worker.put_queue = _noop_put
|
|
225
|
+
return worker
|
|
226
|
+
|
|
227
|
+
def _doc(self, worker):
|
|
228
|
+
with open(worker.status.path) as handle:
|
|
229
|
+
return json.load(handle)
|
|
230
|
+
|
|
231
|
+
def test_text_frame_counts_received_and_emitted(self, tmp_path, monkeypatch):
|
|
232
|
+
worker = self._worker(tmp_path)
|
|
233
|
+
monkeypatch.setattr(
|
|
234
|
+
classes.dronecot,
|
|
235
|
+
"dji_handle_text_line",
|
|
236
|
+
lambda data, config: [b"<event/>", b"<event/>"],
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
asyncio.run(worker.handle_data("dji_O,test"))
|
|
240
|
+
|
|
241
|
+
doc = self._doc(worker)
|
|
242
|
+
assert doc["counters"] == {"rx": 1, "emitted": 2}
|
|
243
|
+
assert doc["recent"][-1] == {
|
|
244
|
+
"t": doc["recent"][-1]["t"],
|
|
245
|
+
"source": "dji-text",
|
|
246
|
+
"placed": True,
|
|
247
|
+
"events": 2,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
def test_unrenderable_binary_frame_is_visible(self, tmp_path, monkeypatch):
|
|
251
|
+
worker = self._worker(tmp_path)
|
|
252
|
+
monkeypatch.setattr(
|
|
253
|
+
classes.dronecot, "dji_handle_frame", lambda data, config: []
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
asyncio.run(worker.handle_data(b"binary-frame"))
|
|
257
|
+
|
|
258
|
+
doc = self._doc(worker)
|
|
259
|
+
assert doc["counters"] == {"rx": 1, "no_cot": 1}
|
|
260
|
+
assert doc["recent"][-1]["source"] == "dji-binary"
|
|
261
|
+
assert doc["recent"][-1]["placed"] is False
|
|
262
|
+
|
|
263
|
+
def test_heartbeat_refreshes_a_quiet_dji_feed(self, tmp_path):
|
|
264
|
+
worker = self._worker(tmp_path)
|
|
265
|
+
|
|
266
|
+
async def _drive():
|
|
267
|
+
task = asyncio.ensure_future(worker._heartbeat(interval=0.01))
|
|
268
|
+
await asyncio.sleep(0.05)
|
|
269
|
+
task.cancel()
|
|
270
|
+
|
|
271
|
+
asyncio.run(_drive())
|
|
272
|
+
|
|
273
|
+
assert self._doc(worker)["counters"] == {}
|
|
274
|
+
|
|
275
|
+
|
|
212
276
|
class TestStatusDegradesVisibly:
|
|
213
277
|
"""A pytak without StatusWriter must not take the gateway down.
|
|
214
278
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2.3.3
|
|
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
|