diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..5ca7137 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: ampledata +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: ampledata +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: https://www.buymeacoffee.com/ampledata diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml new file mode 100644 index 0000000..64eb243 --- /dev/null +++ b/.github/workflows/debian.yml @@ -0,0 +1,49 @@ +name: Build Debian Package + +on: + push: + tags: + - '*' + +env: + DEB_BUILD_OPTIONS: nocheck + +jobs: + build-artifact: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@master + + - name: Install Debian Package Building Dependencies + run: sudo bash debian/install_pkg_build_deps.sh + + - name: Create Debian Package + run: make clean package + + - name: Upload Artifacts to GitHub + uses: actions/upload-artifact@master + with: + name: artifact-deb + path: deb_dist/*.deb + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Upload Release Asset to GitHub + id: upload-release-asset + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: deb_dist/*.deb + tag: ${{ github.ref }} + overwrite: true + file_glob: true \ No newline at end of file diff --git a/.github/workflows/python-publish_to_pypi.yml b/.github/workflows/python-publish_to_pypi.yml new file mode 100644 index 0000000..a068d8a --- /dev/null +++ b/.github/workflows/python-publish_to_pypi.yml @@ -0,0 +1,31 @@ +# This workflow will upload a Python Package using Twine when a release is created +name: Publish package to PyPI + +on: + push: + tags: + - '*' + +jobs: + deploy: + + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install setuptools wheel twine + - name: Build + run: | + python3 setup.py sdist bdist_wheel + - name: Publish package + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/python-test_and_lint.yml b/.github/workflows/python-test_and_lint.yml new file mode 100644 index 0000000..14b51c4 --- /dev/null +++ b/.github/workflows/python-test_and_lint.yml @@ -0,0 +1,39 @@ +name: Lint & Test Code + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + # python-version: [3.6, 3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install test requirements + run: | + make install_test_requirements + - name: Install package itself (editable) + run: | + make editable + - name: Lint with pylint + run: | + make pylint + - name: Lint with flake8 + run: | + make flake8 + - name: Test with pytest-cov + run: | + make test_cov diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e02d63a --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +.vscode/ +.devcontainer/ +*.deb +*.egg +*.egg-info/ +*.egg/ +*.ignore +*.py[co] +*.py[oc] +*.spl +*.vagrant +.DS_Store +.coverage +.eggs/ +.eggs/* +.idea +.idea/ +.pt +.vagrant/ +RELEASE-VERSION.txt +build/ +cover/ +dist/ +dump.rdb +flake8.log +local/ +local_* +metadata/ +nosetests.xml +output.xml +pylint.log +redis-server.log +redis-server/ +__pycache__ +known_craft.csv diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..e941dac --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,17 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +mkdocs: + configuration: mkdocs.yml + +python: + install: + - requirements: docs/requirements.txt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9d1ca23 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## DJICOT 1.0.0 + +- Initial release. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..52df472 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..645a28c --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.rst LICENSE requirements.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..982a4d8 --- /dev/null +++ b/Makefile @@ -0,0 +1,103 @@ +# Makefile from https://github.com/snstac/pytak +# PyTAK Makefile +# +# Copyright Sensors & Signals LLC https://www.snstac.com/ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +REPO_NAME ?= $(shell echo $(wildcard */__init__.py) | awk -F'/' '{print $$1}') +SHELL := /bin/bash +.DEFAULT_GOAL := editable +# postinst = $(wildcard debian/*.postinst.sh) +# service = $(wildcard debian/*.service) + +prepare: + mkdir -p build/ + +develop: + python3 setup.py develop + +editable: + python3 -m pip install -e . + +install_test_requirements: + python3 -m pip install -r requirements_test.txt + +install: + python3 setup.py install + +uninstall: + python3 -m pip uninstall -y $(REPO_NAME) + +reinstall: uninstall install + +publish: + python3 setup.py publish + +clean: + @rm -rf *.egg* build dist *.py[oc] */*.py[co] cover doctest_pypi.cfg \ + nosetests.xml pylint.log output.xml flake8.log tests.log \ + test-result.xml htmlcov fab.log .coverage __pycache__ \ + */__pycache__ deb_dist .mypy_cache + +pep8: + flake8 --max-line-length=88 --extend-ignore=E203 --exit-zero $(REPO_NAME)/*.py + +flake8: pep8 + +lint: + pylint --msg-template="{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" \ + --max-line-length=88 -r n $(REPO_NAME)/*.py || exit 0 + +pylint: lint + +checkmetadata: + python3 setup.py check -s --restructuredtext + +mypy: + mypy --strict . + +pytest: + pytest + +test: editable install_test_requirements pytest + +test_cov: + pytest --cov=$(REPO_NAME) --cov-report term-missing + +black: + black . + +mkdocs: + pip install -r docs/requirements.txt + mkdocs serve + +deb_dist: + python3 setup.py --command-packages=stdeb.command sdist_dsc + +deb_custom: + cp debian/$(REPO_NAME).conf $(wildcard deb_dist/*/debian)/$(REPO_NAME).default + cp debian/$(REPO_NAME).postinst $(wildcard deb_dist/*/debian)/$(REPO_NAME).postinst + cp debian/$(REPO_NAME).service $(wildcard deb_dist/*/debian)/$(REPO_NAME).service + +bdist_deb: deb_dist deb_custom + cd deb_dist/$(REPO_NAME)-*/ && dpkg-buildpackage -rfakeroot -uc -us + +faux_latest: + cp deb_dist/$(REPO_NAME)_*-1_all.deb deb_dist/$(REPO_NAME)_latest_all.deb + cp deb_dist/$(REPO_NAME)_*-1_all.deb deb_dist/python3-$(REPO_NAME)_latest_all.deb + +package: bdist_deb faux_latest + +extract: + dpkg-deb -e $(wildcard deb_dist/*latest_all.deb) deb_dist/extract + dpkg-deb -x $(wildcard deb_dist/*latest_all.deb) deb_dist/extract diff --git a/README.md b/README.md new file mode 100644 index 0000000..8444b65 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Display DJI Drones in TAK + +DJICOT is software for monitoring and analyzing over-the-air DJI drone data via the Team Awareness Kit (TAK) ecosystem of products. + +Requires [ANTSDR DJI DroneID Firmware](https://github.com/alphafox02/antsdr_dji_droneid/tree/main) + +[Documentation is available here.](https://djicot.rtfd.io) + +## Copyright & License + +Copyright Sensors & Signals LLC https://www.snstac.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/debian/djicot.conf b/debian/djicot.conf new file mode 100644 index 0000000..71a6e0e --- /dev/null +++ b/debian/djicot.conf @@ -0,0 +1,74 @@ +# djicot.conf from https://github.com/snstac/djicot +# +# Debian specific runtime configuration for DJICOT. +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# DJICOT Configuration: https://djicot.rtfd.io/ +# Change values here, and restart with: sudo systemctl restart djicot + +# Enable or Disable the DJICOT software daemon. +# ENABLED=1 + +# URL to CoT destination. Must be a URL, e.g. tcp://1.2.3.4:1234 or tls://...:1234, etc. +# COT_URL=udp+wo://239.2.3.1:6969 + +# CoT Stale period ("timeout"), in seconds. +# COT_STALE=3600 + +# Override COT Event Type ("marker type"). +# COT_TYPE=a-u-S-X-M + +# Set URL of feed. +# FEED_URL=tcp://192.168.1.10:41030 + + +# PyTAK Configuration: https://pytak.rtfd.io/ +# Change values here, and restart with: sudo systemctl restart aiscot + +# Sets TAK Protocol to use for CoT output. +# TAK_PROTO=0 + +# Sets debug-level logging. Any value other than 0 is considered True. False if unset. +# DEBUG= + +# TAK Data Packages containing TAK Server connection settings, TLS certificates, etc. +# PREF_PACKAGE= + +# Path to a file containing the unencrypted plain-text PEM format Client Certificate. +# This file can contain both the Client Cert & Client Key, or the Client Cert alone. +# In the later case (cert alone), PYTAK_TLS_CLIENT_KEY must be set to the Client Key. +# PYTAK_TLS_CLIENT_CERT= + +# Path to a file containing the unencrypted plain-text PEM format Client Private Key +# for the associated PYTAK_TLS_CLIENT_CERT. +# PYTAK_TLS_CLIENT_KEY= + +# Password for password protected certificates or password protected Private Keys. +# PYTAK_TLS_CLIENT_PASSWORD= + +# Disable destination TLS Certificate Verification. +# PYTAK_TLS_DONT_VERIFY= + +# Disable destination TLS Certificate Common Name (CN) Verification. +# PYTAK_TLS_DONT_CHECK_HOSTNAME= + +# File containing the CA Trust Store to use for remote certificate verification. +# PYTAK_TLS_CLIENT_CAFILE= + +# Expected hostname or CN of the connected server. Not used unless verifying hostname. +# PYTAK_TLS_SERVER_EXPECTED_HOSTNAME= + diff --git a/debian/djicot.postinst b/debian/djicot.postinst new file mode 100644 index 0000000..804a365 --- /dev/null +++ b/debian/djicot.postinst @@ -0,0 +1,83 @@ +#!/bin/bash +# djicot.postinst from https://github.com/snstac/djicot +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +echo "djicot Post Install" + +# shellcheck source=djicot.conf +[ -f "/etc/default/djicot" ] && . /etc/default/djicot + +# Sane defaults: +[ -z "$SERVER_HOME" ] && SERVER_HOME=/run/djicot +[ -z "$SERVER_USER" ] && SERVER_USER=djicot +[ -z "$SERVER_NAME" ] && SERVER_NAME="DJICOT System User" +[ -z "$SERVER_GROUP" ] && SERVER_GROUP=djicot + +# Groups that the user will be added to, if undefined, then none. +ADDGROUP="" + +# create user to avoid running server as root +# 1. create group if not existing +if ! getent group | grep -q "^$SERVER_GROUP:" ; then + echo -n "Adding group $SERVER_GROUP.." + addgroup --quiet --system "$SERVER_GROUP" 2>/dev/null ||true + echo "..done" +fi +# 2. create homedir if not existing +test -d "$SERVER_HOME" || mkdir "$SERVER_HOME" +# 3. create user if not existing +if ! getent passwd | grep -q "^$SERVER_USER:"; then + echo -n "Adding system user $SERVER_USER.." + adduser --quiet \ + --system \ + --ingroup "$SERVER_GROUP" \ + --no-create-home \ + --disabled-password \ + "$SERVER_USER" 2>/dev/null || true + echo "..done" +fi +# 4. adjust passwd entry +usermod -c "$SERVER_NAME" \ + -d "$SERVER_HOME" \ + -g "$SERVER_GROUP" \ + "$SERVER_USER" +# 5. adjust file and directory permissions +if ! dpkg-statoverride --list "$SERVER_HOME" >/dev/null +then + chown -R "$SERVER_USER":adm "$SERVER_HOME" + chmod u=rwx,g=rxs,o= "$SERVER_HOME" +fi +# 6. Add the user to the ADDGROUP group +if test -n "$ADDGROUP" +then + if ! groups "$SERVER_USER" | cut -d: -f2 | \ + grep -qw "$ADDGROUP"; then + adduser "$SERVER_USER" "$ADDGROUP" + fi +fi + +systemctl enable djicot.service + +echo "---- djicot Install Complete ----" +echo "" +echo "Documentation: https://djicot.rtfd.io/" +echo "Configure: sudo nano /etc/default/djicot" +echo "Start: sudo systemctl start djicot.service" +echo "Logs: sudo journalctl -fu djicot" +echo "" + +exit 0 diff --git a/debian/djicot.service b/debian/djicot.service new file mode 100644 index 0000000..f5884cd --- /dev/null +++ b/debian/djicot.service @@ -0,0 +1,39 @@ +# djicot.service from https://github.com/snstac/djicot +# +# djicot service for systemd +# +# Copyright Sensors & Signals LLC https://www.snstac.com/ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +[Unit] +Description=DJICOT: Display DJI Drones in TAK +Documentation=https://github.com/snstac/djicot +Wants=network.target +After=network.target + +[Service] +User=djicot +ExecStart=/usr/bin/djicot +RuntimeDirectory=djicot +SyslogIdentifier=djicot +EnvironmentFile=/etc/default/djicot +Type=simple +Restart=always +RestartSec=20 +StartLimitInterval=1 +StartLimitBurst=100 +RestartPreventExitStatus=64 +Nice=-1 + +[Install] +WantedBy=default.target diff --git a/debian/install_pkg_build_deps.sh b/debian/install_pkg_build_deps.sh new file mode 100644 index 0000000..6713a78 --- /dev/null +++ b/debian/install_pkg_build_deps.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +echo "Installing Debian package build dependencies" + +apt-get update -qq + +apt-get install -y \ + python3 python3-dev python3-pip python3-venv python3-all \ + dh-python debhelper devscripts dput software-properties-common \ + python3-distutils python3-setuptools python3-wheel python3-stdeb diff --git a/djicot/__init__.py b/djicot/__init__.py new file mode 100644 index 0000000..b1fa498 --- /dev/null +++ b/djicot/__init__.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""DJI to TAK Gateway.""" + +__version__ = "1.0.0" + +from .constants import DEFAULT_FEED_URL # NOQA + +from .functions import dji_to_cot, create_tasks # NOQA + +from .classes import DJIWorker, NetWorker # NOQA diff --git a/djicot/classes.py b/djicot/classes.py new file mode 100644 index 0000000..792f730 --- /dev/null +++ b/djicot/classes.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""DJICOT Class Definitions.""" + +import asyncio + +from typing import Optional +from urllib.parse import urlparse, ParseResult + +import pytak +import djicot + + +class DJIWorker(pytak.QueueWorker): + """Read DJI data from inputs, serialize to CoT, and put on TX queue.""" + + def __init__( + self, + tx_queue: asyncio.Queue, + config: dict, + net_queue: asyncio.Queue, + ) -> None: + """Initialize the DJIWorker.""" + super().__init__(tx_queue, config) + self.net_queue = net_queue + + async def handle_data(self, data) -> None: + """Handle Data from ADS-B receiver: Render to CoT, put on TX queue.""" + event: Optional[bytes] = djicot.dji_to_cot(data, self.config) + await self.put_queue(event) + + async def run(self, _=-1) -> None: + """Run this Thread, Reads from Pollers.""" + + self._logger.info("Running %s", self.__class__) + + while 1: + received = await self.net_queue.get() + if not received: + continue + await self.handle_data(received) + + +class NetWorker(pytak.QueueWorker): # pylint: disable=too-few-public-methods + """Read DJI Data from network and puts on queue.""" + + async def handle_data(self, data) -> None: + """Handle Data from network.""" + self.queue.put_nowait(data) + + async def run(self, _=-1) -> None: + """Run the main process loop.""" + url: ParseResult = urlparse( + self.config.get("FEED_URL", djicot.DEFAULT_FEED_URL) + ) + + self._logger.info("Running %s for %s", self.__class__, url.geturl()) + + host, port = url.netloc.split(":") + + self._logger.debug("host=%s port=%s", host, port) + + reader, _ = await asyncio.open_connection(host, port) + + while 1: + received = await reader.read(1024) + await self.handle_data(received) diff --git a/djicot/commands.py b/djicot/commands.py new file mode 100644 index 0000000..742ff70 --- /dev/null +++ b/djicot/commands.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""PyTAK Command Line.""" + +import pytak + + +def main() -> None: + """Boilerplate main func.""" + # PyTAK CLI tool boilerplate: + pytak.cli(__name__.split(".", maxsplit=1)[0]) + + +if __name__ == "__main__": + main() diff --git a/djicot/constants.py b/djicot/constants.py new file mode 100644 index 0000000..574527a --- /dev/null +++ b/djicot/constants.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""DJICOT Constants.""" + +# Feed URL to use out of the box, in this case the JSON from the local filesystem. +DEFAULT_FEED_URL: str = "tcp://192.168.1.10:41030" diff --git a/djicot/functions.py b/djicot/functions.py new file mode 100644 index 0000000..74f49f8 --- /dev/null +++ b/djicot/functions.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""DJICOT Functions.""" + +import asyncio +import struct +import xml.etree.ElementTree as ET + +from configparser import SectionProxy +from typing import Optional, Set, Union + +import pytak +import djicot + + +APP_NAME = "djicot" + + +def create_tasks(config: SectionProxy, clitool: pytak.CLITool) -> Set[pytak.Worker,]: + """Create specific coroutine task set for this application. + + Parameters + ---------- + config : `SectionProxy` + Configuration options & values. + clitool : `pytak.CLITool` + A PyTAK Worker class instance. + + Returns + ------- + `set` + Set of PyTAK Worker classes for this application. + """ + tasks = set() + + net_queue: asyncio.Queue = asyncio.Queue() + + tasks.add(djicot.DJIWorker(clitool.tx_queue, config, net_queue)) + tasks.add(djicot.NetWorker(net_queue, config)) + + return tasks + + +def parse_frame(frame): + frame_header = frame[:2] + package_type = frame[2] + length_bytes = frame[3:5] + package_length = struct.unpack("H", length_bytes)[0] + data = frame[5 : 5 + package_length - 5] + return package_type, data + + +def parse_data(data): + payload = { + "serial_number": None, + "device_type": None, + "device_type_8": None, + "op_lat": None, + "op_lon": None, + "uas_lat": None, + "uas_lon": None, + "height": None, + "altitude": None, + "home_lat": None, + "home_lon": None, + "freq": None, + "speed_e": None, + "speed_n": None, + "speed_u": None, + "rssi": None, + } + + try: + payload = { + "serial_number": data[:64].decode("utf-8").rstrip("\x00"), + "device_type": data[64:128].decode("utf-8").rstrip("\x00"), + "device_type_8": data[128], + "op_lat": struct.unpack("d", data[129:137])[0], + "op_lon": struct.unpack("d", data[137:145])[0], + "uas_lat": struct.unpack("d", data[145:153])[0], + "uas_lon": struct.unpack("d", data[153:161])[0], + "height": struct.unpack("d", data[161:169])[0], + "altitude": struct.unpack("d", data[169:177])[0], + "home_lat": struct.unpack("d", data[177:185])[0], + "home_lon": struct.unpack("d", data[185:193])[0], + "freq": struct.unpack("d", data[193:201])[0], + "speed_e": struct.unpack("d", data[201:209])[0], + "speed_n": struct.unpack("d", data[209:217])[0], + "speed_u": struct.unpack("d", data[217:225])[0], + "rssi": struct.unpack("h", data[225:227])[0], + } + except UnicodeDecodeError: + # If we fail to decode, it may indicate encrypted or partial data + payload = { + "device_type": "Got a DJI drone with encryption", + "device_type_8": 255, + } + + return payload + + +def dji_to_cot_xml( # NOQA pylint: disable=too-many-locals,too-many-branches,too-many-statements + data, config: Union[SectionProxy, dict, None] = None +) -> Optional[ET.Element]: + """ + Serialize DJI data as Cursor on Target. + + Returns + ------- + `xml.etree.ElementTree.Element` + Cursor-On-Target XML ElementTree object. + """ + package_type, _data = parse_frame(data) + if package_type != 0x01: + return None + + parsed_data = parse_data(_data) + print(parsed_data) + + # Extract relevant info for CoT + lat = parsed_data.get("uas_lat", 0.0) + lon = parsed_data.get("uas_lon", 0.0) + uas_sn = parsed_data.get("serial_number", "") + uas_type = parsed_data.get("device_type", "") + + if lat is None or lon is None: + return None + + config = config or {} + + remarks_fields: list = [] + + cot_type = "a-u-G" + cot_stale: int = int(config.get("COT_STALE", pytak.DEFAULT_COT_STALE)) + cot_host_id: str = config.get("COT_HOST_ID", pytak.DEFAULT_HOST_ID) + + cuas = ET.Element("__cuas") + cuas.set("cot_host_id", cot_host_id) + + cot_uid = f"DJI.{uas_sn}.uas" + callsign = f"DJI-{uas_sn}" + + contact: ET.Element = ET.Element("contact") + contact.set("callsign", callsign) + + track: ET.Element = ET.Element("track") + track.set("course", parsed_data.get("course_point", "9999999.0")) + track.set("speed", parsed_data.get("speed_point", "9999999.0")) + + detail = ET.Element("detail") + + # Remarks should always be the first sub-entity within the Detail entity. + remarks = ET.Element("remarks") + remarks_fields.append(f"Serial Number: {uas_sn}") + remarks_fields.append(f"Type: {uas_type}") + remarks_fields.append(f"Freq: {parsed_data.get('freq', 0.0)}") + remarks_fields.append(f"RSSI: {parsed_data.get('RSSI', 0)}") + remarks_fields.append(f"Speed: {parsed_data.get('speed_E', 0.0)}") + remarks_fields.append(f"{cot_host_id}") + _remarks = " ".join(list(filter(None, remarks_fields))) + remarks.text = _remarks + detail.append(remarks) + + detail.append(contact) + detail.append(track) + detail.append(cuas) + + cot_d = { + "lat": str(lat), + "lon": str(lon), + "ce": str(parsed_data.get("nac_p", "9999999.0")), + "le": str(parsed_data.get("nac_v", "9999999.0")), + "hae": str(parsed_data.get("alt_geom", "9999999.0")), + "uid": cot_uid, + "cot_type": cot_type, + "stale": cot_stale, + } + cot = pytak.gen_cot_xml(**cot_d) + cot.set("access", config.get("COT_ACCESS", pytak.DEFAULT_COT_ACCESS)) + + _detail = cot.findall("detail")[0] + flowtags = _detail.findall("_flow-tags_") + detail.extend(flowtags) + cot.remove(_detail) + cot.append(detail) + + return cot + + +def dji_to_cot( + craft: dict, + config: Union[SectionProxy, dict, None] = None, +) -> Optional[bytes]: + """Return CoT XML object as an XML string.""" + cot: Optional[ET.Element] = dji_to_cot_xml(craft, config) + return ( + b"\n".join([pytak.DEFAULT_XML_DECLARATION, ET.tostring(cot)]) if cot else None + ) diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..f4d16fd --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1 @@ +{!CHANGELOG.md!} diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..2b1ba7b --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,18 @@ +DJICOT's configuration parameters can be set two ways: + +1. In an INI-style configuration file. (ex. ``djicot -c config.ini``) +2. As environment variables. (ex. ``export DEBUG=1;djicot``) + +DJICOT has the following built-in configuration parameters: + +* **`FEED_URL`**: + * Default: ``tcp://192.168.1.10:41030`` + + DJI data source URL. + +Additional configuration parameters, including TAK Server configuration, are included in the [PyTAK Configuration](https://pytak.readthedocs.io/en/latest/configuration/) documentation. + + + + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..10882f6 --- /dev/null +++ b/docs/index.md @@ -0,0 +1 @@ +{!README.md!} \ No newline at end of file diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..d4b1eab --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,33 @@ +DJICOT's functionality provided by a command-line program called `djicot`. + +There are several methods of installing DJICOT. They are listed below, in order of complexity. + +## Debian, Ubuntu, Raspberry Pi + +Install DJICOT, and prerequisite package [PyTAK](https://pytak.rtfd.io) + +```sh linenums="1" +sudo apt update -qq +wget https://github.com/snstac/aircot/releases/latest/download/aircot_latest_all.deb +sudo apt install -f ./pytak_latest_all.deb +wget https://github.com/snstac/djicot/releases/latest/download/djicot_latest_all.deb +sudo apt install -f ./djicot_latest_all.deb +``` + +## Windows, Linux + +Install from the Python Package Index (PyPI) [Advanced Users]:: + +```sh +sudo python3 -m pip install djicot +``` + +## Developers + +PRs welcome! + +```sh linenums="1" +git clone https://github.com/snstac/djicot.git +cd djicot/ +python3 setup.py install +``` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..b00f781 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,23 @@ +click +ghp-import +griffe +importlib-metadata +jinja2 +markdown +markdown-include +markupsafe +mergedeep +mkdocs +mkdocs-autorefs +mkdocs-include-markdown-plugin +mkdocs-material +mkdocstrings-python +mkdocstrings[python] +packaging +pymdown-extensions +pyparsing +python-dateutil +pyyaml +pyyaml-env-tag +six +zipp diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..344108d --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,26 @@ + +To report bugs, please set the DEBUG=1 environment variable to collect logs: + +```sh +DEBUG=1 djicot +``` + +Or: + +```sh linenums="1" +export DEBUG=1 +djicot +``` + +Or: + +```sh linenums="1" +echo 'DEBUG=1' >> djicot.ini +djicot -c djicot.ini +``` + +You can view systemd/systemctl/service logs via: + +```journalctl -fu djicot``` + +Please use GitHub issues for support requests. Please note that DJICOT is free open source software and comes with no warranty. See LICENSE. \ No newline at end of file diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..1776566 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,46 @@ +## Command-line + +Command-line usage is available by running ``djicot -h``. + +``` +usage: djicot [-h] [-c CONFIG_FILE] [-p PREF_PACKAGE] + +options: + -h, --help show this help message and exit + -c CONFIG_FILE, --CONFIG_FILE CONFIG_FILE + Optional configuration file. Default: config.ini + -p PREF_PACKAGE, --PREF_PACKAGE PREF_PACKAGE + Optional connection preferences package zip file (aka data package). +``` + +## Run as a service / Run forever + +1. Add the text contents below a file named `/etc/systemd/system/djicot.service` + You can use `nano` or `vi` editors: `sudo nano /etc/systemd/system/djicot.service` +2. Reload systemctl: `sudo systemctl daemon-reload` +3. Enable DJICOT: `sudo systemctl enable djicot` +4. Start DJICOT: `sudo systemctl start djicot` + +### `djicot.service` Content +```ini +[Unit] +Description=DJICOT - Display DJI drones in TAK +Documentation=https://djicot.rtfd.io +Wants=network.target +After=network.target + +[Service] +RuntimeDirectoryMode=0755 +ExecStart=/usr/local/bin/djicot -c /etc/djicot.ini +SyslogIdentifier=djicot +Type=simple +Restart=always +RestartSec=30 +RestartPreventExitStatus=64 +Nice=-5 + +[Install] +WantedBy=default.target +``` + +> Pay special attention to the `ExecStart` line above. You'll need to provide the full local filesystem path to both your djicot executable & djicot configuration files. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..72ac3a1 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,40 @@ +site_name: Display DJI Drones in TAK +site_url: https://djicot.rtfd.io/ +repo_url: https://github.com/snstac/djicot/ +site_description: Software for monitoring and analyzing over-the-air DJI drone data via the Team Awareness Kit (TAK) ecosystem of products. +site_author: Greg Albrecht +copyright: Copyright Sensors & Signals LLC https://www.snstac.com + +theme: + name: material + highlightjs: true + features: + - content.code.copy + - content.code.select + - content.code.annotate + +plugins: + - include-markdown: + opening_tag: "{!" + closing_tag: "!}" + - search + - mkdocstrings: + handlers: + # See: https://mkdocstrings.github.io/python/usage/ + python: + options: + docstring_style: sphinx + +markdown_extensions: + - markdown_include.include: + base_path: . + - admonition + - toc: + permalink: True + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences \ No newline at end of file diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..9120a8a --- /dev/null +++ b/requirements_test.txt @@ -0,0 +1,5 @@ +pytest-asyncio +pytest-cov +pylint +flake8 +black diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..39e61f9 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,106 @@ +# Setup configuration for DJICOT: ADS-B to TAK Gateway +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +[metadata] +name = djicot +version = attr: djicot.__version__ +url = https://github.com/snstac/djicot +project_urls = + CI: GitHub Actions = https://github.com/snstac/djicot/actions + GitHub: issues = https://github.com/snstac/djicot/issues + GitHub: repo = https://github.com/snstac/djicot +description = Monitor and analyze aviation surveillance data with TAK. +long_description = file: README.md +long_description_content_type = text/markdown +maintainer = Greg Albrecht +maintainer_email = oss@undef.net +license = Apache 2.0 +license_files = LICENSE +classifiers = + License :: OSI Approved :: Apache Software License + + Intended Audience :: Developers + + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + # Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 + + Development Status :: 5 - Production/Stable + + Operating System :: POSIX + Operating System :: MacOS :: MacOS X + Operating System :: Microsoft :: Windows + Operating System :: OS Independent +keywords = + Cursor on Target + CoT + ATAK + TAK + WinTAK + TAK + TAK Server + ADS-B + ADSB + Aircraft + + +[options.entry_points] +console_scripts = + djicot = djicot.commands:main + + +[options] +packages = djicot +package_dir = + djicot = djicot +python_requires = >=3.6, <4 +install_requires = + aircot >= 1.2.0 + pytak >= 5.4.0 + aiohttp < 4.0.0 + + +[options.extras_require] +with_pymodes = pymodes >= 2.8 +with_takproto = takproto >= 2.0.0 +with_asyncinotify = asyncinotify +test = + pytest-asyncio + pytest-cov + pylint + flake8 + black +[isort] +profile = black + +[flake8] +max-line-length = 88 +extend-ignore = E203, E704 + +[pylint] +max-line-length = 88 + +[pycodestyle] +ignore = E203 +max_line_length = 88 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..cf04f3a --- /dev/null +++ b/setup.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright Sensors & Signals LLC https://www.snstac.com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""Setup for DJICOT.""" + +from setuptools import setup + +if __name__ == "__main__": + setup() diff --git a/stdeb.cfg b/stdeb.cfg new file mode 100644 index 0000000..9aadaf3 --- /dev/null +++ b/stdeb.cfg @@ -0,0 +1,4 @@ +[DEFAULT] +Package3: djicot +Replaces3: python3-djicot +Depends3: pytak \ No newline at end of file diff --git a/tests/test_functions.py b/tests/test_functions.py new file mode 100644 index 0000000..f1890fc --- /dev/null +++ b/tests/test_functions.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright Sensors & Signals LLC https://www.snstac.com/ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +"""DJICOT Function Tests.""" + +import struct +import unittest + +import djicot + + +class FunctionsTestCase(unittest.TestCase): + """ + Test class for functions... functions. + """ + + def test_parse_data(self): + """Test parse_data function with valid data.""" + + data = bytearray(227) + data[:64] = b"serial_number".ljust(64, b"\x00") + data[64:128] = b"device_type".ljust(64, b"\x00") + data[128] = 8 + data[129:137] = struct.pack("d", 37.7749) # app_lat + data[137:145] = struct.pack("d", -122.4194) # app_lon + data[145:153] = struct.pack("d", 37.7749) # uas_lat + data[153:161] = struct.pack("d", -122.4194) # uas_lon + data[161:169] = struct.pack("d", 100.0) # height + data[169:177] = struct.pack("d", 200.0) # altitude + data[177:185] = struct.pack("d", 37.7749) # home_lat + data[185:193] = struct.pack("d", -122.4194) # home_lon + data[193:201] = struct.pack("d", 2.4) # freq + data[201:209] = struct.pack("d", 10.0) # speed_e + data[209:217] = struct.pack("d", 5.0) # speed_n + data[217:225] = struct.pack("d", 1.0) # speed_u + data[225:227] = struct.pack("h", -50) # rssi + + parsed_data = djicot.functions.parse_data(data) + print("parsed_data") + print(parsed_data) + assert parsed_data.get("serial_number") == "serial_number" + assert parsed_data.get("device_type") == "device_type" + assert parsed_data.get("device_type_8") == 8 + assert parsed_data.get("op_lat") == 37.7749 + assert parsed_data.get("op_lon") == -122.4194 + assert parsed_data.get("uas_lat") == 37.7749 + assert parsed_data.get("uas_lon") == -122.4194 + assert parsed_data.get("height") == 100.0 + assert parsed_data.get("altitude") == 200.0 + assert parsed_data.get("home_lat") == 37.7749 + assert parsed_data.get("home_lon") == -122.4194 + assert parsed_data.get("freq") == 2.4 + assert parsed_data.get("speed_e") == 10.0 + assert parsed_data.get("speed_n") == 5.0 + assert parsed_data.get("speed_u") == 1.0 + assert parsed_data.get("rssi") == -50 + + def _test_parse_data_with_invalid_data(self): + """Test parse_data function with invalid data.""" + data = b"invalid_data" + parsed_data = djicot.functions.parse_data(data) + assert parsed_data["device_type"] == "Got a DJI drone with encryption" + assert parsed_data["device_type_8"] == 255 + + def test_parse_frame(self): + """Test parse_frame function with valid frame data.""" + frame = bytearray(10) + frame[:2] = b"\x01\x02" # frame_header + frame[2] = 0x03 # package_type + frame[3:5] = struct.pack("H", 10) # package_length + frame[5:10] = b"hello" # data + + package_type, data = djicot.functions.parse_frame(frame) + assert package_type == 0x03 + assert data == b"hello" + + def test_parse_frame_with_invalid_length(self): + """Test parse_frame function with invalid length.""" + frame = bytearray(8) + frame[:2] = b"\x01\x02" # frame_header + frame[2] = 0x03 # package_type + frame[3:5] = struct.pack("H", 10) # package_length + frame[5:8] = b"hel" # data + + package_type, data = djicot.functions.parse_frame(frame) + assert package_type == 0x03 + assert data == b"hel" + + +if __name__ == "__main__": + unittest.main()