From 110b85a781f5a84be08c916e6464fe6399404175 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Mon, 1 May 2023 02:41:05 +0100 Subject: [PATCH] Add EAS to gen_packets. --- man/gen_packets.1 | 2 + src/gen_packets.c | 95 ++++++++++++++++++++++----- src/gen_tone.c | 161 ++++++++++++++++++++++++++++++++++++++++++++-- src/gen_tone.h | 4 +- src/hdlc_send.c | 80 ++++++++++++++++++++++- src/hdlc_send.h | 7 +- 6 files changed, 324 insertions(+), 25 deletions(-) diff --git a/man/gen_packets.1 b/man/gen_packets.1 index ba782fe..740d4db 100644 --- a/man/gen_packets.1 +++ b/man/gen_packets.1 @@ -46,6 +46,8 @@ Data rate in bits/sec for first channel. Standard values are 300, 1200, 2400, 4 4800 bps uses 8PSK based on V.27 standard. .P 9600 bps and up uses K9NG/G3RUH standard. +.P +EAS for Emergency Alert System (EAS) Specific Area Message Encoding (SAME). .RE .RE .PD diff --git a/src/gen_packets.c b/src/gen_packets.c index 9728611..57b2741 100644 --- a/src/gen_packets.c +++ b/src/gen_packets.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019, 2021 John Langner, WB2OSZ +// Copyright (C) 2011, 2013, 2014, 2015, 2016, 2019, 2021, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -118,13 +118,48 @@ static void send_packet (char *str) packet_t pp; unsigned char fbuf[AX25_MAX_PACKET_LEN+2]; int flen; - int c; + int c = 0; // channel number. if (g_morse_wpm > 0) { - // TODO: Why not use the destination field instead of command line option? + // Why not use the destination field instead of command line option? + // For one thing, this is not in TNC-2 monitor format. - morse_send (0, str, g_morse_wpm, 100, 100); + morse_send (c, str, g_morse_wpm, 100, 100); + } + else if (modem.achan[0].modem_type == MODEM_EAS) { + +// Generate EAS SAME signal FOR RESEARCH AND TESTING ONLY!!! +// There could be legal consequences for sending unauhorized SAME +// over the radio so don't do it! + + // I'm expecting to see TNC 2 monitor format. + // The source and destination are ignored. + // The optional destination SSID is the number of times to repeat. + // The user defined data type indicator can optionally be used + // for compatibility with how it is received and presented to client apps. + // Examples: + // X>X-3:{DEZCZC-WXR-RWT-033019-033017-033015-033013-033011-025011-025017-033007-033005-033003-033001-025009-025027-033009+0015-1691525-KGYX/NWS- + // X>X:NNNN + + pp = ax25_from_text (str, 1); + if (pp == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\" is not valid TNC2 monitoring format.\n", str); + return; + } + unsigned char *pinfo; + int info_len = ax25_get_info (pp, &pinfo); + if (info_len >= 3 && strncmp((char*)pinfo, "{DE", 3) == 0) { + pinfo += 3; + info_len -= 3; + } + + int repeat = ax25_get_ssid (pp, AX25_DESTINATION); + if (repeat == 0) repeat = 1; + + eas_send (c, pinfo, repeat, 500, 500); + ax25_delete (pp); } else { pp = ax25_from_text (str, 1); @@ -135,6 +170,9 @@ static void send_packet (char *str) } flen = ax25_pack (pp, fbuf); (void)flen; + + // If stereo, put same thing in each channel. + for (c=0; c MAX_BAUD)) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); - exit (EXIT_FAILURE); - } /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* that need to be kept in sync. Maybe it could be a common function someday. */ - if (modem.achan[0].baud == 100) { + if (modem.achan[0].baud == 100) { // What was this for? modem.achan[0].modem_type = MODEM_AFSK; modem.achan[0].mark_freq = 1615; modem.achan[0].space_freq = 1785; } + else if (modem.achan[0].baud == 0xEA5EA5) { + modem.achan[0].baud = 521; // Fine tuned later. 520.83333 + // Proper fix is to make this float. + modem.achan[0].modem_type = MODEM_EAS; + modem.achan[0].mark_freq = 2083.3333; // Ideally these should be floating point. + modem.achan[0].space_freq = 1562.5000 ; + } else if (modem.achan[0].baud < 600) { modem.achan[0].modem_type = MODEM_AFSK; modem.achan[0].mark_freq = 1600; // Typical for HF SSB @@ -334,6 +380,11 @@ int main(int argc, char **argv) text_color_set(DW_COLOR_INFO); dw_printf ("Using scrambled baseband signal rather than AFSK.\n"); } + if (modem.achan[0].baud != 100 && (modem.achan[0].baud < MIN_BAUD || modem.achan[0].baud > MAX_BAUD)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Use a more reasonable bit rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD); + exit (EXIT_FAILURE); + } break; case 'g': /* -g for g3ruh scrambling */ @@ -740,14 +791,23 @@ int main(int argc, char **argv) } else { + // This should send a total of 6. + // Note that sticking in the user defined type {DE is optional. + + if (modem.achan[0].modem_type == MODEM_EAS) { + send_packet ("X>X-3:{DEZCZC-WXR-RWT-033019-033017-033015-033013-033011-025011-025017-033007-033005-033003-033001-025009-025027-033009+0015-1691525-KGYX/NWS-"); + send_packet ("X>X-2:{DENNNN"); + send_packet ("X>X:NNNN"); + } + else { /* * Builtin default 4 packets. */ - - send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 1 of 4"); - send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 2 of 4"); - send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 3 of 4"); - send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 4 of 4"); + send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 1 of 4"); + send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 2 of 4"); + send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 3 of 4"); + send_packet ("WB2OSZ-15>TEST:,The quick brown fox jumps over the lazy dog! 4 of 4"); + } } audio_file_close(); @@ -765,7 +825,7 @@ static void usage (char **argv) dw_printf ("Options:\n"); dw_printf (" -a Signal amplitude in range of 0 - 200%%. Default 50.\n"); dw_printf (" -b Bits / second for data. Default is %d.\n", DEFAULT_BAUD); - dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600.\n"); + dw_printf (" -B Bits / second for data. Proper modem selected for 300, 1200, 2400, 4800, 9600, EAS.\n"); dw_printf (" -g Scrambled baseband rather than AFSK.\n"); dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n"); dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n"); @@ -788,6 +848,7 @@ static void usage (char **argv) dw_printf ("the default built-in message. The format should correspond to\n"); dw_printf ("the standard packet monitoring representation such as,\n\n"); dw_printf (" WB2OSZ-1>APDW12,WIDE2-2:!4237.14NS07120.83W#\n"); + dw_printf ("User defined content can't be used with -n option.\n"); dw_printf ("\n"); dw_printf ("Example: gen_packets -o x.wav \n"); dw_printf ("\n"); diff --git a/src/gen_tone.c b/src/gen_tone.c index 3317aa3..6a81655 100644 --- a/src/gen_tone.c +++ b/src/gen_tone.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2014, 2015, 2016, 2019 John Langner, WB2OSZ +// Copyright (C) 2011, 2014, 2015, 2016, 2019, 2023 John Langner, WB2OSZ // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -70,6 +70,7 @@ static int ticks_per_sample[MAX_CHANS]; /* Same for both channels of same soundc static int ticks_per_bit[MAX_CHANS]; static int f1_change_per_sample[MAX_CHANS]; static int f2_change_per_sample[MAX_CHANS]; +static float samples_per_symbol[MAX_CHANS]; static short sine_table[256]; @@ -198,8 +199,11 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud * 0.5)) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. + samples_per_symbol[chan] = 2. * (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud; tone_phase[chan] = PHASE_SHIFT_45; // Just to mimic first attempt. + // ??? Why? We are only concerned with the difference + // from one symbol to the next. break; case MODEM_8PSK: @@ -211,6 +215,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / ((double)audio_config_p->achan[chan].baud / 3.)) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); f2_change_per_sample[chan] = f1_change_per_sample[chan]; // Not used. + samples_per_symbol[chan] = 3. * (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud; break; case MODEM_BASEBAND: @@ -220,11 +225,23 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) // Tone is half baud. ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].baud * 0.5 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + samples_per_symbol[chan] = (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud; + break; + + case MODEM_EAS: // EAS. + + // TODO: Proper fix would be to use float for baud, mark, space. + + ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / 520.833333333333 ) + 0.5); + samples_per_symbol[chan] = (int)((audio_config_p->adev[a].samples_per_sec / 520.83333) + 0.5); + f1_change_per_sample[chan] = (int) ((2083.33333333333 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); + f2_change_per_sample[chan] = (int) ((1562.5000000 * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); break; default: // AFSK ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); + samples_per_symbol[chan] = (float)audio_config_p->adev[a].samples_per_sec / (float)audio_config_p->achan[chan].baud; f1_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].mark_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); f2_change_per_sample[chan] = (int) (((double)audio_config_p->achan[chan].space_freq * TICKS_PER_CYCLE / (double)audio_config_p->adev[a].samples_per_sec ) + 0.5); break; @@ -285,9 +302,64 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) * *--------------------------------------------------------------------*/ +// Interpolate between two values. +// My original approximation simply jumped between phases, producing a discontinuity, +// and increasing bandwidth. +// According to multiple sources, we should transition more gently. +// Below see see a rough approximation of: +// * A step function, immediately going to new value. +// * Linear interpoation. +// * Raised cosine. Square root of cosine is also mentioned. +// +// new - / -- +// | / / +// | / | +// | / / +// old ------- / -- +// step linear raised cosine +// +// Inputs are the old (previous value), new value, and a blending control +// 0 -> take old value +// 1 -> take new value. +// inbetween some sort of weighted average. + +static inline float interpol8 (float oldv, float newv, float bc) +{ + // Step function. + //return (newv); // 78 on 11/7 + + assert (bc >= 0); + assert (bc <= 1.1); + + if (bc < 0) return (oldv); + if (bc > 1) return (newv); + + // Linear interpolation, just for comparison. + //return (bc * newv + (1.0f - bc) * oldv); // 39 on 11/7 + + float rc = 0.5f * (cosf(bc * M_PI - M_PI) + 1.0f); + float rrc = bc >= 0.5f + ? 0.5f * (sqrtf(fabsf(cosf(bc * M_PI - M_PI))) + 1.0f) + : 0.5f * (-sqrtf(fabsf(cosf(bc * M_PI - M_PI))) + 1.0f); + + (void)rrc; + return (rc * newv + (1.0f - bc) * oldv); // 49 on 11/7 + //return (rrc * newv + (1.0f - bc) * oldv); // 55 on 11/7 +} + static const int gray2phase_v26[4] = {0, 1, 3, 2}; static const int gray2phase_v27[8] = {1, 0, 2, 3, 6, 7, 5, 4}; +// #define PSKIQ 1 // not ready for prime time yet. +#if PSKIQ +static int xmit_octant[MAX_CHANS]; // absolute phase in 45 degree units. +static int xmit_prev_octant[MAX_CHANS]; // from previous symbol. + +// For PSK, we generate the final signal by combining fixed frequency cosine and +// sine by the following weights. +static const float ci[8] = { 1, .7071, 0, -.7071, -1, -.7071, 0, .7071 }; +static const float sq[8] = { 0, .7071, 1, .7071, 0, -.7071, -1, -.7071 }; +#endif void tone_gen_put_bit (int chan, int dat) { @@ -324,14 +396,28 @@ void tone_gen_put_bit (int chan, int dat) // All zero bits should give us steady 1800 Hz. // All one bits should flip phase by 180 degrees each time. + // For V.26B, add another 45 degrees. + // This seems to work a little better. dibit = (save_bit[chan] << 1) | dat; - symbol = gray2phase_v26[dibit]; + symbol = gray2phase_v26[dibit]; // 0 .. 3 for QPSK. +#if PSKIQ + // One phase shift unit is 45 degrees. + // Remember what it was last time and calculate new. + // values 0 .. 7. + xmit_prev_octant[chan] = xmit_octant[chan]; + xmit_octant[chan] += symbol * 2; + if (save_audio_config_p->achan[chan].v26_alternative == V26_B) { + xmit_octant[chan] += 1; + } + xmit_octant[chan] &= 0x7; +#else tone_phase[chan] += symbol * PHASE_SHIFT_90; if (save_audio_config_p->achan[chan].v26_alternative == V26_B) { tone_phase[chan] += PHASE_SHIFT_45; } +#endif bit_count[chan]++; } @@ -370,7 +456,9 @@ void tone_gen_put_bit (int chan, int dat) lfsr[chan] = (lfsr[chan] << 1) | (x & 1); dat = x; } - +#if PSKIQ + int blend = 1; +#endif do { /* until enough audio samples for this symbol. */ int sam; @@ -395,9 +483,58 @@ void tone_gen_put_bit (int chan, int dat) gen_tone_put_sample (chan, a, sam); break; - case MODEM_QPSK: - case MODEM_8PSK: + case MODEM_EAS: + tone_phase[chan] += dat ? f1_change_per_sample[chan] : f2_change_per_sample[chan]; + sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; + gen_tone_put_sample (chan, a, sam); + break; + + case MODEM_QPSK: + +#if DEBUG2 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__); +#endif + tone_phase[chan] += f1_change_per_sample[chan]; +#if PSKIQ +#if 1 // blend JWL + // remove loop invariant + float old_i = ci[xmit_prev_octant[chan]]; + float old_q = sq[xmit_prev_octant[chan]]; + + float new_i = ci[xmit_octant[chan]]; + float new_q = sq[xmit_octant[chan]]; + + float b = blend / samples_per_symbol[chan]; // roughly 0 to 1 + blend++; + // b = (b - 0.5) * 20 + 0.5; + // if (b < 0) b = 0; + // if (b > 1) b = 1; + // b = b > 0.5; + //b = 1; // 78 decoded with this. + // only 39 without. + + + //float blended_i = new_i * b + old_i * (1.0f - b); + //float blended_q = new_q * b + old_q * (1.0f - b); + + float blended_i = interpol8 (old_i, new_i, b); + float blended_q = interpol8 (old_q, new_q, b); + + sam = blended_i * sine_table[((tone_phase[chan] - PHASE_SHIFT_90) >> 24) & 0xff] + + blended_q * sine_table[(tone_phase[chan] >> 24) & 0xff]; +#else // jump + sam = ci[xmit_octant[chan]] * sine_table[((tone_phase[chan] - PHASE_SHIFT_90) >> 24) & 0xff] + + sq[xmit_octant[chan]] * sine_table[(tone_phase[chan] >> 24) & 0xff]; +#endif +#else + sam = sine_table[(tone_phase[chan] >> 24) & 0xff]; +#endif + gen_tone_put_sample (chan, a, sam); + break; + + case MODEM_8PSK: #if DEBUG2 text_color_set(DW_COLOR_DEBUG); dw_printf ("tone_gen_put_bit %d PSK\n", __LINE__); @@ -521,6 +658,20 @@ void gen_tone_put_sample (int chan, int a, int sam) { } } +void gen_tone_put_quiet_ms (int chan, int time_ms) { + + int a = ACHAN2ADEV(chan); /* device for channel. */ + int sam = 0; + + int nsamples = (int) ((time_ms * (float)save_audio_config_p->adev[a].samples_per_sec / 1000.) + 0.5); + + for (int j=0; j>= 1; + } +} + +int eas_send (int chan, unsigned char *str, int repeat, int txdelay, int txtail) +{ + int bytes_sent = 0; + const int gap = 1000; + int gaps_sent = 0; + + gen_tone_put_quiet_ms (chan, txdelay); + + for (int r=0; r