kopia lustrzana https://github.com/projecthorus/horusdemodlib
Porównaj commity
309 Commity
Autor | SHA1 | Data |
---|---|---|
Mark Jessop | ea608f4ba0 | |
Mark Jessop | 5366134aac | |
Mark Jessop | 8fde8b9ad2 | |
Mark Jessop | 4439fbc430 | |
Mark Jessop | 3660d0cc7c | |
Mark Jessop | 60b88fd495 | |
Mark Jessop | 9df66628a4 | |
Mark Jessop | 7f098068bb | |
Mark Jessop | 744e0e7071 | |
Mark Jessop | f094c0c0ce | |
Mark Jessop | d00d984e78 | |
Mark Jessop | eb911c8c0e | |
Mark Jessop | df1b81fc41 | |
Mark Jessop | 24f34fe6cd | |
Mark Jessop | fa959febea | |
Mark Jessop | 2aa0dda4bb | |
Mark Jessop | 0956b08d36 | |
Mark Jessop | 1265b069eb | |
Mark Jessop | fdae85612c | |
Mark Jessop | d3e8e903c9 | |
Mark Jessop | 3311211ef6 | |
sp6qkm | 1e2ef897c7 | |
Mark Jessop | afd180be66 | |
Mark Jessop | 7ab762727e | |
Mark Jessop | 5f7475b56d | |
Mark Jessop | d4cbfa4998 | |
Mark Jessop | 202454bf44 | |
Mark Jessop | dd40912fc8 | |
Mark Jessop | c85f8d3260 | |
Mark Jessop | 61aa309389 | |
Mark Jessop | bb75540904 | |
Mark Jessop | 02058a6d38 | |
Mark Jessop | b9d370df7e | |
Mark Jessop | bc67f77121 | |
Mark Jessop | 03b7abc6c7 | |
Mark Jessop | 0445e2d569 | |
Mark Jessop | 189a0b23e5 | |
Mark Jessop | b0a7df7336 | |
Mark Jessop | 64b477f17a | |
Mark Jessop | b26ab6ab14 | |
Mark Jessop | 6b28a30bbe | |
Mark Jessop | 6b7a6bf21f | |
Mark Jessop | 5818abc1fc | |
Mark Jessop | d6ba4e95f9 | |
Mark Jessop | 256228b107 | |
Mark Jessop | c9b3104cd0 | |
Mark Jessop | da46126062 | |
sp6qkm | f70d7bbb79 | |
Mark Jessop | 997261377c | |
Mark Jessop | a88afd4f4e | |
Mark Jessop | 562525088c | |
Mark Jessop | a916fab44d | |
Mark Jessop | 794a2c1e5a | |
Mark Jessop | cfef63cb29 | |
Mark Jessop | a63f1c9488 | |
Mark Jessop | 367fc797c7 | |
Mark Jessop | 5f63666f5f | |
Mark Jessop | 80121545f3 | |
xssfox | d38e55de42 | |
Mark Jessop | 0e3df15067 | |
Mark Jessop | aa0ebdf6d1 | |
Mark Jessop | 18726f6d5f | |
Mark Jessop | fd6adaa7cb | |
Mark Jessop | 7790a7dd66 | |
Mark Jessop | ff75d157db | |
Mark Jessop | 1494277fb9 | |
Mark Jessop | 62c094e697 | |
Mark Jessop | 1a7e30cd16 | |
Mark Jessop | 384a6ebc5e | |
Mark Jessop | 44ea165663 | |
Mark Jessop | 9ab5b88786 | |
sp6qkm | 7d95fa890f | |
Mark Jessop | d982fad0e7 | |
Mark Jessop | 36a938f68a | |
Mark Jessop | ee97011d95 | |
sp6qkm | 3fbe8de5b9 | |
Mark Jessop | 6d04e36909 | |
Mark Jessop | 390c73cafa | |
Mark Jessop | f79c810a22 | |
Mark Jessop | a5c22243e9 | |
Mark Jessop | 68f03a1eab | |
Mark Jessop | 38b394ee17 | |
Mark Jessop | 681ff9e554 | |
Mark Jessop | 0fc090c757 | |
Mark Jessop | 05b9e90650 | |
Mark Jessop | ea04c308cd | |
Mark Jessop | d1eec81173 | |
Mark Jessop | 804a42d9f9 | |
Mark Jessop | de74cc4a1a | |
Mark Jessop | f7d5d54bf6 | |
Mark Jessop | 197b481914 | |
Mark Jessop | 7ba9d50946 | |
Mark Jessop | 0a7ef43e1b | |
Mark Jessop | e0f140eae7 | |
Mark Jessop | a096a1af9a | |
Mark Jessop | e0c584f616 | |
Mark Jessop | 46835b3d55 | |
cixio | 4abbcbdfb9 | |
Mark Jessop | 71981aed87 | |
Mark Jessop | 82dcae13b4 | |
Mark Jessop | ac2efeada7 | |
Mark Jessop | fb694d78ce | |
Mark Jessop | 78b5847da8 | |
Mark Jessop | b67e8ee46b | |
Mark Jessop | 8278436333 | |
Jade M7NGO | 7295d8bc53 | |
Mark Jessop | 680acafdfb | |
Mark Jessop | c94a9f758d | |
Mark Jessop | 9548a23d07 | |
Mark Jessop | 4b57a65385 | |
Mark Jessop | 67f7626ab8 | |
Mark Jessop | 3198663109 | |
Mark Jessop | 28904aa5b4 | |
Mark Jessop | 2c7c7c4251 | |
Mark Jessop | 158b2e3354 | |
Mark Jessop | 4fbc574979 | |
Mark Jessop | e02e38e8d4 | |
Mark Jessop | 948e97b3eb | |
Mark Jessop | 5f2d8416f1 | |
Mark Jessop | 6768c53397 | |
Mark Jessop | c8ce3cc6aa | |
Mark Jessop | 6dda766205 | |
Mark Jessop | 6faffa18aa | |
Mark Jessop | c7c5a6eb5d | |
Mark Jessop | dad39621f0 | |
Mark Jessop | b1459c68c8 | |
Mark Jessop | 40559e1d62 | |
Mark Jessop | 7982bda6e7 | |
Mark Jessop | ce137dba70 | |
Mark Jessop | 10a7e90382 | |
Mark Jessop | e0e3203280 | |
Mark Jessop | 88a04ea691 | |
Mark Jessop | 7574b10d59 | |
Mark Jessop | 8c6bdd29a2 | |
Mark Jessop | d1cd35e36d | |
Mark Jessop | 1759df560c | |
Mark Jessop | 652300bb24 | |
Mark Jessop | 72e7e4923a | |
Mark Jessop | 4a1597cda6 | |
Mark Jessop | 6258bcc33e | |
Mark Jessop | c8e83cf998 | |
Mark Jessop | 6a3718a715 | |
Mark Jessop | 9fb4baeb4b | |
Mark Jessop | 333cc8f58e | |
Mark Jessop | 7abf447200 | |
Mark Jessop | fe9ca5fbcb | |
Mark Jessop | 8f322bee50 | |
Mark Jessop | 7f881692d7 | |
Mark Jessop | cea3b37230 | |
Mark Jessop | 28f35909b2 | |
Mark Jessop | 956608e76e | |
Mark Jessop | f069764fc6 | |
Mark Jessop | 43abc6d08f | |
Mark Jessop | 1bc30e2310 | |
Mark Jessop | 503f392002 | |
Mark Jessop | 34d90a6df5 | |
Mikael Nousiainen | 5ed247dc30 | |
Mark Jessop | 138449d7aa | |
Mark Jessop | c944da0bf2 | |
Mark Jessop | e5842a6710 | |
sp6qkm | 712bbdd27b | |
Mark Jessop | 6dfa34cac8 | |
Mark Jessop | dd964915ca | |
Mark Jessop | 8dc2624d19 | |
Daniel Ekman | a6f5ade965 | |
Daniel Ekman | 1ffb9a9726 | |
Mark Jessop | 50a7d976b6 | |
Mark Jessop | e4f7a45d24 | |
Mark Jessop | 753eaadf66 | |
Mark Jessop | 1d573fde9b | |
Mark Jessop | be5654ccea | |
Mark Jessop | 020fdd9af2 | |
Mark Jessop | 642c5b8a53 | |
Mark Jessop | 7816115c63 | |
Mark Jessop | a59ce1d6ac | |
Mark Jessop | 566c498a7b | |
Mark Jessop | 61bb55bb12 | |
Mark Jessop | c8c1a33677 | |
Mark Jessop | 9e2cf89b03 | |
Mark Jessop | 187526e048 | |
Mark Jessop | 5cc986678e | |
Mark Jessop | df7203635b | |
Mark Jessop | 8bb7274dd4 | |
Mark Jessop | 8e910425fb | |
Mark Jessop | 1fc2bfbc90 | |
Mark Jessop | 1a06db585d | |
Mark Jessop | 65d285b3b5 | |
Mark Jessop | 7717c04bb6 | |
Mark Jessop | a2e4c1499a | |
Mark Jessop | 13243eecfc | |
Mark Jessop | 485cb95aaf | |
Mark Jessop | 06e32fa6db | |
Mark Jessop | b37e006526 | |
Mark Jessop | 7e59d0871e | |
Mark Jessop | e98f17eba9 | |
Daniel Ekman | 43ccf61661 | |
Daniel Ekman | 90b95f81e1 | |
Daniel Ekman | f526eb7cdc | |
Mark Jessop | 67f817e78d | |
Mark Jessop | 3d75b9803c | |
Mark Jessop | 45049beb78 | |
Mark Jessop | d52f214e08 | |
Mark Jessop | c0498fa063 | |
Arek Papaj | f985f22ea5 | |
Mark Jessop | ad628782b6 | |
Mark Jessop | a01931a57c | |
Mark Jessop | 50d7e18bed | |
Mark Jessop | b3a4537551 | |
Mark Jessop | b5f0aeb3fd | |
Mark Jessop | 65f9073d5e | |
Rohan Ahmad | 2c6930a862 | |
Mark Jessop | d246f1e302 | |
Mark Jessop | 7f46efb8fe | |
Mark Jessop | 773c0c9d1b | |
Mark Jessop | bedfab7aa2 | |
Piotr Lewandowski | 9bd3a25250 | |
Mark Jessop | 434ef66459 | |
Mark Jessop | 7e9866e735 | |
Mark Jessop | bfcf935d75 | |
Mark Jessop | 1ea53e7c2c | |
Mark Jessop | 5270334ba5 | |
Mark Jessop | 924ea8a471 | |
Mark Jessop | 92c01ce7c9 | |
Mark Jessop | 8e8e5bc0a9 | |
Mark Jessop | 5069789364 | |
Mark Jessop | 2abbed1f10 | |
Mark Jessop | bc15d2e743 | |
Mark Jessop | fdf0a8d8f2 | |
Mark Jessop | 31ee507ec8 | |
Mark Jessop | b89bf69abf | |
Mark Jessop | 84ad11e310 | |
Mark Jessop | a377c0a2c2 | |
Mark Jessop | 7959544ca1 | |
Rohan Ahmad | de1b0ce130 | |
Rohan Ahmad | d70ab2475a | |
Mark Jessop | f1c9d1eea0 | |
Mark Jessop | 11bd0386c8 | |
Mark Jessop | 10c53dd3d1 | |
Mark Jessop | ff0c378094 | |
Mark Jessop | 8c2f434b1a | |
Mark Jessop | 15ee6a549f | |
Mark Jessop | 4310885959 | |
Mark Jessop | afebdab448 | |
Mark Jessop | dc66f63a5f | |
Mark Jessop | 34f38fa6c6 | |
Mark Jessop | 1aa59feb31 | |
Mark Jessop | 6defed3404 | |
Mark Jessop | 87cea7a206 | |
Mark Jessop | 1e47384241 | |
Mark Jessop | 1cca89145a | |
Mark Jessop | a7709b1fca | |
Mark Jessop | 2dcb15269d | |
Mark Jessop | bee62eb29f | |
Mark Jessop | f04ffad63b | |
Mark Jessop | 3ad81e0094 | |
Mark Jessop | ba24b76353 | |
Mark Jessop | 77a258070a | |
Mark Jessop | 2ccb69a675 | |
Rohan Ahmad | c102db80e4 | |
Mark Jessop | e5a75afb2c | |
Mark Jessop | 6eefb2758f | |
Mark Jessop | cc12c6493b | |
Mark Jessop | 44544c0724 | |
Mark Jessop | ae9e12e220 | |
Mark Jessop | 58a3bc54d0 | |
Mark Jessop | e3a386b2bb | |
Mark Jessop | 405d894be3 | |
Mark Jessop | e2b597474a | |
Mark Jessop | a328cc7d11 | |
Mark Jessop | ce76d2d6e1 | |
Mark Jessop | 4492724b2e | |
Mark Jessop | 88e3bcd845 | |
Mark Jessop | 41e297e4f1 | |
Mark Jessop | 9f94677067 | |
Mark Jessop | 2628ded179 | |
Mark Jessop | 2faf80385f | |
Mark Jessop | 56de7d6efd | |
Mark Jessop | dbcea5bad5 | |
Mark Jessop | 039834fdfb | |
Mark Jessop | 7935b6369a | |
Mark Jessop | 3ac7be6614 | |
Mark Jessop | 7b7e0cfaec | |
Mark Jessop | 3b89dbce1b | |
Mark Jessop | 05a886ce76 | |
MrARM | 733280497b | |
Mark Jessop | d88f554937 | |
Mark Jessop | fec9b7c2c6 | |
Mark Jessop | 19342975a0 | |
Filip Tomczyk | 27f30d4f1d | |
Mark Jessop | 19428b5e1e | |
Mark Jessop | 1ebdff2a79 | |
Mark Jessop | 2f079df199 | |
Mark Jessop | f63cd7e738 | |
Mark Jessop | a08e977329 | |
Mark Jessop | be24fcc006 | |
Mark Jessop | 939544e05d | |
Mark Jessop | 32485a43f1 | |
Mark Jessop | 3cef0956af | |
Mark Jessop | fa8a0094ec | |
Mark Jessop | 63ab639727 | |
Mark Jessop | 66e519f2d6 | |
Mark Jessop | 503614eb2c | |
Mark Jessop | 258497d5ef | |
Mark Jessop | 6881922e3b | |
Mark Jessop | 443bdd5727 | |
Mark Jessop | 7f81e3b94b | |
Mark Jessop | 136ed393bd | |
Mark Jessop | e289ff7f1e | |
Mark Jessop | 15fa103b40 |
|
@ -0,0 +1,2 @@
|
|||
samples
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
name: Container Images
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'testing'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Calculate Container Metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Setup Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Images
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/386, linux/arm64, linux/arm/v6, linux/arm/v7
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Build and Push Images
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/386, linux/arm64, linux/arm/v6, linux/arm/v7
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache
|
||||
cache-to: type=registry,ref=ghcr.io/${{ github.repository }}:buildcache,mode=max
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
|
@ -30,3 +30,15 @@
|
|||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Custom config file
|
||||
user.env
|
||||
user.cfg
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# cmake
|
||||
build
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
FROM debian:bullseye as builder
|
||||
MAINTAINER sa2kng <knegge@gmail.com>
|
||||
|
||||
RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recommends \
|
||||
cmake \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
git \
|
||||
libusb-1.0-0-dev \
|
||||
libatlas-base-dev \
|
||||
libsoapysdr-dev \
|
||||
soapysdr-module-all &&\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# install everything in /target and it will go in to / on destination image. symlink make it easier for builds to find files installed by this.
|
||||
RUN mkdir -p /target/usr && rm -rf /usr/local && ln -sf /target/usr /usr/local && mkdir /target/etc
|
||||
|
||||
COPY . /horusdemodlib
|
||||
|
||||
RUN cd /horusdemodlib &&\
|
||||
cmake -B build -DCMAKE_INSTALL_PREFIX=/target/usr -DCMAKE_BUILD_TYPE=Release &&\
|
||||
cmake --build build --target install
|
||||
|
||||
RUN git clone --depth 1 https://github.com/rxseger/rx_tools.git &&\
|
||||
cd rx_tools &&\
|
||||
cmake -B build -DCMAKE_INSTALL_PREFIX=/target/usr -DCMAKE_BUILD_TYPE=Release &&\
|
||||
cmake --build build --target install
|
||||
|
||||
COPY scripts/* /target/usr/bin/
|
||||
|
||||
# to support arm wheels
|
||||
RUN echo '[global]\nextra-index-url=https://www.piwheels.org/simple' > /target/etc/pip.conf
|
||||
|
||||
FROM debian:bullseye as prod
|
||||
RUN apt-get -y update && apt -y upgrade && apt-get -y install --no-install-recommends \
|
||||
libusb-1.0-0 \
|
||||
python3-venv \
|
||||
python3-crcmod \
|
||||
python3-dateutil \
|
||||
python3-numpy \
|
||||
python3-requests \
|
||||
python3-pip \
|
||||
sox \
|
||||
bc \
|
||||
rtl-sdr \
|
||||
libatlas3-base \
|
||||
soapysdr-module-all &&\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pip install --system --no-cache-dir --prefer-binary horusdemodlib
|
||||
|
||||
COPY --from=builder /target /
|
||||
CMD ["bash"]
|
|
@ -0,0 +1,114 @@
|
|||
# Guide for running on docker
|
||||
|
||||
## Host system
|
||||
This guide will assume an updated installation of Debian bullseye (11), it should work on a lot of different linux OS and architectures.<br>
|
||||
You will need to install the libraries and supporting sw/fw for your sdr device, including udev rules and blacklists.<br>
|
||||
Additional software such as soapysdr is not needed on the host, but can certainly be installed.<br>
|
||||
```shell
|
||||
sudo apt install rtl-sdr
|
||||
echo "blacklist dvb_usb_rtl28xxu" | sudo tee /etc/modprobe.d/blacklist-rtlsdr.conf
|
||||
sudo modprobe -r dvb_usb_rtl28xxu
|
||||
```
|
||||
|
||||
See the [docker installation](#install-dockerio) at the bottom of this page.
|
||||
|
||||
## Building the image
|
||||
If the docker image is not available, or you want to build from your own branch etc.
|
||||
```shell
|
||||
git clone https://github.com/projecthorus/horusdemodlib.git
|
||||
cd horusdemodlib
|
||||
docker-compose build
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Start with creating a directory with a name representing the station, this will be shown in several places in the resulting stack.
|
||||
```shell
|
||||
mkdir -p projecthorus
|
||||
cd projecthorus
|
||||
wget https://raw.githubusercontent.com/projecthorus/horusdemodlib/master/docker-compose.yml
|
||||
wget -O user.cfg https://raw.githubusercontent.com/projecthorus/horusdemodlib/master/user.cfg.example
|
||||
wget -O user.env https://raw.githubusercontent.com/projecthorus/horusdemodlib/master/user.env.example
|
||||
```
|
||||
|
||||
The file `user.env` contains all the station variables (earlier in each script), the DEMODSCRIPT is used by compose.<br>
|
||||
The `user.cfg` sets the details sent to Sondehub.<br>
|
||||
Use your favourite editor to configure these:
|
||||
```shell
|
||||
nano user.env
|
||||
nano user.cfg
|
||||
```
|
||||
Please note that the values in user.env should not be escaped with quotes or ticks.
|
||||
|
||||
## Bringing the stack up
|
||||
|
||||
The `docker-compose` (on some systems it's `docker compose` without the hyphen) is the program controlling the creation, updating, building and termination of the stack.
|
||||
The basic commands you will use is `docker-compose up` and `docker-compose down`.
|
||||
When you edit the compose file or configuration it will try to figure out what needs to be done to bring the stack in sync of what has changed.
|
||||
|
||||
Starting the stack in foreground (terminate with Ctrl-C):
|
||||
```shell
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
Starting the stack in background:
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Stopping the stack:
|
||||
```shell
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
Updating the images and bringing the stack up:
|
||||
```shell
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Over time there will be old images accumulating, these can be removed with `docker image prune -af`
|
||||
|
||||
## Using SoapySDR with rx_tools
|
||||
|
||||
If you want to use other SDR than rtl_sdr, the docker build includes rx_tools.<br>
|
||||
Select docker_soapy_single.sh and add the extra argument to select the sdr in SDR_EXTRA:
|
||||
```shell
|
||||
# Script name
|
||||
DEMODSCRIPT="docker_soapy_single.sh"
|
||||
SDR_EXTRA="-d driver=rtlsdr"
|
||||
```
|
||||
|
||||
## Monitoring and maintenance
|
||||
|
||||
Inside each container, the logs are output to stdout, which makes them visible from outside the container in the logs.
|
||||
Starting to monitor the running stack:
|
||||
```shell
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
If you want to run commands inside the containers, this can be done with the following command:
|
||||
````shell
|
||||
docker-compose exec horusdemod bash
|
||||
````
|
||||
The container needs to be running. Exit with Ctrl-D or typing `exit`.
|
||||
|
||||
# Install Docker.io
|
||||
(Or you can install Docker.com engine via the [convenience script](https://docs.docker.com/engine/install/debian/#install-using-the-convenience-script))
|
||||
|
||||
In Debian bullseye there's already a docker package, so installation is easy:
|
||||
```shell
|
||||
sudo apt install docker.io apparmor
|
||||
sudo apt -t bullseye-backports install docker-compose
|
||||
sudo adduser $(whoami) docker
|
||||
```
|
||||
Re-login for the group permission to take effect.
|
||||
|
||||
The reason for using backports is the version of compose in bullseye is 1.25 and lacks cgroup support, the backport is version 1.27
|
||||
<br>If your dist doesn't have backports, enable with this, and try the installation of docker-compose again:
|
||||
```shell
|
||||
echo "deb http://deb.debian.org/debian bullseye-backports main contrib non-free" | sudo tee /etc/apt/sources.list.d/backports.list
|
||||
suod apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 648ACFD622F3D138 0E98404D386FA1D9
|
||||
sudo apt update
|
||||
```
|
||||
If you cannot get a good compose version with your dist, please follow [the official guide](https://docs.docker.com/compose/install/linux/#install-the-plugin-manually).
|
|
@ -65,7 +65,7 @@ A Python wrapper is also available (via the horusdemodlib Python library which i
|
|||
## HorusDemodLib Python Library
|
||||
The horusdemodlib Python library contains decoders for the different Project Horus telemetry formats, including:
|
||||
* Horus Binary v1 (Legacy 22-byte Golay-encoded format)
|
||||
* Horus Binary v2 (LDPC-Encoded 16 and 32-byte formats)
|
||||
* Horus Binary v2 (Golay-encoded 32-byte format)
|
||||
|
||||
It also contains a wrapper around the C library (mentioned above), which contains the Horus modem demodulators.
|
||||
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
]
|
||||
},
|
||||
"4FSKTEST-V2": {
|
||||
"struct": "<BfBBH",
|
||||
"comment": "Default custom fields for RS41ng",
|
||||
"struct": "<hhBHxx",
|
||||
"fields": [
|
||||
["test_field 1", "none"],
|
||||
["test_field 2", "none"],
|
||||
["test_field 3", "battery_5v_byte"],
|
||||
["test_field 4", "divide_by_10"],
|
||||
["test_field 5", "divide_by_100"]
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"]
|
||||
]
|
||||
},
|
||||
"MAGNU-V2": {
|
||||
|
@ -37,5 +37,125 @@
|
|||
["Travel", "divide_by_100"],
|
||||
["Heading", "divide_by_100"]
|
||||
]
|
||||
},
|
||||
"HORUS-V2": {
|
||||
"struct": "<hhBHxx",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"]
|
||||
]
|
||||
},
|
||||
"VK5BRL-V2": {
|
||||
"struct": "<hhBHxx",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"]
|
||||
]
|
||||
},
|
||||
"ON4IR": {
|
||||
"comment": "ON4IR - UBA/IRM balloon custom fields",
|
||||
"struct": "<hhBHH",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"],
|
||||
["pulse_counts","none"]
|
||||
]
|
||||
},
|
||||
"HORUSRADMON": {
|
||||
"comment": "Horus Radiation Monitor Payload, Photodiode",
|
||||
"struct": "<hxxxxxH",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["pulse_counts","none"]
|
||||
]
|
||||
},
|
||||
"HORUSGEIGER": {
|
||||
"comment": "Horus Radiation Monitor Payload, Geiger Counter",
|
||||
"struct": "<hxxxxxH",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["pulse_counts","none"]
|
||||
]
|
||||
},
|
||||
"SHSSPGEIGER": {
|
||||
"comment": "Horus Radiation Monitor Payload, Geiger Counter",
|
||||
"struct": "<hxxxxxH",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["pulse_counts","none"]
|
||||
]
|
||||
},
|
||||
"OH3HAB-4FSK-V2": {
|
||||
"comment": "OH3HAB custom fields with RadSens and BME280 sensors",
|
||||
"struct": "<HhBHH",
|
||||
"fields": [
|
||||
["radiation_intensity", "none"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"],
|
||||
["pulse_counts","none"]
|
||||
]
|
||||
},
|
||||
"AJ4XE": {
|
||||
"comment": "custom fields for AJ4XE tracker",
|
||||
"struct": "<HHBHH",
|
||||
"fields": [
|
||||
["HDOP", "divide_by_100"],
|
||||
["VDOP", "divide_by_100"],
|
||||
["TTF", "none"],
|
||||
["PDOP", "divide_by_100"],
|
||||
["AVG_SNR", "divide_by_100"]
|
||||
]
|
||||
},
|
||||
"KD2EAT-DFM": {
|
||||
"comment": "custom fields for KD2EAT DFM17 testing",
|
||||
"struct": "<hhBHBx",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"],
|
||||
["mcu_calibration", "none"]
|
||||
]
|
||||
},
|
||||
"DF7PN": {
|
||||
"comment": "private values see https://github.com/whallmann/RS41HUP_V2",
|
||||
"struct": "<HHxxxxx",
|
||||
"fields": [
|
||||
["flight_number", "none"],
|
||||
["sonde_type", "none"]
|
||||
]
|
||||
},
|
||||
"SP9SKP": {
|
||||
"comment": "SKP telemetry",
|
||||
"struct": "<BBBBBHH",
|
||||
"fields": [
|
||||
["fix_voltage", "battery_5v_byte"],
|
||||
["io_voltage", "battery_5v_byte"],
|
||||
["reset_voltage", "battery_5v_byte"],
|
||||
["pv_voltage", "divide_by_100"],
|
||||
["time_to_fix", "none"],
|
||||
["uv_max", "none"],
|
||||
["uv_avg", "none"]
|
||||
]
|
||||
},
|
||||
"SKPQKM": {
|
||||
"comment": "SKPQKM telemetry",
|
||||
"struct": "<BBBBBHH",
|
||||
"fields": [
|
||||
["fix_voltage", "battery_5v_byte"],
|
||||
["io_voltage", "battery_5v_byte"],
|
||||
["reset_voltage", "battery_5v_byte"],
|
||||
["pv_voltage", "divide_by_100"],
|
||||
["time_to_fix", "none"],
|
||||
["uv_max", "none"],
|
||||
["uv_avg", "none"]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
<mxfile host="app.diagrams.net" modified="2023-06-14T05:16:33.079Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" etag="yB5OTHza8nv0mLeuWLmY" version="21.3.4" type="device">
|
||||
<diagram name="Page-1" id="9Tq_Rg6amuM9MWHMLjdR">
|
||||
<mxGraphModel dx="1022" dy="658" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-1" value="<font style="font-size: 19px;">0</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="160" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-3" value="<span style="font-size: 19px;">8</span>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="200" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-4" value="<font style="font-size: 19px;">16<br></font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="240" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-5" value="<font style="font-size: 14px;">Payload ID</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="160" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-7" value="<font style="font-size: 14px;">Frame No.</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="160" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-8" value="<font style="font-size: 14px;">HH</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="160" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-9" value="<font style="font-size: 14px;">MM</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="160" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-10" value="<font style="font-size: 14px;">SS</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="160" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-11" value="<font style="font-size: 14px;">Lat</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="160" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-12" value="<font style="font-size: 14px;">Latitude</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="200" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-13" value="<font style="font-size: 14px;">Longitude</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="200" width="160" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-14" value="<font style="font-size: 14px;">Alt</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="200" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-15" value="<font style="font-size: 14px;">Alt</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="240" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-16" value="<font style="font-size: 14px;">Vel.</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" vertex="1" parent="1">
|
||||
<mxGeometry x="200" y="240" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-17" value="<font style="font-size: 14px;">Sats</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad9d5;strokeColor=#ae4132;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="240" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-18" value="<font style="font-size: 14px;">Temp</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="240" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-19" value="<font style="font-size: 14px;">Batt</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="240" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-20" value="<font style="font-size: 14px;">Custom</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d0cee2;strokeColor=#56517e;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="240" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-21" value="<font style="font-size: 14px;">Custom</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d0cee2;strokeColor=#56517e;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="280" width="240" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-22" value="<font style="font-size: 14px;">CRC16</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#bac8d3;strokeColor=#23445d;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="280" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-23" value="<font style="font-size: 19px;">24<br></font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="280" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-24" value="<font style="font-size: 15px;">Byte</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;rotation=-90;" vertex="1" parent="1">
|
||||
<mxGeometry x="20" y="220" width="160" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-25" value="<font style="font-size: 19px;">0</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="400" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-26" value="<span style="font-size: 19px;">8</span>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="440" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-27" value="<font style="font-size: 19px;">16<br></font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="480" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-28" value="<font style="font-size: 14px;">Payload ID</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="400" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-29" value="<font style="font-size: 14px;">Frame No.</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="400" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-30" value="<font style="font-size: 14px;">HH</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="400" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-31" value="<font style="font-size: 14px;">MM</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="400" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-32" value="<font style="font-size: 14px;">SS</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="400" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-33" value="<font style="font-size: 14px;">Lat</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="400" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-34" value="<font style="font-size: 14px;">Latitude</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="440" width="120" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-35" value="<font style="font-size: 14px;">Longitude</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="440" width="160" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-36" value="<font style="font-size: 14px;">Alt</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="440" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-37" value="<font style="font-size: 14px;">Alt</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="480" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-38" value="Vel-H" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad7ac;strokeColor=#b46504;" vertex="1" parent="1">
|
||||
<mxGeometry x="200" y="480" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-39" value="<font style="font-size: 14px;">Sats</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#fad9d5;strokeColor=#ae4132;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="480" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-40" value="<font style="font-size: 14px;">Temp</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b0e3e6;strokeColor=#0e8088;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="480" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-41" value="<font style="font-size: 14px;">Batt</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#b1ddf0;strokeColor=#10739e;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="480" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-42" value="<font style="font-size: 14px;">Ascent<br>Rate<br></font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d0cee2;strokeColor=#56517e;strokeWidth=2;dashed=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="480" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-44" value="<font style="font-size: 14px;">CRC16</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#bac8d3;strokeColor=#23445d;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="520" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-45" value="<font style="font-size: 19px;">24<br></font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="520" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-46" value="<font style="font-size: 15px;">Byte</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;rotation=-90;" vertex="1" parent="1">
|
||||
<mxGeometry x="20" y="460" width="160" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-47" value="<font style="font-size: 14px;">Ext.<br>Temp</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;strokeWidth=2;dashed=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="480" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-50" value="<span style="font-size: 14px;">Hum.</span>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;strokeWidth=2;dashed=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="200" y="520" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-51" value="<span style="font-size: 14px;">Pressure</span>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;strokeWidth=2;dashed=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="520" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-52" value="<span style="font-size: 14px;">Pulse <br>Count</span>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;strokeWidth=2;dashed=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="520" width="80" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-53" value="<font style="font-size: 19px;">0</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-54" value="<font style="font-size: 19px;">1</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="200" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-55" value="<font style="font-size: 19px;">2</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="240" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-56" value="<font style="font-size: 19px;">3</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-57" value="<font style="font-size: 19px;">4</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="320" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-58" value="<font style="font-size: 19px;">5</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-59" value="<font style="font-size: 19px;">6</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="400" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-60" value="<font style="font-size: 19px;">7</font>" style="rounded=0;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="1">
|
||||
<mxGeometry x="440" y="360" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="HftDXYYa0DBVADGEEzue-61" value="<font style="font-size: 14px;">Ext.<br>Temp</font>" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;strokeWidth=2;dashed=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="520" width="40" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 180 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 48 KiB |
|
@ -0,0 +1,21 @@
|
|||
version: '3.8'
|
||||
services:
|
||||
horusdemod:
|
||||
build:
|
||||
context: .
|
||||
target: prod
|
||||
image: 'ghcr.io/projecthorus/horusdemodlib:latest'
|
||||
#read_only: true
|
||||
device_cgroup_rules:
|
||||
- 'c 189:* rwm'
|
||||
env_file:
|
||||
- './user.env'
|
||||
command: 'bash -c $${DEMODSCRIPT}'
|
||||
devices:
|
||||
- '/dev/bus/usb'
|
||||
volumes:
|
||||
- type: 'tmpfs'
|
||||
target: '/tmp'
|
||||
- type: 'bind'
|
||||
source: './user.cfg'
|
||||
target: '/user.cfg'
|
|
@ -1 +1 @@
|
|||
__version__ = "0.3.2"
|
||||
__version__ = "0.3.13"
|
||||
|
|
|
@ -50,6 +50,27 @@ def check_packet_crc(data:bytes, checksum:str='crc16'):
|
|||
raise ValueError(f"Checksum - Unknown Checksym type {checksum}.")
|
||||
|
||||
|
||||
def add_packet_crc(data:bytes, checksum:str='crc16'):
|
||||
"""
|
||||
Add a CRC onto the end of provided bytes
|
||||
|
||||
Support CRC types: CRC16-CCITT
|
||||
|
||||
"""
|
||||
|
||||
if (checksum == 'crc16') or (checksum == 'CRC16') or (checksum == 'crc16-ccitt') or (checksum == 'CRC16-CCITT'):
|
||||
# Calculate a CRC over the data
|
||||
_crc16 = crcmod.predefined.mkCrcFun('crc-ccitt-false')
|
||||
_calculated_crc = _crc16(data)
|
||||
|
||||
_packet_crc = struct.pack('<H', _calculated_crc)
|
||||
|
||||
return data + _packet_crc
|
||||
|
||||
|
||||
else:
|
||||
raise ValueError(f"Checksum - Unknown Checksym type {checksum}.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
|
|
@ -99,9 +99,17 @@ def decode_packet(data:bytes, packet_format:dict = None, ignore_crc:bool = False
|
|||
'packet_format': packet_format,
|
||||
'crc_ok': False,
|
||||
'payload_id': 0,
|
||||
'raw': codecs.encode(data, 'hex').decode().upper()
|
||||
'raw': codecs.encode(data, 'hex').decode().upper(),
|
||||
}
|
||||
|
||||
# Report the modulation type
|
||||
if 'v1' in packet_format['name']:
|
||||
_output['modulation'] = 'Horus Binary v1'
|
||||
elif 'v2' in packet_format['name']:
|
||||
_output['modulation'] = 'Horus Binary v2'
|
||||
else:
|
||||
_output['modulation'] = 'Horus Binary'
|
||||
|
||||
# Check the length provided in the packet format matches up with the length defined by the struct.
|
||||
_struct_length = struct.calcsize(packet_format['struct'])
|
||||
if _struct_length != packet_format['length']:
|
||||
|
@ -138,15 +146,27 @@ def decode_packet(data:bytes, packet_format:dict = None, ignore_crc:bool = False
|
|||
if _field_name == 'custom':
|
||||
# Attempt to interpret custom fields.
|
||||
# Note: This requires that the payload ID has been decoded prior to this field being parsed.
|
||||
|
||||
if _output['payload_id'] in horusdemodlib.payloads.HORUS_CUSTOM_FIELDS:
|
||||
(_custom_data, _custom_str) = decode_custom_fields(_field_data, _output['payload_id'])
|
||||
# If this payload has a specific custom field description, use that.
|
||||
_custom_field_name = _output['payload_id']
|
||||
else:
|
||||
# Otherwise use the default from 4FSKTEST-V2, which matches
|
||||
# the default fields from RS41ng
|
||||
_custom_field_name = '4FSKTEST-V2'
|
||||
|
||||
(_custom_data, _custom_str) = decode_custom_fields(_field_data, _custom_field_name)
|
||||
|
||||
# Add custom fields to string
|
||||
_ukhas_fields.append(_custom_str)
|
||||
|
||||
# Add custom fields to output dict.
|
||||
for _field in _custom_data:
|
||||
_output[_field] = _custom_data[_field]
|
||||
|
||||
_output['custom_field_names'] = list(_custom_data.keys())
|
||||
|
||||
# Add custom fields to string
|
||||
_ukhas_fields.append(_custom_str)
|
||||
|
||||
# Add custom fields to output dict.
|
||||
for _field in _custom_data:
|
||||
_output[_field] = _custom_data[_field]
|
||||
|
||||
# Ignore checksum field. (and maybe other fields?)
|
||||
elif _field_name not in ['checksum']:
|
||||
|
@ -158,6 +178,11 @@ def decode_packet(data:bytes, packet_format:dict = None, ignore_crc:bool = False
|
|||
_ukhas_fields.append(_decoded_str)
|
||||
|
||||
|
||||
# Check the payload ID if > 256 for a Horus v2 packet.
|
||||
if _output['modulation'] == 'Horus Binary v2':
|
||||
if _raw_fields[0] < 256:
|
||||
logging.warning("Found Payload ID < 256 in a Horus Binary v2 packet! This may lead to undefined behaviour. Please use a payload ID > 256!")
|
||||
|
||||
# Convert to a UKHAS-compliant string.
|
||||
_ukhas_str = ",".join(_ukhas_fields)
|
||||
_ukhas_crc = ukhas_crc(_ukhas_str.encode('ascii'))
|
||||
|
@ -249,6 +274,7 @@ def parse_ukhas_string(sentence:str) -> dict:
|
|||
# Produce a dict output which is compatible with the output of the binary decoder.
|
||||
_telem = {
|
||||
'raw': _raw,
|
||||
'modulation': 'RTTY',
|
||||
'callsign': _callsign,
|
||||
'sequence_number': _sequence_number,
|
||||
'time': _time,
|
||||
|
@ -314,7 +340,9 @@ if __name__ == "__main__":
|
|||
['horus_binary_v1', b'\x01\x12\x00\x00\x00\x23\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x1C\x9A\x95\x45', 'error'],
|
||||
['horus_binary_v2_16byte', b'\x01\x12\x02\x00\x02\xbc\xeb!AR\x10\x00\xff\x00\xe1\x7e', ''],
|
||||
# id seq_no HH MM SS lat lon alt spd sat tmp bat custom data -----------------------| crc16
|
||||
['horus_binary_v2_32byte', b'\x00\x01\x02\x00\x0C\x22\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xB4\xC6', '']
|
||||
['horus_binary_v2_32byte', b'\x00\x01\x02\x00\x0C\x22\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xB4\xC6', ''],
|
||||
# id seq_no HH MM SS lat lon alt spd sat tmp bat custom data -----------------------| crc16
|
||||
['horus_binary_v2_32byte_noident', b'\xff\xff\x02\x00\x0C\x22\x38\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x17\x1c', '']
|
||||
]
|
||||
|
||||
for _test in tests:
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# HorusDemodLib - Encoder helper functions
|
||||
#
|
||||
import ctypes
|
||||
from ctypes import *
|
||||
import codecs
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
from enum import Enum
|
||||
import os
|
||||
import logging
|
||||
from .decoder import decode_packet, hex_to_bytes
|
||||
|
||||
|
||||
class Encoder():
|
||||
"""
|
||||
Horus Binary Encoder class.
|
||||
|
||||
Allows creation of a Horus Binary packet.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
libpath=f"",
|
||||
):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
libpath : str
|
||||
Path to libhorus
|
||||
"""
|
||||
|
||||
if sys.platform == "darwin":
|
||||
libpath = os.path.join(libpath, "libhorus.dylib")
|
||||
elif sys.platform == "win32":
|
||||
libpath = os.path.join(libpath, "libhorus.dll")
|
||||
else:
|
||||
libpath = os.path.join(libpath, "libhorus.so")
|
||||
|
||||
# future improvement would be to try a few places / names
|
||||
self.c_lib = ctypes.cdll.LoadLibrary(libpath)
|
||||
|
||||
|
||||
# Encoder/decoder functions
|
||||
self.c_lib.horus_l2_get_num_tx_data_bytes.restype = c_int
|
||||
|
||||
self.c_lib.horus_l2_encode_tx_packet.restype = c_int
|
||||
# self.c_lib.horus_l2_encode_tx_packet.argtype = [
|
||||
# POINTER(c_ubyte),
|
||||
# c_ubyte *
|
||||
# POINTER(c_ubyte),
|
||||
# c_int
|
||||
# ]
|
||||
|
||||
self.c_lib.horus_l2_decode_rx_packet.argtype = [
|
||||
POINTER(c_ubyte),
|
||||
POINTER(c_ubyte),
|
||||
c_int
|
||||
]
|
||||
|
||||
self.c_lib.horus_l2_gen_crc16.restype = c_ushort
|
||||
self.c_lib.horus_l2_gen_crc16.argtype = [
|
||||
POINTER(c_ubyte),
|
||||
c_uint8
|
||||
]
|
||||
|
||||
# Init
|
||||
self.c_lib.horus_l2_init()
|
||||
|
||||
# in case someone wanted to use `with` style. I'm not sure if closing the modem does a lot.
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
"""
|
||||
Closes Horus modem. Does nothing here.
|
||||
"""
|
||||
pass
|
||||
|
||||
# Wrappers for libhorus C functions that we need.
|
||||
|
||||
def get_num_tx_data_bytes(self, packet_size) -> int:
|
||||
"""
|
||||
Calculate the number of transmit data bytes (uw+packet+fec) for a given
|
||||
input packet size.
|
||||
"""
|
||||
return int(self.c_lib.horus_l2_get_num_tx_data_bytes(int(packet_size)))
|
||||
|
||||
|
||||
def horus_l2_encode_packet(self, packet):
|
||||
"""
|
||||
Encode a packet using the Horus Binary FEC scheme, which:
|
||||
- Generates Golay (23,12) FEC for the packet
|
||||
- Adds this onto the packet contents, and interleaves/scrambles
|
||||
- Adds a unique word (0x2424) to the start.
|
||||
|
||||
Packet input must be provided as bytes.
|
||||
"""
|
||||
|
||||
if type(packet) != bytes:
|
||||
raise TypeError("Input to encode_packet must be bytes!")
|
||||
|
||||
_unencoded = c_ubyte * len(packet)
|
||||
_unencoded = _unencoded.from_buffer_copy(packet)
|
||||
_num_encoded_bytes = self.get_num_tx_data_bytes(len(packet))
|
||||
_encoded = c_ubyte * _num_encoded_bytes
|
||||
_encoded = _encoded()
|
||||
|
||||
self.c_lib.horus_l2_encode_tx_packet.argtype = [
|
||||
c_ubyte * _num_encoded_bytes,
|
||||
c_ubyte * len(packet),
|
||||
c_int
|
||||
]
|
||||
|
||||
_num_bytes = int(self.c_lib.horus_l2_encode_tx_packet(_encoded, _unencoded, int(len(packet))))
|
||||
|
||||
return (bytes(_encoded), _num_bytes)
|
||||
|
||||
|
||||
def horus_l2_decode_packet(self, packet, num_payload_bytes):
|
||||
"""
|
||||
Decode a Horus-Binary encoded data packet.
|
||||
|
||||
The packet must be provided as bytes, and must have the 2-byte unique word (0x2424)
|
||||
at the start.
|
||||
|
||||
The expected number of output bytes must also be provided (22 or 32 for Horus v1 / v2 respectively)
|
||||
"""
|
||||
|
||||
if type(packet) != bytes:
|
||||
raise TypeError("Input to encode_packet must be bytes!")
|
||||
_encoded = c_ubyte * len(packet)
|
||||
_encoded = _encoded.from_buffer_copy(packet)
|
||||
_decoded = c_ubyte * num_payload_bytes
|
||||
_decoded = _decoded()
|
||||
|
||||
self.c_lib.horus_l2_encode_tx_packet.argtype = [
|
||||
c_ubyte * num_payload_bytes,
|
||||
c_ubyte * len(packet),
|
||||
c_int
|
||||
]
|
||||
|
||||
self.c_lib.horus_l2_decode_rx_packet(_decoded, _encoded, num_payload_bytes)
|
||||
|
||||
return bytes(_decoded)
|
||||
|
||||
|
||||
def create_horus_v2_packet(self,
|
||||
payload_id = 256,
|
||||
sequence_number = 0,
|
||||
time_dt = datetime.datetime.utcnow(),
|
||||
latitude = 0.0,
|
||||
longitude = 0.0,
|
||||
altitude = 0.0,
|
||||
speed = 0.0,
|
||||
satellites = 0,
|
||||
temperature = 0.0,
|
||||
battery_voltage = 0.0,
|
||||
# Default fields used for the 'custom' section of the packet.
|
||||
ascent_rate = 0.0,
|
||||
ext_temperature = 0.0,
|
||||
ext_humidity = 0.0,
|
||||
ext_pressure = 0.0,
|
||||
# Alternate custom data - must be bytes, and length=9
|
||||
custom_data = None
|
||||
):
|
||||
pass
|
||||
|
||||
# Sanity check input data.
|
||||
if payload_id < 256 or payload_id > 65535:
|
||||
raise ValueError("Invalid Horus v2 Payload ID. (Must be 256-65535)")
|
||||
|
||||
# Clip sequence number
|
||||
sequence_number = int(sequence_number) % 65536
|
||||
|
||||
# Try and extract HHMMSS from time
|
||||
try:
|
||||
hours = int(time_dt.hour)
|
||||
minutes = int(time_dt.minute)
|
||||
seconds = int(time_dt.second)
|
||||
except:
|
||||
raise ValueError("Could not parse input datetime object.")
|
||||
|
||||
# Assume lat/lon are fine. They are just sent as floats anyway.
|
||||
|
||||
# Clip Altitude
|
||||
altitude = int(altitude)
|
||||
if altitude < 0:
|
||||
altitude = 0
|
||||
if altitude > 65535:
|
||||
altitude = 65535
|
||||
|
||||
# Clip Speed (kph)
|
||||
speed = int(speed)
|
||||
if speed < 0:
|
||||
speed = 0
|
||||
if speed > 255:
|
||||
speed = 255
|
||||
|
||||
# Clip sats
|
||||
satellites = int(satellites)
|
||||
if satellites < 0:
|
||||
satellites = 0
|
||||
if satellites > 255:
|
||||
satellites = 255
|
||||
|
||||
# Temperature
|
||||
|
||||
# Battery voltage clip and conversion
|
||||
|
||||
|
||||
|
||||
# Custom data
|
||||
|
||||
# Ascent rate
|
||||
|
||||
# PTU data
|
||||
|
||||
|
||||
# 'struct': '<HH3sffHBBbB9sH',
|
||||
# 'checksum': 'crc16',
|
||||
# 'fields': [
|
||||
# ['payload_id', 'payload_id'],
|
||||
# ['sequence_number', 'none'],
|
||||
# ['time', 'time_hms'],
|
||||
# ['latitude', 'degree_float'],
|
||||
# ['longitude', 'degree_float'],
|
||||
# ['altitude', 'none'],
|
||||
# ['speed', 'none'],
|
||||
# ['satellites', 'none'],
|
||||
# ['temperature', 'none'],
|
||||
# ['battery_voltage', 'battery_5v_byte'],
|
||||
# ['custom', 'custom'],
|
||||
# ['checksum', 'none']
|
||||
# ]
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
e = Encoder()
|
||||
|
||||
# Check of get_num_tx_data_bytes
|
||||
print(f"Horus v1: 22 bytes in, {e.get_num_tx_data_bytes(22)} bytes out.")
|
||||
print(f"Horus v2: 32 bytes in, {e.get_num_tx_data_bytes(32)} bytes out.")
|
||||
|
||||
print("Encoder Tests: ")
|
||||
horus_v1_unencoded = "000900071E2A000000000000000000000000259A6B14"
|
||||
horus_v1_unencoded = "e701010000000000000000000000000000000022020000000000000000006e8e"
|
||||
print(f" Horus v1 Input: {horus_v1_unencoded}")
|
||||
horus_v1_unencoded_bytes = codecs.decode(horus_v1_unencoded, 'hex')
|
||||
(_encoded, _num_bytes) = e.horus_l2_encode_packet(horus_v1_unencoded_bytes)
|
||||
print(f" Horus v1 Output: {codecs.encode(_encoded, 'hex').decode().upper()}")
|
||||
|
||||
|
||||
print("Decoder Tests:")
|
||||
horus_v1_encoded = "2424C06B300D0415C5DBD332EFD7C190D7AF7F3C2891DE9F4BA1EB2B437BE1E2D8419D3DC9E44FDF78DAA07A98"
|
||||
print(f" Horus v1 Input: {horus_v1_encoded}")
|
||||
horus_v1_encoded_bytes = codecs.decode(horus_v1_encoded, 'hex')
|
||||
_decoded = e.horus_l2_decode_packet(horus_v1_encoded_bytes, 22)
|
||||
print(f" Horus v1 Output: {codecs.encode(_decoded, 'hex').decode().upper()}")
|
||||
|
|
@ -39,6 +39,7 @@ class HabitatUploader(object):
|
|||
listener_lon=0.0,
|
||||
listener_radio="",
|
||||
listener_antenna="",
|
||||
listener_upload_rate=3, # Hours
|
||||
queue_size=64,
|
||||
upload_timeout=10,
|
||||
upload_retries=5,
|
||||
|
@ -60,7 +61,22 @@ class HabitatUploader(object):
|
|||
self.listener_lon = listener_lon
|
||||
self.listener_radio = listener_radio
|
||||
self.listener_antenna = listener_antenna
|
||||
self.listener_upload_rate = listener_upload_rate
|
||||
self.position_uploaded = False
|
||||
self.last_listener_upload_time = 0
|
||||
|
||||
# Try and convert the supplied listener lat/lon to a float
|
||||
# if this fails, just set the lat/lon to 0/0
|
||||
try:
|
||||
_lat = float(self.listener_lat)
|
||||
_lon = float(self.listener_lon)
|
||||
|
||||
self.listener_lat = _lat
|
||||
self.listener_lon = _lon
|
||||
except:
|
||||
logging.error("Could not parse listener lat/lon, setting both to 0.0")
|
||||
self.listener_lat = 0.0
|
||||
self.listener_lon = 0.0
|
||||
|
||||
self.last_freq_hz = None
|
||||
|
||||
|
@ -117,7 +133,7 @@ class HabitatUploader(object):
|
|||
# Run the request.
|
||||
try:
|
||||
_req = requests.put(
|
||||
_url, data=json.dumps(_data), timeout=self.upload_timeout
|
||||
_url, data=json.dumps(_data), timeout=(self.upload_timeout, 6.1)
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error("Habitat - Upload Failed: %s" % str(e))
|
||||
|
@ -175,29 +191,24 @@ class HabitatUploader(object):
|
|||
# Wait for a short time before checking the queue again.
|
||||
time.sleep(0.5)
|
||||
|
||||
if not self.position_uploaded:
|
||||
# Validate the lat/lon entries.
|
||||
try:
|
||||
_lat = float(self.listener_lat)
|
||||
_lon = float(self.listener_lon)
|
||||
# Listener position upload
|
||||
if (
|
||||
time.time() - self.last_listener_upload_time
|
||||
) > self.listener_upload_rate * 3600:
|
||||
# Time to upload a listener postion
|
||||
if (self.listener_lat != 0.0) or (self.listener_lon != 0.0):
|
||||
_success = self.uploadListenerPosition(
|
||||
self.user_callsign,
|
||||
self.listener_lat,
|
||||
self.listener_lon,
|
||||
self.listener_radio,
|
||||
self.listener_antenna,
|
||||
)
|
||||
if _success:
|
||||
logging.info(f"Habitat - Listener information uploaded. Re-uploading in {self.listener_upload_rate} hours.")
|
||||
|
||||
if (_lat != 0.0) or (_lon != 0.0):
|
||||
_success = self.uploadListenerPosition(
|
||||
self.user_callsign,
|
||||
_lat,
|
||||
_lon,
|
||||
self.listener_radio,
|
||||
self.listener_antenna,
|
||||
)
|
||||
else:
|
||||
logging.warning("Listener position set to 0.0/0.0 - not uploading.")
|
||||
|
||||
except Exception as e:
|
||||
logging.error("Error uploading listener position: %s" % str(e))
|
||||
|
||||
# Set this flag regardless if the upload worked.
|
||||
# The user can trigger a re-upload.
|
||||
self.position_uploaded = True
|
||||
# Update the last upload time.
|
||||
self.last_listener_upload_time = time.time()
|
||||
|
||||
|
||||
logging.info("Stopped Habitat Uploader Thread.")
|
||||
|
@ -221,7 +232,7 @@ class HabitatUploader(object):
|
|||
try:
|
||||
self.habitat_upload_queue.put_nowait(sentence)
|
||||
except Exception as e:
|
||||
logging.error("Error adding sentence to queue: %s" % str(e))
|
||||
logging.error("Habitat - Error adding sentence to queue, queue full.")
|
||||
|
||||
def close(self):
|
||||
""" Shutdown uploader thread. """
|
||||
|
@ -320,7 +331,6 @@ class HabitatUploader(object):
|
|||
# post position to habitat
|
||||
resp = self.postListenerData(doc)
|
||||
if resp is True:
|
||||
logging.info("Habitat - Listener information uploaded.")
|
||||
return True
|
||||
else:
|
||||
logging.error("Habitat - Unable to upload listener information.")
|
||||
|
|
|
@ -53,6 +53,11 @@ def send_payload_summary(telemetry, port=55672, comment="HorusDemodLib"):
|
|||
if 'speed' in telemetry:
|
||||
packet['speed'] = telemetry['speed']
|
||||
|
||||
# Add in any field names from the custom field section
|
||||
if "custom_field_names" in telemetry:
|
||||
for _custom_field_name in telemetry["custom_field_names"]:
|
||||
if _custom_field_name in telemetry:
|
||||
packet[_custom_field_name] = telemetry[_custom_field_name]
|
||||
|
||||
# Set up our UDP socket
|
||||
_s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
|
|
|
@ -7,7 +7,7 @@ import requests
|
|||
import struct
|
||||
|
||||
# Global payload list - Basic version
|
||||
HORUS_PAYLOAD_LIST = {0:'4FSKTEST', 1:'HORUSBINARY', 257:'4FSKTEST32', 65535:'HORUSTEST'}
|
||||
HORUS_PAYLOAD_LIST = {0:'4FSKTEST', 1:'HORUSBINARY', 256: '4FSKTEST-V2'}
|
||||
|
||||
# URL for payload list
|
||||
PAYLOAD_ID_LIST_URL = "https://raw.githubusercontent.com/projecthorus/horusdemodlib/master/payload_id_list.txt"
|
||||
|
@ -35,18 +35,13 @@ HORUS_CUSTOM_FIELDS = {
|
|||
]
|
||||
},
|
||||
"4FSKTEST-V2": {
|
||||
"struct": "<BBBBBBBBB",
|
||||
"struct": "<hhBHxx",
|
||||
"fields": [
|
||||
["test_field 1", "none"],
|
||||
["test_field 2", "none"],
|
||||
["test_field 3", "none"],
|
||||
["test_field 4", "none"],
|
||||
["test_field 5", "none"],
|
||||
["test_field 6", "none"],
|
||||
["test_field 7", "none"],
|
||||
["test_field 8", "none"],
|
||||
["test_field 9", "none"],
|
||||
]
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -332,15 +327,20 @@ def update_payload_lists(payload_list, custom_field_list):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
# Setup Logging
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s %(levelname)s: %(message)s", level=logging.DEBUG
|
||||
)
|
||||
# Read command-line arguments
|
||||
parser = argparse.ArgumentParser(description="Test script for payload ID lists", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("--download", action="store_false", default=True, help="Download lists from github, then check")
|
||||
parser.add_argument("--print", action="store_true", default=False, help="Print content of payload ID lists")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging.DEBUG)
|
||||
|
||||
init_payload_id_list()
|
||||
print(HORUS_PAYLOAD_LIST)
|
||||
init_payload_id_list(nodownload=args.download)
|
||||
init_custom_field_list(nodownload=args.download)
|
||||
|
||||
init_custom_field_list()
|
||||
print(HORUS_CUSTOM_FIELDS)
|
||||
if args.print:
|
||||
print(HORUS_PAYLOAD_LIST)
|
||||
print(HORUS_CUSTOM_FIELDS)
|
|
@ -155,6 +155,18 @@ class SondehubAmateurUploader(object):
|
|||
# Datetime
|
||||
try:
|
||||
_datetime = fix_datetime(telemetry['time'])
|
||||
|
||||
# Compare system time and payload time, to look for issues where system time is way out.
|
||||
_timedelta = abs((_datetime - datetime.datetime.utcnow()).total_seconds())
|
||||
|
||||
if _timedelta > 3*60:
|
||||
# Greater than 3 minutes time difference. Discard packet in this case.
|
||||
self.log_error("Payload and Receiver times are offset by more than 3 minutes. Either payload does not have GNSS lock, or your system time is not set correctly. Not uploading.")
|
||||
return None
|
||||
|
||||
if _timedelta > 60:
|
||||
self.log_warning("Payload and Receiver times are offset by more than 1 minute. Either payload does not have GNSS lock, or your system time is not set correctly.")
|
||||
|
||||
_output["datetime"] = _datetime.strftime(
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ"
|
||||
)
|
||||
|
@ -165,7 +177,16 @@ class SondehubAmateurUploader(object):
|
|||
self.log_debug("Offending datetime_dt: %s" % str(telemetry["time"]))
|
||||
return None
|
||||
|
||||
# Callsign
|
||||
|
||||
|
||||
# Callsign - Break if this is an unknown payload ID.
|
||||
if telemetry["callsign"] == "UNKNOWN_PAYLOAD_ID":
|
||||
self.log_error("Not uploading telemetry from unknown payload ID. Is your payload ID list old?")
|
||||
return None
|
||||
|
||||
if '4FSKTEST' in telemetry['callsign']:
|
||||
self.log_warning(f"Payload ID {telemetry['callsign']} is for testing purposes only, and should not be used on an actual flight. Refer here: https://github.com/projecthorus/horusdemodlib/wiki#how-do-i-transmit-it")
|
||||
|
||||
_output['payload_callsign'] = telemetry["callsign"]
|
||||
|
||||
# Frame Number
|
||||
|
@ -188,6 +209,15 @@ class SondehubAmateurUploader(object):
|
|||
if telemetry["battery_voltage"] >= 0.0:
|
||||
_output["batt"] = telemetry["battery_voltage"]
|
||||
|
||||
if "speed" in telemetry:
|
||||
_output["speed"] = telemetry["speed"]
|
||||
|
||||
if "vel_h" in telemetry:
|
||||
_output["vel_h"] = telemetry["vel_h"]
|
||||
|
||||
if "vel_v" in telemetry:
|
||||
_output["vel_v"] = telemetry["vel_v"]
|
||||
|
||||
# Handle the additional SNR and frequency estimation if we have it
|
||||
if "snr" in telemetry:
|
||||
_output["snr"] = telemetry["snr"]
|
||||
|
@ -198,6 +228,22 @@ class SondehubAmateurUploader(object):
|
|||
if "raw" in telemetry:
|
||||
_output["raw"] = telemetry["raw"]
|
||||
|
||||
if "modulation" in telemetry:
|
||||
_output["modulation"] = telemetry["modulation"]
|
||||
|
||||
if "modulation_detail" in telemetry:
|
||||
_output["modulation_detail"] = telemetry["modulation_detail"]
|
||||
|
||||
if "baud_rate" in telemetry:
|
||||
_output["baud_rate"] = telemetry["baud_rate"]
|
||||
|
||||
# Add in any field names from the custom field section
|
||||
if "custom_field_names" in telemetry:
|
||||
for _custom_field_name in telemetry["custom_field_names"]:
|
||||
if _custom_field_name in telemetry:
|
||||
_output[_custom_field_name] = telemetry[_custom_field_name]
|
||||
|
||||
|
||||
logging.debug(f"Sondehub Amateur Uploader - Generated Packet: {str(_output)}")
|
||||
|
||||
return _output
|
||||
|
@ -298,7 +344,31 @@ class SondehubAmateurUploader(object):
|
|||
_upload_success = True
|
||||
break
|
||||
|
||||
elif _req.status_code == 500:
|
||||
elif _req.status_code == 202:
|
||||
# A 202 return code means there was some kind of data issue.
|
||||
# We expect a response of the form {"message": "error message", "errors":[], "warnings":[]}
|
||||
try:
|
||||
_resp_json = _req.json()
|
||||
|
||||
for _error in _resp_json['errors']:
|
||||
self.log_error("Payload data error: " + _error["error_message"])
|
||||
if 'payload' in _error:
|
||||
self.log_debug("Payload data associated with error: " + str(_error['payload']))
|
||||
|
||||
for _warning in _resp_json['warnings']:
|
||||
self.log_warning("Payload data warning: " + _warning["warning_message"])
|
||||
if 'payload' in _warning:
|
||||
self.log_debug("Payload data associated with warning: " + str(_warning['payload']))
|
||||
|
||||
except Exception as e:
|
||||
self.log_error("Error when parsing 202 response: %s" % str(e))
|
||||
self.log_debug("Content of 202 response: %s" % _req.text)
|
||||
|
||||
_upload_success = True
|
||||
break
|
||||
|
||||
|
||||
elif _req.status_code in [500,501,502,503,504]:
|
||||
# Server Error, Retry.
|
||||
_retries += 1
|
||||
continue
|
||||
|
@ -358,7 +428,8 @@ class SondehubAmateurUploader(object):
|
|||
headers=headers,
|
||||
)
|
||||
except Exception as e:
|
||||
self.log_error("Upload Failed: %s" % str(e))
|
||||
self.log_error("Station position upload failed: %s" % str(e))
|
||||
self.last_user_position_upload = time.time()
|
||||
return
|
||||
|
||||
if _req.status_code == 200:
|
||||
|
@ -427,6 +498,13 @@ class SondehubAmateurUploader(object):
|
|||
"""
|
||||
logging.error("Sondehub Amateur Uploader - %s" % line)
|
||||
|
||||
def log_warning(self, line):
|
||||
""" Helper function to log a warning message with a descriptive heading.
|
||||
Args:
|
||||
line (str): Message to be logged.
|
||||
"""
|
||||
logging.warning("Sondehub Amateur Uploader - %s" % line)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test Script
|
||||
|
|
|
@ -72,6 +72,7 @@ def main():
|
|||
# parser.add_argument("--summary", type=int, default=-1, help="Override user.cfg UDP Summary output port. (NOT IMPLEMENTED)")
|
||||
parser.add_argument("--freq_hz", type=float, default=None, help="Receiver IQ centre frequency in Hz, used in determine the absolute frequency of a telemetry burst.")
|
||||
parser.add_argument("--freq_target_hz", type=float, default=None, help="Receiver 'target' frequency in Hz, used to add metadata to station position info.")
|
||||
parser.add_argument("--baud_rate", type=int, default=None, help="Modulation baud rate (Hz), used to add additional metadata info.")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", default=False, help="Verbose output (set logging level to DEBUG)")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -97,6 +98,9 @@ def main():
|
|||
else:
|
||||
_logfile = None
|
||||
|
||||
# Some variables to handle re-downloading of payload ID lists.
|
||||
min_download_time = 30*60 # Only try and download new payload ID / custom field lists every 30 min.
|
||||
next_download_time = time.time()
|
||||
|
||||
if args.rtty == False:
|
||||
|
||||
|
@ -105,7 +109,7 @@ def main():
|
|||
horusdemodlib.payloads.HORUS_PAYLOAD_LIST = read_payload_list(filename=args.payload_list)
|
||||
horusdemodlib.payloads.HORUS_CUSTOM_FIELDS = read_custom_field_list(filename=args.custom_fields)
|
||||
else:
|
||||
# Downlaod
|
||||
# Download
|
||||
horusdemodlib.payloads.HORUS_PAYLOAD_LIST = init_payload_id_list(filename=args.payload_list)
|
||||
horusdemodlib.payloads.HORUS_CUSTOM_FIELDS = init_custom_field_list(filename=args.custom_fields)
|
||||
|
||||
|
@ -120,15 +124,6 @@ def main():
|
|||
else:
|
||||
_listener_freq_str = ""
|
||||
|
||||
habitat_uploader = HabitatUploader(
|
||||
user_callsign = user_config['user_call'],
|
||||
listener_lat = user_config['station_lat'],
|
||||
listener_lon = user_config['station_lon'],
|
||||
listener_radio = user_config['radio_comment'] + _listener_freq_str,
|
||||
listener_antenna = user_config['antenna_comment'],
|
||||
inhibit=args.noupload
|
||||
)
|
||||
|
||||
if user_config['station_lat'] == 0.0 and user_config['station_lon'] == 0.0:
|
||||
_sondehub_user_pos = None
|
||||
else:
|
||||
|
@ -138,7 +133,7 @@ def main():
|
|||
upload_rate = 2,
|
||||
user_callsign = user_config['user_call'],
|
||||
user_position = _sondehub_user_pos,
|
||||
user_radio = user_config['radio_comment'],
|
||||
user_radio = user_config['radio_comment'] + _listener_freq_str,
|
||||
user_antenna = user_config['antenna_comment'],
|
||||
software_name = "horusdemodlib",
|
||||
software_version = horusdemodlib.__version__,
|
||||
|
@ -158,7 +153,7 @@ def main():
|
|||
|
||||
if (data == ''):
|
||||
# Empty line means stdin has been closed.
|
||||
logging.info("Caught EOF, exiting.")
|
||||
logging.critical("Caught EOF (rtl_fm / horus_demod processes have exited, maybe because there's no RTLSDR?), exiting.")
|
||||
break
|
||||
|
||||
# Otherwise, strip any newlines, and continue.
|
||||
|
@ -182,14 +177,18 @@ def main():
|
|||
# Add in frequency estimate, if we have been supplied a receiver frequency.
|
||||
if args.freq_hz:
|
||||
_decoded['f_centre'] = int(demod_stats.fest_mean) + int(args.freq_hz)
|
||||
habitat_uploader.last_freq_hz = _decoded['f_centre']
|
||||
#habitat_uploader.last_freq_hz = _decoded['f_centre']
|
||||
|
||||
# Add in baud rate, if provided.
|
||||
if args.baud_rate:
|
||||
_decoded['baud_rate'] = int(args.baud_rate)
|
||||
|
||||
# Send via UDP
|
||||
send_payload_summary(_decoded, port=user_config['summary_port'])
|
||||
|
||||
# Upload the string to Habitat
|
||||
_decoded_str = "$$" + data.split('$')[-1] + '\n'
|
||||
habitat_uploader.add(_decoded_str)
|
||||
#_decoded_str = "$$" + data.split('$')[-1] + '\n'
|
||||
#habitat_uploader.add(_decoded_str)
|
||||
|
||||
# Upload the string to Sondehub Amateur
|
||||
sondehub_uploader.add(_decoded)
|
||||
|
@ -219,6 +218,23 @@ def main():
|
|||
try:
|
||||
_decoded = decode_packet(_binary_string)
|
||||
# If we get here, we have a valid packet!
|
||||
|
||||
if (_decoded['callsign'] == "UNKNOWN_PAYLOAD_ID") and not args.nodownload:
|
||||
# We haven't seen this payload ID. Our payload ID list might be out of date.
|
||||
if time.time() > next_download_time:
|
||||
logging.info("Observed unknown Payload ID, attempting to re-download lists.")
|
||||
|
||||
# Download lists.
|
||||
horusdemodlib.payloads.HORUS_PAYLOAD_LIST = init_payload_id_list(filename=args.payload_list)
|
||||
horusdemodlib.payloads.HORUS_CUSTOM_FIELDS = init_custom_field_list(filename=args.custom_fields)
|
||||
|
||||
# Update next_download_time so we don't re-attempt to download with every new packet.
|
||||
next_download_time = time.time() + min_download_time
|
||||
|
||||
# Re-attempt to decode the packet.
|
||||
_decoded = decode_packet(_binary_string)
|
||||
if _decoded['callsign'] != "UNKNOWN_PAYLOAD_ID":
|
||||
logging.info(f"Payload found in new payload ID list - {_decoded['callsign']}")
|
||||
|
||||
# Add in SNR data.
|
||||
_snr = demod_stats.snr
|
||||
|
@ -227,13 +243,17 @@ def main():
|
|||
# Add in frequency estimate, if we have been supplied a receiver frequency.
|
||||
if args.freq_hz:
|
||||
_decoded['f_centre'] = int(demod_stats.fest_mean) + int(args.freq_hz)
|
||||
habitat_uploader.last_freq_hz = _decoded['f_centre']
|
||||
#habitat_uploader.last_freq_hz = _decoded['f_centre']
|
||||
|
||||
# Add in baud rate, if provided.
|
||||
if args.baud_rate:
|
||||
_decoded['baud_rate'] = int(args.baud_rate)
|
||||
|
||||
# Send via UDP
|
||||
send_payload_summary(_decoded, port=user_config['summary_port'])
|
||||
|
||||
# Upload to Habitat
|
||||
habitat_uploader.add(_decoded['ukhas_str']+'\n')
|
||||
# Do not upload Horus Binary packets to the Habitat endpoint.
|
||||
# habitat_uploader.add(_decoded['ukhas_str']+'\n')
|
||||
|
||||
# Upload the string to Sondehub Amateur
|
||||
sondehub_uploader.add(_decoded)
|
||||
|
@ -243,13 +263,19 @@ def main():
|
|||
_logfile.flush()
|
||||
|
||||
logging.info(f"Decoded Binary Packet (SNR {demod_stats.snr:.1f} dB): {_decoded['ukhas_str']}")
|
||||
# Remove a few fields from the packet before printing.
|
||||
_temp_packet = _decoded.copy()
|
||||
_temp_packet.pop('packet_format')
|
||||
_temp_packet.pop('ukhas_str')
|
||||
logging.debug(f"Binary Packet Contents: {_temp_packet}")
|
||||
except Exception as e:
|
||||
logging.error(f"Decode Failed: {str(e)}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logging.info("Caught CTRL-C, exiting.")
|
||||
|
||||
habitat_uploader.close()
|
||||
#habitat_uploader.close()
|
||||
sondehub_uploader.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -22,7 +22,7 @@
|
|||
12, DK0WT-4FSK
|
||||
13, PE1ANS-4FSK
|
||||
14, WBLSCOUTS-4FSK
|
||||
15, PB0AHX-4FSK
|
||||
15, PA0CAB-4FSK
|
||||
16, PRESCOTTSOUTH
|
||||
17, EAGLE-1-4FSK
|
||||
18, VK8TH-4FSK
|
||||
|
@ -48,7 +48,7 @@
|
|||
38, DH3SUP-4FSK
|
||||
39, LUX2
|
||||
40, SP5YAM-4FSK
|
||||
41, PD5MF-4FSK
|
||||
41, PA5MF-4FSK
|
||||
42, SQ3XBD-4FSK
|
||||
43, SP6QKM-4FSK
|
||||
44, SQ6RQT-4FSK
|
||||
|
@ -63,7 +63,7 @@
|
|||
53, SP3MCY-4FSK
|
||||
54, VK3TRO-4FSK
|
||||
55, SP3QDX-4FSK
|
||||
56, SP10POW-4FSK
|
||||
56, SP3POW-4FSK
|
||||
57, SP8ESA-4FSK
|
||||
58, 9A4AM-4FSK
|
||||
59, 9A3ZI-4FSK
|
||||
|
@ -73,9 +73,9 @@
|
|||
63, SP6VWX-4FSK
|
||||
64, SV3IRG-4FSK
|
||||
65, LUX3
|
||||
66, OH3VHH-4FSK
|
||||
66, OH3HAB-4FSK
|
||||
67, SQ3KNL-4FSK
|
||||
68, PE1PSI-4FSK
|
||||
68, Flybag-2
|
||||
69, SP6KZ-4FSK
|
||||
70, LY2BAW-4FSK
|
||||
71, SQ2DEF-4FSK
|
||||
|
@ -94,14 +94,377 @@
|
|||
84, SP9DEV-4FSK
|
||||
85, 9A4VS-4FSK
|
||||
86, WD6DRI
|
||||
87, KD2EAT
|
||||
87, KD2EAT-4FSK
|
||||
88, SQ3KNO-4FSK
|
||||
89, SP6ZHP-4FSK
|
||||
90, UPANDUP
|
||||
91, 9A6NDZ-4FSK
|
||||
92, DG8JT-4FSK
|
||||
93, SP6HD-4FSK
|
||||
94, SP6SK-4FSK
|
||||
95, Peanut127
|
||||
96, Flybag-4FSK
|
||||
97, SQ1FYB-4FSK
|
||||
98, W3YP
|
||||
99, KN6SPE
|
||||
100, SQ3MP-4FSK
|
||||
101, KN6SNE
|
||||
102, KN6SNF
|
||||
103, SQ3TLE
|
||||
104, SP6TO
|
||||
105, PA3EIV-4FSK
|
||||
106, SP6V-4FSK
|
||||
107, PD7R-4FSK
|
||||
108, SP2SGF-4FSK
|
||||
109, SP5RAF-4FSK
|
||||
110, VK5ST-4FSK
|
||||
111, 9A1FAB-4FSK
|
||||
112, 9A1GIJ-4FSK
|
||||
113, 9A9Y-4FSK
|
||||
114, SQ7MHX-4FSK
|
||||
115, W2CXM-4FSK
|
||||
116, SP3WRO-4FSK
|
||||
117, SP3YDE-4FSK
|
||||
118, PE9GHZ-4FSK
|
||||
119, ICARO
|
||||
120, SP3GTP-4FSK
|
||||
121, SP3LM-4FSK
|
||||
122, SQ6ODL-4FSK
|
||||
123, HF8FRN-4FSK
|
||||
124, SP3CET-4FSK
|
||||
# IDs for 32-byte payloads
|
||||
256, 4FSKTEST-V2
|
||||
257, OH3VHH-4FSK-V2
|
||||
257, OH3HAB-4FSK-V2
|
||||
258, MAGNU-V2
|
||||
259, 9A4AM-V2
|
||||
260, Peanut127-V2
|
||||
261, 9A3ZI-V2
|
||||
262, SQ1FYB-V2
|
||||
263, SQ3MP-V2
|
||||
264, VK5BRL-V2
|
||||
265, PE2BZ-V2
|
||||
266, SQ9GIN-V2
|
||||
267, HF9ZHP-V2
|
||||
268, SP9DEV-1-V2
|
||||
269, SP9DEV-2-V2
|
||||
270, SP9DEV-3-V2
|
||||
271, W6SUN-V2
|
||||
272, WD6DRI-V2
|
||||
273, HAPPYSAT-V2
|
||||
274, SQ3TLE-8-V2
|
||||
275, SQ3TLE-9-V2
|
||||
276, SQ3TLE-10-V2
|
||||
277, SP6TO-V2
|
||||
278, N0UUU-V2
|
||||
279, SP6ZWR-V2
|
||||
280, SP6QKM-V2
|
||||
281, SQ6RQT-V2
|
||||
282, SQ6NLN-V2
|
||||
283, SP6ZHP-V2
|
||||
284, SQ6ODL-V2
|
||||
285, KQ6RS-V2
|
||||
286, KA9Q-V2
|
||||
287, KN6SPE-V2
|
||||
288, KN6SNE-V2
|
||||
289, KN6SNF-V2
|
||||
290, WB9COY-V2
|
||||
291, SP6V-V2
|
||||
292, SP6HD-V2
|
||||
293, PD7R-V2
|
||||
294, SQ6NEI-V2
|
||||
295, SP2SGF-V2
|
||||
296, PE1ANS-V2
|
||||
297, SQ7MHX-V2
|
||||
298, SP6KZ-V2
|
||||
299, VK3TRO-V2
|
||||
300, EMDRC-V2
|
||||
301, VK5ST-V2
|
||||
302, BAROSSA
|
||||
303, OH3HAB-2-4FSK-V2
|
||||
304, OH3HAB-3-4FSK-V2
|
||||
305, SN75ZOT-V2
|
||||
306, PD5A-4FSK
|
||||
307, PA3DJR-4FSK
|
||||
308, DJOAMF-V2
|
||||
309, SQ9HP-V2
|
||||
310, ON4IR
|
||||
311, ON3PFD
|
||||
312, PH1M-V2
|
||||
313, PSC1-4FSK
|
||||
314, KK6NOW
|
||||
315, SP3VSS-V2
|
||||
316, SP9ZHP-V2
|
||||
317, SP3CET-V2
|
||||
318, ON4IR-1
|
||||
319, ON4IR-2
|
||||
320, ON4IR-3
|
||||
321, ON4IR-4
|
||||
322, VK3PZ-V2
|
||||
323, DK7SCH
|
||||
324, PA3CPF-V2
|
||||
325, SP6TKN-V2
|
||||
326, ON5RTR
|
||||
327, ON4BCY
|
||||
328, ON2KGC
|
||||
329, SQ3KNL-V2
|
||||
330, KB3ZOX
|
||||
331, KB3ZOW
|
||||
332, SP3POW-V2
|
||||
333, G7PMO-V2
|
||||
334, PD2SDV
|
||||
335, PE1PSI
|
||||
336, KI5RQB
|
||||
337, YT3GTI-V2
|
||||
338, YU1WAT
|
||||
339, SQ9P-4FSK
|
||||
340, 9A6NDZ
|
||||
341, 9A4VS
|
||||
342, F5MVO
|
||||
343, F5RZC
|
||||
344, F6ASP
|
||||
345, MGGS-V2
|
||||
346, KI6ZUM
|
||||
347, KJ6KDZ
|
||||
348, DO2JMG
|
||||
349, BIOWL1
|
||||
350, AirSlicer-A
|
||||
351, AirSlicer-B
|
||||
352, DG0CCO
|
||||
353, SHSSP2023
|
||||
354, SP9ARK-V2
|
||||
355, ON2ON-V2
|
||||
356, CHEFERIK
|
||||
357, PD9BN-V2
|
||||
358, YU1WAT-2
|
||||
359, DF8AY
|
||||
360, SHSSPGEIGER
|
||||
361, VK2ZTH
|
||||
362, W3YP
|
||||
363, SP3BTT
|
||||
364, SP3OEY
|
||||
365, SP3QFO
|
||||
366, SQ3GOS
|
||||
367, YT1DRGTI
|
||||
368, AirSlicer-C
|
||||
369, AirSlicer-D
|
||||
370, DG2FS
|
||||
371, M7ONS
|
||||
372, K6UCI
|
||||
373, WOHA-4FSK
|
||||
374, OK1OMG
|
||||
375, OK1GAL
|
||||
376, OK1MDX
|
||||
377, OK1MDR
|
||||
378, F6KMF
|
||||
379, PD3EGE
|
||||
380, W0MXX
|
||||
381, VK2ARX
|
||||
382, Altostratus
|
||||
383, SpaceBoy
|
||||
384, F5APQ
|
||||
385, F4HRD
|
||||
386, F1OIL
|
||||
387, SQ3RAX
|
||||
388, KC1MOL
|
||||
389, ALIEN-UFO-1
|
||||
390, DO2LMV
|
||||
391, YU4BRE
|
||||
392, SP3WRO
|
||||
393, DM7RM-2
|
||||
394, ON3RC
|
||||
395, F4HQO
|
||||
396, YU4VMP
|
||||
397, IKAR-1
|
||||
398, PD2HSB
|
||||
399, YU7PDA
|
||||
400, F1DZP
|
||||
401, SP6ONZ
|
||||
402, OH3CUF
|
||||
403, W3YP-2
|
||||
404, IW1DBF
|
||||
405, SP7AR
|
||||
406, VK3DNS-JMSS
|
||||
407, HA8LOU
|
||||
408, DM2DL
|
||||
409, W1STR
|
||||
410, WS-RS1
|
||||
411, WS-RS2
|
||||
412, FHS-RS1
|
||||
413, SP3ABS-V2
|
||||
414, SP5LOT
|
||||
415, 9A7DI
|
||||
416, SQ5RB
|
||||
417, KD2EAT
|
||||
418, W2CXM
|
||||
419, OM2OFA-1
|
||||
420, OM2OFA-2
|
||||
421, OM2OFA-3
|
||||
422, OM2OFA-4
|
||||
423, OM2OFA-5
|
||||
424, OM2OFA-6
|
||||
425, DO5IO
|
||||
426, SP5GFN
|
||||
427, PA5MF
|
||||
428, KN6ZTT
|
||||
429, SP3ZHP
|
||||
430, HG8LXL
|
||||
431, SQ3XBD
|
||||
432, SOLflight
|
||||
433, W1U
|
||||
434, MONASAT
|
||||
435, SP3ET-2
|
||||
436, SNSF01
|
||||
437, DJ9AS
|
||||
438, F8DKG
|
||||
439, SP5WWL
|
||||
440, SP5YAM
|
||||
441, KB4RKS
|
||||
442, O38
|
||||
443, SOLARBAG
|
||||
444, DL1MRZ-1
|
||||
445, DL1MRZ-2
|
||||
446, DL1MRZ-3
|
||||
447, DL1MRZ-4
|
||||
448, KI5RZY
|
||||
449, PA0CAB
|
||||
450, DL6PI
|
||||
451, PA3GPU-GSN-4FSK
|
||||
452, FUN-4FSK
|
||||
453, KE2BOK
|
||||
454, SP4CE
|
||||
455, FLIGHTDESIGN
|
||||
456, FLIGHTDESIGN2
|
||||
457, KM6OIR
|
||||
458, M7NGO
|
||||
459, DK0WT-V2
|
||||
460, PD2JM
|
||||
461, DB7XO
|
||||
462, FEMTO
|
||||
463, OH7FES
|
||||
464, K4KDR
|
||||
465, 2E0NNF
|
||||
466, OK1RAJ
|
||||
467, COSMO-PK
|
||||
468, FlightKW
|
||||
469, AJ4XE
|
||||
470, WJ2B
|
||||
471, BINAR
|
||||
472, SKYSITE
|
||||
473, KO6BXV
|
||||
474, KO6BZF
|
||||
475, KO6CAP
|
||||
476, KO6BZU
|
||||
477, KO6BYH
|
||||
478, KO6BZW
|
||||
479, KO6BZV
|
||||
480, KO6CAM
|
||||
481, KO6CAN
|
||||
482, KO6CAO
|
||||
483, KO6CAC
|
||||
484, KO6BZN
|
||||
485, KG6EQU
|
||||
486, DF7PN
|
||||
487, WOHA-SOLAR
|
||||
488, F5IKO
|
||||
489, DJ2DS
|
||||
490, DS-11
|
||||
491, DO2LYB
|
||||
492, Stratonaut
|
||||
493, DK7TD
|
||||
494, DC1NSK
|
||||
495, DL2ALY
|
||||
496, StratoSack
|
||||
497, SP9SKP
|
||||
498, SP6ZWR
|
||||
499, SP3RST
|
||||
500, SP2ROC
|
||||
501, KD2EAT-DFM
|
||||
502, SN32WOSP
|
||||
503, KA7NSR
|
||||
504, Lyoner
|
||||
505, G8FJG
|
||||
506, IW1DBF
|
||||
507, ICARO
|
||||
508, DEDALO
|
||||
509, DL1XH
|
||||
510, F4EHY
|
||||
511, IU0MUN
|
||||
512, IZ0CGP
|
||||
513, VK3FUR
|
||||
514, RXSONDE
|
||||
515, Molly-1
|
||||
516, IT9EWK
|
||||
517, NEC_CQ73
|
||||
518, YT1C
|
||||
519, 9A4GE
|
||||
520, PE9M
|
||||
521, PD3T
|
||||
522, PE1GLG
|
||||
523, KITE-1
|
||||
524, 9A1FER
|
||||
525, SP6QKM
|
||||
526, SP8KDE
|
||||
527, SQ8AOL
|
||||
528, SQ9AOL
|
||||
529, NANO-1
|
||||
530, SKPQKM
|
||||
531, DG4CG
|
||||
532, KJ0RE
|
||||
533, PA4VB
|
||||
534, PA4VB-KITE
|
||||
535, F6AGV-BHAF
|
||||
536, KE2CBS
|
||||
537, PA3EIV
|
||||
538, PA9K
|
||||
539, YU7TDA
|
||||
540, 9A4GE-2
|
||||
541, F1AQE
|
||||
542, PD1EG
|
||||
543, Xpico
|
||||
544, SP7XSS
|
||||
545, SP7XSS-2
|
||||
546, SP9RKF
|
||||
547, KB3ZOX-2
|
||||
548, 9A3SWO
|
||||
549, KD2KPZ
|
||||
550, F4KLR
|
||||
551, PD1NW
|
||||
552, PA3ADE-KITE
|
||||
553, SQ6DAG
|
||||
554, StratoSoar
|
||||
555, DG4CG-2
|
||||
556, DG4CG-3
|
||||
557, DG4CG-1
|
||||
558, AK3F
|
||||
559, Martinus-MZ
|
||||
560, DG4CG-4
|
||||
561, DG4CG-5
|
||||
562, DG4CG-6
|
||||
563, SQ2CPA
|
||||
564, Party-Time
|
||||
565, PWrSpace-in
|
||||
566, RFA-0
|
||||
567, RFA-1
|
||||
568, RFA-2
|
||||
569, 9A4GE-3
|
||||
570, MATHRO
|
||||
571, RUEDGUE
|
||||
572, IDA
|
||||
573, DL9GKJ
|
||||
574, PA0ESH
|
||||
575, Ypico
|
||||
576, SP9WAK
|
||||
577, DG4CG-Solar
|
||||
578, DB1TH
|
||||
579, DL1BRF
|
||||
580, PD7BOR
|
||||
581, PA0CAB-lastflight
|
||||
582, SQ2CPA-31
|
||||
583, SQ2CPA-32
|
||||
584, SQ2CPA-33
|
||||
31415, HORUS-V2
|
||||
31416, ITSWINDY
|
||||
31417, HORUSRADMON
|
||||
31418, HORUSGEIGER
|
||||
31419, VI25AREG
|
||||
31420, VK5ARG
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "horusdemodlib"
|
||||
version = "0.3.2"
|
||||
version = "0.3.13"
|
||||
description = "Project Horus HAB Telemetry Demodulators"
|
||||
authors = ["Mark Jessop"]
|
||||
license = "LGPL-2.1-or-later"
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Dual Horus Binary Decoder Script
|
||||
# Intended for use with Dual Launches, where both launches have 4FSK payloads closely spaced (~10 kHz)
|
||||
#
|
||||
# The SDR is tuned 5 kHz below the Lower 4FSK frequency, and the frequency estimators are set across the two frequencies.
|
||||
# Modem statistics are sent out via a new 'MODEM_STATS' UDP broadcast message every second.
|
||||
#
|
||||
|
||||
# Calculate the frequency estimator limits
|
||||
# Note - these are somewhat hard-coded for this dual-RX application.
|
||||
MFSK1_LOWER=$(echo "$MFSK1_SIGNAL - $RXBANDWIDTH/2" | bc)
|
||||
MFSK1_UPPER=$(echo "$MFSK1_SIGNAL + $RXBANDWIDTH/2" | bc)
|
||||
MFSK1_CENTRE=$(echo "$RXFREQ + $MFSK1_SIGNAL" | bc)
|
||||
|
||||
MFSK2_LOWER=$(echo "$MFSK2_SIGNAL - $RXBANDWIDTH/2" | bc)
|
||||
MFSK2_UPPER=$(echo "$MFSK2_SIGNAL + $RXBANDWIDTH/2" | bc)
|
||||
MFSK2_CENTRE=$(echo "$RXFREQ + $MFSK2_SIGNAL" | bc)
|
||||
|
||||
echo "Using SDR Centre Frequency: $RXFREQ Hz."
|
||||
echo "Using MFSK1 estimation range: $MFSK1_LOWER - $MFSK1_UPPER Hz"
|
||||
echo "Using MFSK2 estimation range: $MFSK2_LOWER - $MFSK2_UPPER Hz"
|
||||
|
||||
BIAS_SETTING=""
|
||||
|
||||
if [ "$BIAS" = "1" ]; then
|
||||
echo "Enabling Bias Tee."
|
||||
BIAS_SETTING=" -T"
|
||||
fi
|
||||
|
||||
GAIN_SETTING=""
|
||||
if [ "$GAIN" = "0" ]; then
|
||||
echo "Using AGC."
|
||||
GAIN_SETTING=""
|
||||
else
|
||||
echo "Using Manual Gain"
|
||||
GAIN_SETTING=" -g $GAIN"
|
||||
fi
|
||||
|
||||
STATS_SETTING=""
|
||||
|
||||
if [ "$STATS_OUTPUT" = "1" ]; then
|
||||
echo "Enabling Modem Statistics."
|
||||
STATS_SETTING=" --stats=100"
|
||||
fi
|
||||
|
||||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($RXFREQ) and 'target' signal frequency ($MFSK1_CENTRE)
|
||||
# to enable providing additional metadata to Habitat / Sondehub.
|
||||
rtl_fm -M raw -F9 -d $SDR_DEVICE -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $RXFREQ | tee >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK1_LOWER --fsk_upper=$MFSK1_UPPER - - | python3 -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK1_CENTRE ) >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK2_LOWER --fsk_upper=$MFSK2_UPPER - - | python3 -m horusdemodlib.uploader --freq_hz $RXFREQ ) > /dev/null
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Dual RTTY / Horus Binary Decoder Script
|
||||
# Intended for use on Horus flights, with the following payload frequencies:
|
||||
# RTTY: 434.650 MHz - Callsign 'HORUS'
|
||||
# MFSK: 434.660 MHz - Callsign 'HORUSBINARY'
|
||||
#
|
||||
# The SDR is tuned 5 kHz below the RTTY frequency, and the frequency estimators are set across the two frequencies.
|
||||
# Modem statistics are sent out via a new 'MODEM_STATS' UDP broadcast message every second.
|
||||
#
|
||||
|
||||
# Calculate the frequency estimator limits
|
||||
# Note - these are somewhat hard-coded for this dual-RX application.
|
||||
RTTY_LOWER=$(echo "$RTTY_SIGNAL - $RXBANDWIDTH/2" | bc)
|
||||
RTTY_UPPER=$(echo "$RTTY_SIGNAL + $RXBANDWIDTH/2" | bc)
|
||||
RTTY_CENTRE=$(echo "$RXFREQ + $RTTY_SIGNAL" | bc)
|
||||
|
||||
MFSK_LOWER=$(echo "$MFSK_SIGNAL - $RXBANDWIDTH/2" | bc)
|
||||
MFSK_UPPER=$(echo "$MFSK_SIGNAL + $RXBANDWIDTH/2" | bc)
|
||||
MFSK_CENTRE=$(echo "$RXFREQ + $MFSK_SIGNAL" | bc)
|
||||
|
||||
echo "Using SDR Centre Frequency: $RXFREQ Hz."
|
||||
echo "Using RTTY estimation range: $RTTY_LOWER - $RTTY_UPPER Hz"
|
||||
echo "Using MFSK estimation range: $MFSK_LOWER - $MFSK_UPPER Hz"
|
||||
|
||||
BIAS_SETTING=""
|
||||
|
||||
if [ "$BIAS" = "1" ]; then
|
||||
echo "Enabling Bias Tee."
|
||||
BIAS_SETTING=" -T"
|
||||
fi
|
||||
|
||||
GAIN_SETTING=""
|
||||
if [ "$GAIN" = "0" ]; then
|
||||
echo "Using AGC."
|
||||
GAIN_SETTING=""
|
||||
else
|
||||
echo "Using Manual Gain"
|
||||
GAIN_SETTING=" -g $GAIN"
|
||||
fi
|
||||
|
||||
STATS_SETTING=""
|
||||
|
||||
if [ "$STATS_OUTPUT" = "1" ]; then
|
||||
echo "Enabling Modem Statistics."
|
||||
STATS_SETTING=" --stats=100"
|
||||
fi
|
||||
|
||||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($RXFREQ) and 'target' signal frequency ($RTTY_CENTRE / $MFSK_CENTRE)
|
||||
# to enable providing additional metadata to Habitat / Sondehub.
|
||||
rtl_fm -M raw -F9 -d $SDR_DEVICE -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $RXFREQ | tee >($DECODER -q --stats=5 -g -m RTTY --fsk_lower=$RTTY_LOWER --fsk_upper=$RTTY_UPPER - - | python3 -m horusdemodlib.uploader --rtty --freq_hz $RXFREQ --freq_target_hz $RTTY_CENTRE ) >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK_LOWER --fsk_upper=$MFSK_UPPER - - | python3 -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK_CENTRE ) > /dev/null
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Horus Binary RTLSDR Helper Script
|
||||
#
|
||||
# Uses rtl_fm to receive a chunk of spectrum, and passes it into horus_demod.
|
||||
#
|
||||
|
||||
# Calculate the SDR tuning frequency
|
||||
SDR_RX_FREQ=$(echo "$RXFREQ - $RXBANDWIDTH/2 - 1000" | bc)
|
||||
|
||||
# Calculate the frequency estimator limits
|
||||
FSK_LOWER=1000
|
||||
FSK_UPPER=$(echo "$FSK_LOWER + $RXBANDWIDTH" | bc)
|
||||
|
||||
echo "Using SDR Centre Frequency: $SDR_RX_FREQ Hz."
|
||||
echo "Using FSK estimation range: $FSK_LOWER - $FSK_UPPER Hz"
|
||||
|
||||
BIAS_SETTING=""
|
||||
|
||||
if [ "$BIAS" = "1" ]; then
|
||||
echo "Enabling Bias Tee."
|
||||
BIAS_SETTING=" -T"
|
||||
fi
|
||||
|
||||
GAIN_SETTING=""
|
||||
if [ "$GAIN" = "0" ]; then
|
||||
echo "Using AGC."
|
||||
GAIN_SETTING=""
|
||||
else
|
||||
echo "Using Manual Gain"
|
||||
GAIN_SETTING=" -g $GAIN"
|
||||
fi
|
||||
|
||||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($SDR_RX_FREQ) and 'target' signal frequency ($RXFREQ)
|
||||
# to enable providing additional metadata to Habitat / Sondehub.
|
||||
rtl_fm -M raw -F9 -d $SDR_DEVICE -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $SDR_RX_FREQ | $DECODER -q --stats=5 -g -m binary --fsk_lower=$FSK_LOWER --fsk_upper=$FSK_UPPER - - | python3 -m horusdemodlib.uploader --freq_hz $SDR_RX_FREQ --freq_target_hz $RXFREQ $@
|
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Horus Binary RTLSDR Helper Script
|
||||
#
|
||||
# Uses rtl_fm to receive a chunk of spectrum, and passes it into horus_demod.
|
||||
#
|
||||
|
||||
# Calculate the SDR tuning frequency
|
||||
SDR_RX_FREQ=$(echo "$RXFREQ - $RXBANDWIDTH/2 - 1000" | bc)
|
||||
|
||||
# Calculate the frequency estimator limits
|
||||
FSK_LOWER=1000
|
||||
FSK_UPPER=$(echo "$FSK_LOWER + $RXBANDWIDTH" | bc)
|
||||
|
||||
echo "Using SDR Centre Frequency: $SDR_RX_FREQ Hz."
|
||||
echo "Using FSK estimation range: $FSK_LOWER - $FSK_UPPER Hz"
|
||||
|
||||
BIAS_SETTING=""
|
||||
|
||||
if [ "$BIAS" = "1" ]; then
|
||||
echo "Enabling Bias Tee."
|
||||
BIAS_SETTING=" -T"
|
||||
fi
|
||||
|
||||
GAIN_SETTING=""
|
||||
if [ "$GAIN" = "0" ]; then
|
||||
echo "Using AGC."
|
||||
GAIN_SETTING=""
|
||||
else
|
||||
echo "Using Manual Gain"
|
||||
GAIN_SETTING=" -g $GAIN"
|
||||
fi
|
||||
|
||||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($SDR_RX_FREQ) and 'target' signal frequency ($RXFREQ)
|
||||
# to enable providing additional metadata to Habitat / Sondehub.
|
||||
rx_fm $SDR_EXTRA -M raw -F9 -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $SDR_RX_FREQ | $DECODER -q --stats=5 -g -m binary --fsk_lower=$FSK_LOWER --fsk_upper=$FSK_UPPER - - | python3 -m horusdemodlib.uploader --freq_hz $SDR_RX_FREQ --freq_target_hz $RXFREQ $@
|
||||
|
|
@ -63,3 +63,5 @@ add_definitions(-DHORUS_L2_RX -DINTERLEAVER -DSCRAMBLER -DRUN_TIME_TABLES)
|
|||
add_executable(horus_demod horus_demod.c horus_api.c horus_l2.c golay23.c fsk.c kiss_fft.c)
|
||||
target_link_libraries(horus_demod m horus ${CMAKE_REQUIRED_LIBRARIES})
|
||||
|
||||
install(TARGETS fsk_mod fsk_demod fsk_get_test_bits fsk_put_test_bits drs232 drs232_ldpc horus_gen_test_bits horus_demod DESTINATION bin)
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ int main(int argc, char *argv[]) {
|
|||
float loop_time;
|
||||
int enable_stats = 0;
|
||||
int quadrature = 0;
|
||||
int fsk_lower = -1;
|
||||
int fsk_lower = -99999;
|
||||
int fsk_upper = -1;
|
||||
int Rs = -1;
|
||||
int tone_spacing = -1;
|
||||
|
@ -224,9 +224,11 @@ int main(int argc, char *argv[]) {
|
|||
stats_ctr = 0;
|
||||
}
|
||||
|
||||
if((fsk_lower> 0) && (fsk_upper > fsk_lower)){
|
||||
if((fsk_upper > fsk_lower) && (fsk_lower > -99999)){
|
||||
horus_set_freq_est_limits(hstates, fsk_lower, fsk_upper);
|
||||
fprintf(stderr,"Setting estimator limits to %d to %d Hz.\n",fsk_lower, fsk_upper);
|
||||
} else {
|
||||
printf(stderr,"Not setting estimator limits, upper must be higher than lower.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
//
|
||||
// An approximation of the function
|
||||
//
|
||||
// This file is generated by the gen_phi0 scritps
|
||||
// This file is generated by the gen_phi0 script, from codec2
|
||||
// https://github.com/drowe67/codec2/blob/master/script/gen_phi0
|
||||
// Any changes should be made to that file, not this one
|
||||
|
||||
#include <stdint.h>
|
||||
|
|
|
@ -29,6 +29,11 @@ MFSK2_SIGNAL=15000
|
|||
# but the higher the chance that the modem will lock on to a strong spurious signal.
|
||||
RXBANDWIDTH=5000
|
||||
|
||||
# RTLSDR Device Selection
|
||||
# If you want to use a specific RTLSDR, you can change this setting to match the
|
||||
# device identifier of your SDR (use rtl_test to get a list)
|
||||
SDR_DEVICE=0
|
||||
|
||||
# Receiver Gain. Set this to 0 to use automatic gain control, otherwise if running a
|
||||
# preamplifier, you may want to experiment with different gain settings to optimize
|
||||
# your receiver setup.
|
||||
|
@ -113,4 +118,4 @@ fi
|
|||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($RXFREQ) and 'target' signal frequency ($MFSK1_CENTRE)
|
||||
# to enable providing additional metadata to Habitat / Sondehub.
|
||||
rtl_fm -M raw -F9 -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $RXFREQ | tee >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK1_LOWER --fsk_upper=$MFSK1_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK1_CENTRE ) >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK2_LOWER --fsk_upper=$MFSK2_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ ) > /dev/null
|
||||
rtl_fm -M raw -F9 -d $SDR_DEVICE -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $RXFREQ | tee >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK1_LOWER --fsk_upper=$MFSK1_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK1_CENTRE ) >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK2_LOWER --fsk_upper=$MFSK2_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ ) > /dev/null
|
||||
|
|
|
@ -31,6 +31,11 @@ MFSK_SIGNAL=15000
|
|||
# but the higher the chance that the modem will lock on to a strong spurious signal.
|
||||
RXBANDWIDTH=8000
|
||||
|
||||
# RTLSDR Device Selection
|
||||
# If you want to use a specific RTLSDR, you can change this setting to match the
|
||||
# device identifier of your SDR (use rtl_test to get a list)
|
||||
SDR_DEVICE=0
|
||||
|
||||
# Receiver Gain. Set this to 0 to use automatic gain control, otherwise if running a
|
||||
# preamplifier, you may want to experiment with different gain settings to optimize
|
||||
# your receiver setup.
|
||||
|
@ -115,4 +120,4 @@ fi
|
|||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($RXFREQ) and 'target' signal frequency ($RTTY_CENTRE / $MFSK_CENTRE)
|
||||
# to enable providing additional metadata to Habitat / Sondehub.
|
||||
rtl_fm -M raw -F9 -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $RXFREQ | tee >($DECODER -q --stats=5 -g -m RTTY --fsk_lower=$RTTY_LOWER --fsk_upper=$RTTY_UPPER - - | python -m horusdemodlib.uploader --rtty --freq_hz $RXFREQ --freq_target_hz $RTTY_CENTRE ) >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK_LOWER --fsk_upper=$MFSK_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK_CENTRE ) > /dev/null
|
||||
rtl_fm -M raw -F9 -d $SDR_DEVICE -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $RXFREQ | tee >($DECODER -q --stats=5 -g -m RTTY --fsk_lower=$RTTY_LOWER --fsk_upper=$RTTY_UPPER - - | python -m horusdemodlib.uploader --rtty --freq_hz $RXFREQ --freq_target_hz $RTTY_CENTRE ) >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK_LOWER --fsk_upper=$MFSK_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK_CENTRE ) > /dev/null
|
||||
|
|
|
@ -15,6 +15,12 @@ cd /home/pi/horusdemodlib/
|
|||
# Note: The SDR will be tuned to RXBANDWIDTH/2 below this frequency.
|
||||
RXFREQ=434200000
|
||||
|
||||
|
||||
# RTLSDR Device Selection
|
||||
# If you want to use a specific RTLSDR, you can change this setting to match the
|
||||
# device identifier of your SDR (use rtl_test to get a list)
|
||||
SDR_DEVICE=0
|
||||
|
||||
# Receiver Gain. Set this to 0 to use automatic gain control, otherwise if running a
|
||||
# preamplifier, you may want to experiment with different gain settings to optimize
|
||||
# your receiver setup.
|
||||
|
@ -91,4 +97,4 @@ fi
|
|||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($SDR_RX_FREQ) and 'target' signal frequency ($RXFREQ)
|
||||
# to enable providing additional metadata to Habitat / Sondehub.
|
||||
rtl_fm -M raw -F9 -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $SDR_RX_FREQ | $DECODER -q --stats=5 -g -m binary --fsk_lower=$FSK_LOWER --fsk_upper=$FSK_UPPER - - | python -m horusdemodlib.uploader --freq_hz $SDR_RX_FREQ --freq_target_hz $RXFREQ $@
|
||||
rtl_fm -M raw -F9 -d $SDR_DEVICE -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $SDR_RX_FREQ | $DECODER -q --stats=5 -g -m binary --fsk_lower=$FSK_LOWER --fsk_upper=$FSK_UPPER - - | python -m horusdemodlib.uploader --freq_hz $SDR_RX_FREQ --freq_target_hz $RXFREQ $@
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Horus Binary *Triple* Decoder Script
|
||||
# Intended for situations with three payloads in the air, spaced ~10 kHz apart.
|
||||
# NOTE - Ensure your horus_demod build is from newer than ~5th April 2024, else this
|
||||
# will not work correctly!
|
||||
#
|
||||
# It also possible to extend this approach further to handle as many transmissions
|
||||
# as can be safely fitted into the receiver passband (+/- 24 kHz). Spacings of
|
||||
# 5 kHz between transmissions is possible with frequency stable transmitters (e.g. RS41s)
|
||||
# Don't try this with DFM17's, which drift a lot!
|
||||
#
|
||||
|
||||
# Change directory to the horusdemodlib directory.
|
||||
# If running as a different user, you will need to change this line
|
||||
cd /home/pi/horusdemodlib/
|
||||
|
||||
# The following settings are an example for a situation where there are three transmitters in the air,
|
||||
# on: 434.190 MHz, 434.200 MHz, and 434.210 MHz.
|
||||
|
||||
# The *centre* frequency of the SDR Receiver, in Hz.
|
||||
# It's recommended that this not be tuned directly on top of one of the signals we want to receive,
|
||||
# as sometimes we can get a DC spike which can affect the demodulators.
|
||||
# In this example the receiver has been tuned in the middle of 2 of the signals, at 434.195 MHz.
|
||||
RXFREQ=434195000
|
||||
|
||||
# Where to find the first signal - in this case at 434.190 MHz, so -5000 Hz below the centre.
|
||||
MFSK1_SIGNAL=-5000
|
||||
|
||||
# Where to find the second signal - in this case at 434.200 MHz, so 5000 Hz above the centre.
|
||||
MFSK2_SIGNAL=5000
|
||||
|
||||
# Where to find the third signal - in this case at 434.210 MHz, so 15000 Hz above the centre.
|
||||
MFSK3_SIGNAL=15000
|
||||
|
||||
# Frequency estimator bandwidth. The wider the bandwidth, the more drift and frequency error the modem can tolerate,
|
||||
# but the higher the chance that the modem will lock on to a strong spurious signal.
|
||||
RXBANDWIDTH=8000
|
||||
|
||||
# RTLSDR Device Selection
|
||||
# If you want to use a specific RTLSDR, you can change this setting to match the
|
||||
# device identifier of your SDR (use rtl_test to get a list)
|
||||
SDR_DEVICE=0
|
||||
|
||||
# Receiver Gain. Set this to 0 to use automatic gain control, otherwise if running a
|
||||
# preamplifier, you may want to experiment with different gain settings to optimize
|
||||
# your receiver setup.
|
||||
# You can find what gain range is valid for your RTLSDR by running: rtl_test
|
||||
GAIN=0
|
||||
|
||||
# Bias Tee Enable (1) or Disable (0)
|
||||
# NOTE: This uses the -T bias-tee option which is only available on recent versions
|
||||
# of rtl-sdr. Check if your version has this option by running rtl_fm --help and looking
|
||||
# for it in the option list.
|
||||
# If not, you may need to uninstall that version, and then compile from source: https://github.com/osmocom/rtl-sdr
|
||||
BIAS=0
|
||||
|
||||
# Receiver PPM offset
|
||||
PPM=0
|
||||
|
||||
|
||||
|
||||
|
||||
# Check that the horus_demod decoder has been compiled.
|
||||
DECODER=./build/src/horus_demod
|
||||
if [ -f "$DECODER" ]; then
|
||||
echo "Found horus_demod."
|
||||
else
|
||||
echo "ERROR - $DECODER does not exist - have you compiled it yet?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that bc is available on the system path.
|
||||
if echo "1+1" | bc > /dev/null; then
|
||||
echo "Found bc."
|
||||
else
|
||||
echo "ERROR - Cannot find bc - Did you install it?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use a local venv if it exists
|
||||
VENV_DIR=venv
|
||||
if [ -d "$VENV_DIR" ]; then
|
||||
echo "Entering venv."
|
||||
source $VENV_DIR/bin/activate
|
||||
fi
|
||||
|
||||
|
||||
# Calculate the frequency estimator limits for each decoder
|
||||
MFSK1_LOWER=$(echo "$MFSK1_SIGNAL - $RXBANDWIDTH/2" | bc)
|
||||
MFSK1_UPPER=$(echo "$MFSK1_SIGNAL + $RXBANDWIDTH/2" | bc)
|
||||
MFSK1_CENTRE=$(echo "$RXFREQ + $MFSK1_SIGNAL" | bc)
|
||||
|
||||
MFSK2_LOWER=$(echo "$MFSK2_SIGNAL - $RXBANDWIDTH/2" | bc)
|
||||
MFSK2_UPPER=$(echo "$MFSK2_SIGNAL + $RXBANDWIDTH/2" | bc)
|
||||
MFSK2_CENTRE=$(echo "$RXFREQ + $MFSK2_SIGNAL" | bc)
|
||||
|
||||
MFSK3_LOWER=$(echo "$MFSK3_SIGNAL - $RXBANDWIDTH/2" | bc)
|
||||
MFSK3_UPPER=$(echo "$MFSK3_SIGNAL + $RXBANDWIDTH/2" | bc)
|
||||
MFSK3_CENTRE=$(echo "$RXFREQ + $MFSK3_SIGNAL" | bc)
|
||||
|
||||
echo "Using SDR Centre Frequency: $RXFREQ Hz."
|
||||
echo "Using MFSK1 estimation range: $MFSK1_LOWER - $MFSK1_UPPER Hz"
|
||||
echo "Using MFSK2 estimation range: $MFSK2_LOWER - $MFSK2_UPPER Hz"
|
||||
echo "Using MFSK3 estimation range: $MFSK3_LOWER - $MFSK3_UPPER Hz"
|
||||
|
||||
BIAS_SETTING=""
|
||||
|
||||
if [ "$BIAS" = "1" ]; then
|
||||
echo "Enabling Bias Tee."
|
||||
BIAS_SETTING=" -T"
|
||||
fi
|
||||
|
||||
GAIN_SETTING=""
|
||||
if [ "$GAIN" = "0" ]; then
|
||||
echo "Using AGC."
|
||||
GAIN_SETTING=""
|
||||
else
|
||||
echo "Using Manual Gain"
|
||||
GAIN_SETTING=" -g $GAIN"
|
||||
fi
|
||||
|
||||
STATS_SETTING=""
|
||||
|
||||
if [ "$STATS_OUTPUT" = "1" ]; then
|
||||
echo "Enabling Modem Statistics."
|
||||
STATS_SETTING=" --stats=100"
|
||||
fi
|
||||
|
||||
# Start the receive chain.
|
||||
# Note that we now pass in the SDR centre frequency ($RXFREQ) and 'target' signal frequency ($MFSK1_CENTRE)
|
||||
# to enable providing additional metadata to SondeHub
|
||||
rtl_fm -M raw -F9 -d $SDR_DEVICE -s 48000 -p $PPM $GAIN_SETTING$BIAS_SETTING -f $RXFREQ \
|
||||
| tee >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK1_LOWER --fsk_upper=$MFSK1_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK1_CENTRE ) \
|
||||
| tee >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK3_LOWER --fsk_upper=$MFSK3_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ --freq_target_hz $MFSK3_CENTRE ) \
|
||||
>($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK2_LOWER --fsk_upper=$MFSK2_UPPER - - | python -m horusdemodlib.uploader --freq_hz $RXFREQ ) > /dev/null
|
|
@ -3,8 +3,7 @@
|
|||
#
|
||||
|
||||
[user]
|
||||
# Your callsign - used when uploading to the HabHub Tracker.
|
||||
# Note that we now also upload to the experimental SondeHub Amateur DB as well by default.
|
||||
# Your callsign - used when uploading to the SondeHub-Amateur Tracker
|
||||
callsign = YOUR_CALL_HERE
|
||||
|
||||
# Your station latitude/longitude, which will show up on tracker.habhub.org.
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
### General SDR settings ###
|
||||
|
||||
# RTLSDR Device Selection
|
||||
# If you want to use a specific RTLSDR, you can change this setting to match the
|
||||
# device identifier of your SDR (use rtl_test to get a list)
|
||||
SDR_DEVICE=0
|
||||
|
||||
# Receiver Gain. Set this to 0 to use automatic gain control, otherwise if running a
|
||||
# preamplifier, you may want to experiment with different gain settings to optimize
|
||||
# your receiver setup.
|
||||
# You can find what gain range is valid for your RTLSDR by running: rtl_test
|
||||
GAIN=0
|
||||
|
||||
# Bias Tee Enable (1) or Disable (0)
|
||||
BIAS=0
|
||||
|
||||
# Receiver PPM offset
|
||||
PPM=0
|
||||
|
||||
# Enable (1) or disable (0) modem statistics output.
|
||||
# If enabled, modem statistics are written to stats.txt, and can be observed
|
||||
# during decoding by running: tail -f stats.txt | python fskstats.py
|
||||
STATS_OUTPUT=0
|
||||
|
||||
# Select decoder to tun
|
||||
DECODER=horus_demod
|
||||
|
||||
# For use with SoapySDR via rx_tools
|
||||
#SDR_EXTRA="-d driver=rtlsdr"
|
||||
|
||||
##########################################################
|
||||
### NOTE: Only uncomment one of the settings sections! ###
|
||||
##########################################################
|
||||
|
||||
### Single 4FSK settings ###
|
||||
|
||||
# Script name
|
||||
DEMODSCRIPT="docker_single.sh"
|
||||
|
||||
# Receive *centre* frequency, in Hz
|
||||
# Note: The SDR will be tuned to RXBANDWIDTH/2 below this frequency.
|
||||
RXFREQ=434200000
|
||||
|
||||
# Frequency estimator bandwidth. The wider the bandwidth, the more drift and frequency error the modem can tolerate,
|
||||
# but the higher the chance that the modem will lock on to a strong spurious signal.
|
||||
# Note: The SDR will be tuned to RXFREQ-RXBANDWIDTH/2, and the estimator set to look at 0-RXBANDWIDTH Hz.
|
||||
RXBANDWIDTH=10000
|
||||
|
||||
|
||||
|
||||
### Dual 4FSK settings ###
|
||||
|
||||
# Script name
|
||||
#DEMODSCRIPT="docker_dual_4fsk.sh"
|
||||
|
||||
# Receive requency, in Hz. This is the frequency the SDR is tuned to.
|
||||
#RXFREQ=434195000
|
||||
|
||||
# Frequency estimator bandwidth. The wider the bandwidth, the more drift and frequency error the modem can tolerate,
|
||||
# but the higher the chance that the modem will lock on to a strong spurious signal.
|
||||
#RXBANDWIDTH=5000
|
||||
|
||||
# Where in the passband we expect to find the Lower Horus Binary (MFSK) signal, in Hz.
|
||||
# For this example, this is on 434.290 MHz, so with a SDR frequency of 434.195 MHz,
|
||||
# we expect to find the signal at approx +5 kHz.
|
||||
# Note that the signal must be located ABOVE the centre frequency of the receiver.
|
||||
#MFSK1_SIGNAL=5000
|
||||
|
||||
# Where in the receiver passband we expect to find the higher Horus Binary (MFSK) signal, in Hz.
|
||||
# In this example, our second frequency is at 434.210 MHz, so with a SDR frequency of 434.195 MHz,
|
||||
# we expect to find the signal at approx +15 kHz.
|
||||
#MFSK2_SIGNAL=15000
|
||||
|
||||
|
||||
|
||||
## Dual RTTY 4FSK settings ###
|
||||
|
||||
# Script name
|
||||
#DEMODSCRIPT="docker_dual_rtty_4fsk.sh"
|
||||
|
||||
# Receive requency, in Hz. This is the frequency the SDR is tuned to.
|
||||
#RXFREQ=434645000
|
||||
|
||||
# Frequency estimator bandwidth. The wider the bandwidth, the more drift and frequency error the modem can tolerate,
|
||||
# but the higher the chance that the modem will lock on to a strong spurious signal.
|
||||
#RXBANDWIDTH=8000
|
||||
|
||||
# Where in the passband we expect to find the RTTY signal, in Hz.
|
||||
# For Horus flights, this is on 434.650 MHz, so with a SDR frequency of 434.645 MHz,
|
||||
# we expect to find the RTTY signal at approx +5 kHz.
|
||||
# Note that the signal must be located ABOVE the centre frequency of the receiver.
|
||||
#RTTY_SIGNAL=5000
|
||||
|
||||
# Where in the receiver passband we expect to find the Horus Binary (MFSK) signal, in Hz.
|
||||
# For Horus flights, this is on 434.660 MHz, so with a SDR frequency of 434.645 MHz,
|
||||
# we expect to find the RTTY signal at approx +15 kHz.
|
||||
#MFSK_SIGNAL=15000
|
||||
|
Ładowanie…
Reference in New Issue