kopia lustrzana https://github.com/projecthorus/horusdemodlib
Porównaj commity
382 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 | |
Mark Jessop | e3f948d6d6 | |
Mark Jessop | 4f3947e6cb | |
Mark Jessop | fe9c1192d2 | |
Mark Jessop | 4957c3b8b9 | |
Mark Jessop | a190a436bc | |
Mark Jessop | bac78e1081 | |
Mark Jessop | 906e8cc576 | |
Mark Jessop | 481cad1894 | |
Mark Jessop | 7c81ad48d4 | |
Mark Jessop | fbf576fb55 | |
Mark Jessop | e1ea13da9b | |
Mark Jessop | 365870241c | |
Mark Jessop | 46c9c71266 | |
Mark Jessop | 62bc5453b1 | |
Mark Jessop | b0ec5f1df9 | |
Mark Jessop | 68f73ffd3d | |
Mark Jessop | 73e945230e | |
Mark Jessop | cc336f0ba5 | |
Mark Jessop | a7cd06a4c6 | |
Mark Jessop | bf8dce6c82 | |
Mark Jessop | d302b9f6af | |
Mark Jessop | 8f9de1e27a | |
Mark Jessop | 8cdae38de9 | |
Mark Jessop | 990c222495 | |
Mark Jessop | 9b65e1fa19 | |
dfgg12 | ab165ad41a | |
Mark Jessop | 4f6e398f4b | |
Mark Jessop | 88aeed2282 | |
Mark Jessop | e864e0036a | |
Mark Jessop | 7a1b174bdf | |
Mark Jessop | 6e86c3e559 | |
Mark Jessop | 8d11d9ecf1 | |
Mark Jessop | b1908f7cc7 | |
Mark Jessop | e3ee7d4ae4 | |
Mark Jessop | c8debeaab4 | |
Mark Jessop | aeee65e81e | |
Mark Jessop | 312eba4a9a | |
Mark Jessop | 96e71ac04c | |
Mark Jessop | 03038b3823 | |
Mark Jessop | a555089582 | |
Mark Jessop | 66894a3243 | |
Mark Jessop | c6093c1e13 | |
Mark Jessop | 8c8b1eb988 | |
Mark Jessop | 2d4dfccad7 | |
Mark Jessop | c0f301f732 | |
Mark Jessop | 845c36b1d5 | |
Mark Jessop | e9a60bec61 | |
Mark Jessop | 164b6619bb | |
Mark Jessop | e39ad3e706 | |
Mark Jessop | b7817ec924 | |
Mark Jessop | 1e1fb83a1d | |
Mark Jessop | b7af91ced6 | |
Mark Jessop | 2ac511f8d0 | |
Mark Jessop | afb016bf49 | |
Mark Jessop | ad7d18dd72 | |
Mark Jessop | d99e38827f | |
Mark Jessop | 8ab168e6b7 | |
Mark Jessop | 448626092d | |
Mark Jessop | 1605b2ef14 | |
Mark Jessop | 40b88b2d43 | |
Mark Jessop | 33c40e030b | |
Mark Jessop | 0117631f69 | |
Mark Jessop | 8394d3120b | |
Mark Jessop | 5fcafebb82 | |
Mark Jessop | dac5ebc297 | |
Mark Jessop | 248d813eff | |
Mark Jessop | 9858aff192 | |
Mark Jessop | 68e507fd34 | |
Mark Jessop | e49a276723 | |
Mark Jessop | 39594f184d | |
Mark Jessop | 62623aab18 | |
Mark Jessop | d3c2bbdf0f | |
Mark Jessop | 5334e44da9 |
|
@ -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
|
||||
|
||||
|
|
|
@ -59,6 +59,13 @@ enable_testing()
|
|||
)
|
||||
set_tests_properties(test_horus_binary PROPERTIES PASS_REGULAR_EXPRESSION "1C9A9545")
|
||||
|
||||
add_test(NAME test_horus_binary_v2
|
||||
COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src;
|
||||
sox -t raw -r 8000 -e signed-integer -b 16 -c 1 ${CMAKE_CURRENT_SOURCE_DIR}/samples/horus_v2_100bd.raw -r 48000 -t raw - |
|
||||
./horus_demod -m binary - -"
|
||||
)
|
||||
set_tests_properties(test_horus_binary_v2 PROPERTIES PASS_REGULAR_EXPRESSION "0102030405060708091DBB")
|
||||
|
||||
add_test(NAME test_horus_rtty_7n1
|
||||
COMMAND sh -c "cd ${CMAKE_CURRENT_BINARY_DIR}/src;
|
||||
sox ${CMAKE_CURRENT_SOURCE_DIR}/samples/rtty_7n1.wav -r 48000 -t raw - |
|
||||
|
|
|
@ -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).
|
32
README.md
32
README.md
|
@ -35,9 +35,11 @@ $ git clone https://github.com/projecthorus/horusdemodlib.git
|
|||
$ cd horusdemodlib && mkdir build && cd build
|
||||
$ cmake ..
|
||||
$ make
|
||||
$ make install
|
||||
$ sudo make install
|
||||
```
|
||||
|
||||
Refer to the [install guide](https://github.com/projecthorus/horusdemodlib/wiki/1.2--Raspberry-Pi-'Headless'-RX-Guide) for a more complete guide, including what dependencies are required.
|
||||
|
||||
### Testing
|
||||
Unit tests for the various demodulators can be run using:
|
||||
|
||||
|
@ -46,6 +48,14 @@ $ cd build
|
|||
$ ctest
|
||||
```
|
||||
|
||||
### Updates
|
||||
In most cases, you can update this library by running:
|
||||
```
|
||||
$ git pull
|
||||
```
|
||||
and then following the build steps above from the `cd horusdemodlib` line.
|
||||
|
||||
|
||||
### API Reference
|
||||
The main demodulator API is [horus_api.h](https://github.com/projecthorus/horusdemodlib/blob/master/src/horus_api.h). An example of it in use in a C program is available in [horus_demod.c](https://github.com/projecthorus/horusdemodlib/blob/master/src/horus_demod.c)
|
||||
|
||||
|
@ -55,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.
|
||||
|
||||
|
@ -69,6 +79,24 @@ If you want to install directly from this repository, you can run:
|
|||
$ pip install -r requirements.txt
|
||||
$ pip install -e .
|
||||
```
|
||||
(Note - this has some issues relating to setuptools currently... use pip)
|
||||
|
||||
### Updating
|
||||
If you have installed horusdemodlib via pypi, then you can run (from within your venv, if you are using one):
|
||||
```
|
||||
$ pip install -U horusdemodlib
|
||||
```
|
||||
This will also install any new dependencies.
|
||||
|
||||
|
||||
If you have installed 'directly', then you will need to run:
|
||||
```
|
||||
$ git stash
|
||||
$ git pull
|
||||
$ pip install -r requirements.txt
|
||||
$ pip install -e .
|
||||
```
|
||||
(Note - this has some issues relating to setuptools currently... use pip)
|
||||
|
||||
|
||||
## Further Reading
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# Decoder / Encoder Testing Notes
|
||||
|
||||
## Generating Test Frames
|
||||
`horus_gen_test_bits` can be used to generate either horus v1 (mode 0) or horus v2 (mode 1) frames, in one-bit-per-byte format.
|
||||
|
||||
```
|
||||
$ ./horus_gen_test_bits 0 1000 > horus_v1_test_frames.bin
|
||||
```
|
||||
|
||||
These can be piped into fsk_mod to produce modulated audio:
|
||||
```
|
||||
$ ./horus_gen_test_bits 0 100 | ./fsk_mod 4 48000 100 1000 270 - - > horus_v1_test_frames_8khz.raw
|
||||
```
|
||||
|
||||
You can play the frames out your speakers using sox:
|
||||
```
|
||||
$ ./horus_gen_test_bits 0 100 | ./fsk_mod 4 48000 100 1000 270 - - | play -t raw -r 48000 -e signed-integer -b 16 -c 1 -
|
||||
```
|
||||
|
||||
... or pipe them straight back into horus_demod and decode them:
|
||||
```
|
||||
$ ./horus_gen_test_bits 0 100 | ./fsk_mod 4 48000 100 1000 270 - - | ./horus_demod -m binary - -
|
||||
Using Horus Mode 0.
|
||||
Generating 100 frames.
|
||||
0000000000000000000000000000000000000000B8F6
|
||||
0001000000000000000000000000000000000000A728
|
||||
0002000000000000000000000000000000000000A75A
|
||||
0003000000000000000000000000000000000000B884
|
||||
... continues.
|
||||
```
|
||||
|
||||
If we get the cohpsk_ch utility from the codec2 repository, then we can also add noise:
|
||||
```
|
||||
./horus_gen_test_bits 0 100 | ./fsk_mod 4 8000 100 1000 270 - - | ./cohpsk_ch - - -24 | sox -t raw -r 8000 -e signed-integer -b 16 -c 1 - -r 48000 -t raw - | ./horus_demod -m binary - -
|
||||
```
|
||||
In this case, we are adding enough noise that the decoder is barely hanging on. Have a listen to the signal:
|
||||
```
|
||||
$ ./horus_gen_test_bits 0 100 | ./fsk_mod 4 8000 100 1000 270 - - | ./cohpsk_ch - - -24 | play -t raw -r 8000 -e signed-integer -b 16 -c 1 -
|
||||
```
|
||||
|
||||
Note that we have to use a 8kHz sample rate for cohpsk_ch to work, and hence we use sox to get the audio back into the 48 kHz sample rate expected by horus_demod.
|
|
@ -17,5 +17,145 @@
|
|||
["test_counter", "none"],
|
||||
["test_int_field", "none"]
|
||||
]
|
||||
},
|
||||
"4FSKTEST-V2": {
|
||||
"comment": "Default custom fields for RS41ng",
|
||||
"struct": "<hhBHxx",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"]
|
||||
]
|
||||
},
|
||||
"MAGNU-V2": {
|
||||
"struct": "<hHhHb",
|
||||
"fields": [
|
||||
["Vertical", "divide_by_100"],
|
||||
["Lateral", "divide_by_100"],
|
||||
["Orthogonal", "divide_by_100"],
|
||||
["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.1.21"
|
||||
__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__":
|
||||
|
||||
|
|
|
@ -98,9 +98,18 @@ def decode_packet(data:bytes, packet_format:dict = None, ignore_crc:bool = False
|
|||
_output = {
|
||||
'packet_format': packet_format,
|
||||
'crc_ok': False,
|
||||
'payload_id': 0
|
||||
'payload_id': 0,
|
||||
'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']:
|
||||
|
@ -137,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']:
|
||||
|
@ -157,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'))
|
||||
|
@ -188,6 +214,7 @@ def parse_ukhas_string(sentence:str) -> dict:
|
|||
# Try and proceed through the following. If anything fails, we have a corrupt sentence.
|
||||
# Strip out any leading/trailing whitespace.
|
||||
_sentence = sentence.strip()
|
||||
_raw = _sentence
|
||||
|
||||
# First, try and find the start of the sentence, which always starts with '$$''
|
||||
_sentence = _sentence.split('$')[-1]
|
||||
|
@ -208,6 +235,7 @@ def parse_ukhas_string(sentence:str) -> dict:
|
|||
_fields = _telem.split(',')
|
||||
try:
|
||||
_callsign = _fields[0]
|
||||
_sequence_number = int(_fields[1])
|
||||
_time = _fields[2]
|
||||
_latitude = float(_fields[3])
|
||||
_longitude = float(_fields[4])
|
||||
|
@ -245,16 +273,19 @@ 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,
|
||||
'latitude': _latitude,
|
||||
'longitude': _longitude,
|
||||
'altitude': _altitude,
|
||||
'speed': -1,
|
||||
'heading': -1,
|
||||
'temp': -1,
|
||||
'sats': -1,
|
||||
'batt_voltage': -1
|
||||
# 'speed': -1,
|
||||
# 'heading': -1,
|
||||
# 'temperature': -1,
|
||||
# 'satellites': -1,
|
||||
# 'battery_voltage': -1
|
||||
}
|
||||
|
||||
return _telem
|
||||
|
@ -269,7 +300,7 @@ if __name__ == "__main__":
|
|||
parser = argparse.ArgumentParser(description="Project Horus Binary Telemetry Decoder", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("--test", action="store_true", default=False, help="Run unit tests.")
|
||||
parser.add_argument("--update", action="store_true", default=False, help="Download latest payload ID and custom fields files before continuing.")
|
||||
parser.add_argument("--decode", type=str, default=None, help="Attempt to decode a hexadecial packet.")
|
||||
parser.add_argument("--decode", type=str, default=None, help="Attempt to decode a hexadecial packet supplied as an argument.")
|
||||
parser.add_argument("-v", "--verbose", action="store_true", default=False, help="Verbose output (set logging level to DEBUG)")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -284,8 +315,14 @@ if __name__ == "__main__":
|
|||
)
|
||||
|
||||
if args.update:
|
||||
# Download latest list from github.
|
||||
init_payload_id_list()
|
||||
init_custom_field_list()
|
||||
else:
|
||||
# Use whatever is available in the current directory
|
||||
logging.info("Using existing payload/custom-field files.")
|
||||
init_payload_id_list(nodownload=True)
|
||||
init_custom_field_list(nodownload=True)
|
||||
|
||||
if args.decode is not None:
|
||||
try:
|
||||
|
@ -303,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'\xFF\xFF\x12\x00\x00\x00\x23\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe8\x82', '']
|
||||
['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:
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
import struct
|
||||
import time
|
||||
|
||||
import datetime
|
||||
from dateutil.parser import parse
|
||||
import horusdemodlib.payloads
|
||||
|
||||
|
||||
# Payload ID
|
||||
|
||||
def decode_payload_id(data: int) -> str:
|
||||
|
@ -141,13 +143,39 @@ def decode_battery_5v_byte(data: int) -> str:
|
|||
return (_batt, f"{_batt:.2f}")
|
||||
|
||||
|
||||
def decode_divide_by_10(data: int) -> str:
|
||||
"""
|
||||
Accepts an fixed-point integer, and returns it as its value divided by 10, as a string.
|
||||
"""
|
||||
if type(data) != int:
|
||||
raise ValueError("divide_by_10 - Invalid input type")
|
||||
|
||||
_val = data/10.0
|
||||
|
||||
return (_val, f"{_val:.1f}")
|
||||
|
||||
|
||||
def decode_divide_by_100(data: int) -> str:
|
||||
"""
|
||||
Accepts an fixed-point integer, and returns it as its value divided by 100, as a string.
|
||||
"""
|
||||
if type(data) != int:
|
||||
raise ValueError("divide_by_100 - Invalid input type")
|
||||
|
||||
_val = data/100.0
|
||||
|
||||
return (_val, f"{_val:.2f}")
|
||||
|
||||
|
||||
delegate_list = {
|
||||
'payload_id': decode_payload_id,
|
||||
'time_hms': decode_time_hms,
|
||||
'time_biseconds': decode_time_biseconds,
|
||||
'degree_float': decode_degree_float,
|
||||
'degree_fixed3': decode_degree_fixed3,
|
||||
'battery_5v_byte': decode_battery_5v_byte
|
||||
'battery_5v_byte': decode_battery_5v_byte,
|
||||
'divide_by_10': decode_divide_by_10,
|
||||
'divide_by_100': decode_divide_by_100,
|
||||
}
|
||||
|
||||
def decode_field(field_type:str, data):
|
||||
|
@ -158,7 +186,9 @@ def decode_field(field_type:str, data):
|
|||
else:
|
||||
if (field_type == 'none') or (field_type == 'None') or (field_type == None):
|
||||
# Basic datatype, just convert to a string using Pythons internal conversions.
|
||||
if (type(data) == float) or (type(data) == int) or (type(data) == str):
|
||||
if (type(data) == float):
|
||||
return (data, f"{data:.6f}")
|
||||
elif (type(data) == int) or (type(data) == str):
|
||||
return (data, f"{data}")
|
||||
else:
|
||||
raise ValueError(f"Data has unknown type ({str(type(data))}) and could not be decoded.")
|
||||
|
@ -208,6 +238,41 @@ def decode_custom_fields(data:bytes, payload_id:str):
|
|||
return (_output_dict, _output_fields_str)
|
||||
|
||||
|
||||
def fix_datetime(datetime_str, local_dt_str=None):
|
||||
"""
|
||||
Given a HH:MM:SS string from a telemetry sentence, produce a complete timestamp, using the current system time as a guide for the date.
|
||||
"""
|
||||
|
||||
if local_dt_str is None:
|
||||
_now = datetime.datetime.utcnow()
|
||||
else:
|
||||
_now = parse(local_dt_str)
|
||||
|
||||
# Are we in the rollover window?
|
||||
if _now.hour == 23 or _now.hour == 0:
|
||||
_outside_window = False
|
||||
else:
|
||||
_outside_window = True
|
||||
|
||||
# Parsing just a HH:MM:SS will return a datetime object with the year, month and day replaced by values in the 'default'
|
||||
# argument.
|
||||
_imet_dt = parse(datetime_str, default=_now)
|
||||
|
||||
if _outside_window:
|
||||
# We are outside the day-rollover window, and can safely use the current zulu date.
|
||||
return _imet_dt
|
||||
else:
|
||||
# We are within the window, and need to adjust the day backwards or forwards based on the sonde time.
|
||||
if _imet_dt.hour == 23 and _now.hour == 0:
|
||||
# Assume system clock running slightly fast, and subtract a day from the telemetry date.
|
||||
_imet_dt = _imet_dt - datetime.timedelta(days=1)
|
||||
|
||||
elif _imet_dt.hour == 00 and _now.hour == 23:
|
||||
# System clock running slow. Add a day.
|
||||
_imet_dt = _imet_dt + datetime.timedelta(days=1)
|
||||
|
||||
return _imet_dt
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -227,7 +292,12 @@ if __name__ == "__main__":
|
|||
['battery_5v_byte', 0, "0.00"],
|
||||
['battery_5v_byte', 128, "2.51"],
|
||||
['battery_5v_byte', 255, "5.00"],
|
||||
['payload_id', 0, '4FSKTEST']
|
||||
['payload_id', 0, '4FSKTEST'],
|
||||
['divide_by_10', 123, "12.3"],
|
||||
['divide_by_10', -456, "-45.6"],
|
||||
['divide_by_100', 123, "1.23"],
|
||||
['divide_by_100', -456, "-4.56"],
|
||||
|
||||
]
|
||||
|
||||
for _test in tests:
|
||||
|
|
|
@ -62,12 +62,11 @@ class Mode(Enum):
|
|||
"""
|
||||
BINARY = 0
|
||||
BINARY_V1 = 0
|
||||
BINARY_V2 = 0
|
||||
RTTY_7N1 = 89
|
||||
RTTY_7N2 = 90
|
||||
RTTY = 90
|
||||
RTTY_8N2 = 91
|
||||
BINARY_V2_256BIT = 1
|
||||
BINARY_V2_128BIT = 2
|
||||
|
||||
|
||||
class Frame():
|
||||
|
@ -309,7 +308,8 @@ class HorusLib():
|
|||
)
|
||||
elif (self.mode != Mode.RTTY_7N2) and (self.mode != Mode.RTTY_8N2) and (self.mode != Mode.RTTY_7N1):
|
||||
try:
|
||||
data_out = bytes.fromhex(data_out.decode("ascii"))
|
||||
# Strip out any additional nulls.
|
||||
data_out = bytes.fromhex(data_out.decode("ascii").rstrip('\0'))
|
||||
except ValueError:
|
||||
logging.debug(data_out)
|
||||
logging.error("Couldn't decode the hex from the modem")
|
||||
|
|
|
@ -56,6 +56,7 @@ class FSKDemodStats(object):
|
|||
# Output State variables.
|
||||
self.snr = -999.0
|
||||
self.fest = [0.0,0.0, 0.0,0.0]
|
||||
self.fest_mean = 0.0
|
||||
self.fft = []
|
||||
self.ppm = 0.0
|
||||
|
||||
|
@ -110,6 +111,8 @@ class FSKDemodStats(object):
|
|||
self.fest[3] = _data['f4_est']
|
||||
else:
|
||||
self.fest = self.fest[:2]
|
||||
|
||||
self.fest_mean = np.mean(self.fest)
|
||||
|
||||
# Time-series data
|
||||
self.in_times = np.append(self.in_times, _time)
|
||||
|
|
|
@ -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,24 @@ 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
|
||||
|
||||
self.callsign_init = False
|
||||
self.uuids = []
|
||||
|
@ -90,10 +108,15 @@ class HabitatUploader(object):
|
|||
) # Convert back to a string to be serialisable
|
||||
},
|
||||
"receivers": {
|
||||
_user_call: {"time_created": _date, "time_uploaded": _date,},
|
||||
_user_call: {"time_created": _date, "time_uploaded": _date},
|
||||
},
|
||||
}
|
||||
|
||||
if self.last_freq_hz:
|
||||
# Add in frequency information if we have it.
|
||||
_data["receivers"][_user_call]["rig_info"] = {"frequency": self.last_freq_hz}
|
||||
|
||||
|
||||
# The URl to upload to.
|
||||
_url = f"{self.HABITAT_URL}{self.HABITAT_DB}/_design/payload_telemetry/_update/add_listener/{sha256(_sentence_b64).hexdigest()}"
|
||||
|
||||
|
@ -110,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))
|
||||
|
@ -168,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.")
|
||||
|
@ -214,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. """
|
||||
|
@ -313,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)
|
||||
|
|
|
@ -4,14 +4,16 @@
|
|||
import json
|
||||
import logging
|
||||
import requests
|
||||
import struct
|
||||
|
||||
# Global payload list - Basic version
|
||||
HORUS_PAYLOAD_LIST = {0:'4FSKTEST', 1:'HORUSBINARY', 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"
|
||||
|
||||
# Custom field data.
|
||||
HORUS_CUSTOM_FIELD_LENGTH = 9
|
||||
HORUS_CUSTOM_FIELDS = {
|
||||
"HORUSTEST": {
|
||||
"struct": "<BbBfH",
|
||||
|
@ -31,6 +33,15 @@ HORUS_CUSTOM_FIELDS = {
|
|||
["test_counter", "none"],
|
||||
["test_int_field", "none"]
|
||||
]
|
||||
},
|
||||
"4FSKTEST-V2": {
|
||||
"struct": "<hhBHxx",
|
||||
"fields": [
|
||||
["ascent_rate", "divide_by_100"],
|
||||
["ext_temperature", "divide_by_10"],
|
||||
["ext_humidity", "none"],
|
||||
["ext_pressure", "divide_by_10"]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,14 +144,18 @@ def download_latest_payload_id_list(url=PAYLOAD_ID_LIST_URL, filename=None, time
|
|||
|
||||
|
||||
|
||||
def init_payload_id_list(filename="payload_id_list.txt"):
|
||||
def init_payload_id_list(filename="payload_id_list.txt", nodownload=False):
|
||||
""" Initialise and update the local payload ID list. """
|
||||
|
||||
_list = download_latest_payload_id_list(filename=filename)
|
||||
if _list:
|
||||
HORUS_PAYLOAD_LIST = _list
|
||||
if not nodownload:
|
||||
_list = download_latest_payload_id_list(filename=filename)
|
||||
|
||||
if _list:
|
||||
HORUS_PAYLOAD_LIST = _list
|
||||
else:
|
||||
logging.warning("Could not download Payload ID List - attempting to use local version.")
|
||||
HORUS_PAYLOAD_LIST = read_payload_list(filename=filename)
|
||||
else:
|
||||
logging.warning("Could not download Payload ID List - attempting to use local version.")
|
||||
HORUS_PAYLOAD_LIST = read_payload_list(filename=filename)
|
||||
|
||||
return HORUS_PAYLOAD_LIST
|
||||
|
@ -173,11 +188,21 @@ def read_custom_field_list(filename="custom_field_list.json"):
|
|||
_data = _field_data[_payload]
|
||||
|
||||
if ("struct" in _data) and ("fields" in _data):
|
||||
_custom_field_list[_payload] = {
|
||||
"struct": _data["struct"],
|
||||
"fields": _data["fields"]
|
||||
}
|
||||
logging.debug(f"Loaded custom field data for {_payload}.")
|
||||
# Check the struct value has the right length
|
||||
try:
|
||||
_structsize = struct.calcsize(_data["struct"])
|
||||
|
||||
if _structsize == HORUS_CUSTOM_FIELD_LENGTH:
|
||||
_custom_field_list[_payload] = {
|
||||
"struct": _data["struct"],
|
||||
"fields": _data["fields"]
|
||||
}
|
||||
logging.debug(f"Loaded custom field data for {_payload}.")
|
||||
else:
|
||||
logging.error(f"Struct field for {_payload} has incorrect length ({_structsize}).")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Could not parse custom field data for {_payload}: {str(e)}")
|
||||
|
||||
return _custom_field_list
|
||||
|
||||
|
@ -246,11 +271,23 @@ def download_latest_custom_field_list(url=HORUS_CUSTOM_FIELD_URL, filename=None,
|
|||
_data = _field_data[_payload]
|
||||
|
||||
if ("struct" in _data) and ("fields" in _data):
|
||||
_custom_field_list[_payload] = {
|
||||
"struct": _data["struct"],
|
||||
"fields": _data["fields"]
|
||||
}
|
||||
logging.debug(f"Loaded custom field data for {_payload}.")
|
||||
# Check the struct value has the right length
|
||||
try:
|
||||
_structsize = struct.calcsize(_data["struct"])
|
||||
|
||||
if _structsize == HORUS_CUSTOM_FIELD_LENGTH:
|
||||
_custom_field_list[_payload] = {
|
||||
"struct": _data["struct"],
|
||||
"fields": _data["fields"]
|
||||
}
|
||||
logging.debug(f"Loaded custom field data for {_payload}.")
|
||||
else:
|
||||
logging.error(f"Struct field for {_payload} has incorrect length ({_structsize}).")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Could not parse custom field data for {_payload}: {str(e)}")
|
||||
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Could not parse downloaded custom field list - {str(e)}")
|
||||
|
@ -266,13 +303,17 @@ def download_latest_custom_field_list(url=HORUS_CUSTOM_FIELD_URL, filename=None,
|
|||
return _custom_field_list
|
||||
|
||||
|
||||
def init_custom_field_list(filename="custom_field_list.json"):
|
||||
def init_custom_field_list(filename="custom_field_list.json", nodownload=False):
|
||||
""" Initialise and update the local custom field list """
|
||||
_list = download_latest_custom_field_list(filename=filename)
|
||||
if _list:
|
||||
HORUS_CUSTOM_FIELDS = _list
|
||||
|
||||
if not nodownload:
|
||||
_list = download_latest_custom_field_list(filename=filename)
|
||||
if _list:
|
||||
HORUS_CUSTOM_FIELDS = _list
|
||||
else:
|
||||
logging.warning("Could not download Custom Field List - attempting to use local version.")
|
||||
HORUS_CUSTOM_FIELDS = read_custom_field_list(filename=filename)
|
||||
else:
|
||||
logging.warning("Could not download Custom Field List - attempting to use local version.")
|
||||
HORUS_CUSTOM_FIELDS = read_custom_field_list(filename=filename)
|
||||
|
||||
return HORUS_CUSTOM_FIELDS
|
||||
|
@ -286,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)
|
|
@ -0,0 +1,516 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# HorusDemodLib - SondeHub Amateur Uploader
|
||||
#
|
||||
# Uploads telemetry to the SondeHub ElasticSearch cluster,
|
||||
# in the new 'universal' format descried here:
|
||||
# https://github.com/projecthorus/sondehub-infra/wiki/%5BDRAFT%5D-Amateur-Balloon-Telemetry-Format
|
||||
#
|
||||
# Copyright (C) 2022 Mark Jessop <vk5qi@rfhead.net>
|
||||
# Released under GNU GPL v3 or later
|
||||
#
|
||||
import horusdemodlib
|
||||
import datetime
|
||||
import glob
|
||||
import gzip
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
from threading import Thread
|
||||
from email.utils import formatdate
|
||||
from .delegates import fix_datetime
|
||||
|
||||
try:
|
||||
# Python 2
|
||||
from Queue import Queue
|
||||
except ImportError:
|
||||
# Python 3
|
||||
from queue import Queue
|
||||
|
||||
|
||||
class SondehubAmateurUploader(object):
|
||||
""" Sondehub (Amateur) Uploader Class.
|
||||
|
||||
Accepts telemetry dictionaries from a decoder, buffers them up, and then compresses and uploads
|
||||
them to the Sondehub Elasticsearch cluster.
|
||||
|
||||
"""
|
||||
|
||||
# SondeHub API endpoint
|
||||
SONDEHUB_AMATEUR_URL = "https://api.v2.sondehub.org/amateur/telemetry"
|
||||
SONDEHUB_AMATEUR_STATION_POSITION_URL = "https://api.v2.sondehub.org/amateur/listeners"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
upload_rate=30,
|
||||
upload_timeout=20,
|
||||
upload_retries=5,
|
||||
user_callsign="N0CALL",
|
||||
user_position=None,
|
||||
user_radio="",
|
||||
user_antenna="",
|
||||
contact_email="",
|
||||
user_position_update_rate=6,
|
||||
software_name="horusdemodlib",
|
||||
software_version="",
|
||||
inhibit=False
|
||||
):
|
||||
""" Initialise and start a Sondehub (Amateur) uploader
|
||||
|
||||
Args:
|
||||
upload_rate (int): How often to upload batches of data.
|
||||
upload_timeout (int): Upload timeout.
|
||||
|
||||
"""
|
||||
|
||||
self.upload_rate = upload_rate
|
||||
self.upload_timeout = upload_timeout
|
||||
self.upload_retries = upload_retries
|
||||
self.user_callsign = user_callsign
|
||||
self.user_position = user_position
|
||||
self.user_radio = user_radio
|
||||
self.user_antenna = user_antenna
|
||||
self.contact_email = contact_email
|
||||
self.user_position_update_rate = user_position_update_rate
|
||||
self.software_name = software_name
|
||||
self.software_version = software_version
|
||||
self.inhibit = inhibit
|
||||
|
||||
if self.user_position is None:
|
||||
self.inhibit_position_upload = True
|
||||
else:
|
||||
self.inhibit_position_upload = False
|
||||
|
||||
# Input Queue.
|
||||
self.input_queue = Queue()
|
||||
|
||||
# Record of when we last uploaded a user station position to Sondehub.
|
||||
self.last_user_position_upload = 0
|
||||
|
||||
try:
|
||||
# Python 2 check. Python 2 doesnt have gzip.compress so this will throw an exception.
|
||||
gzip.compress(b"\x00\x00")
|
||||
|
||||
# Start queue processing thread.
|
||||
if self.inhibit:
|
||||
logging.info("SondeHub Amateur Uploader Inhibited.")
|
||||
else:
|
||||
self.input_processing_running = True
|
||||
self.input_process_thread = Thread(target=self.process_queue)
|
||||
self.input_process_thread.start()
|
||||
|
||||
except:
|
||||
logging.error(
|
||||
"Detected Python 2.7, which does not support gzip.compress. Sondehub DB uploading will be disabled."
|
||||
)
|
||||
self.input_processing_running = False
|
||||
|
||||
def update_station_position(self, lat, lon, alt):
|
||||
""" Update the internal station position record. Used when determining the station position by GPSD """
|
||||
if self.inhibit_position_upload:
|
||||
# Don't update the internal position array if we aren't uploading our position.
|
||||
return
|
||||
else:
|
||||
self.user_position = (lat, lon, alt)
|
||||
|
||||
def add(self, telemetry):
|
||||
""" Add a dictionary of telemetry to the input queue.
|
||||
|
||||
Args:
|
||||
telemetry (dict): Telemetry dictionary to add to the input queue.
|
||||
"""
|
||||
|
||||
if self.inhibit:
|
||||
return
|
||||
|
||||
# Attempt to reformat the data.
|
||||
_telem = self.reformat_data(telemetry)
|
||||
# self.log_debug("Telem: %s" % str(_telem))
|
||||
|
||||
# Add it to the queue if we are running.
|
||||
if self.input_processing_running and _telem:
|
||||
self.input_queue.put(_telem)
|
||||
else:
|
||||
self.log_debug("Processing not running, discarding.")
|
||||
|
||||
def reformat_data(self, telemetry):
|
||||
""" Take an input dictionary and convert it to the universal format """
|
||||
|
||||
# Init output dictionary
|
||||
_output = {
|
||||
"software_name": self.software_name,
|
||||
"software_version": self.software_version,
|
||||
"uploader_callsign": self.user_callsign,
|
||||
"uploader_position": self.user_position,
|
||||
"uploader_radio": self.user_radio,
|
||||
"uploader_antenna": self.user_antenna,
|
||||
"time_received": datetime.datetime.utcnow().strftime(
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ"
|
||||
),
|
||||
}
|
||||
|
||||
# Mandatory Fields
|
||||
# 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"
|
||||
)
|
||||
except Exception as e:
|
||||
self.log_error(
|
||||
"Error converting telemetry datetime to string - %s" % str(e)
|
||||
)
|
||||
self.log_debug("Offending datetime_dt: %s" % str(telemetry["time"]))
|
||||
return None
|
||||
|
||||
|
||||
|
||||
# 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
|
||||
_output["frame"] = telemetry["sequence_number"]
|
||||
|
||||
# Position
|
||||
_output["lat"] = telemetry["latitude"]
|
||||
_output["lon"] = telemetry["longitude"]
|
||||
_output["alt"] = telemetry["altitude"]
|
||||
|
||||
# # Optional Fields
|
||||
if "temperature" in telemetry:
|
||||
if telemetry["temperature"] > -273.15:
|
||||
_output["temp"] = telemetry["temperature"]
|
||||
|
||||
if "satellites" in telemetry:
|
||||
_output["sats"] = telemetry["satellites"]
|
||||
|
||||
if "battery_voltage" in telemetry:
|
||||
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"]
|
||||
|
||||
if "f_centre" in telemetry:
|
||||
_output["frequency"] = telemetry["f_centre"] / 1e6 # Hz -> MHz
|
||||
|
||||
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
|
||||
|
||||
def process_queue(self):
|
||||
""" Process data from the input queue, and write telemetry to log files.
|
||||
"""
|
||||
self.log_info("Started Sondehub Amateur Uploader Thread.")
|
||||
|
||||
while self.input_processing_running:
|
||||
|
||||
# Process everything in the queue.
|
||||
_to_upload = []
|
||||
|
||||
while self.input_queue.qsize() > 0:
|
||||
try:
|
||||
_to_upload.append(self.input_queue.get_nowait())
|
||||
except Exception as e:
|
||||
self.log_error("Error grabbing telemetry from queue - %s" % str(e))
|
||||
|
||||
# Upload data!
|
||||
if len(_to_upload) > 0:
|
||||
self.upload_telemetry(_to_upload)
|
||||
|
||||
# If we haven't uploaded our station position recently, re-upload it.
|
||||
if (
|
||||
time.time() - self.last_user_position_upload
|
||||
) > self.user_position_update_rate * 3600:
|
||||
self.station_position_upload()
|
||||
|
||||
# Sleep while waiting for some new data.
|
||||
for i in range(self.upload_rate):
|
||||
time.sleep(1)
|
||||
if self.input_processing_running == False:
|
||||
break
|
||||
|
||||
self.log_info("Stopped Sondehub Amateur Uploader Thread.")
|
||||
|
||||
def upload_telemetry(self, telem_list):
|
||||
""" Upload an list of telemetry data to Sondehub """
|
||||
|
||||
_data_len = len(telem_list)
|
||||
|
||||
try:
|
||||
_start_time = time.time()
|
||||
_telem_json = json.dumps(telem_list).encode("utf-8")
|
||||
_compressed_payload = gzip.compress(_telem_json)
|
||||
except Exception as e:
|
||||
self.log_error(
|
||||
"Error serialising and compressing telemetry list for upload - %s"
|
||||
% str(e)
|
||||
)
|
||||
return
|
||||
|
||||
_compression_time = time.time() - _start_time
|
||||
self.log_debug(
|
||||
"Pre-compression: %d bytes, post: %d bytes. %.1f %% compression ratio, in %.1f s"
|
||||
% (
|
||||
len(_telem_json),
|
||||
len(_compressed_payload),
|
||||
(len(_compressed_payload) / len(_telem_json)) * 100,
|
||||
_compression_time,
|
||||
)
|
||||
)
|
||||
|
||||
_retries = 0
|
||||
_upload_success = False
|
||||
|
||||
_start_time = time.time()
|
||||
|
||||
while _retries < self.upload_retries:
|
||||
# Run the request.
|
||||
try:
|
||||
headers = {
|
||||
"User-Agent": "horusdemodlib-" + horusdemodlib.__version__,
|
||||
"Content-Encoding": "gzip",
|
||||
"Content-Type": "application/json",
|
||||
"Date": formatdate(timeval=None, localtime=False, usegmt=True),
|
||||
}
|
||||
_req = requests.put(
|
||||
self.SONDEHUB_AMATEUR_URL,
|
||||
_compressed_payload,
|
||||
# TODO: Revisit this second timeout value.
|
||||
timeout=(self.upload_timeout, 6.1),
|
||||
headers=headers,
|
||||
)
|
||||
except Exception as e:
|
||||
self.log_error("Upload Failed: %s" % str(e))
|
||||
return
|
||||
|
||||
if _req.status_code == 200:
|
||||
# 200 is the only status code that we accept.
|
||||
_upload_time = time.time() - _start_time
|
||||
self.log_info(
|
||||
"Uploaded %d telemetry packets to Sondehub Amateur in %.1f seconds."
|
||||
% (_data_len, _upload_time)
|
||||
)
|
||||
_upload_success = True
|
||||
break
|
||||
|
||||
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
|
||||
|
||||
else:
|
||||
self.log_error(
|
||||
"Error uploading to Sondehub Amateur. Status Code: %d %s."
|
||||
% (_req.status_code, _req.text)
|
||||
)
|
||||
break
|
||||
|
||||
if not _upload_success:
|
||||
self.log_error("Upload failed after %d retries" % (_retries))
|
||||
|
||||
def station_position_upload(self):
|
||||
"""
|
||||
Upload a station position packet to SondeHub.
|
||||
|
||||
This uses the PUT /listeners API described here:
|
||||
https://github.com/projecthorus/sondehub-infra/wiki/API-(Beta)
|
||||
|
||||
"""
|
||||
|
||||
if self.inhibit_position_upload:
|
||||
# Position upload inhibited. Ensure user position is set to None, and continue upload of other info.
|
||||
self.log_debug("Sondehub station position upload inhibited.")
|
||||
|
||||
_position = {
|
||||
"software_name": self.software_name,
|
||||
"software_version": self.software_version,
|
||||
"uploader_callsign": self.user_callsign,
|
||||
"uploader_position": self.user_position,
|
||||
"uploader_radio": self.user_radio,
|
||||
"uploader_antenna": self.user_antenna,
|
||||
"uploader_contact_email": self.contact_email,
|
||||
"mobile": False, # Hardcoded mobile=false setting - Mobile stations should be using Chasemapper.
|
||||
}
|
||||
|
||||
_retries = 0
|
||||
_upload_success = False
|
||||
|
||||
_start_time = time.time()
|
||||
|
||||
while _retries < self.upload_retries:
|
||||
# Run the request.
|
||||
try:
|
||||
headers = {
|
||||
"User-Agent": "horusdemodlib-" + horusdemodlib.__version__,
|
||||
"Content-Type": "application/json",
|
||||
"Date": formatdate(timeval=None, localtime=False, usegmt=True),
|
||||
}
|
||||
_req = requests.put(
|
||||
self.SONDEHUB_AMATEUR_STATION_POSITION_URL,
|
||||
json=_position,
|
||||
# TODO: Revisit this second timeout value.
|
||||
timeout=(self.upload_timeout, 6.1),
|
||||
headers=headers,
|
||||
)
|
||||
except Exception as e:
|
||||
self.log_error("Station position upload failed: %s" % str(e))
|
||||
self.last_user_position_upload = time.time()
|
||||
return
|
||||
|
||||
if _req.status_code == 200:
|
||||
# 200 is the only status code that we accept.
|
||||
_upload_time = time.time() - _start_time
|
||||
self.log_info("Uploaded station information to Sondehub.")
|
||||
_upload_success = True
|
||||
break
|
||||
|
||||
elif _req.status_code == 500:
|
||||
# Server Error, Retry.
|
||||
_retries += 1
|
||||
continue
|
||||
|
||||
elif _req.status_code == 404:
|
||||
# API doesn't exist yet!
|
||||
self.log_debug("Sondehub Amateur position upload API not implemented yet!")
|
||||
_upload_success = True
|
||||
break
|
||||
|
||||
else:
|
||||
self.log_error(
|
||||
"Error uploading station information to Sondehub. Status Code: %d %s."
|
||||
% (_req.status_code, _req.text)
|
||||
)
|
||||
break
|
||||
|
||||
if not _upload_success:
|
||||
self.log_error(
|
||||
"Station information upload failed after %d retries" % (_retries)
|
||||
)
|
||||
self.log_debug(f"Attempted to upload {json.dumps(_position)}")
|
||||
|
||||
self.last_user_position_upload = time.time()
|
||||
|
||||
def close(self):
|
||||
""" Close input processing thread. """
|
||||
self.input_processing_running = False
|
||||
|
||||
def running(self):
|
||||
""" Check if the uploader thread is running.
|
||||
|
||||
Returns:
|
||||
bool: True if the uploader thread is running.
|
||||
"""
|
||||
return self.input_processing_running
|
||||
|
||||
def log_debug(self, line):
|
||||
""" Helper function to log a debug message with a descriptive heading.
|
||||
Args:
|
||||
line (str): Message to be logged.
|
||||
"""
|
||||
logging.debug("Sondehub Amateur Uploader - %s" % line)
|
||||
|
||||
def log_info(self, line):
|
||||
""" Helper function to log an informational message with a descriptive heading.
|
||||
Args:
|
||||
line (str): Message to be logged.
|
||||
"""
|
||||
logging.info("Sondehub Amateur Uploader - %s" % line)
|
||||
|
||||
def log_error(self, line):
|
||||
""" Helper function to log an error message with a descriptive heading.
|
||||
Args:
|
||||
line (str): Message to be logged.
|
||||
"""
|
||||
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
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s %(levelname)s:%(message)s", level=logging.DEBUG
|
||||
)
|
||||
_test = SondehubAmateurUploader()
|
||||
time.sleep(5)
|
||||
_test.close()
|
|
@ -15,12 +15,14 @@ import traceback
|
|||
from configparser import RawConfigParser
|
||||
|
||||
from .habitat import *
|
||||
from .sondehubamateur import *
|
||||
from .decoder import decode_packet, parse_ukhas_string
|
||||
from .payloads import *
|
||||
from .horusudp import send_payload_summary
|
||||
from .payloads import init_custom_field_list, init_payload_id_list
|
||||
from .demodstats import FSKDemodStats
|
||||
import horusdemodlib.payloads
|
||||
import horusdemodlib
|
||||
|
||||
def read_config(filename):
|
||||
''' Read in the user configuation file.'''
|
||||
|
@ -65,8 +67,12 @@ def main():
|
|||
parser.add_argument("--debuglog", type=str, default="horusb_debug.log", help="Write debug log to this file.")
|
||||
parser.add_argument("--payload-list", type=str, default="payload_id_list.txt", help="List of known payload IDs.")
|
||||
parser.add_argument("--custom-fields", type=str, default="custom_field_list.json", help="List of payload Custom Fields")
|
||||
parser.add_argument("--nodownload", action="store_true", default=False, help="Do not download new lists.")
|
||||
# parser.add_argument("--ozimux", type=int, default=-1, help="Override user.cfg OziMux output UDP port. (NOT IMPLEMENTED)")
|
||||
# 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()
|
||||
|
||||
|
@ -92,30 +98,51 @@ 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:
|
||||
# Initialize Payload List
|
||||
horusdemodlib.payloads.HORUS_PAYLOAD_LIST = init_payload_id_list(filename=args.payload_list)
|
||||
|
||||
if args.nodownload:
|
||||
logging.info("Using local lists.")
|
||||
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:
|
||||
# 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)
|
||||
|
||||
logging.info(f"Payload list contains {len(list(horusdemodlib.payloads.HORUS_PAYLOAD_LIST.keys()))} entries.")
|
||||
|
||||
# Init Custom Fields List
|
||||
horusdemodlib.payloads.HORUS_CUSTOM_FIELDS = init_custom_field_list(filename=args.custom_fields)
|
||||
logging.info(f"Custom Field list contains {len(list(horusdemodlib.payloads.HORUS_CUSTOM_FIELDS.keys()))} entries.")
|
||||
|
||||
# Start the Habitat uploader thread.
|
||||
habitat_uploader = HabitatUploader(
|
||||
|
||||
if args.freq_target_hz:
|
||||
_listener_freq_str = f" ({args.freq_target_hz/1e6:.3f} MHz)"
|
||||
else:
|
||||
_listener_freq_str = ""
|
||||
|
||||
if user_config['station_lat'] == 0.0 and user_config['station_lon'] == 0.0:
|
||||
_sondehub_user_pos = None
|
||||
else:
|
||||
_sondehub_user_pos = [user_config['station_lat'], user_config['station_lon'], 0.0]
|
||||
|
||||
sondehub_uploader = SondehubAmateurUploader(
|
||||
upload_rate = 2,
|
||||
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_antenna = user_config['antenna_comment'],
|
||||
user_position = _sondehub_user_pos,
|
||||
user_radio = user_config['radio_comment'] + _listener_freq_str,
|
||||
user_antenna = user_config['antenna_comment'],
|
||||
software_name = "horusdemodlib",
|
||||
software_version = horusdemodlib.__version__,
|
||||
inhibit=args.noupload
|
||||
)
|
||||
|
||||
logging.info("Using User Callsign: %s" % user_config['user_call'])
|
||||
|
||||
demod_stats = FSKDemodStats()
|
||||
demod_stats = FSKDemodStats(peak_hold=True)
|
||||
|
||||
logging.info("Started Horus Demod Uploader. Hit CTRL-C to exit.")
|
||||
# Main loop
|
||||
|
@ -126,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.
|
||||
|
@ -147,12 +174,24 @@ def main():
|
|||
_snr = demod_stats.snr
|
||||
_decoded['snr'] = _snr
|
||||
|
||||
# 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']
|
||||
|
||||
# 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)
|
||||
|
||||
if _logfile:
|
||||
_logfile.write(_decoded_str)
|
||||
|
@ -179,29 +218,64 @@ 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
|
||||
_decoded['snr'] = _snr
|
||||
|
||||
# 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']
|
||||
|
||||
# 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)
|
||||
|
||||
if _logfile:
|
||||
_logfile.write(_decoded['ukhas_str']+'\n')
|
||||
_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()
|
|
@ -1,8 +1,8 @@
|
|||
# HORUS BINARY PAYLOAD ID LIST
|
||||
# 2020-08-24
|
||||
# 2021-09-03
|
||||
#
|
||||
# Payload IDs 0 through 255 are available. If we get near this limit,
|
||||
# the payload format may need to be re-evaluated.
|
||||
# Payload IDs 0 through 255 are used by the v1 telemetry format (22 byte packet),
|
||||
# Payload IDs 256 through 65535 are used by the v2 telemetry format (32 byte packet).
|
||||
#
|
||||
# Request a payload ID by either raising an issue, or submitting a pull request
|
||||
# on the horusdemodlib github page: https://github.com/projecthorus/horusdemodlib
|
||||
|
@ -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
|
||||
|
@ -32,7 +32,7 @@
|
|||
22, PA3FNT-4FSK
|
||||
23, MAGNU
|
||||
24, IDA-4FSK
|
||||
25, GSN-TS-2
|
||||
25, GSN-TS-4FSK
|
||||
26, PA1SDB-4FSK
|
||||
27, PA3DJR-4FSK
|
||||
28, DL1XH-4FSK
|
||||
|
@ -45,3 +45,426 @@
|
|||
35, RPHMELB-4FSK
|
||||
36, KD8YQL-4FSK
|
||||
37, SQ5RB-4FSK
|
||||
38, DH3SUP-4FSK
|
||||
39, LUX2
|
||||
40, SP5YAM-4FSK
|
||||
41, PA5MF-4FSK
|
||||
42, SQ3XBD-4FSK
|
||||
43, SP6QKM-4FSK
|
||||
44, SQ6RQT-4FSK
|
||||
45, SP6ZWR-4FSK
|
||||
46, SQ6QV-4FSK
|
||||
47, SQ6NLN-4FSK
|
||||
48, ICARUS-4FSK
|
||||
49, SQ6NEI-4FSK
|
||||
50, VK5LJG-4FSK
|
||||
51, SQ3PMN-4FSK
|
||||
52, SQ6TW-4FSK
|
||||
53, SP3MCY-4FSK
|
||||
54, VK3TRO-4FSK
|
||||
55, SP3QDX-4FSK
|
||||
56, SP3POW-4FSK
|
||||
57, SP8ESA-4FSK
|
||||
58, 9A4AM-4FSK
|
||||
59, 9A3ZI-4FSK
|
||||
60, OE7CAH-4FSK
|
||||
61, SP5WWL-4FSK
|
||||
62, OE7NCI-4FSK
|
||||
63, SP6VWX-4FSK
|
||||
64, SV3IRG-4FSK
|
||||
65, LUX3
|
||||
66, OH3HAB-4FSK
|
||||
67, SQ3KNL-4FSK
|
||||
68, Flybag-2
|
||||
69, SP6KZ-4FSK
|
||||
70, LY2BAW-4FSK
|
||||
71, SQ2DEF-4FSK
|
||||
72, SQ3GJS-4FSK
|
||||
73, SP3PWL-4FSK
|
||||
74, SQ3DVQ-4FSK
|
||||
75, SP9SKP-4FSK
|
||||
76, W6SUN
|
||||
77, YO3ICT-4FSK
|
||||
78, SP3IZN-4FSK
|
||||
79, KQ6RS
|
||||
80, PD3EGE-4FSK
|
||||
81, SCPWA
|
||||
82, SP6TKN-4FSK
|
||||
83, KA9Q
|
||||
84, SP9DEV-4FSK
|
||||
85, 9A4VS-4FSK
|
||||
86, WD6DRI
|
||||
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, 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.1.21"
|
||||
version = "0.3.13"
|
||||
description = "Project Horus HAB Telemetry Demodulators"
|
||||
authors = ["Mark Jessop"]
|
||||
license = "LGPL-2.1-or-later"
|
||||
|
@ -10,6 +10,7 @@ python = "^3.6"
|
|||
requests = "^2.24.0"
|
||||
crcmod = "^1.7"
|
||||
numpy = "^1.17"
|
||||
python-dateutil = "^2.8"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
requests
|
||||
crcmod
|
||||
numpy
|
||||
numpy
|
||||
python-dateutil
|
Plik binarny nie jest wyświetlany.
|
@ -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)
|
||||
|
||||
|
|
399
src/horus_api.c
399
src/horus_api.c
|
@ -77,7 +77,7 @@ int8_t uw_horus_rtty_8N2[] = {
|
|||
0,0,1,0,0,1,0,0,1,1,0,
|
||||
};
|
||||
|
||||
/* Unique word for Horus Binary V1 */
|
||||
/* Unique word for Horus Binary V1 / V2 */
|
||||
|
||||
int8_t uw_horus_binary_v1[] = {
|
||||
0,0,1,0,0,1,0,0,
|
||||
|
@ -85,19 +85,20 @@ int8_t uw_horus_binary_v1[] = {
|
|||
};
|
||||
|
||||
|
||||
/* Unique word for Horus Binary V2 128/256 bit modes (Last row in the 32x32 Hadamard matrix) */
|
||||
// Old LDPC-mode stuff.
|
||||
// /* Unique word for Horus Binary V2 128/256 bit modes (Last row in the 32x32 Hadamard matrix) */
|
||||
|
||||
int8_t uw_horus_binary_v2[] = {
|
||||
1, 0, 0, 1, 0, 1, 1, 0, // 0x96
|
||||
0, 1, 1, 0, 1, 0, 0, 1, // 0x69
|
||||
0, 1, 1, 0, 1, 0, 0, 1, // 0x69
|
||||
1, 0, 0, 1, 0, 1, 1, 0 // 0x96
|
||||
};
|
||||
// int8_t uw_horus_binary_v2[] = {
|
||||
// 1, 0, 0, 1, 0, 1, 1, 0, // 0x96
|
||||
// 0, 1, 1, 0, 1, 0, 0, 1, // 0x69
|
||||
// 0, 1, 1, 0, 1, 0, 0, 1, // 0x69
|
||||
// 1, 0, 0, 1, 0, 1, 1, 0 // 0x96
|
||||
// };
|
||||
|
||||
|
||||
|
||||
struct horus *horus_open (int mode) {
|
||||
assert((mode == HORUS_MODE_RTTY_7N1) || (mode == HORUS_MODE_RTTY_7N2) || (mode == HORUS_MODE_RTTY_8N2) || (mode == HORUS_MODE_BINARY_V1) || (mode == HORUS_MODE_BINARY_V2_256BIT) || (mode == HORUS_MODE_BINARY_V2_128BIT));
|
||||
assert((mode == HORUS_MODE_RTTY_7N1) || (mode == HORUS_MODE_RTTY_7N2) || (mode == HORUS_MODE_RTTY_8N2) || (mode == HORUS_MODE_BINARY_V1) ); //|| (mode == HORUS_MODE_BINARY_V2_256BIT) || (mode == HORUS_MODE_BINARY_V2_128BIT));
|
||||
|
||||
if (mode == HORUS_MODE_RTTY_7N1){
|
||||
// RTTY Mode defaults - 100 baud, no assumptions about tone spacing.
|
||||
|
@ -115,20 +116,20 @@ struct horus *horus_open (int mode) {
|
|||
// Legacy Horus Binary Mode defaults - 100 baud, Disable mask estimation.
|
||||
return horus_open_advanced(HORUS_MODE_BINARY_V1, HORUS_BINARY_V1_DEFAULT_BAUD, -1);
|
||||
}
|
||||
if (mode == HORUS_MODE_BINARY_V2_128BIT){
|
||||
// V2 Horus Binary Mode defaults - 100 baud, Disable mask estimation.
|
||||
return horus_open_advanced(HORUS_MODE_BINARY_V2_128BIT, HORUS_BINARY_V2_128BIT_DEFAULT_BAUD, -1);
|
||||
}
|
||||
if (mode == HORUS_MODE_BINARY_V2_256BIT){
|
||||
// V2 Horus Binary Mode defaults - 100 baud, Disable mask estimation.
|
||||
return horus_open_advanced(HORUS_MODE_BINARY_V2_256BIT, HORUS_BINARY_V2_256BIT_DEFAULT_BAUD, -1);
|
||||
}
|
||||
// if (mode == HORUS_MODE_BINARY_V2_128BIT){
|
||||
// // V2 Horus Binary Mode defaults - 100 baud, Disable mask estimation.
|
||||
// return horus_open_advanced(HORUS_MODE_BINARY_V2_128BIT, HORUS_BINARY_V2_128BIT_DEFAULT_BAUD, -1);
|
||||
// }
|
||||
// if (mode == HORUS_MODE_BINARY_V2_256BIT){
|
||||
// // V2 Horus Binary Mode defaults - 100 baud, Disable mask estimation.
|
||||
// return horus_open_advanced(HORUS_MODE_BINARY_V2_256BIT, HORUS_BINARY_V2_256BIT_DEFAULT_BAUD, -1);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
struct horus *horus_open_advanced (int mode, int Rs, int tx_tone_spacing) {
|
||||
int i, mask;
|
||||
assert((mode == HORUS_MODE_RTTY_7N1) || (mode == HORUS_MODE_RTTY_7N2) || (mode == HORUS_MODE_RTTY_8N2) || (mode == HORUS_MODE_BINARY_V1) || (mode == HORUS_MODE_BINARY_V2_256BIT) || (mode == HORUS_MODE_BINARY_V2_128BIT));
|
||||
assert((mode == HORUS_MODE_RTTY_7N1) || (mode == HORUS_MODE_RTTY_7N2) || (mode == HORUS_MODE_RTTY_8N2) || (mode == HORUS_MODE_BINARY_V1) );// || (mode == HORUS_MODE_BINARY_V2_256BIT) || (mode == HORUS_MODE_BINARY_V2_128BIT));
|
||||
|
||||
struct horus *hstates = (struct horus *)malloc(sizeof(struct horus));
|
||||
assert(hstates != NULL);
|
||||
|
@ -226,7 +227,7 @@ struct horus *horus_open_advanced (int mode, int Rs, int tx_tone_spacing) {
|
|||
// Parameter setup for the Legacy Horus Binary Mode (22 byte frames, Golay encoding)
|
||||
|
||||
hstates->mFSK = 4;
|
||||
hstates->max_packet_len = HORUS_BINARY_V1_NUM_CODED_BITS;
|
||||
hstates->max_packet_len = HORUS_BINARY_V1V2_MAX_BITS;// HORUS_BINARY_V1_NUM_CODED_BITS;
|
||||
|
||||
// If baud rate not provided, use default
|
||||
if (hstates->Rs == -1){
|
||||
|
@ -251,64 +252,64 @@ struct horus *horus_open_advanced (int mode, int Rs, int tx_tone_spacing) {
|
|||
hstates->rx_bits_len = hstates->max_packet_len;
|
||||
}
|
||||
|
||||
if (mode == HORUS_MODE_BINARY_V2_256BIT) {
|
||||
// if (mode == HORUS_MODE_BINARY_V2_256BIT) {
|
||||
|
||||
hstates->mFSK = 2;
|
||||
hstates->max_packet_len = HORUS_BINARY_V2_256BIT_NUM_CODED_BITS ;
|
||||
// hstates->mFSK = 2;
|
||||
// hstates->max_packet_len = HORUS_BINARY_V2_256BIT_NUM_CODED_BITS ;
|
||||
|
||||
// If baud rate not provided, use default
|
||||
if (hstates->Rs == -1){
|
||||
hstates->Rs = HORUS_BINARY_V2_256BIT_DEFAULT_BAUD;
|
||||
}
|
||||
// // If baud rate not provided, use default
|
||||
// if (hstates->Rs == -1){
|
||||
// hstates->Rs = HORUS_BINARY_V2_256BIT_DEFAULT_BAUD;
|
||||
// }
|
||||
|
||||
if (tx_tone_spacing == -1){
|
||||
// No tone spacing provided. Disable mask estimation, and use the default tone spacing value as a dummy value.
|
||||
tx_tone_spacing = HORUS_BINARY_V2_256BIT_DEFAULT_TONE_SPACING;
|
||||
mask = 0;
|
||||
} else {
|
||||
// Tone spacing provided, enable mask estimation.
|
||||
mask = 1;
|
||||
}
|
||||
// if (tx_tone_spacing == -1){
|
||||
// // No tone spacing provided. Disable mask estimation, and use the default tone spacing value as a dummy value.
|
||||
// tx_tone_spacing = HORUS_BINARY_V2_256BIT_DEFAULT_TONE_SPACING;
|
||||
// mask = 0;
|
||||
// } else {
|
||||
// // Tone spacing provided, enable mask estimation.
|
||||
// mask = 1;
|
||||
// }
|
||||
|
||||
for (i=0; i<sizeof(uw_horus_binary_v2); i++) {
|
||||
hstates->uw[i] = 2*uw_horus_binary_v2[i] - 1;
|
||||
}
|
||||
hstates->uw_len = sizeof(uw_horus_binary_v2);
|
||||
hstates->uw_thresh = sizeof(uw_horus_binary_v2) - 2; /* allow a few bit errors in UW detection */
|
||||
// TODO: Any initialization required?
|
||||
// horus_l2_init();
|
||||
hstates->rx_bits_len = hstates->max_packet_len;
|
||||
}
|
||||
// for (i=0; i<sizeof(uw_horus_binary_v2); i++) {
|
||||
// hstates->uw[i] = 2*uw_horus_binary_v2[i] - 1;
|
||||
// }
|
||||
// hstates->uw_len = sizeof(uw_horus_binary_v2);
|
||||
// hstates->uw_thresh = sizeof(uw_horus_binary_v2) - 2; /* allow a few bit errors in UW detection */
|
||||
// // TODO: Any initialization required?
|
||||
// // horus_l2_init();
|
||||
// hstates->rx_bits_len = hstates->max_packet_len;
|
||||
// }
|
||||
|
||||
if (mode == HORUS_MODE_BINARY_V2_128BIT) {
|
||||
// Parameter setup for the New v2 Horus Binary mode.
|
||||
// if (mode == HORUS_MODE_BINARY_V2_128BIT) {
|
||||
// // Parameter setup for the New v2 Horus Binary mode.
|
||||
|
||||
hstates->mFSK = 2; // Lock to 2FSK until we have decent LLRs for 4FSK.
|
||||
hstates->max_packet_len = HORUS_BINARY_V2_128BIT_NUM_CODED_BITS ;
|
||||
// hstates->mFSK = 2; // Lock to 2FSK until we have decent LLRs for 4FSK.
|
||||
// hstates->max_packet_len = HORUS_BINARY_V2_128BIT_NUM_CODED_BITS ;
|
||||
|
||||
// If baud rate not provided, use default
|
||||
if (hstates->Rs == -1){
|
||||
hstates->Rs = HORUS_BINARY_V2_128BIT_DEFAULT_BAUD;
|
||||
}
|
||||
// // If baud rate not provided, use default
|
||||
// if (hstates->Rs == -1){
|
||||
// hstates->Rs = HORUS_BINARY_V2_128BIT_DEFAULT_BAUD;
|
||||
// }
|
||||
|
||||
if (tx_tone_spacing == -1){
|
||||
// No tone spacing provided. Disable mask estimation, and use the default tone spacing value as a dummy value.
|
||||
tx_tone_spacing = HORUS_BINARY_V2_128BIT_DEFAULT_TONE_SPACING;
|
||||
mask = 0;
|
||||
} else {
|
||||
// Tone spacing provided, enable mask estimation.
|
||||
mask = 1;
|
||||
}
|
||||
// if (tx_tone_spacing == -1){
|
||||
// // No tone spacing provided. Disable mask estimation, and use the default tone spacing value as a dummy value.
|
||||
// tx_tone_spacing = HORUS_BINARY_V2_128BIT_DEFAULT_TONE_SPACING;
|
||||
// mask = 0;
|
||||
// } else {
|
||||
// // Tone spacing provided, enable mask estimation.
|
||||
// mask = 1;
|
||||
// }
|
||||
|
||||
for (i=0; i<sizeof(uw_horus_binary_v2); i++) {
|
||||
hstates->uw[i] = 2*uw_horus_binary_v2[i] - 1;
|
||||
}
|
||||
hstates->uw_len = sizeof(uw_horus_binary_v2);
|
||||
hstates->uw_thresh = sizeof(uw_horus_binary_v2) - 2; /* allow a few bit errors in UW detection */
|
||||
// TODO: Any initialization required?
|
||||
// horus_l2_init();
|
||||
hstates->rx_bits_len = hstates->max_packet_len;
|
||||
}
|
||||
// for (i=0; i<sizeof(uw_horus_binary_v2); i++) {
|
||||
// hstates->uw[i] = 2*uw_horus_binary_v2[i] - 1;
|
||||
// }
|
||||
// hstates->uw_len = sizeof(uw_horus_binary_v2);
|
||||
// hstates->uw_thresh = sizeof(uw_horus_binary_v2) - 2; /* allow a few bit errors in UW detection */
|
||||
// // TODO: Any initialization required?
|
||||
// // horus_l2_init();
|
||||
// hstates->rx_bits_len = hstates->max_packet_len;
|
||||
// }
|
||||
|
||||
// Create the FSK modedm struct. Note that the low-tone-frequency parameter is unused.
|
||||
#define UNUSED 1000
|
||||
|
@ -498,7 +499,9 @@ int extract_horus_rtty(struct horus *hstates, char ascii_out[], int uw_loc, int
|
|||
|
||||
/* make sure we don't overrun storage */
|
||||
|
||||
assert(nout <= horus_get_max_ascii_out_len(hstates));
|
||||
if(nout > horus_get_max_ascii_out_len(hstates)){
|
||||
return 0;
|
||||
}
|
||||
|
||||
hstates->crc_ok = crc_ok;
|
||||
|
||||
|
@ -579,83 +582,6 @@ int extract_horus_binary_v1(struct horus *hstates, char hex_out[], int uw_loc) {
|
|||
return hstates->crc_ok;
|
||||
}
|
||||
|
||||
|
||||
int extract_horus_binary_v2_128(struct horus *hstates, char hex_out[], int uw_loc) {
|
||||
const int nfield = 8; /* 8 bit binary */
|
||||
int st = uw_loc; /* first bit of first char */
|
||||
int en = uw_loc + hstates->max_packet_len; /* last bit of max length packet */
|
||||
|
||||
int j, b, nout;
|
||||
uint8_t rxpacket[hstates->max_packet_len];
|
||||
uint8_t rxbyte, *pout;
|
||||
|
||||
/* convert bits to a packet of bytes */
|
||||
|
||||
pout = rxpacket; nout = 0;
|
||||
|
||||
for (b=st; b<en; b+=nfield) {
|
||||
|
||||
/* assemble bytes MSB to LSB */
|
||||
|
||||
rxbyte = 0;
|
||||
for(j=0; j<nfield; j++) {
|
||||
assert(hstates->rx_bits[b+j] <= 1);
|
||||
rxbyte <<= 1;
|
||||
rxbyte |= hstates->rx_bits[b+j];
|
||||
}
|
||||
|
||||
/* build up output array */
|
||||
|
||||
*pout++ = rxbyte;
|
||||
nout++;
|
||||
}
|
||||
|
||||
if (hstates->verbose) {
|
||||
fprintf(stderr, " extract_horus_binary_v2_128 nout: %d\n Received Packet before decoding:\n ", nout);
|
||||
for (b=0; b<nout; b++) {
|
||||
fprintf(stderr, "%02X", rxpacket[b]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
uint8_t payload_bytes[HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES];
|
||||
float *softbits = hstates->soft_bits + uw_loc + sizeof(uw_horus_binary_v2);
|
||||
horus_ldpc_decode( payload_bytes, softbits , HORUS_MODE_BINARY_V2_128BIT);
|
||||
|
||||
uint16_t crc_tx, crc_rx;
|
||||
crc_rx = horus_l2_gen_crc16(payload_bytes, HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES-2);
|
||||
crc_tx = (uint16_t)payload_bytes[HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES-2] +
|
||||
((uint16_t)payload_bytes[HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES-1]<<8);
|
||||
|
||||
if (hstates->verbose) {
|
||||
fprintf(stderr, " extract_horus_binary_v2_128 crc_tx: %04X crc_rx: %04X\n", crc_tx, crc_rx);
|
||||
}
|
||||
|
||||
/* convert to ASCII string of hex characters */
|
||||
|
||||
hex_out[0] = 0;
|
||||
char hex[3];
|
||||
for (b=0; b<HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES; b++) {
|
||||
sprintf(hex, "%02X", payload_bytes[b]);
|
||||
strcat(hex_out, hex);
|
||||
}
|
||||
|
||||
if (hstates->verbose) {
|
||||
fprintf(stderr, " nout: %d Decoded Payload bytes:\n %s\n", nout, hex_out);
|
||||
}
|
||||
|
||||
/* With noise input to FSK demod we can get occasinal UW matches,
|
||||
so a good idea to only pass on any packets that pass CRC */
|
||||
|
||||
hstates->crc_ok = (crc_tx == crc_rx);
|
||||
if ( hstates->crc_ok) {
|
||||
hstates->total_payload_bits = HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES;
|
||||
}
|
||||
|
||||
return hstates->crc_ok;
|
||||
|
||||
}
|
||||
|
||||
int extract_horus_binary_v2_256(struct horus *hstates, char hex_out[], int uw_loc) {
|
||||
const int nfield = 8; /* 8 bit binary */
|
||||
int st = uw_loc; /* first bit of first char */
|
||||
|
@ -693,10 +619,9 @@ int extract_horus_binary_v2_256(struct horus *hstates, char hex_out[], int uw_lo
|
|||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
|
||||
uint8_t payload_bytes[HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES];
|
||||
float *softbits = hstates->soft_bits + uw_loc + sizeof(uw_horus_binary_v2);
|
||||
horus_ldpc_decode( payload_bytes, softbits , HORUS_MODE_BINARY_V2_256BIT);
|
||||
horus_l2_decode_rx_packet(payload_bytes, rxpacket, HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES);
|
||||
|
||||
uint16_t crc_tx, crc_rx;
|
||||
crc_rx = horus_l2_gen_crc16(payload_bytes, HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES-2);
|
||||
|
@ -717,7 +642,7 @@ int extract_horus_binary_v2_256(struct horus *hstates, char hex_out[], int uw_lo
|
|||
}
|
||||
|
||||
if (hstates->verbose) {
|
||||
fprintf(stderr, " nout: %d Decoded Payload bytes:\n %s\n", nout, hex_out);
|
||||
fprintf(stderr, " nout: %d Decoded Payload bytes:\n %s", nout, hex_out);
|
||||
}
|
||||
|
||||
/* With noise input to FSK demod we can get occasinal UW matches,
|
||||
|
@ -727,11 +652,162 @@ int extract_horus_binary_v2_256(struct horus *hstates, char hex_out[], int uw_lo
|
|||
if ( hstates->crc_ok) {
|
||||
hstates->total_payload_bits = HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES;
|
||||
}
|
||||
|
||||
return hstates->crc_ok;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// int extract_horus_binary_v2_128(struct horus *hstates, char hex_out[], int uw_loc) {
|
||||
// const int nfield = 8; /* 8 bit binary */
|
||||
// int st = uw_loc; /* first bit of first char */
|
||||
// int en = uw_loc + hstates->max_packet_len; /* last bit of max length packet */
|
||||
|
||||
// int j, b, nout;
|
||||
// uint8_t rxpacket[hstates->max_packet_len];
|
||||
// uint8_t rxbyte, *pout;
|
||||
|
||||
// /* convert bits to a packet of bytes */
|
||||
|
||||
// pout = rxpacket; nout = 0;
|
||||
|
||||
// for (b=st; b<en; b+=nfield) {
|
||||
|
||||
// /* assemble bytes MSB to LSB */
|
||||
|
||||
// rxbyte = 0;
|
||||
// for(j=0; j<nfield; j++) {
|
||||
// assert(hstates->rx_bits[b+j] <= 1);
|
||||
// rxbyte <<= 1;
|
||||
// rxbyte |= hstates->rx_bits[b+j];
|
||||
// }
|
||||
|
||||
// /* build up output array */
|
||||
|
||||
// *pout++ = rxbyte;
|
||||
// nout++;
|
||||
// }
|
||||
|
||||
// if (hstates->verbose) {
|
||||
// fprintf(stderr, " extract_horus_binary_v2_128 nout: %d\n Received Packet before decoding:\n ", nout);
|
||||
// for (b=0; b<nout; b++) {
|
||||
// fprintf(stderr, "%02X", rxpacket[b]);
|
||||
// }
|
||||
// fprintf(stderr, "\n");
|
||||
// }
|
||||
|
||||
// uint8_t payload_bytes[HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES];
|
||||
// float *softbits = hstates->soft_bits + uw_loc + sizeof(uw_horus_binary_v2);
|
||||
// horus_ldpc_decode( payload_bytes, softbits , HORUS_MODE_BINARY_V2_128BIT);
|
||||
|
||||
// uint16_t crc_tx, crc_rx;
|
||||
// crc_rx = horus_l2_gen_crc16(payload_bytes, HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES-2);
|
||||
// crc_tx = (uint16_t)payload_bytes[HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES-2] +
|
||||
// ((uint16_t)payload_bytes[HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES-1]<<8);
|
||||
|
||||
// if (hstates->verbose) {
|
||||
// fprintf(stderr, " extract_horus_binary_v2_128 crc_tx: %04X crc_rx: %04X\n", crc_tx, crc_rx);
|
||||
// }
|
||||
|
||||
// /* convert to ASCII string of hex characters */
|
||||
|
||||
// hex_out[0] = 0;
|
||||
// char hex[3];
|
||||
// for (b=0; b<HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES; b++) {
|
||||
// sprintf(hex, "%02X", payload_bytes[b]);
|
||||
// strcat(hex_out, hex);
|
||||
// }
|
||||
|
||||
// if (hstates->verbose) {
|
||||
// fprintf(stderr, " nout: %d Decoded Payload bytes:\n %s\n", nout, hex_out);
|
||||
// }
|
||||
|
||||
// /* With noise input to FSK demod we can get occasinal UW matches,
|
||||
// so a good idea to only pass on any packets that pass CRC */
|
||||
|
||||
// hstates->crc_ok = (crc_tx == crc_rx);
|
||||
// if ( hstates->crc_ok) {
|
||||
// hstates->total_payload_bits = HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES;
|
||||
// }
|
||||
|
||||
// return hstates->crc_ok;
|
||||
|
||||
// }
|
||||
|
||||
// int extract_horus_binary_v2_256(struct horus *hstates, char hex_out[], int uw_loc) {
|
||||
// const int nfield = 8; /* 8 bit binary */
|
||||
// int st = uw_loc; /* first bit of first char */
|
||||
// int en = uw_loc + hstates->max_packet_len; /* last bit of max length packet */
|
||||
|
||||
// int j, b, nout;
|
||||
// uint8_t rxpacket[hstates->max_packet_len];
|
||||
// uint8_t rxbyte, *pout;
|
||||
|
||||
// /* convert bits to a packet of bytes */
|
||||
|
||||
// pout = rxpacket; nout = 0;
|
||||
|
||||
// for (b=st; b<en; b+=nfield) {
|
||||
|
||||
// /* assemble bytes MSB to LSB */
|
||||
|
||||
// rxbyte = 0;
|
||||
// for(j=0; j<nfield; j++) {
|
||||
// assert(hstates->rx_bits[b+j] <= 1);
|
||||
// rxbyte <<= 1;
|
||||
// rxbyte |= hstates->rx_bits[b+j];
|
||||
// }
|
||||
|
||||
// /* build up output array */
|
||||
|
||||
// *pout++ = rxbyte;
|
||||
// nout++;
|
||||
// }
|
||||
|
||||
// if (hstates->verbose) {
|
||||
// fprintf(stderr, " extract_horus_binary_v2_256 nout: %d\n Received Packet before decoding:\n ", nout);
|
||||
// for (b=0; b<nout; b++) {
|
||||
// fprintf(stderr, "%02X", rxpacket[b]);
|
||||
// }
|
||||
// fprintf(stderr, "\n");
|
||||
// }
|
||||
|
||||
// uint8_t payload_bytes[HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES];
|
||||
// float *softbits = hstates->soft_bits + uw_loc + sizeof(uw_horus_binary_v2);
|
||||
// horus_ldpc_decode( payload_bytes, softbits , HORUS_MODE_BINARY_V2_256BIT);
|
||||
|
||||
// uint16_t crc_tx, crc_rx;
|
||||
// crc_rx = horus_l2_gen_crc16(payload_bytes, HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES-2);
|
||||
// crc_tx = (uint16_t)payload_bytes[HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES-2] +
|
||||
// ((uint16_t)payload_bytes[HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES-1]<<8);
|
||||
|
||||
// if (hstates->verbose) {
|
||||
// fprintf(stderr, " extract_horus_binary_v2_256 crc_tx: %04X crc_rx: %04X\n", crc_tx, crc_rx);
|
||||
// }
|
||||
|
||||
// /* convert to ASCII string of hex characters */
|
||||
|
||||
// hex_out[0] = 0;
|
||||
// char hex[3];
|
||||
// for (b=0; b<HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES; b++) {
|
||||
// sprintf(hex, "%02X", payload_bytes[b]);
|
||||
// strcat(hex_out, hex);
|
||||
// }
|
||||
|
||||
// if (hstates->verbose) {
|
||||
// fprintf(stderr, " nout: %d Decoded Payload bytes:\n %s\n", nout, hex_out);
|
||||
// }
|
||||
|
||||
// /* With noise input to FSK demod we can get occasinal UW matches,
|
||||
// so a good idea to only pass on any packets that pass CRC */
|
||||
|
||||
// hstates->crc_ok = (crc_tx == crc_rx);
|
||||
// if ( hstates->crc_ok) {
|
||||
// hstates->total_payload_bits = HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES;
|
||||
// }
|
||||
|
||||
// return hstates->crc_ok;
|
||||
|
||||
// }
|
||||
|
||||
int horus_rx(struct horus *hstates, char ascii_out[], short demod_in[], int quadrature) {
|
||||
int i, j, uw_loc, packet_detected;
|
||||
|
||||
|
@ -825,6 +901,10 @@ int horus_rx(struct horus *hstates, char ascii_out[], short demod_in[], int quad
|
|||
}
|
||||
if (hstates->mode == HORUS_MODE_BINARY_V1) {
|
||||
packet_detected = extract_horus_binary_v1(hstates, ascii_out, uw_loc);
|
||||
if (!packet_detected){
|
||||
// Try v2 256 bit decoder instead
|
||||
packet_detected = extract_horus_binary_v2_256(hstates, ascii_out, uw_loc);
|
||||
}
|
||||
//#define DUMP_BINARY_PACKET
|
||||
#ifdef DUMP_BINARY_PACKET
|
||||
FILE *f = fopen("packetbits.txt", "wt"); assert(f != NULL);
|
||||
|
@ -835,12 +915,12 @@ int horus_rx(struct horus *hstates, char ascii_out[], short demod_in[], int quad
|
|||
exit(0);
|
||||
#endif
|
||||
}
|
||||
if (hstates->mode == HORUS_MODE_BINARY_V2_128BIT){
|
||||
packet_detected = extract_horus_binary_v2_128(hstates, ascii_out, uw_loc);
|
||||
}
|
||||
if (hstates->mode == HORUS_MODE_BINARY_V2_256BIT){
|
||||
packet_detected = extract_horus_binary_v2_256(hstates, ascii_out, uw_loc);
|
||||
}
|
||||
// if (hstates->mode == HORUS_MODE_BINARY_V2_128BIT){
|
||||
// packet_detected = extract_horus_binary_v2_128(hstates, ascii_out, uw_loc);
|
||||
// }
|
||||
// if (hstates->mode == HORUS_MODE_BINARY_V2_256BIT){
|
||||
// packet_detected = extract_horus_binary_v2_256(hstates, ascii_out, uw_loc);
|
||||
// }
|
||||
}
|
||||
return packet_detected;
|
||||
}
|
||||
|
@ -881,7 +961,8 @@ int horus_get_max_ascii_out_len(struct horus *hstates) {
|
|||
return hstates->max_packet_len/11; /* 8 bit ASCII, plus 3 sync bits */
|
||||
}
|
||||
if (hstates->mode == HORUS_MODE_BINARY_V1) {
|
||||
return (HORUS_BINARY_V1_NUM_UNCODED_PAYLOAD_BYTES*2+1); /* Hexadecimal encoded */
|
||||
//return (HORUS_BINARY_V1_NUM_UNCODED_PAYLOAD_BYTES*2+1); /* Hexadecimal encoded */
|
||||
return (HORUS_BINARY_V1V2_MAX_UNCODED_BYTES*2+1);
|
||||
}
|
||||
if (hstates->mode == HORUS_MODE_BINARY_V2_256BIT) {
|
||||
return (HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES*2+1); /* Hexadecimal encoded */
|
||||
|
|
|
@ -37,14 +37,14 @@
|
|||
|
||||
/* Horus API Modes */
|
||||
#define HORUS_MODE_BINARY_V1 0 // Legacy binary mode
|
||||
#define HORUS_MODE_BINARY_V2_256BIT 1 // New 256-bit LDPC-encoded mode
|
||||
#define HORUS_MODE_BINARY_V2_128BIT 2 // New 128-bit LDPC-encoded mode
|
||||
#define HORUS_MODE_BINARY_V2_256BIT 1 // New 256-bit Golay Encoded Mode
|
||||
#define HORUS_MODE_BINARY_V2_128BIT 2 // New 128-bit Golay Encoded Mode (Not used yet)
|
||||
#define HORUS_MODE_RTTY_7N1 89 // RTTY Decoding - 7N1
|
||||
#define HORUS_MODE_RTTY_7N2 90 // RTTY Decoding - 7N2
|
||||
#define HORUS_MODE_RTTY_8N2 91 // RTTY Decoding - 8N2
|
||||
|
||||
|
||||
// Settings for Legacy Horus Binary Mode (Golay Encoding)
|
||||
// Settings for Legacy Horus Binary Mode (Golay (23,12) encoding)
|
||||
#define HORUS_BINARY_V1_NUM_CODED_BITS 360
|
||||
#define HORUS_BINARY_V1_NUM_UNCODED_PAYLOAD_BYTES 22
|
||||
#define HORUS_BINARY_V1_DEFAULT_BAUD 100
|
||||
|
@ -53,18 +53,36 @@
|
|||
// Note that mask estimation is turned off by default for
|
||||
// this mode, and hence this spacing is not used.
|
||||
|
||||
// Settings for Horus Binary 256-bit mode (LDPC Encoding, r=1/3)
|
||||
#define HORUS_BINARY_V2_256BIT_NUM_CODED_BITS (768+32)
|
||||
// Settings for Horus Binary 256-bit mode (Golay (23,12) encoding)
|
||||
#define HORUS_BINARY_V2_256BIT_NUM_CODED_BITS 520
|
||||
#define HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES 32
|
||||
#define HORUS_BINARY_V2_256BIT_DEFAULT_BAUD 100
|
||||
#define HORUS_BINARY_V2_256BIT_DEFAULT_TONE_SPACING 270
|
||||
|
||||
// Settings for Horus Binary 128-bit mode (LDPC Encoding, r=1/3)
|
||||
#define HORUS_BINARY_V2_128BIT_NUM_CODED_BITS (384+32)
|
||||
// Settings for Horus Binary 128-bit mode (Golay (23,12) encoding) - not used yet
|
||||
#define HORUS_BINARY_V2_128BIT_NUM_CODED_BITS 272
|
||||
#define HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES 16
|
||||
#define HORUS_BINARY_V2_128BIT_DEFAULT_BAUD 100
|
||||
#define HORUS_BINARY_V2_128BIT_DEFAULT_TONE_SPACING 270
|
||||
|
||||
|
||||
#define HORUS_BINARY_V1V2_MAX_BITS HORUS_BINARY_V2_256BIT_NUM_CODED_BITS
|
||||
#define HORUS_BINARY_V1V2_MAX_UNCODED_BYTES HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES
|
||||
|
||||
// Not using LDPC any more...
|
||||
// // Settings for Horus Binary 256-bit mode (LDPC Encoding, r=1/3)
|
||||
// #define HORUS_BINARY_V2_256BIT_NUM_CODED_BITS (768+32)
|
||||
// #define HORUS_BINARY_V2_256BIT_NUM_UNCODED_PAYLOAD_BYTES 32
|
||||
// #define HORUS_BINARY_V2_256BIT_DEFAULT_BAUD 100
|
||||
// #define HORUS_BINARY_V2_256BIT_DEFAULT_TONE_SPACING 270
|
||||
|
||||
// // Settings for Horus Binary 128-bit mode (LDPC Encoding, r=1/3)
|
||||
// #define HORUS_BINARY_V2_128BIT_NUM_CODED_BITS (384+32)
|
||||
// #define HORUS_BINARY_V2_128BIT_NUM_UNCODED_PAYLOAD_BYTES 16
|
||||
// #define HORUS_BINARY_V2_128BIT_DEFAULT_BAUD 100
|
||||
// #define HORUS_BINARY_V2_128BIT_DEFAULT_TONE_SPACING 270
|
||||
|
||||
|
||||
// Settings for RTTY Decoder
|
||||
#define HORUS_RTTY_MAX_CHARS 120
|
||||
#define HORUS_RTTY_7N1_NUM_BITS (HORUS_RTTY_MAX_CHARS*9)
|
||||
|
|
|
@ -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;
|
||||
|
@ -93,12 +93,12 @@ int main(int argc, char *argv[]) {
|
|||
if ((strcmp(optarg, "BINARY") == 0) || (strcmp(optarg, "binary") == 0)) {
|
||||
mode = HORUS_MODE_BINARY_V1;
|
||||
}
|
||||
if ((strcmp(optarg, "256BIT") == 0) || (strcmp(optarg, "256bit") == 0)) {
|
||||
mode = HORUS_MODE_BINARY_V2_256BIT;
|
||||
}
|
||||
if ((strcmp(optarg, "128BIT") == 0) || (strcmp(optarg, "128bit") == 0)) {
|
||||
mode = HORUS_MODE_BINARY_V2_128BIT;
|
||||
}
|
||||
// if ((strcmp(optarg, "256BIT") == 0) || (strcmp(optarg, "256bit") == 0)) {
|
||||
// mode = HORUS_MODE_BINARY_V2_256BIT;
|
||||
// }
|
||||
// if ((strcmp(optarg, "128BIT") == 0) || (strcmp(optarg, "128bit") == 0)) {
|
||||
// mode = HORUS_MODE_BINARY_V2_128BIT;
|
||||
// }
|
||||
if (mode == -1) {
|
||||
fprintf(stderr, "use --mode RTTY or --mode binary\n");
|
||||
exit(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.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ struct TBinaryPacket0
|
|||
uint16_t Checksum; // CRC16-CCITT Checksum.
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* Horus Mode 1 (32-byte) Binary Packet */
|
||||
/* Horus v2 Mode 1 (32-byte) Binary Packet */
|
||||
struct TBinaryPacket1
|
||||
{
|
||||
uint16_t PayloadID;
|
||||
|
@ -58,18 +58,14 @@ struct TBinaryPacket1
|
|||
int8_t Temp; // Twos Complement Temp value.
|
||||
uint8_t BattVoltage; // 0 = 0.5v, 255 = 2.0V, linear steps in-between.
|
||||
uint8_t dummy1; // Dummy values for user-configurable section.
|
||||
uint8_t dummy2;
|
||||
uint8_t dummy3;
|
||||
uint8_t dummy4;
|
||||
uint8_t dummy5;
|
||||
uint8_t dummy6;
|
||||
uint8_t dummy7;
|
||||
uint8_t dummy8;
|
||||
uint8_t dummy9;
|
||||
float dummy2; // Float
|
||||
uint8_t dummy3; // battery voltage test
|
||||
uint8_t dummy4; // divide by 10
|
||||
uint16_t dummy5; // divide by 100
|
||||
uint16_t Checksum; // CRC16-CCITT Checksum.
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* Horus Mode 2 (16-byte) Binary Packet */
|
||||
/* Horus v2 Mode 2 (16-byte) Binary Packet (Not currently used) */
|
||||
struct TBinaryPacket2
|
||||
{
|
||||
uint8_t PayloadID;
|
||||
|
@ -92,7 +88,7 @@ int main(int argc,char *argv[]) {
|
|||
int i, framecnt;
|
||||
int horus_mode = 0;
|
||||
|
||||
char usage[] = "usage: %s horus_mode numFrames\nMode 0 = Legacy 22-byte Golay FEC\nMode 1 = 32-byte LDPC FEC\nMode 2 = 16-byte LDPC FEC\n";
|
||||
char usage[] = "usage: %s horus_mode numFrames\nMode 0 = Legacy 22-byte Golay FEC\nMode 1 = 32-byte Golay FEC\n";
|
||||
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, usage, argv[0]);
|
||||
|
@ -135,22 +131,32 @@ int main(int argc,char *argv[]) {
|
|||
}
|
||||
|
||||
} else if(horus_mode == 1){
|
||||
// 32-Byte LDPC Encoded mode.
|
||||
int nbytes = sizeof(struct TBinaryPacket1);
|
||||
struct TBinaryPacket1 input_payload;
|
||||
|
||||
int num_tx_data_bytes = 4 + H_256_768_22_DATA_BYTES + H_256_768_22_PARITY_BYTES;
|
||||
int num_tx_data_bytes = horus_l2_get_num_tx_data_bytes(nbytes);
|
||||
unsigned char tx[num_tx_data_bytes];
|
||||
|
||||
uint16_t counter = 0;
|
||||
|
||||
/* all zeros is nastiest sequence for demod before scrambling */
|
||||
memset(&input_payload, 0, nbytes);
|
||||
input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
|
||||
int ldpc_tx_bytes = ldpc_encode_packet(tx, (unsigned char*)&input_payload, 1);
|
||||
|
||||
int b;
|
||||
uint8_t tx_bit;
|
||||
while(framecnt > 0){
|
||||
memset(&input_payload, 0, nbytes);
|
||||
input_payload.PayloadID = 256;
|
||||
input_payload.Hours = 12;
|
||||
input_payload.Minutes = 34;
|
||||
input_payload.Seconds = 56;
|
||||
input_payload.dummy1 = 1;
|
||||
input_payload.dummy2 = 1.23456789;
|
||||
input_payload.dummy3 = 200;
|
||||
input_payload.dummy4 = 123;
|
||||
input_payload.dummy5 = 1234;
|
||||
input_payload.Counter = counter;
|
||||
input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
|
||||
horus_l2_encode_tx_packet(tx, (unsigned char*)&input_payload, nbytes);
|
||||
|
||||
int b;
|
||||
uint8_t tx_bit;
|
||||
for(i=0; i<num_tx_data_bytes; i++) {
|
||||
for(b=0; b<8; b++) {
|
||||
tx_bit = (tx[i] >> (7-b)) & 0x1; /* msb first */
|
||||
|
@ -159,35 +165,37 @@ int main(int argc,char *argv[]) {
|
|||
}
|
||||
}
|
||||
framecnt -= 1;
|
||||
counter += 1;
|
||||
}
|
||||
} else if(horus_mode == 2){
|
||||
// 16-Byte LDPC Encoded mode.
|
||||
int nbytes = sizeof(struct TBinaryPacket2);
|
||||
struct TBinaryPacket2 input_payload;
|
||||
// Leaving this in place unless we ever decide to do an LDPC mode.
|
||||
// } else if(horus_mode == 2){
|
||||
// // 16-Byte LDPC Encoded mode.
|
||||
// int nbytes = sizeof(struct TBinaryPacket2);
|
||||
// struct TBinaryPacket2 input_payload;
|
||||
|
||||
// TODO: Add Calculation of expected number of TX bytes based on LDPC code.
|
||||
int num_tx_data_bytes = 4 + H_128_384_23_DATA_BYTES + H_128_384_23_PARITY_BYTES;
|
||||
unsigned char tx[num_tx_data_bytes];
|
||||
// // TODO: Add Calculation of expected number of TX bytes based on LDPC code.
|
||||
// int num_tx_data_bytes = 4 + H_128_384_23_DATA_BYTES + H_128_384_23_PARITY_BYTES;
|
||||
// unsigned char tx[num_tx_data_bytes];
|
||||
|
||||
/* all zeros is nastiest sequence for demod before scrambling */
|
||||
memset(&input_payload, 0, nbytes);
|
||||
input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
// /* all zeros is nastiest sequence for demod before scrambling */
|
||||
// memset(&input_payload, 0, nbytes);
|
||||
// input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
|
||||
|
||||
int ldpc_tx_bytes = ldpc_encode_packet(tx, (unsigned char*)&input_payload, 2);
|
||||
// int ldpc_tx_bytes = ldpc_encode_packet(tx, (unsigned char*)&input_payload, 2);
|
||||
|
||||
int b;
|
||||
uint8_t tx_bit;
|
||||
while(framecnt > 0){
|
||||
for(i=0; i<num_tx_data_bytes; i++) {
|
||||
for(b=0; b<8; b++) {
|
||||
tx_bit = (tx[i] >> (7-b)) & 0x1; /* msb first */
|
||||
fwrite(&tx_bit,sizeof(uint8_t),1,stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
framecnt -= 1;
|
||||
}
|
||||
// int b;
|
||||
// uint8_t tx_bit;
|
||||
// while(framecnt > 0){
|
||||
// for(i=0; i<num_tx_data_bytes; i++) {
|
||||
// for(b=0; b<8; b++) {
|
||||
// tx_bit = (tx[i] >> (7-b)) & 0x1; /* msb first */
|
||||
// fwrite(&tx_bit,sizeof(uint8_t),1,stdout);
|
||||
// fflush(stdout);
|
||||
// }
|
||||
// }
|
||||
// framecnt -= 1;
|
||||
// }
|
||||
} else {
|
||||
fprintf(stderr, "Unknown Mode!");
|
||||
}
|
||||
|
|
430
src/horus_l2.c
430
src/horus_l2.c
|
@ -65,15 +65,15 @@
|
|||
#include "mpdecode_core.h"
|
||||
#include "horus_l2.h"
|
||||
#include "golay23.h"
|
||||
#include "H_128_384_23.h"
|
||||
#include "H_256_768_22.h"
|
||||
//#include "H_128_384_23.h"
|
||||
//#include "H_256_768_22.h"
|
||||
|
||||
#ifdef HORUS_L2_UNITTEST
|
||||
#define HORUS_L2_RX
|
||||
#endif
|
||||
|
||||
static const char uw[] = {'$','$'}; // UW for Horus Binary v1
|
||||
static const char uw_v2[] = {0x96, 0x69, 0x69, 0x96}; // UW for Horus Binary v2 modes
|
||||
static const char uw[] = {'$','$'}; // UW for Horus Binary v1 and v2 Golay modes
|
||||
//static const char uw_v2[] = {0x96, 0x69, 0x69, 0x96}; // UW for Horus Binary v2 modes - DEPRECATED.
|
||||
|
||||
/* Function Prototypes ------------------------------------------------*/
|
||||
|
||||
|
@ -748,7 +748,8 @@ int test_sending_bytes(int nbytes, float ber, int error_pattern) {
|
|||
/* unit test designed to run on a PC */
|
||||
|
||||
int main(void) {
|
||||
printf("Horus v1 length packets (22 bytes)\n");
|
||||
int num_tx_data_bytes_v1 = horus_l2_get_num_tx_data_bytes(22);
|
||||
printf("Horus v1 length packets (22 bytes uncoded, %d bytes coded)\n", num_tx_data_bytes_v1);
|
||||
printf("test 0: BER: 0.00 ...........: %d\n", test_sending_bytes(22, 0.00, 0));
|
||||
printf("test 1: BER: 0.01 ...........: %d\n", test_sending_bytes(22, 0.01, 0));
|
||||
printf("test 2: BER: 0.05 ...........: %d\n", test_sending_bytes(22, 0.05, 0));
|
||||
|
@ -768,7 +769,9 @@ int main(void) {
|
|||
|
||||
printf("test 5: 1 error every 12 bits: %d\n", test_sending_bytes(22, 0.00, 2));
|
||||
|
||||
printf("Horus v2 length packets (16 bytes)\n");
|
||||
int num_tx_data_bytes_v2_16 = horus_l2_get_num_tx_data_bytes(16);
|
||||
printf("Horus v2 length packets (16 bytes uncoded, %d bytes coded)\n", num_tx_data_bytes_v2_16);
|
||||
|
||||
printf("test 0: BER: 0.00 ...........: %d\n", test_sending_bytes(16, 0.00, 0));
|
||||
printf("test 1: BER: 0.01 ...........: %d\n", test_sending_bytes(16, 0.01, 0));
|
||||
printf("test 2: BER: 0.05 ...........: %d\n", test_sending_bytes(16, 0.05, 0));
|
||||
|
@ -788,7 +791,8 @@ int main(void) {
|
|||
|
||||
printf("test 5: 1 error every 12 bits: %d\n", test_sending_bytes(16, 0.00, 2));
|
||||
|
||||
printf("Horus v2 length packets (32 bytes)\n");
|
||||
int num_tx_data_bytes_v2_32 = horus_l2_get_num_tx_data_bytes(32);
|
||||
printf("Horus v2 length packets (32 bytes uncoded, %d bytes coded)\n", num_tx_data_bytes_v2_32);
|
||||
printf("test 0: BER: 0.00 ...........: %d\n", test_sending_bytes(32, 0.00, 0));
|
||||
printf("test 1: BER: 0.01 ...........: %d\n", test_sending_bytes(32, 0.01, 0));
|
||||
printf("test 2: BER: 0.05 ...........: %d\n", test_sending_bytes(32, 0.05, 0));
|
||||
|
@ -833,8 +837,6 @@ struct TBinaryPacket
|
|||
|
||||
struct V2SmallBinaryPacket
|
||||
{
|
||||
// 4 byte preamble for high error rates ("0x96696996")
|
||||
// - to improve soft bit prediction
|
||||
uint8_t PayloadID; // Legacy list
|
||||
uint8_t Counter; // 8 bit counter
|
||||
uint16_t BiSeconds; // Time of day / 2
|
||||
|
@ -1004,62 +1006,6 @@ int main(int argc,char *argv[]) {
|
|||
|
||||
horus_l2_encode_tx_packet(tx, (unsigned char*)&input_payload, nbytes);
|
||||
|
||||
int b;
|
||||
uint8_t tx_bit;
|
||||
while(framecnt >= 0){
|
||||
for(i=0; i<num_tx_data_bytes; i++) {
|
||||
for(b=0; b<8; b++) {
|
||||
tx_bit = (tx[i] >> (7-b)) & 0x1; /* msb first */
|
||||
fwrite(&tx_bit,sizeof(uint8_t),1,stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
framecnt -= 1;
|
||||
}
|
||||
|
||||
} else if (mode == 1) {
|
||||
// 32-byte horus mode
|
||||
int nbytes = sizeof(struct V2LargeBinaryPacket);
|
||||
struct V2LargeBinaryPacket input_payload;
|
||||
|
||||
int num_tx_data_bytes = sizeof(uw_v2) + H_256_768_22_DATA_BYTES + H_256_768_22_PARITY_BYTES;
|
||||
unsigned char tx[num_tx_data_bytes];
|
||||
memset(&input_payload, 0, nbytes);
|
||||
|
||||
input_payload.PayloadID = 1;
|
||||
input_payload.Counter = 2;
|
||||
input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
|
||||
int ldpc_tx_bytes = ldpc_encode_packet(tx, (unsigned char*)&input_payload, 1);
|
||||
|
||||
int b;
|
||||
uint8_t tx_bit;
|
||||
while(framecnt >= 0){
|
||||
for(i=0; i<num_tx_data_bytes; i++) {
|
||||
for(b=0; b<8; b++) {
|
||||
tx_bit = (tx[i] >> (7-b)) & 0x1; /* msb first */
|
||||
fwrite(&tx_bit,sizeof(uint8_t),1,stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
framecnt -= 1;
|
||||
}
|
||||
|
||||
} else if (mode == 2) {
|
||||
// 16-byte horus mode
|
||||
int nbytes = sizeof(struct V2SmallBinaryPacket);
|
||||
struct V2SmallBinaryPacket input_payload;
|
||||
|
||||
int num_tx_data_bytes = sizeof(uw_v2) + H_128_384_23_DATA_BYTES + H_128_384_23_PARITY_BYTES;
|
||||
unsigned char tx[num_tx_data_bytes];
|
||||
memset(&input_payload, 0, nbytes);
|
||||
input_payload.PayloadID = 1;
|
||||
input_payload.Counter = 2;
|
||||
|
||||
input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
|
||||
int ldpc_tx_bytes = ldpc_encode_packet(tx, (unsigned char*)&input_payload, 2);
|
||||
|
||||
int b;
|
||||
uint8_t tx_bit;
|
||||
while(framecnt >= 0){
|
||||
|
@ -1073,6 +1019,62 @@ int main(int argc,char *argv[]) {
|
|||
framecnt -= 1;
|
||||
}
|
||||
}
|
||||
// } else if (mode == 1) {
|
||||
// // 32-byte horus mode
|
||||
// int nbytes = sizeof(struct V2LargeBinaryPacket);
|
||||
// struct V2LargeBinaryPacket input_payload;
|
||||
|
||||
// int num_tx_data_bytes = sizeof(uw_v2) + H_256_768_22_DATA_BYTES + H_256_768_22_PARITY_BYTES;
|
||||
// unsigned char tx[num_tx_data_bytes];
|
||||
// memset(&input_payload, 0, nbytes);
|
||||
|
||||
// input_payload.PayloadID = 1;
|
||||
// input_payload.Counter = 2;
|
||||
// input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
|
||||
// int ldpc_tx_bytes = ldpc_encode_packet(tx, (unsigned char*)&input_payload, 1);
|
||||
|
||||
// int b;
|
||||
// uint8_t tx_bit;
|
||||
// while(framecnt >= 0){
|
||||
// for(i=0; i<num_tx_data_bytes; i++) {
|
||||
// for(b=0; b<8; b++) {
|
||||
// tx_bit = (tx[i] >> (7-b)) & 0x1; /* msb first */
|
||||
// fwrite(&tx_bit,sizeof(uint8_t),1,stdout);
|
||||
// fflush(stdout);
|
||||
// }
|
||||
// }
|
||||
// framecnt -= 1;
|
||||
// }
|
||||
|
||||
// } else if (mode == 2) {
|
||||
// // 16-byte horus mode
|
||||
// int nbytes = sizeof(struct V2SmallBinaryPacket);
|
||||
// struct V2SmallBinaryPacket input_payload;
|
||||
|
||||
// int num_tx_data_bytes = sizeof(uw_v2) + H_128_384_23_DATA_BYTES + H_128_384_23_PARITY_BYTES;
|
||||
// unsigned char tx[num_tx_data_bytes];
|
||||
// memset(&input_payload, 0, nbytes);
|
||||
// input_payload.PayloadID = 1;
|
||||
// input_payload.Counter = 2;
|
||||
|
||||
// input_payload.Checksum = horus_l2_gen_crc16((unsigned char*)&input_payload, nbytes-2);
|
||||
|
||||
// int ldpc_tx_bytes = ldpc_encode_packet(tx, (unsigned char*)&input_payload, 2);
|
||||
|
||||
// int b;
|
||||
// uint8_t tx_bit;
|
||||
// while(framecnt >= 0){
|
||||
// for(i=0; i<num_tx_data_bytes; i++) {
|
||||
// for(b=0; b<8; b++) {
|
||||
// tx_bit = (tx[i] >> (7-b)) & 0x1; /* msb first */
|
||||
// fwrite(&tx_bit,sizeof(uint8_t),1,stdout);
|
||||
// fflush(stdout);
|
||||
// }
|
||||
// }
|
||||
// framecnt -= 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1093,108 +1095,108 @@ unsigned short horus_l2_gen_crc16(unsigned char* data_p, unsigned char length) {
|
|||
}
|
||||
|
||||
|
||||
// Take payload data bytes, prepend a unique word and append parity bits
|
||||
int ldpc_encode_packet(unsigned char *out_data, unsigned char *in_data, int mode) {
|
||||
unsigned int i, last = 0;
|
||||
unsigned char *pout;
|
||||
// // Take payload data bytes, prepend a unique word and append parity bits
|
||||
// int ldpc_encode_packet(unsigned char *out_data, unsigned char *in_data, int mode) {
|
||||
// unsigned int i, last = 0;
|
||||
// unsigned char *pout;
|
||||
|
||||
unsigned int data_bytes, parity_bytes, number_parity_bits, max_row_weight;
|
||||
// unsigned int data_bytes, parity_bytes, number_parity_bits, max_row_weight;
|
||||
|
||||
if (mode == 1){
|
||||
// 32-byte Mode.
|
||||
data_bytes = H_256_768_22_DATA_BYTES;
|
||||
parity_bytes = H_256_768_22_PARITY_BYTES;
|
||||
number_parity_bits = H_256_768_22_NUMBERPARITYBITS;
|
||||
max_row_weight = H_256_768_22_MAX_ROW_WEIGHT;
|
||||
} else {
|
||||
// 16-byte Mode.
|
||||
data_bytes = H_128_384_23_DATA_BYTES;
|
||||
parity_bytes = H_128_384_23_PARITY_BYTES;
|
||||
number_parity_bits = H_128_384_23_NUMBERPARITYBITS;
|
||||
max_row_weight = H_128_384_23_MAX_ROW_WEIGHT;
|
||||
}
|
||||
// if (mode == 1){
|
||||
// // 32-byte Mode.
|
||||
// data_bytes = H_256_768_22_DATA_BYTES;
|
||||
// parity_bytes = H_256_768_22_PARITY_BYTES;
|
||||
// number_parity_bits = H_256_768_22_NUMBERPARITYBITS;
|
||||
// max_row_weight = H_256_768_22_MAX_ROW_WEIGHT;
|
||||
// } else {
|
||||
// // 16-byte Mode.
|
||||
// data_bytes = H_128_384_23_DATA_BYTES;
|
||||
// parity_bytes = H_128_384_23_PARITY_BYTES;
|
||||
// number_parity_bits = H_128_384_23_NUMBERPARITYBITS;
|
||||
// max_row_weight = H_128_384_23_MAX_ROW_WEIGHT;
|
||||
// }
|
||||
|
||||
pout = out_data;
|
||||
memcpy(pout, uw_v2, sizeof(uw_v2));
|
||||
pout += sizeof(uw_v2);
|
||||
memcpy(pout, in_data, data_bytes);
|
||||
pout += data_bytes;
|
||||
memset(pout, 0, parity_bytes);
|
||||
// pout = out_data;
|
||||
// memcpy(pout, uw_v2, sizeof(uw_v2));
|
||||
// pout += sizeof(uw_v2);
|
||||
// memcpy(pout, in_data, data_bytes);
|
||||
// pout += data_bytes;
|
||||
// memset(pout, 0, parity_bytes);
|
||||
|
||||
// process parity bit offsets
|
||||
for (i = 0; i < number_parity_bits; i++) {
|
||||
unsigned int shift, j;
|
||||
uint8_t tmp;
|
||||
// // process parity bit offsets
|
||||
// for (i = 0; i < number_parity_bits; i++) {
|
||||
// unsigned int shift, j;
|
||||
// uint8_t tmp;
|
||||
|
||||
for(j = 0; j < max_row_weight; j++) {
|
||||
// This is a bit silly, move this out of this loop.
|
||||
if (mode == 1){
|
||||
tmp = H_256_768_22_H_rows[i + number_parity_bits * j];
|
||||
} else if (mode == 2) {
|
||||
tmp = H_128_384_23_H_rows[i + number_parity_bits * j];
|
||||
}
|
||||
if (!tmp)
|
||||
continue;
|
||||
tmp--;
|
||||
shift = 7 - (tmp & 7); // MSB
|
||||
last ^= in_data[tmp >> 3] >> shift;
|
||||
}
|
||||
shift = 7 - (i & 7); // MSB
|
||||
pout[i >> 3] |= (last & 1) << shift;
|
||||
}
|
||||
// for(j = 0; j < max_row_weight; j++) {
|
||||
// // This is a bit silly, move this out of this loop.
|
||||
// if (mode == 1){
|
||||
// tmp = H_256_768_22_H_rows[i + number_parity_bits * j];
|
||||
// } else if (mode == 2) {
|
||||
// tmp = H_128_384_23_H_rows[i + number_parity_bits * j];
|
||||
// }
|
||||
// if (!tmp)
|
||||
// continue;
|
||||
// tmp--;
|
||||
// shift = 7 - (tmp & 7); // MSB
|
||||
// last ^= in_data[tmp >> 3] >> shift;
|
||||
// }
|
||||
// shift = 7 - (i & 7); // MSB
|
||||
// pout[i >> 3] |= (last & 1) << shift;
|
||||
// }
|
||||
|
||||
pout = out_data + sizeof(uw_v2);
|
||||
interleave(pout, data_bytes + parity_bytes, 0);
|
||||
scramble(pout, data_bytes + parity_bytes);
|
||||
// pout = out_data + sizeof(uw_v2);
|
||||
// interleave(pout, data_bytes + parity_bytes, 0);
|
||||
// scramble(pout, data_bytes + parity_bytes);
|
||||
|
||||
return data_bytes + parity_bytes + sizeof(uw_v2);
|
||||
}
|
||||
// return data_bytes + parity_bytes + sizeof(uw_v2);
|
||||
// }
|
||||
|
||||
/* Scramble and interleave are 8bit lsb, but bitstream is sent msb */
|
||||
#define LSB2MSB(X) (X + 7 - 2 * (X & 7) )
|
||||
// /* Scramble and interleave are 8bit lsb, but bitstream is sent msb */
|
||||
// #define LSB2MSB(X) (X + 7 - 2 * (X & 7) )
|
||||
|
||||
/* Invert bits - ldpc expects negative floats for high hits */
|
||||
void soft_unscramble(float *in, float* out, int nbits) {
|
||||
int i, ibit;
|
||||
uint16_t scrambler = 0x4a80; /* init additive scrambler at start of every frame */
|
||||
uint16_t scrambler_out;
|
||||
// /* Invert bits - ldpc expects negative floats for high hits */
|
||||
// void soft_unscramble(float *in, float* out, int nbits) {
|
||||
// int i, ibit;
|
||||
// uint16_t scrambler = 0x4a80; /* init additive scrambler at start of every frame */
|
||||
// uint16_t scrambler_out;
|
||||
|
||||
for ( i = 0; i < nbits; i++ ) {
|
||||
scrambler_out = ( (scrambler >> 1) ^ scrambler) & 0x1;
|
||||
// for ( i = 0; i < nbits; i++ ) {
|
||||
// scrambler_out = ( (scrambler >> 1) ^ scrambler) & 0x1;
|
||||
|
||||
/* modify i-th bit by xor-ing with scrambler output sequence */
|
||||
ibit = LSB2MSB(i);
|
||||
if ( scrambler_out ) {
|
||||
out[ibit] = in[ibit];
|
||||
} else {
|
||||
out[ibit] = -in[ibit];
|
||||
}
|
||||
// /* modify i-th bit by xor-ing with scrambler output sequence */
|
||||
// ibit = LSB2MSB(i);
|
||||
// if ( scrambler_out ) {
|
||||
// out[ibit] = in[ibit];
|
||||
// } else {
|
||||
// out[ibit] = -in[ibit];
|
||||
// }
|
||||
|
||||
scrambler >>= 1;
|
||||
scrambler |= scrambler_out << 14;
|
||||
}
|
||||
}
|
||||
// scrambler >>= 1;
|
||||
// scrambler |= scrambler_out << 14;
|
||||
// }
|
||||
// }
|
||||
|
||||
// soft bit deinterleave
|
||||
void soft_deinterleave(float *in, float* out, int mode) {
|
||||
int n, i, j, bits_per_packet, coprime;
|
||||
// // soft bit deinterleave
|
||||
// void soft_deinterleave(float *in, float* out, int mode) {
|
||||
// int n, i, j, bits_per_packet, coprime;
|
||||
|
||||
if (mode == 1) {
|
||||
// 256_768
|
||||
bits_per_packet = H_256_768_22_BITS_PER_PACKET;
|
||||
coprime = H_256_768_22_COPRIME;
|
||||
} else {
|
||||
bits_per_packet = H_128_384_23_BITS_PER_PACKET;
|
||||
coprime = H_128_384_23_COPRIME;
|
||||
}
|
||||
// if (mode == 1) {
|
||||
// // 256_768
|
||||
// bits_per_packet = H_256_768_22_BITS_PER_PACKET;
|
||||
// coprime = H_256_768_22_COPRIME;
|
||||
// } else {
|
||||
// bits_per_packet = H_128_384_23_BITS_PER_PACKET;
|
||||
// coprime = H_128_384_23_COPRIME;
|
||||
// }
|
||||
|
||||
|
||||
for ( n = 0; n < bits_per_packet; n++ ) {
|
||||
i = LSB2MSB(n);
|
||||
j = LSB2MSB( (coprime * n) % bits_per_packet);
|
||||
out[i] = in[j];
|
||||
}
|
||||
}
|
||||
// for ( n = 0; n < bits_per_packet; n++ ) {
|
||||
// i = LSB2MSB(n);
|
||||
// j = LSB2MSB( (coprime * n) % bits_per_packet);
|
||||
// out[i] = in[j];
|
||||
// }
|
||||
// }
|
||||
|
||||
// // packed bit deinterleave - same as Golay version , but different Coprime
|
||||
// void bitwise_deinterleave(uint8_t *inout, int nbytes)
|
||||
|
@ -1249,76 +1251,76 @@ void soft_deinterleave(float *in, float* out, int mode) {
|
|||
// set_error_count( percentage );
|
||||
// }
|
||||
|
||||
/* LDPC decode */
|
||||
void horus_ldpc_decode(uint8_t *payload, float *sd, int mode) {
|
||||
float sum, mean, sumsq, estEsN0, x;
|
||||
int bits_per_packet, payload_bytes;
|
||||
// /* LDPC decode */
|
||||
// void horus_ldpc_decode(uint8_t *payload, float *sd, int mode) {
|
||||
// float sum, mean, sumsq, estEsN0, x;
|
||||
// int bits_per_packet, payload_bytes;
|
||||
|
||||
if(mode == 1){
|
||||
bits_per_packet = H_256_768_22_BITS_PER_PACKET;
|
||||
payload_bytes = H_256_768_22_DATA_BYTES;
|
||||
} else {
|
||||
bits_per_packet = H_128_384_23_BITS_PER_PACKET;
|
||||
payload_bytes = H_128_384_23_DATA_BYTES;
|
||||
}
|
||||
// if(mode == 1){
|
||||
// bits_per_packet = H_256_768_22_BITS_PER_PACKET;
|
||||
// payload_bytes = H_256_768_22_DATA_BYTES;
|
||||
// } else {
|
||||
// bits_per_packet = H_128_384_23_BITS_PER_PACKET;
|
||||
// payload_bytes = H_128_384_23_DATA_BYTES;
|
||||
// }
|
||||
|
||||
double sd_double[bits_per_packet];
|
||||
float llr[bits_per_packet];
|
||||
float temp[bits_per_packet];
|
||||
uint8_t outbits[bits_per_packet];
|
||||
// double sd_double[bits_per_packet];
|
||||
// float llr[bits_per_packet];
|
||||
// float temp[bits_per_packet];
|
||||
// uint8_t outbits[bits_per_packet];
|
||||
|
||||
int b, i, parityCC;
|
||||
struct LDPC ldpc;
|
||||
// int b, i, parityCC;
|
||||
// struct LDPC ldpc;
|
||||
|
||||
// cast incoming SDs to doubles for sd_to_llr
|
||||
// For some reason I need to flip the sign ?!?!
|
||||
for ( i = 0; i < bits_per_packet; i++ )
|
||||
sd_double[i] = (double)sd[i]*-1.0;
|
||||
// // cast incoming SDs to doubles for sd_to_llr
|
||||
// // For some reason I need to flip the sign ?!?!
|
||||
// for ( i = 0; i < bits_per_packet; i++ )
|
||||
// sd_double[i] = (double)sd[i]*-1.0;
|
||||
|
||||
|
||||
sd_to_llr(llr, sd_double, bits_per_packet);
|
||||
// sd_to_llr(llr, sd_double, bits_per_packet);
|
||||
|
||||
/* reverse whitening and re-order bits */
|
||||
soft_unscramble(llr, temp, bits_per_packet);
|
||||
soft_deinterleave(temp, llr, mode);
|
||||
// /* reverse whitening and re-order bits */
|
||||
// soft_unscramble(llr, temp, bits_per_packet);
|
||||
// soft_deinterleave(temp, llr, mode);
|
||||
|
||||
/* correct errors */
|
||||
if (mode == 1){
|
||||
// 32-byte mode H_256_768_22
|
||||
ldpc.max_iter = H_256_768_22_MAX_ITER;
|
||||
ldpc.dec_type = 0;
|
||||
ldpc.q_scale_factor = 1;
|
||||
ldpc.r_scale_factor = 1;
|
||||
ldpc.CodeLength = H_256_768_22_CODELENGTH;
|
||||
ldpc.NumberParityBits = H_256_768_22_NUMBERPARITYBITS;
|
||||
ldpc.NumberRowsHcols = H_256_768_22_NUMBERROWSHCOLS;
|
||||
ldpc.max_row_weight = H_256_768_22_MAX_ROW_WEIGHT;
|
||||
ldpc.max_col_weight = H_256_768_22_MAX_COL_WEIGHT;
|
||||
ldpc.H_rows = (uint16_t *)H_256_768_22_H_rows;
|
||||
ldpc.H_cols = (uint16_t *)H_256_768_22_H_cols;
|
||||
} else {
|
||||
// 16-byte mode
|
||||
ldpc.max_iter = H_128_384_23_MAX_ITER;
|
||||
ldpc.dec_type = 0;
|
||||
ldpc.q_scale_factor = 1;
|
||||
ldpc.r_scale_factor = 1;
|
||||
ldpc.CodeLength = H_128_384_23_CODELENGTH;
|
||||
ldpc.NumberParityBits = H_128_384_23_NUMBERPARITYBITS;
|
||||
ldpc.NumberRowsHcols = H_128_384_23_NUMBERROWSHCOLS;
|
||||
ldpc.max_row_weight = H_128_384_23_MAX_ROW_WEIGHT;
|
||||
ldpc.max_col_weight = H_128_384_23_MAX_COL_WEIGHT;
|
||||
ldpc.H_rows = (uint16_t *)H_128_384_23_H_rows;
|
||||
ldpc.H_cols = (uint16_t *)H_128_384_23_H_cols;
|
||||
}
|
||||
// /* correct errors */
|
||||
// if (mode == 1){
|
||||
// // 32-byte mode H_256_768_22
|
||||
// ldpc.max_iter = H_256_768_22_MAX_ITER;
|
||||
// ldpc.dec_type = 0;
|
||||
// ldpc.q_scale_factor = 1;
|
||||
// ldpc.r_scale_factor = 1;
|
||||
// ldpc.CodeLength = H_256_768_22_CODELENGTH;
|
||||
// ldpc.NumberParityBits = H_256_768_22_NUMBERPARITYBITS;
|
||||
// ldpc.NumberRowsHcols = H_256_768_22_NUMBERROWSHCOLS;
|
||||
// ldpc.max_row_weight = H_256_768_22_MAX_ROW_WEIGHT;
|
||||
// ldpc.max_col_weight = H_256_768_22_MAX_COL_WEIGHT;
|
||||
// ldpc.H_rows = (uint16_t *)H_256_768_22_H_rows;
|
||||
// ldpc.H_cols = (uint16_t *)H_256_768_22_H_cols;
|
||||
// } else {
|
||||
// // 16-byte mode
|
||||
// ldpc.max_iter = H_128_384_23_MAX_ITER;
|
||||
// ldpc.dec_type = 0;
|
||||
// ldpc.q_scale_factor = 1;
|
||||
// ldpc.r_scale_factor = 1;
|
||||
// ldpc.CodeLength = H_128_384_23_CODELENGTH;
|
||||
// ldpc.NumberParityBits = H_128_384_23_NUMBERPARITYBITS;
|
||||
// ldpc.NumberRowsHcols = H_128_384_23_NUMBERROWSHCOLS;
|
||||
// ldpc.max_row_weight = H_128_384_23_MAX_ROW_WEIGHT;
|
||||
// ldpc.max_col_weight = H_128_384_23_MAX_COL_WEIGHT;
|
||||
// ldpc.H_rows = (uint16_t *)H_128_384_23_H_rows;
|
||||
// ldpc.H_cols = (uint16_t *)H_128_384_23_H_cols;
|
||||
// }
|
||||
|
||||
i = run_ldpc_decoder(&ldpc, outbits, llr, &parityCC);
|
||||
fprintf(stderr,"iterations: %d\n", i);
|
||||
// i = run_ldpc_decoder(&ldpc, outbits, llr, &parityCC);
|
||||
// fprintf(stderr,"iterations: %d\n", i);
|
||||
|
||||
/* convert MSB bits to a packet of bytes */
|
||||
for (b = 0; b < payload_bytes; b++) {
|
||||
uint8_t rxbyte = 0;
|
||||
for(i=0; i<8; i++)
|
||||
rxbyte |= outbits[b*8+i] << (7 - i);
|
||||
payload[b] = rxbyte;
|
||||
}
|
||||
}
|
||||
// /* convert MSB bits to a packet of bytes */
|
||||
// for (b = 0; b < payload_bytes; b++) {
|
||||
// uint8_t rxbyte = 0;
|
||||
// for(i=0; i<8; i++)
|
||||
// rxbyte |= outbits[b*8+i] << (7 - i);
|
||||
// payload[b] = rxbyte;
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -27,10 +27,10 @@ void horus_l2_decode_rx_packet(unsigned char *output_payload_data,
|
|||
|
||||
unsigned short horus_l2_gen_crc16(unsigned char* data_p, unsigned char length);
|
||||
|
||||
int ldpc_encode_packet(uint8_t *buff_mfsk, uint8_t *FSK, int mode);
|
||||
// int ldpc_encode_packet(uint8_t *buff_mfsk, uint8_t *FSK, int mode);
|
||||
|
||||
void soft_unscramble(float *in, float* out, int nbits);
|
||||
void soft_deinterleave(float *in, float* out, int mode);
|
||||
void horus_ldpc_decode(uint8_t *payload, float *sd, int mode);
|
||||
// void soft_unscramble(float *in, float* out, int nbits);
|
||||
// void soft_deinterleave(float *in, float* out, int mode);
|
||||
// void horus_ldpc_decode(uint8_t *payload, float *sd, int mode);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
#!/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.
|
||||
#
|
||||
|
||||
# Change directory to the horusdemodlib directory.
|
||||
# If running as a different user, you will need to change this line
|
||||
cd /home/pi/horusdemodlib/
|
||||
|
||||
# Receive requency, in Hz. This is the frequency the SDR is tuned to.
|
||||
RXFREQ=434195000
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# 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 - - | 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
|
|
@ -9,6 +9,10 @@
|
|||
# Modem statistics are sent out via a new 'MODEM_STATS' UDP broadcast message every second.
|
||||
#
|
||||
|
||||
# Change directory to the horusdemodlib directory.
|
||||
# If running as a different user, you will need to change this line
|
||||
cd /home/pi/horusdemodlib/
|
||||
|
||||
# Receive requency, in Hz. This is the frequency the SDR is tuned to.
|
||||
RXFREQ=434645000
|
||||
|
||||
|
@ -27,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.
|
||||
|
@ -75,9 +84,11 @@ fi
|
|||
# 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"
|
||||
|
@ -107,4 +118,6 @@ if [ "$STATS_OUTPUT" = "1" ]; then
|
|||
fi
|
||||
|
||||
# Start the receive chain.
|
||||
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) >($DECODER -q --stats=5 -g -m binary --fsk_lower=$MFSK_LOWER --fsk_upper=$MFSK_UPPER - - | python -m horusdemodlib.uploader) > /dev/null
|
||||
# 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 - - | 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
|
|
@ -5,9 +5,21 @@
|
|||
# Uses rtl_fm to receive a chunk of spectrum, and passes it into horus_demod.
|
||||
#
|
||||
|
||||
|
||||
# Change directory to the horusdemodlib directory.
|
||||
# If running as a different user, you will need to change this line
|
||||
cd /home/pi/horusdemodlib/
|
||||
|
||||
|
||||
# Receive *centre* frequency, in Hz
|
||||
# Note: The SDR will be tuned to RXBANDWIDTH/2 below this frequency.
|
||||
RXFREQ=434660000
|
||||
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
|
||||
|
@ -83,4 +95,6 @@ else
|
|||
fi
|
||||
|
||||
# Start the receive chain.
|
||||
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 $@
|
||||
# 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 - - | 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,7 +3,7 @@
|
|||
#
|
||||
|
||||
[user]
|
||||
# Your callsign - used when uploading to the HabHub Tracker.
|
||||
# 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