Porównaj commity

...

382 Commity

Autor SHA1 Wiadomość Data
Mark Jessop ea608f4ba0 Add 3 v2 IDs 2024-05-12 21:08:00 +09:30
Mark Jessop 5366134aac add V2 ID 2024-05-12 15:34:38 +09:30
Mark Jessop 8fde8b9ad2 Add PD7BOR 2024-05-10 09:18:35 +09:30
Mark Jessop 4439fbc430 add DL1BRF 2024-05-08 07:44:17 +09:30
Mark Jessop 3660d0cc7c Add VK5ARG id 2024-05-07 17:59:26 +09:30
Mark Jessop 60b88fd495 Add custom fields for SKPQKM 2024-05-07 07:54:00 +09:30
Mark Jessop 9df66628a4 Add 2 V2 IDs 2024-05-05 07:17:56 +09:30
Mark Jessop 7f098068bb Add SP9WAK 2024-05-03 22:17:27 +09:30
Mark Jessop 744e0e7071 add 2 V2 IDs 2024-05-02 07:52:25 +09:30
Mark Jessop f094c0c0ce Add 4 V2 IDs 2024-05-01 08:07:17 +09:30
Mark Jessop d00d984e78 Add 9A4GE-3 2024-04-30 20:57:48 +09:30
Mark Jessop eb911c8c0e add 4 new v2 IDs 2024-04-30 07:58:59 +09:30
Mark Jessop df1b81fc41 Add Party-Time 2024-04-26 17:36:33 +09:30
Mark Jessop 24f34fe6cd add SQ2CPA 2024-04-23 07:54:04 +09:30
Mark Jessop fa959febea Add more DG4CG IDs 2024-04-22 20:04:30 +09:30
Mark Jessop 2aa0dda4bb add Martinus-MZ 2024-04-22 07:48:49 +09:30
Mark Jessop 0956b08d36 Add AK3F 2024-04-20 20:30:44 +09:30
Mark Jessop 1265b069eb Add DG4CG-1 2024-04-20 18:45:26 +09:30
Mark Jessop fdae85612c Add DG4CG-2/3 2024-04-19 23:07:58 +09:30
Mark Jessop d3e8e903c9 Add StratoSoar 2024-04-18 08:15:45 +09:30
Mark Jessop 3311211ef6
Merge pull request #231 from sp6qkm/patch-8
Update payload_id_list.txt
2024-04-15 07:16:13 +09:30
sp6qkm 1e2ef897c7
Update payload_id_list.txt
Add:  ID 553 SQ6DAG
2024-04-14 19:50:06 +02:00
Mark Jessop afd180be66 add PA3ADE-KITE 2024-04-11 21:19:06 +09:30
Mark Jessop 7ab762727e rename PE1NW to PD1NW 2024-04-11 18:32:16 +09:30
Mark Jessop 5f7475b56d add PE1NW 2024-04-10 23:28:56 +09:30
Mark Jessop d4cbfa4998 Add F4KLR 2024-04-07 08:25:40 +09:30
Mark Jessop 202454bf44 Allow decoding of negative frequencies, add triple rx script 2024-04-06 10:14:32 +10:30
Mark Jessop dd40912fc8 Rename OLHZN to KD2KPZ 2024-04-05 23:34:40 +10:30
Mark Jessop c85f8d3260 add OLHZN 2024-04-05 22:54:20 +10:30
Mark Jessop 61aa309389 add 9A3SWO 2024-04-04 17:39:55 +10:30
Mark Jessop bb75540904 Add KB3ZOX-2 2024-04-02 07:59:34 +10:30
Mark Jessop 02058a6d38 Add SP9RKF 2024-03-24 13:54:47 +10:30
Mark Jessop b9d370df7e Add SP7XSS-2 2024-03-23 18:22:27 +10:30
Mark Jessop bc67f77121 Add SP7XSS 2024-03-22 23:33:12 +10:30
Mark Jessop 03b7abc6c7 add Xpico 2024-03-19 07:55:38 +10:30
Mark Jessop 0445e2d569 Add PD1EG 2024-03-11 09:20:57 +10:30
Mark Jessop 189a0b23e5 add F1AQE 2024-03-07 07:47:58 +10:30
Mark Jessop b0a7df7336 Add 9a4ge-2 2024-03-02 09:11:09 +10:30
Mark Jessop 64b477f17a Add 2 V2 IDs 2024-02-27 07:40:15 +10:30
Mark Jessop b26ab6ab14 add PA3EIV 2024-02-26 08:51:51 +10:30
Mark Jessop 6b28a30bbe add KE2CBS 2024-02-25 14:11:15 +10:30
Mark Jessop 6b7a6bf21f add custom data for SP9SKP 2024-02-25 09:07:01 +10:30
Mark Jessop 5818abc1fc Add F6AGV-BHAF 2024-02-24 16:51:18 +10:30
Mark Jessop d6ba4e95f9 Add 2 V2 IDs 2024-02-23 22:13:22 +10:30
Mark Jessop 256228b107 add KJ0RE 2024-02-21 07:56:24 +10:30
Mark Jessop c9b3104cd0 Add DG4CG 2024-02-19 07:48:07 +10:30
Mark Jessop da46126062
Merge pull request #217 from sp6qkm/patch-7
Update payload_id_list.txt
2024-02-18 08:07:42 +10:30
sp6qkm f70d7bbb79
Update payload_id_list.txt
Hello Mark, could I ask you to accept the added IDs:
525, SP6QKM
526, SP8KDE
527, SQ8AOL
528, SQ9AOL
529, NANO-1
530, SKPQKM

Thank you in advance
2024-02-17 20:21:37 +01:00
Mark Jessop 997261377c Add v2 id 2024-02-17 09:31:54 +10:30
Mark Jessop a88afd4f4e Add 2 v2 IDs 2024-02-13 11:31:28 +10:30
Mark Jessop 562525088c ADd PD3T 2024-02-09 12:34:32 +10:30
Mark Jessop a916fab44d Add 3 V2 IDs 2024-02-09 07:58:05 +10:30
Mark Jessop 794a2c1e5a Add NEC_CQ73 2024-02-05 20:18:18 +10:30
Mark Jessop cfef63cb29 Add IT9EWK 2024-02-05 17:03:26 +10:30
Mark Jessop a63f1c9488 Add v2 ID 2024-02-05 17:02:50 +10:30
Mark Jessop 367fc797c7 add RXSONDE 2024-02-04 18:48:11 +10:30
Mark Jessop 5f63666f5f Handle 202 responses from Sondehub better. 2024-02-03 15:56:02 +10:30
Mark Jessop 80121545f3
Merge pull request #209 from xssfox/vk3fur
Add VK3FUR payload id
2024-02-03 14:29:24 +10:30
xssfox d38e55de42 Add VK3FUR payload id 2024-02-03 14:55:41 +11:00
Mark Jessop 0e3df15067 Add testing script to payloads module 2024-01-31 11:51:06 +10:30
Mark Jessop aa0ebdf6d1 Fix DF7PM custom fields 2024-01-31 07:32:50 +10:30
Mark Jessop 18726f6d5f Add DF7PN custom fields 2024-01-30 18:09:51 +10:30
Mark Jessop fd6adaa7cb Add IZ0CGP 2024-01-25 21:11:22 +10:30
Mark Jessop 7790a7dd66 Add IU0MUN 2024-01-25 19:48:08 +10:30
Mark Jessop ff75d157db Add F4EHY 2024-01-21 10:13:58 +10:30
Mark Jessop 1494277fb9 Add DL1XH 2024-01-18 10:29:49 +10:30
Mark Jessop 62c094e697 Add ICARO 2024-01-15 16:09:20 +10:30
Mark Jessop 1a7e30cd16 add IW1DBF 2024-01-12 08:58:31 +10:30
Mark Jessop 384a6ebc5e add G8FJG 2024-01-10 07:46:35 +10:30
Mark Jessop 44ea165663 Add 2 V2 IDs 2024-01-08 07:56:05 +10:30
Mark Jessop 9ab5b88786
Merge pull request #205 from sp6qkm/patch-5
Update payload_id_list.txt
2024-01-08 07:07:19 +10:30
sp6qkm 7d95fa890f
Update payload_id_list.txt
Could I ask you to add a new ID : 502, SN32WOSP
2024-01-07 21:26:59 +01:00
Mark Jessop d982fad0e7 Add KD2EAT-DFM ID and Custom fields 2023-12-30 14:35:25 +10:30
Mark Jessop 36a938f68a Added V2 ID 2023-12-30 09:17:56 +10:30
Mark Jessop ee97011d95
Merge pull request #200 from sp6qkm/patch-4
Update payload_id_list.txt
2023-12-29 08:16:57 +10:30
sp6qkm 3fbe8de5b9
Update payload_id_list.txt 2023-12-28 21:31:40 +01:00
Mark Jessop 6d04e36909 Add SP6ZWR 2023-12-21 19:58:53 +10:30
Mark Jessop 390c73cafa change PE1PSI to Flybag-2 2023-12-19 08:29:18 +10:30
Mark Jessop f79c810a22 add SP9SKP 2023-12-18 20:40:50 +10:30
Mark Jessop a5c22243e9 Update v2 IDs 2023-12-17 08:41:50 +10:30
Mark Jessop 68f03a1eab add DC1NSK 2023-12-15 07:56:54 +10:30
Mark Jessop 38b394ee17 add 3 v2 IDs 2023-12-13 07:54:08 +10:30
Mark Jessop 681ff9e554 add 2 v2 IDs 2023-12-12 16:07:47 +10:30
Mark Jessop 0fc090c757 add AJ4XE custom fields 2023-12-08 08:17:50 +10:30
Mark Jessop 05b9e90650 Add F5IKO 2023-12-05 21:26:11 +10:30
Mark Jessop ea04c308cd Add 2 V2 IDs 2023-12-04 07:58:21 +10:30
Mark Jessop d1eec81173 Add many new IDs 2023-12-01 08:13:04 +10:30
Mark Jessop 804a42d9f9 Add 2 v2 IDs 2023-11-26 19:07:26 +10:30
Mark Jessop de74cc4a1a Add WJ2B 2023-11-23 07:54:56 +10:30
Mark Jessop f7d5d54bf6 add AJ4XE 2023-11-20 07:02:02 +10:30
Mark Jessop 197b481914 Add FlightKW 2023-11-17 09:02:32 +10:30
Mark Jessop 7ba9d50946 Add COSMO-PK 2023-11-14 07:54:10 +10:30
Mark Jessop 0a7ef43e1b add OK1RAJ 2023-11-04 16:44:40 +10:30
Mark Jessop e0f140eae7 add 2E0NNF 2023-11-01 08:04:03 +10:30
Mark Jessop a096a1af9a add K4KDR 2023-10-30 16:31:57 +10:30
Mark Jessop e0c584f616 Add OH7FES 2023-10-28 09:26:33 +10:30
Mark Jessop 46835b3d55
Merge pull request #188 from cixio/master
Update payload_id_list.txt
2023-10-27 17:47:50 +10:30
cixio 4abbcbdfb9 Update payload_id_list.txt 2023-10-27 09:09:39 +02:00
Mark Jessop 71981aed87 add DB7XO 2023-10-25 07:55:23 +10:30
Mark Jessop 82dcae13b4 Add PD2JM 2023-10-09 07:54:00 +10:30
Mark Jessop ac2efeada7 Initial starts of encoder functionality, wrappers for horuslib l2 functions 2023-09-29 11:40:13 +09:30
Mark Jessop fb694d78ce DK0WT-V2 2023-09-29 08:59:42 +09:30
Mark Jessop 78b5847da8 Add DK0WT 2023-09-29 08:41:39 +09:30
Mark Jessop b67e8ee46b Change PD3EGE to SPECTRE 2023-09-27 08:18:42 +09:30
Mark Jessop 8278436333
Merge pull request #185 from a-a/master
Update payload_id_list.txt
2023-09-26 08:29:08 +09:30
Jade M7NGO 7295d8bc53 add M7NGO 2023-09-25 21:29:20 +01:00
Mark Jessop 680acafdfb add KM6OIR 2023-09-22 08:05:49 +09:30
Mark Jessop c94a9f758d add flightdesign payload IDs 2023-09-15 17:51:07 +09:30
Mark Jessop 9548a23d07 add SP4CE 2023-09-09 07:43:29 +09:30
Mark Jessop 4b57a65385 add KE2BOK 2023-09-04 07:04:11 +09:30
Mark Jessop 67f7626ab8 Add 2 V2 IDs 2023-08-31 07:53:53 +09:30
Mark Jessop 3198663109 Add DL6PI 2023-08-26 19:32:02 +09:30
Mark Jessop 28904aa5b4 add PA0CAB 2023-08-23 09:06:44 +09:30
Mark Jessop 2c7c7c4251 Add KI5RZY 2023-08-22 12:49:26 +09:30
Mark Jessop 158b2e3354 update DL1MRZ 2023-08-19 09:21:17 +09:30
Mark Jessop 4fbc574979 add DL1MRZ 2023-08-15 08:19:59 +09:30
Mark Jessop e02e38e8d4 Add SOLARBAG 2023-08-10 17:11:34 +09:30
Mark Jessop 948e97b3eb Narrow allowed payload-system time window, add warnings for various issues. 2023-07-28 21:30:06 +09:30
Mark Jessop 5f2d8416f1 add O38 2023-07-22 18:47:04 +09:30
Mark Jessop 6768c53397 add VI25AREG 2023-07-16 10:16:33 +09:30
Mark Jessop c8ce3cc6aa add KB4RKS 2023-07-15 08:06:40 +09:30
Mark Jessop 6dda766205 added SP5YAM 2023-07-09 17:16:29 +09:30
Mark Jessop 6faffa18aa Add SP5WWL 2023-07-09 15:48:04 +09:30
Mark Jessop c7c5a6eb5d Upload speed field to sondehub 2023-07-08 10:21:49 +09:30
Mark Jessop dad39621f0 Add F8DKG 2023-07-07 07:43:35 +09:30
Mark Jessop b1459c68c8 Add DJ9AS 2023-07-07 07:37:45 +09:30
Mark Jessop 40559e1d62 Add SNSF01 2023-07-01 12:24:15 +09:30
Mark Jessop 7982bda6e7 Add JOTACross IDs 2023-06-23 08:03:15 +09:30
Mark Jessop ce137dba70 Add SP3ET-2 2023-06-22 07:18:01 +09:30
Mark Jessop 10a7e90382 add MONASAT 2023-06-20 07:21:50 +09:30
Mark Jessop e0e3203280 Add W1U 2023-06-19 07:53:15 +09:30
Mark Jessop 88a04ea691 Add Horus V2 packet format diagram 2023-06-14 15:17:00 +10:00
Mark Jessop 7574b10d59 Add Horus V2 packet format diagram 2023-06-14 15:14:09 +10:00
Mark Jessop 8c6bdd29a2 Add SOLflight 2023-06-10 17:50:28 +09:30
Mark Jessop d1cd35e36d add SQ3XBD 2023-06-04 09:03:34 +09:30
Mark Jessop 1759df560c add HG8LXL 2023-06-03 11:23:12 +09:30
Mark Jessop 652300bb24 add SP3ZHP 2023-06-02 09:35:34 +09:30
Mark Jessop 72e7e4923a added KN6ZTT 2023-05-31 07:09:08 +09:30
Mark Jessop 4a1597cda6 PD5MF -> PA5MF 2023-05-25 17:11:48 +09:30
Mark Jessop 6258bcc33e add SP5GFN 2023-05-25 07:07:36 +09:30
Mark Jessop c8e83cf998 add DO5IO 2023-05-24 18:46:27 +09:30
Mark Jessop 6a3718a715 Add OM2OFA IDs 2023-05-18 23:10:15 +09:30
Mark Jessop 9fb4baeb4b Add 2 new v2 IDs 2023-05-18 11:25:22 +09:30
Mark Jessop 333cc8f58e add SQ5RB 2023-05-17 08:04:50 +09:30
Mark Jessop 7abf447200 Add 9A7DI 2023-05-16 19:36:00 +09:30
Mark Jessop fe9ca5fbcb Add SP5LOT 2023-05-13 08:22:12 +09:30
Mark Jessop 8f322bee50 add 4x v2 IDs 2023-05-12 09:24:35 +09:30
Mark Jessop 7f881692d7 add W1STR 2023-05-04 09:00:11 +09:30
Mark Jessop cea3b37230 add DM2DL 2023-05-03 07:53:34 +09:30
Mark Jessop 28f35909b2 add HA8LOU 2023-05-01 08:31:31 +09:30
Mark Jessop 956608e76e Change JMSS to VK3DNS-JMSS 2023-04-29 13:06:40 +09:30
Mark Jessop f069764fc6 add JMSS 2023-04-29 12:48:24 +09:30
Mark Jessop 43abc6d08f add SP7AR 2023-04-27 20:07:54 +09:30
Mark Jessop 1bc30e2310 Merge branch 'master' of github.com:projecthorus/horusdemodlib 2023-04-26 08:15:07 +09:30
Mark Jessop 503f392002 add IW1DBF 2023-04-26 08:03:08 +09:30
Mark Jessop 34d90a6df5
Merge pull request #150 from mikaelnousiainen/patch-1
Set custom fields for OH3HAB-4FSK-V2
2023-04-25 16:19:27 +09:30
Mikael Nousiainen 5ed247dc30
Set custom fields for OH3HAB-4FSK-V2 2023-04-25 09:48:06 +03:00
Mark Jessop 138449d7aa Add W3YP-2 2023-04-19 07:10:16 +09:30
Mark Jessop c944da0bf2 add OH3CUF 2023-04-18 17:32:05 +09:30
Mark Jessop e5842a6710
Merge pull request #148 from sp6qkm/patch-3
Update payload_id_list.txt
2023-04-14 07:07:02 +09:30
sp6qkm 712bbdd27b
Update payload_id_list.txt
Please add ID: 401 SP6ONZ
2023-04-13 18:32:20 +02:00
Mark Jessop 6dfa34cac8 Merge branch 'master' of github.com:projecthorus/horusdemodlib 2023-04-12 21:42:08 +09:30
Mark Jessop dd964915ca Add F1DZP 2023-04-12 21:41:57 +09:30
Mark Jessop 8dc2624d19
Merge pull request #145 from kng/docker
Docker implementation
2023-04-12 18:02:38 +09:30
Daniel Ekman a6f5ade965
Add workflow to build images, modify git/dockerignore
Signed-off-by: Daniel Ekman <knegge@gmail.com>
2023-04-12 09:30:37 +02:00
Daniel Ekman 1ffb9a9726 Add docker readme, update compose and env example 2023-04-11 17:49:26 +02:00
Mark Jessop 50a7d976b6 add YU7PDA 2023-03-31 22:25:39 +10:30
Mark Jessop e4f7a45d24 add PD2HSB 2023-03-25 07:46:35 +10:30
Mark Jessop 753eaadf66 Add YU4VMP and IKAR-1 2023-03-14 08:01:06 +10:30
Mark Jessop 1d573fde9b add F4HQO 2023-03-11 09:01:33 +10:30
Mark Jessop be5654ccea Add link to phi0 generator 2023-03-09 07:11:53 +10:30
Mark Jessop 020fdd9af2 Add ON3RC 2023-03-06 08:04:05 +10:30
Mark Jessop 642c5b8a53 Add DM7RM-2 2023-03-04 11:37:19 +10:30
Mark Jessop 7816115c63 add SP3WRO 2023-03-03 08:33:34 +10:30
Mark Jessop a59ce1d6ac add YU4BRE 2023-03-01 07:59:08 +10:30
Mark Jessop 566c498a7b add DO2LMV 2023-02-27 08:06:04 +10:30
Mark Jessop 61bb55bb12 add ALIEN-UFO-1 2023-02-25 20:43:08 +10:30
Mark Jessop c8c1a33677 Add KC1MOL 2023-02-24 16:17:42 +10:30
Mark Jessop 9e2cf89b03 Add 4 V2 IDs 2023-02-23 07:56:57 +10:30
Mark Jessop 187526e048 Add SpaceBoy 2023-02-21 09:47:38 +10:30
Mark Jessop 5cc986678e add DM7RM 2023-02-19 13:48:59 +10:30
Mark Jessop df7203635b Add VK2ARX 2023-02-15 19:12:46 +10:30
Mark Jessop 8bb7274dd4 More V2 IDs 2023-02-15 08:01:08 +10:30
Mark Jessop 8e910425fb More V2 IDs 2023-02-13 08:17:26 +10:30
Mark Jessop 1fc2bfbc90 add OK1OMG 2023-02-12 09:41:40 +10:30
Mark Jessop 1a06db585d Add WOHA-4FSK 2023-02-10 23:10:33 +10:30
Mark Jessop 65d285b3b5 add K6UCI 2023-02-10 08:12:42 +10:30
Mark Jessop 7717c04bb6 add M7ONS 2023-02-07 07:57:45 +10:30
Mark Jessop a2e4c1499a add DG2FS 2023-02-04 09:14:57 +10:30
Mark Jessop 13243eecfc AirSlicer C/D 2023-02-03 13:52:52 +10:30
Mark Jessop 485cb95aaf add YT1DRGTI 2023-02-02 17:13:17 +10:30
Mark Jessop 06e32fa6db add some V2 calls 2023-01-29 07:45:02 +10:30
Mark Jessop b37e006526 Add V2 id for W3YP 2023-01-26 09:01:40 +10:30
Mark Jessop 7e59d0871e Add custom fields for shsspgeiger 2023-01-16 19:44:14 +10:30
Mark Jessop e98f17eba9 Add 2 callsigns 2023-01-16 10:40:28 +10:30
Daniel Ekman 43ccf61661
Add rx_tools and soapysdr support
Signed-off-by: Daniel Ekman <knegge@gmail.com>
2023-01-14 13:13:50 +01:00
Daniel Ekman 90b95f81e1
Change to multistage builder and add run scripts
Signed-off-by: Daniel Ekman <knegge@gmail.com>
2023-01-12 21:22:18 +01:00
Daniel Ekman f526eb7cdc
Add docker compose and build
Signed-off-by: Daniel Ekman <knegge@gmail.com>
2023-01-12 13:46:45 +01:00
Mark Jessop 67f817e78d Add DF8AY 2023-01-11 19:03:18 +11:00
Mark Jessop 3d75b9803c Add YU1WAT-2 2023-01-11 08:57:39 +11:00
Mark Jessop 45049beb78 Add PD9BN-V2 2023-01-03 08:59:08 +10:30
Mark Jessop d52f214e08 add 2 V2 IDs 2022-12-31 10:33:06 +10:30
Mark Jessop c0498fa063
Merge pull request #115 from arkadiusz-papaj/master
Update payload_id_list.txt
2022-12-22 22:51:16 +10:30
Arek Papaj f985f22ea5
Update payload_id_list.txt
Assign ID 354 to SP9ARK-V2
2022-12-22 13:06:31 +01:00
Mark Jessop ad628782b6 Add V2 IDs 2022-12-22 09:08:11 +10:30
Mark Jessop a01931a57c Add 2 V2 IDs 2022-12-20 12:13:48 +10:30
Mark Jessop 50d7e18bed Disable habitat uploader, add warning when uploading 4FSKTEST telemetry 2022-12-18 17:14:22 +10:30
Mark Jessop b3a4537551 Add 2 new V2 IDs 2022-12-18 09:00:25 +10:30
Mark Jessop b5f0aeb3fd add 2 v2 ids 2022-12-09 08:50:52 +11:00
Mark Jessop 65f9073d5e
Merge pull request #114 from rohbot/master
Update payload_id_list.txt
2022-12-08 12:11:25 +11:00
Rohan Ahmad 2c6930a862
Update payload_id_list.txt
Adds MGGS-V2
2022-12-08 12:05:42 +11:00
Mark Jessop d246f1e302 Add F6ASP 2022-12-05 07:58:06 +11:00
Mark Jessop 7f46efb8fe Add 2 more V2 IDs 2022-11-29 10:11:43 +11:00
Mark Jessop 773c0c9d1b Add 2 V2 calls 2022-11-29 08:03:13 +11:00
Mark Jessop bedfab7aa2
Merge pull request #109 from piotr022/payload-id-sq9p
adding-sq9p
2022-11-28 06:48:55 +11:00
Piotr Lewandowski 9bd3a25250
adding-sq9p 2022-11-27 15:02:22 +01:00
Mark Jessop 434ef66459 Add YU1WAT 2022-11-26 10:11:19 +10:30
Mark Jessop 7e9866e735 Add YT3GTI 2022-11-24 18:39:54 +10:30
Mark Jessop bfcf935d75 Add KI5RQB 2022-11-17 16:13:03 +10:30
Mark Jessop 1ea53e7c2c Add PE1PSI v2 2022-11-13 09:38:32 +10:30
Mark Jessop 5270334ba5 Add horus geiger 2022-10-29 13:47:29 +10:30
Mark Jessop 924ea8a471 Add PD2SDV 2022-10-21 10:11:43 +10:30
Mark Jessop 92c01ce7c9 Add G7PMO-V2 2022-10-17 07:12:24 +10:30
Mark Jessop 8e8e5bc0a9 Add SP3POW-V2 2022-10-12 07:04:21 +10:30
Mark Jessop 5069789364 Add KB3ZOX/WOX 2022-10-10 07:15:52 +10:30
Mark Jessop 2abbed1f10 Modify SQ3KNL 2022-09-28 07:03:19 +09:30
Mark Jessop bc15d2e743 Add SQ3KNL 2022-09-27 21:24:59 +09:30
Mark Jessop fdf0a8d8f2 Added 2x ON callsigns 2022-09-27 08:08:48 +09:30
Mark Jessop 31ee507ec8 Add ON5RTR 2022-09-24 08:47:20 +09:30
Mark Jessop b89bf69abf Add SP6TKN-V2 2022-09-19 18:48:27 +09:30
Mark Jessop 84ad11e310 Send all custom fields via UDP broadcast, add baud rate and modulation details to sondehub uploader 2022-09-17 19:21:21 +09:30
Mark Jessop a377c0a2c2 Add some v2 IDs 2022-09-16 08:10:14 +09:30
Mark Jessop 7959544ca1
Merge pull request #101 from rohbot/master
Adds VK3PZ-V2 to payload_id
2022-09-14 17:10:17 +09:30
Rohan Ahmad de1b0ce130
Update payload_id_list.txt
adds VK3PZ-V2
2022-09-14 16:39:16 +10:00
Rohan Ahmad d70ab2475a
Update payload_id_list.txt
Adds  VK3PZ-HAB
2022-09-14 16:34:22 +10:00
Mark Jessop f1c9d1eea0 Add more ON4IR 2022-09-14 08:16:31 +09:30
Mark Jessop 11bd0386c8 Add SP3CET 2022-09-09 22:34:47 +09:30
Mark Jessop 10c53dd3d1 Added SP9ZHP 2022-09-09 07:19:23 +09:30
Mark Jessop ff0c378094 Add SP3VSS-V2 2022-09-06 06:44:12 +09:30
Mark Jessop 8c2f434b1a Add some v2 IDs 2022-09-04 06:39:36 +09:30
Mark Jessop 15ee6a549f Update horusradmon fioeld name 2022-09-02 15:41:52 +09:30
Mark Jessop 4310885959 Add Horus Radiation Monitior Payload 2022-09-02 15:22:46 +09:30
Mark Jessop afebdab448 add PH1M-V2 2022-08-29 20:22:12 +09:30
Mark Jessop dc66f63a5f Add some v1 IDs 2022-08-25 18:46:04 +09:30
Mark Jessop 34f38fa6c6 Add ON3PFD 2022-08-21 21:19:38 +09:30
Mark Jessop 1aa59feb31 Fix ON4IR custom field list 2022-08-16 07:11:15 +09:30
Mark Jessop 6defed3404 Add ON4IR custom field data 2022-08-14 15:53:53 +09:30
Mark Jessop 87cea7a206 Add ON4IR v2 ID 2022-08-14 08:44:03 +09:30
Mark Jessop 1e47384241 Add SQ9HP-V2 2022-08-13 10:30:06 +09:30
Mark Jessop 1cca89145a Add DJOAMF-V2 2022-08-11 18:50:46 +09:30
Mark Jessop a7709b1fca add 2 new v2 calls 2022-07-13 20:03:42 +09:30
Mark Jessop 2dcb15269d Add V2 ID for SN75ZOT-V2 2022-07-11 12:51:04 +09:30
Mark Jessop bee62eb29f OH3HAB alternate IDs 2022-07-02 20:38:51 +09:30
Mark Jessop f04ffad63b Add VK5ST-V2, Barossa 2022-07-01 18:07:08 +09:30
Mark Jessop 3ad81e0094 Handle EOF properly, handle no internet a bit better too 2022-06-26 10:32:53 +09:30
Mark Jessop ba24b76353 Add 2 new v1 ids 2022-06-25 14:30:15 +09:30
Mark Jessop 77a258070a OH3VHH to OH3HAB 2022-06-16 15:29:48 +09:30
Mark Jessop 2ccb69a675
Merge pull request #83 from rohbot/emdrc
Update payload_id_list.txt
2022-06-13 12:34:39 +09:30
Rohan Ahmad c102db80e4
Update payload_id_list.txt
adds EMDRC-V2 and VK3TRO-V2
2022-06-13 11:58:04 +10:00
Mark Jessop e5a75afb2c Add ICARO 2022-06-12 08:38:40 +09:30
Mark Jessop 6eefb2758f Add PE9GHZ-4FSK 2022-06-12 08:00:44 +09:30
Mark Jessop cc12c6493b Add SP3YDE-4FSK 2022-06-11 07:29:14 +09:30
Mark Jessop 44544c0724 Add Its windy 2022-06-03 17:22:45 +09:30
Mark Jessop ae9e12e220 Add SP3WRO 2022-05-24 18:07:22 +09:30
Mark Jessop 58a3bc54d0 Add SP6KZ-V2 2022-05-14 10:03:36 +09:30
Mark Jessop e3a386b2bb Reallocate ID 15 2022-05-04 18:24:49 +09:30
Mark Jessop 405d894be3 Rename KD2EAT, add W2CXM 2022-04-23 13:26:19 +09:30
Mark Jessop e2b597474a Add SQ7MHX 2022-04-21 08:08:45 +09:30
Mark Jessop a328cc7d11 more v1 ids 2022-04-20 19:54:03 +09:30
Mark Jessop ce76d2d6e1 Add 9A1FAB-4FSK 2022-04-20 08:06:25 +09:30
Mark Jessop 4492724b2e Add VK5ST-4FSK 2022-04-19 19:14:01 +09:30
Mark Jessop 88e3bcd845 Add PE1ANS-V2 2022-04-12 08:01:01 +09:30
Mark Jessop 41e297e4f1 Report modulation type 2022-04-10 11:34:22 +09:30
Mark Jessop 9f94677067 Reupload habitat listener data periodically 2022-04-10 10:54:24 +09:30
Mark Jessop 2628ded179 Add SP5RAF-4FSK 2022-04-03 11:09:06 +09:30
Mark Jessop 2faf80385f Add SP2SGF 2022-03-24 17:26:38 +10:30
Mark Jessop 56de7d6efd Add SQ6NEI-V2 2022-03-23 08:13:08 +10:30
Mark Jessop dbcea5bad5 add P 2022-03-17 07:55:32 +10:30
Mark Jessop 039834fdfb add SP6HD-V2 2022-03-15 07:16:08 +10:30
Mark Jessop 7935b6369a Add SP6V 2022-03-13 20:53:15 +10:30
Mark Jessop 3ac7be6614 V2 IDs 2022-03-10 17:41:01 +10:30
Mark Jessop 7b7e0cfaec Added some v2 IDs 2022-03-08 08:05:49 +10:30
Mark Jessop 3b89dbce1b add PA3EIV-4FSK 2022-03-08 08:02:24 +10:30
Mark Jessop 05a886ce76
Merge pull request #65 from MrARM/patch-1
Insert a payload V2 ID request
2022-03-08 07:35:19 +10:30
MrARM 733280497b
Insert a payload ID request 2022-03-07 11:19:52 -06:00
Mark Jessop d88f554937 Update after merge 2022-03-07 19:33:55 +10:30
Mark Jessop fec9b7c2c6 Add SP6TO 2022-03-07 19:32:34 +10:30
Mark Jessop 19342975a0
Merge pull request #63 from sq3tle/patch-1
Added payload_id
2022-03-05 00:56:16 +10:30
Filip Tomczyk 27f30d4f1d
Update payload_id_list.txt 2022-03-04 15:21:12 +01:00
Mark Jessop 19428b5e1e Add happysat 2022-03-04 16:22:21 +10:30
Mark Jessop 1ebdff2a79 Added 2 new v2 IDs 2022-03-03 17:18:27 +10:30
Mark Jessop 2f079df199 Add SP9DEV V2 IDs 2022-03-02 22:51:42 +10:30
Mark Jessop f63cd7e738 A few new payload IDs 2022-02-28 07:50:36 +10:30
Mark Jessop a08e977329 Add 2 new v2 IDs 2022-02-27 09:49:52 +10:30
Mark Jessop be24fcc006 Add PE2BZ-V2 2022-02-26 20:56:32 +10:30
Mark Jessop 939544e05d Update pyproj 2022-02-26 10:41:41 +10:30
Mark Jessop 32485a43f1 Fix uploading of custom fields to sondehub-amateur 2022-02-26 10:40:50 +10:30
Mark Jessop 3cef0956af Add a few new IDs, use 4FSKTEST-V2 custom fields by default 2022-02-20 18:50:37 +10:30
Mark Jessop fa8a0094ec Added 2 new calls 2022-02-19 09:27:43 +10:30
Mark Jessop 63ab639727 Added custom fields for HORUS-V2 2022-02-18 18:19:00 +10:30
Mark Jessop 66e519f2d6 Add sdrplusplus doc images 2022-02-18 15:45:28 +10:30
Mark Jessop 503614eb2c Add HORUS-V2 2022-02-17 21:12:34 +10:30
Mark Jessop 258497d5ef Add SQ1FYB 2022-02-17 16:48:00 +10:30
Mark Jessop 6881922e3b Add Flybag-4FSK 2022-02-13 10:31:22 +10:30
Mark Jessop 443bdd5727 Add some new V2 IDs 2022-02-12 15:10:52 +10:30
Mark Jessop 7f81e3b94b Add 9A4AM-V2 2022-02-11 21:38:15 +10:30
Mark Jessop 136ed393bd Add Peanut127 2022-02-09 17:30:57 +10:30
Mark Jessop e289ff7f1e add SP6SK-4FSK 2022-02-07 21:54:15 +10:30
Mark Jessop 15fa103b40 add SP6SK-4FSK 2022-02-07 21:53:45 +10:30
Mark Jessop e3f948d6d6 readme updates 2022-02-05 18:57:06 +10:30
Mark Jessop 4f3947e6cb Add frequency upload to habitat 2022-02-05 18:50:42 +10:30
Mark Jessop fe9c1192d2 Remove unnecessary print statement 2022-02-05 16:07:04 +10:30
Mark Jessop 4957c3b8b9 Add Sondehub Amateur uploader. 2022-02-05 16:02:53 +10:30
Mark Jessop a190a436bc Add SP6HD-4FSK as 93 2022-01-31 20:43:01 +10:30
Mark Jessop bac78e1081 add DG8JT-4FSK 2022-01-28 07:37:28 +10:30
Mark Jessop 906e8cc576 9A6NDZ-4FSK 2022-01-01 19:52:01 +10:30
Mark Jessop 481cad1894 Add UPANDUP 2021-12-21 20:55:12 +10:30
Mark Jessop 7c81ad48d4 Add SP6ZHPw 2021-12-11 08:55:26 +10:30
Mark Jessop fbf576fb55 Add SQ3KNO-4FSK 2021-11-29 21:15:44 +10:30
Mark Jessop e1ea13da9b Add KD2EAT 2021-11-28 08:53:03 +10:30
Mark Jessop 365870241c Add WD6DRI 2021-11-18 12:59:07 +10:30
Mark Jessop 46c9c71266 Add htt9A4VS-4FSK 2021-11-16 18:19:07 +10:30
Mark Jessop 62bc5453b1 Add SP9DEV-4FSK 2021-11-13 21:28:52 +10:30
Mark Jessop b0ec5f1df9 Add SP6TKN and KA9Q 2021-11-08 15:46:41 +10:30
Mark Jessop 68f73ffd3d Add SCPWA and PD3EGE 2021-11-05 09:42:04 +10:30
Mark Jessop 73e945230e Add KQ6RS 2021-10-25 11:10:49 +10:30
Mark Jessop cc336f0ba5 add SP3IZN-4FSK 2021-10-25 09:59:38 +10:30
Mark Jessop a7cd06a4c6 Add YO3ICT-4FSK 2021-10-24 20:48:25 +10:30
Mark Jessop bf8dce6c82 Add W6SUN 2021-10-10 09:27:56 +10:30
Mark Jessop d302b9f6af Add MAGNU-V2 and custom field lists 2021-09-29 21:53:12 +09:30
Mark Jessop 8f9de1e27a Add cd's to horusdemodlib to startup scripts 2021-09-26 18:11:01 +09:30
Mark Jessop 8cdae38de9 Add SP9SKP-4FSK 2021-09-23 07:03:50 +09:30
Mark Jessop 990c222495 More polish ids! 2021-09-17 20:33:56 +09:30
Mark Jessop 9b65e1fa19
Merge pull request #47 from dfgg12/dfgg12-patch-1
add SQ2DEF-4FSK
2021-09-14 21:20:04 +00:00
dfgg12 ab165ad41a
add SQ2DEF-4FSK 2021-09-14 15:53:15 +02:00
Mark Jessop 4f6e398f4b add LY2BAW-4FSK 2021-09-14 20:14:26 +09:30
Mark Jessop 88aeed2282 Add OH3VHH-4FSK-V2 2021-09-12 15:54:56 +09:30
Mark Jessop e864e0036a bump pyproj version 2021-09-11 22:22:21 +09:30
Mark Jessop 7a1b174bdf Add additional checks when parsing custom field list, add additional divide by 10 and 100 converters 2021-09-11 22:21:02 +09:30
Mark Jessop 6e86c3e559 Handle extra trailing nulls 2021-09-04 19:12:01 +09:30
Mark Jessop 8d11d9ecf1 update pyproject 2021-09-04 17:42:36 +09:30
Mark Jessop b1908f7cc7 Added v2 sample and test, removed assert for rtty decoder, added option to use local lists for uploader 2021-09-04 17:26:25 +09:30
Mark Jessop e3ee7d4ae4 Update custom field list 2021-09-03 14:08:10 +09:30
Mark Jessop c8debeaab4 Merge branch 'master' into horus_v2_golay 2021-09-03 14:07:57 +09:30
Mark Jessop aeee65e81e add SP6KZ-4FSK 2021-09-03 12:28:27 +09:30
Mark Jessop 312eba4a9a Add PE1PSI-4FSK 2021-08-28 17:27:15 +09:30
Mark Jessop 96e71ac04c More additions for combined v1/v2 2021-08-25 21:13:44 +09:30
Mark Jessop 03038b3823 Initial stab at combined 22/32-byte decoders 2021-08-25 20:56:04 +09:30
Mark Jessop a555089582 Update horus gen test bits to produce 32-byte v2 golay-encoded frames 2021-08-25 20:26:47 +09:30
Mark Jessop 66894a3243 Add link to wiki page in readme 2021-08-20 22:20:15 +09:30
Mark Jessop c6093c1e13 Add SQ3KNL-4FSK 2021-08-14 18:01:35 +09:30
Mark Jessop 8c8b1eb988 Add OH3VHH-4FSK 2021-08-11 18:30:50 +09:30
Mark Jessop 2d4dfccad7 Update default freq 2021-07-31 16:19:20 +09:30
Mark Jessop c0f301f732 Add LUX3 2021-07-27 22:41:48 +09:30
Mark Jessop 845c36b1d5 Add SV3IRG-4FSK 2021-07-21 19:14:59 +09:30
Mark Jessop e9a60bec61 add SP6VWX-4FSK 2021-07-18 21:44:59 +09:30
Mark Jessop 164b6619bb Add OE7NCI-4FSK 2021-07-06 18:05:35 +09:30
Mark Jessop e39ad3e706 add SP5WWL-4FSK 2021-06-24 20:31:22 +09:30
Mark Jessop b7817ec924 Add OE7CAH-4FSK 2021-06-18 15:48:51 +09:30
Mark Jessop 1e1fb83a1d Add 9A3ZI-4FSK 2021-06-10 18:25:00 +09:30
Mark Jessop b7af91ced6 add 9A4AM-4FSK 2021-06-10 07:08:22 +09:30
Mark Jessop 2ac511f8d0 Add SP8ESA-4FSK 2021-06-02 16:15:21 +09:30
Mark Jessop afb016bf49 Added SP3WDX 2021-05-20 22:22:07 +09:30
Mark Jessop ad7d18dd72 MRF68 -> PD5MF 2021-05-19 07:09:47 +09:30
Mark Jessop d99e38827f Add VK3TRO-4FSK 2021-05-08 16:49:00 +09:30
Mark Jessop 8ab168e6b7 Add SP3MCY, Change GSN-TS 2021-05-02 09:43:13 +09:30
Mark Jessop 448626092d 53 -> 52 2021-04-17 21:58:07 +09:30
Mark Jessop 1605b2ef14 Add SQ6TW-4FSK 2021-04-17 21:57:36 +09:30
Mark Jessop 40b88b2d43 Add SQ3PMN-4FSK 2021-04-11 06:57:36 +09:30
Mark Jessop 33c40e030b Add VK5LJG 2021-04-02 14:24:43 +10:30
Mark Jessop 0117631f69 Added SQ6NEI-4FSK 2021-03-30 08:12:58 +10:30
Mark Jessop 8394d3120b change ICARUS to ICARUS-4FSK 2021-03-18 19:35:13 +10:30
Mark Jessop 5fcafebb82 Add ICARUS 2021-03-16 07:51:15 +10:30
Mark Jessop dac5ebc297 Add more SP/SQ calls 2021-03-15 09:39:14 +10:30
Mark Jessop 248d813eff Add SQ6RQT-4FSK 2021-03-15 07:09:46 +10:30
Mark Jessop 9858aff192 Add SP6QKM-4FSK 2021-03-01 20:25:21 +10:30
Mark Jessop 68e507fd34 Add SQ3XBD 2021-02-26 07:18:42 +10:30
Mark Jessop e49a276723 Add MRF68-4FSK 2021-02-20 00:14:02 +10:30
Mark Jessop 39594f184d Add SP5YAM-4FSK 2021-02-07 11:26:51 +10:30
Mark Jessop 62623aab18 Added dual 4FSK script. 2021-02-05 20:00:44 +10:30
Mark Jessop d3c2bbdf0f Add LUX2 2021-02-05 19:04:49 +10:30
Mark Jessop 5334e44da9 Add DH3SUP 2021-02-05 09:29:33 +10:30
47 zmienionych plików z 3357 dodań i 544 usunięć

2
.dockerignore 100644
Wyświetl plik

@ -0,0 +1,2 @@
samples

59
.github/workflows/container.yml vendored 100644
Wyświetl plik

@ -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 }}

12
.gitignore vendored
Wyświetl plik

@ -30,3 +30,15 @@
*.exe
*.out
*.app
# Custom config file
user.env
user.cfg
# IDE
.vscode
.idea
# cmake
build

Wyświetl plik

@ -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 - |

53
Dockerfile 100644
Wyświetl plik

@ -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"]

114
README-Docker.md 100644
Wyświetl plik

@ -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).

Wyświetl plik

@ -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

41
TESTING.md 100644
Wyświetl plik

@ -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.

Wyświetl plik

@ -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"]
]
}
}
}

Wyświetl plik

@ -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="&lt;font style=&quot;font-size: 19px;&quot;&gt;0&lt;/font&gt;" 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="&lt;span style=&quot;font-size: 19px;&quot;&gt;8&lt;/span&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;16&lt;br&gt;&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Payload ID&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Frame No.&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;HH&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;MM&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;SS&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Lat&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Latitude&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Longitude&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Alt&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Alt&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Vel.&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Sats&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Temp&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Batt&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Custom&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Custom&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;CRC16&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;24&lt;br&gt;&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 15px;&quot;&gt;Byte&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;0&lt;/font&gt;" 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="&lt;span style=&quot;font-size: 19px;&quot;&gt;8&lt;/span&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;16&lt;br&gt;&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Payload ID&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Frame No.&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;HH&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;MM&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;SS&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Lat&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Latitude&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Longitude&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Alt&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Alt&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Sats&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Temp&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Batt&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Ascent&lt;br&gt;Rate&lt;br&gt;&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;CRC16&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;24&lt;br&gt;&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 15px;&quot;&gt;Byte&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Ext.&lt;br&gt;Temp&lt;/font&gt;" 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="&lt;span style=&quot;font-size: 14px;&quot;&gt;Hum.&lt;/span&gt;" 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="&lt;span style=&quot;font-size: 14px;&quot;&gt;Pressure&lt;/span&gt;" 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="&lt;span style=&quot;font-size: 14px;&quot;&gt;Pulse &lt;br&gt;Count&lt;/span&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;0&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;1&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;2&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;3&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;4&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;5&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;6&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 19px;&quot;&gt;7&lt;/font&gt;" 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="&lt;font style=&quot;font-size: 14px;&quot;&gt;Ext.&lt;br&gt;Temp&lt;/font&gt;" 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

21
docker-compose.yml 100644
Wyświetl plik

@ -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'

Wyświetl plik

@ -1 +1 @@
__version__ = "0.1.21"
__version__ = "0.3.13"

Wyświetl plik

@ -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__":

Wyświetl plik

@ -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:

Wyświetl plik

@ -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:

Wyświetl plik

@ -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")

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()}")

Wyświetl plik

@ -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.")

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -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]

Wyświetl plik

@ -1,3 +1,4 @@
requests
crcmod
numpy
numpy
python-dateutil

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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 $@

Wyświetl plik

@ -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 $@

Wyświetl plik

@ -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)

Wyświetl plik

@ -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 */

Wyświetl plik

@ -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)

Wyświetl plik

@ -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.");
}

Wyświetl plik

@ -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!");
}

Wyświetl plik

@ -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;
// }
// }

Wyświetl plik

@ -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

Wyświetl plik

@ -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>

121
start_dual_4fsk.sh 100755
Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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 $@

Wyświetl plik

@ -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

Wyświetl plik

@ -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.

98
user.env.example 100644
Wyświetl plik

@ -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