From ef573f2acf53b250295e11ccf1c23f775e61bab2 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Mon, 23 Jan 2023 23:11:34 +0000 Subject: [PATCH 01/29] Pull request 439 - Fix audio level display for B demodulator. --- src/ax25_pad.c | 6 ++++-- src/demod_afsk.c | 23 ++++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/ax25_pad.c b/src/ax25_pad.c index b5d47639..0f075808 100644 --- a/src/ax25_pad.c +++ b/src/ax25_pad.c @@ -1866,7 +1866,7 @@ packet_t ax25_get_nextp (packet_t this_p) * * Inputs: this_p - Current packet object. * - * release_time - Time as returned by dtime_now(). + * release_time - Time as returned by dtime_monotonic(). * *------------------------------------------------------------------------------*/ @@ -2923,7 +2923,9 @@ int ax25_alevel_to_text (alevel_t alevel, char text[AX25_ALEVEL_TO_TEXT_SIZE]) snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d(%+d/%+d)", alevel.rec, alevel.mark, alevel.space); } - else if (alevel.mark == -1 && alevel.space == -1) { /* PSK - single number. */ + else if ((alevel.mark == -1 && alevel.space == -1) || /* PSK */ + (alevel.mark == -99 && alevel.space == -99)) { /* v. 1.7 "B" FM demodulator. */ + // ?? Where does -99 come from? snprintf (text, AX25_ALEVEL_TO_TEXT_SIZE, "%d", alevel.rec); } diff --git a/src/demod_afsk.c b/src/demod_afsk.c index f32137c5..b4d6c295 100644 --- a/src/demod_afsk.c +++ b/src/demod_afsk.c @@ -309,10 +309,6 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, D->lp_window = BP_WINDOW_TRUNCATED; } - D->agc_fast_attack = 0.820; - D->agc_slow_decay = 0.000214; - D->agc_fast_attack = 0.45; - D->agc_slow_decay = 0.000195; D->agc_fast_attack = 0.70; D->agc_slow_decay = 0.000090; @@ -372,10 +368,16 @@ void demod_afsk_init (int samples_per_sec, int baud, int mark_freq, // For scaling phase shift into normallized -1 to +1 range for mark and space. D->u.afsk.normalize_rpsam = 1.0 / (0.5 * abs(mark_freq - space_freq) * 2 * M_PI / samples_per_sec); + // New "B" demodulator does not use AGC but demod.c needs this to derive "quick" and + // "sluggish" values for overall signal amplitude. That probably should be independent + // of these values. + D->agc_fast_attack = 0.70; + D->agc_slow_decay = 0.000090; + D->pll_locked_inertia = 0.74; D->pll_searching_inertia = 0.50; - D->alevel_mark_peak = -1; // FIXME: disable display + D->alevel_mark_peak = -1; // Disable received signal (m/s) display. D->alevel_space_peak = -1; break; @@ -868,6 +870,7 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct { D->slicer[slice].prev_d_c_pll = D->slicer[slice].data_clock_pll; + // Perform the add as unsigned to avoid signed overflow error. D->slicer[slice].data_clock_pll = (signed)((unsigned)(D->slicer[slice].data_clock_pll) + (unsigned)(D->pll_step_per_sample)); @@ -901,7 +904,15 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct #endif + +#if 1 hdlc_rec_bit (chan, subchan, slice, demod_out > 0, 0, quality); +#else // TODO: new feature to measure data speed error. +// Maybe hdlc_rec_bit could provide indication when frame starts. + hdlc_rec_bit_new (chan, subchan, slice, demod_out > 0, 0, quality, + &(D->slicer[slice].pll_nudge_total), &(D->slicer[slice].pll_symbol_count)); + D->slicer[slice].pll_symbol_count++; +#endif pll_dcd_each_symbol2 (D, chan, subchan, slice); } @@ -912,12 +923,14 @@ static void nudge_pll (int chan, int subchan, int slice, float demod_out, struct pll_dcd_signal_transition2 (D, slice, D->slicer[slice].data_clock_pll); +// TODO: signed int before = (signed int)(D->slicer[slice].data_clock_pll); // Treat as signed. if (D->slicer[slice].data_detect) { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_locked_inertia); } else { D->slicer[slice].data_clock_pll = (int)(D->slicer[slice].data_clock_pll * D->pll_searching_inertia); } +// TODO: D->slicer[slice].pll_nudge_total += (int64_t)((signed int)(D->slicer[slice].data_clock_pll)) - (int64_t)before; } /* From 031c937cdb123d2501180b6dbf1cc6cff9be3e2f Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sat, 28 Jan 2023 16:36:51 +0000 Subject: [PATCH 02/29] Issue 444 - Command line -x calibrate tones are reversed --- src/direwolf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/direwolf.c b/src/direwolf.c index 3ad404e6..f2aeca14 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -1053,7 +1053,7 @@ int main (int argc, char *argv[]) audio_config.achan[x_opt_chan].mark_freq, x_opt_chan); while (n-- > 0) { - tone_gen_put_bit(x_opt_chan, 0); + tone_gen_put_bit(x_opt_chan, 1); } break; case 's': // "Space" tone: -x s @@ -1061,7 +1061,7 @@ int main (int argc, char *argv[]) audio_config.achan[x_opt_chan].space_freq, x_opt_chan); while (n-- > 0) { - tone_gen_put_bit(x_opt_chan, 1); + tone_gen_put_bit(x_opt_chan, 0); } break; case 'p': // Silence - set PTT only: -x p From fedfef92cdbc155220525e5495381559550678cb Mon Sep 17 00:00:00 2001 From: dforsi Date: Sat, 28 Jan 2023 21:58:09 +0100 Subject: [PATCH 03/29] Fix spellling (#409) Fixed with codespell --skip external,symbols*.txt,tocalls.txt,./cmake/* --ignore-words-list clen,convers,dout,feets,fo,inout,ist,ot,parm,pres,ro,siz,usng,xwindows --summary running first with only --write-changes and then adding --interactive=2 Co-authored-by: Daniele Forsi --- src/agwlib.c | 2 +- src/audio.c | 2 +- src/audio.h | 4 ++-- src/audio_portaudio.c | 2 +- src/audio_win.c | 4 ++-- src/ax25_link.c | 4 ++-- src/config.c | 2 +- src/decode_aprs.c | 4 ++-- src/demod.c | 2 +- src/fsk_demod_state.h | 2 +- src/igate.c | 4 ++-- src/il2p_header.c | 2 +- src/il2p_payload.c | 2 +- src/server.c | 2 +- src/symbols.c | 2 +- src/tt_user.c | 2 +- src/waypoint.c | 2 +- src/xmit.c | 2 +- 18 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/agwlib.c b/src/agwlib.c index 33d490c8..2c03adaa 100644 --- a/src/agwlib.c +++ b/src/agwlib.c @@ -365,7 +365,7 @@ static void * tnc_listen_thread (void *arg) } /* - * Call to/from fields are 10 bytes but contents must not exceeed 9 characters. + * Call to/from fields are 10 bytes but contents must not exceed 9 characters. * It's not guaranteed that unused bytes will contain 0 so we * don't issue error message in this case. */ diff --git a/src/audio.c b/src/audio.c index b8cf6b11..5335db57 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1532,7 +1532,7 @@ int audio_flush (int a) * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time - * and wait for additoinal time if required. + * and wait for additional time if required. * *----------------------------------------------------------------*/ diff --git a/src/audio.h b/src/audio.h index 78327a73..87d6c9c7 100644 --- a/src/audio.h +++ b/src/audio.h @@ -72,7 +72,7 @@ struct audio_s { struct adev_param_s { - /* Properites of the sound device. */ + /* Properties of the sound device. */ int defined; /* Was device defined? */ /* First one defaults to yes. */ @@ -102,7 +102,7 @@ struct audio_s { /* This is the probability, in per cent, of randomly corrupting it. */ /* Normally this is 0. 25 would mean corrupt it 25% of the time. */ - int recv_error_rate; /* Similar but the % probablity of dropping a received frame. */ + int recv_error_rate; /* Similar but the % probability of dropping a received frame. */ float recv_ber; /* Receive Bit Error Rate (BER). */ /* Probability of inverting a bit coming out of the modem. */ diff --git a/src/audio_portaudio.c b/src/audio_portaudio.c index 836390bc..cb6ccf10 100644 --- a/src/audio_portaudio.c +++ b/src/audio_portaudio.c @@ -1260,7 +1260,7 @@ int audio_flush (int a) * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time - * and wait for additoinal time if required. + * and wait for additional time if required. * *----------------------------------------------------------------*/ diff --git a/src/audio_win.c b/src/audio_win.c index 1ba64bba..85a1548b 100644 --- a/src/audio_win.c +++ b/src/audio_win.c @@ -84,7 +84,7 @@ static struct audio_s *save_audio_config_p; */ /* - * Originally, we had an abitrary buf time of 40 mS. + * Originally, we had an arbitrary buf time of 40 mS. * * For mono, the buffer size was rounded up from 3528 to 4k so * it was really about 50 mS per buffer or about 20 per second. @@ -1074,7 +1074,7 @@ int audio_flush (int a) * (3) Call this function, which might or might not wait long enough. * (4) Add (1) and (2) resulting in when PTT should be turned off. * (5) Take difference between current time and desired PPT off time - * and wait for additoinal time if required. + * and wait for additional time if required. * *----------------------------------------------------------------*/ diff --git a/src/ax25_link.c b/src/ax25_link.c index 09e71359..ab2875d9 100644 --- a/src/ax25_link.c +++ b/src/ax25_link.c @@ -347,7 +347,7 @@ typedef struct ax25_dlsm_s { // Sometimes the flow chart has SAT instead of SRT. // I think that is a typographical error. - float t1v; // How long to wait for an acknowlegement before resending. + float t1v; // How long to wait for an acknowledgement before resending. // Value used when starting timer T1, in seconds. // "FRACK" parameter in some implementations. // Typically it might be 3 seconds after frame has been @@ -6049,7 +6049,7 @@ static void check_need_for_response (ax25_dlsm_t *S, ax25_frame_type_t frame_typ * * Outputs: S->srt New smoothed roundtrip time. * - * S->t1v How long to wait for an acknowlegement before resending. + * S->t1v How long to wait for an acknowledgement before resending. * Value used when starting timer T1, in seconds. * Here it is dynamically adjusted. * diff --git a/src/config.c b/src/config.c index 5a8eead4..2588a96e 100644 --- a/src/config.c +++ b/src/config.c @@ -5829,7 +5829,7 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ /* * Process symbol now that we have any later overlay. * - * FIXME: Someone who used this was surprized to end up with Solar Powser (S-). + * FIXME: Someone who used this was surprised to end up with Solar Powser (S-). * overlay=S symbol="/-" * We should complain if overlay used with symtab other than \. */ diff --git a/src/decode_aprs.c b/src/decode_aprs.c index e2c25248..d00ceb62 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -1375,7 +1375,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int } } -/* 6th character of destintation indicates east / west. */ +/* 6th character of destination indicates east / west. */ /* * Example of apparently invalid encoding. 6th character missing. @@ -1579,7 +1579,7 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int * Purpose: Decode "Message Format." * The word message is used loosely all over the place, but it has a very specific meaning here. * - * Inputs: info - Pointer to Information field. Be carefull not to modify it here! + * Inputs: info - Pointer to Information field. Be careful not to modify it here! * ilen - Information field length. * quiet - suppress error messages. * diff --git a/src/demod.c b/src/demod.c index 482c1076..9f94dd8d 100644 --- a/src/demod.c +++ b/src/demod.c @@ -832,7 +832,7 @@ int demod_init (struct audio_s *pa) * * Name: demod_get_sample * - * Purpose: Get one audio sample fromt the specified sound input source. + * Purpose: Get one audio sample from the specified sound input source. * * Inputs: a - Index for audio device. 0 = first. * diff --git a/src/fsk_demod_state.h b/src/fsk_demod_state.h index efb9d2ca..c9b26c23 100644 --- a/src/fsk_demod_state.h +++ b/src/fsk_demod_state.h @@ -367,7 +367,7 @@ struct demodulator_state_s // Add a sample to the total when putting it in our array of recent samples. // Subtract it from the total when it gets pushed off the end. // We can also eliminate the need to shift them all down by using a circular buffer. - // This only works with integers because float would have cummulated round off errors. + // This only works with integers because float would have cumulated round off errors. cic_t cic_center1; cic_t cic_above; diff --git a/src/igate.c b/src/igate.c index 719bef41..dea0cba6 100644 --- a/src/igate.c +++ b/src/igate.c @@ -328,7 +328,7 @@ static int stats_uplink_packets; /* Number of packets passed along to the IGate /* server after filtering. */ static int stats_uplink_bytes; /* Total number of bytes sent to IGate server */ - /* including login, packets, and hearbeats. */ + /* including login, packets, and heartbeats. */ static int stats_downlink_bytes; /* Total number of bytes from IGate server including */ /* packets, heartbeats, other messages. */ @@ -1221,7 +1221,7 @@ static void send_packet_to_server (packet_t pp, int chan) * Name: send_msg_to_server * * Purpose: Send something to the IGate server. - * This one function should be used for login, hearbeats, + * This one function should be used for login, heartbeats, * and packets. * * Inputs: imsg - Message. We will add CR/LF here. diff --git a/src/il2p_header.c b/src/il2p_header.c index 9a1e9ea4..0ab34a01 100644 --- a/src/il2p_header.c +++ b/src/il2p_header.c @@ -437,7 +437,7 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed) // However, I have seen cases, where the error rate is very high, where the RS decoder // thinks it found a valid code block by changing one symbol but it was the wrong one. // The result is trash. This shows up as address fields like 'R&G4"A' and 'TEW\ !'. -// I added a sanity check here to catch characters other than uppper case letters and digits. +// I added a sanity check here to catch characters other than upper case letters and digits. // The frame should be rejected in this case. The question is whether to discard it // silently or print a message so the user can see that something strange is happening? // My current thinking is that it should be silently ignored if the header has been diff --git a/src/il2p_payload.c b/src/il2p_payload.c index 67c79a98..d5fb4887 100644 --- a/src/il2p_payload.c +++ b/src/il2p_payload.c @@ -194,7 +194,7 @@ int il2p_encode_payload (unsigned char *payload, int payload_size, int max_fec, * Purpose: Extract original data from encoded payload. * * Inputs: received Array of bytes. Size is unknown but in practice it - * must not exceeed IL2P_MAX_ENCODED_SIZE. + * must not exceed IL2P_MAX_ENCODED_SIZE. * payload_size 0 to 1023. (IL2P_MAX_PAYLOAD_SIZE) * Expected result size based on header. * max_fec true for 16 parity symbols, false for automatic. diff --git a/src/server.c b/src/server.c index 8103ea3a..da20d0df 100644 --- a/src/server.c +++ b/src/server.c @@ -1421,7 +1421,7 @@ static THREAD_F cmd_listen_thread (void *arg) } /* - * Call to/from fields are 10 bytes but contents must not exceeed 9 characters. + * Call to/from fields are 10 bytes but contents must not exceed 9 characters. * It's not guaranteed that unused bytes will contain 0 so we * don't issue error message in this case. */ diff --git a/src/symbols.c b/src/symbols.c index 35dba807..c9f07e6e 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -681,7 +681,7 @@ void symbols_from_dest_or_src (char dti, char *src, char *dest, char *symtab, ch // The position and object formats all contain a proper symbol and table. // There doesn't seem to be much reason to have a symbol for something without -// a postion because it would not show up on a map. +// a position because it would not show up on a map. // This just seems to be a remnant of something used long ago and no longer needed. // The protocol spec mentions a "MIM tracker" but I can't find any references to it. diff --git a/src/tt_user.c b/src/tt_user.c index 46e44453..a73d6a46 100644 --- a/src/tt_user.c +++ b/src/tt_user.c @@ -882,7 +882,7 @@ static void xmit_object_report (int i, int first_time) * IGate. * * When transmitting over the radio, it gets sent multiple times, to help - * probablity of being heard, with increasing delays between. + * probability of being heard, with increasing delays between. * * The other methods are reliable so we only want to send it once. */ diff --git a/src/waypoint.c b/src/waypoint.c index 70ea3205..20c1cdbc 100644 --- a/src/waypoint.c +++ b/src/waypoint.c @@ -298,7 +298,7 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol); #endif -// Don't waste time if no destintations specified. +// Don't waste time if no destinations specified. if (s_waypoint_serial_port_fd == MYFDERROR && s_waypoint_udp_sock_fd == -1) { diff --git a/src/xmit.c b/src/xmit.c index 9494dac9..13bbaecb 100644 --- a/src/xmit.c +++ b/src/xmit.c @@ -600,7 +600,7 @@ static void * xmit_thread (void *arg) // I don't know if this in some official specification // somewhere, but it is generally agreed that APRS digipeaters // should send only one frame at a time rather than - // bunding multiple frames into a single transmission. + // bundling multiple frames into a single transmission. // Discussion here: http://lists.tapr.org/pipermail/aprssig_lists.tapr.org/2021-September/049034.html break; From 0f92f463f4e0817355ffa58851d326156b195238 Mon Sep 17 00:00:00 2001 From: ra1nb0w Date: Sat, 28 Jan 2023 22:07:56 +0100 Subject: [PATCH 04/29] github actions implementation (#396) * Create codeql-analysis.yml * cmake: add support for Visual Studio 2019 * enable github actions (aka continuous integration) basic implementation to enable github actions with: - triggered each push or pull request - built on ubuntu (multiple version), macOS, windows - the binary has debug facilities enabled - ignore any commit/push on the .github folder - run all tests - create an archive with binaries (available for 90 days) - can manually triggered setting custom cmake flags * cmake: fix MSVC check * github actions: remove ubuntu 18.04; add ubuntu 22.04 * github actions: fix windows ci --------- Co-authored-by: wb2osz --- .github/workflows/ci.yml | 170 ++++++++++++++++++++++++++ .github/workflows/codeql-analysis.yml | 73 +++++++++++ CMakeLists.txt | 17 +-- cmake/modules/FindCompiler.cmake | 4 +- 4 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..82c129b0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,170 @@ +name: 'build direwolf' + +on: + # permit to manually trigger the CI + workflow_dispatch: + inputs: + cmake_flags: + description: 'Custom CMAKE flags' + required: false + push: + paths-ignore: + - '.github/**' + pull_request: + paths-ignore: + - '.github/**' + +jobs: + build: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - { + name: 'Windows Latest MinGW 64bit', + os: windows-latest, + cc: 'x86_64-w64-mingw32-gcc', + cxx: 'x86_64-w64-mingw32-g++', + ar: 'x86_64-w64-mingw32-ar', + windres: 'x86_64-w64-mingw32-windres', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '-G "MinGW Makefiles"' + } + - { + name: 'Windows 2019 MinGW 32bit', + os: windows-2019, + cc: 'i686-w64-mingw32-gcc', + cxx: 'i686-w64-mingw32-g++', + ar: 'i686-w64-mingw32-ar', + windres: 'i686-w64-mingw32-windres', + arch: 'i686', + build_type: 'Release', + cmake_extra_flags: '-G "MinGW Makefiles"' + } + - { + name: 'macOS latest', + os: macos-latest, + cc: 'clang', + cxx: 'clang++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu latest Debug', + os: ubuntu-latest, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Debug', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu 22.04', + os: ubuntu-22.04, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu 20.04', + os: ubuntu-20.04, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + - { + name: 'Ubuntu 18.04', + os: ubuntu-18.04, + cc: 'gcc', + cxx: 'g++', + arch: 'x86_64', + build_type: 'Release', + cmake_extra_flags: '' + } + steps: + - name: checkout + uses: actions/checkout@v2 + with: + fetch-depth: 8 + - name: dependency + shell: bash + run: | + # this is not perfect but enought for now + if [ "$RUNNER_OS" == "Linux" ]; then + sudo apt-get update + sudo apt-get install libasound2-dev libudev-dev libhamlib-dev gpsd + elif [ "$RUNNER_OS" == "macOS" ]; then + # just to simplify I use homebrew but + # we can use macports (latest direwolf is already available as port) + brew install portaudio hamlib gpsd + elif [ "$RUNNER_OS" == "Windows" ]; then + # add the folder to PATH + echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH + fi + - name: create build environment + run: | + cmake -E make_directory ${{github.workspace}}/build + - name: configure + shell: bash + working-directory: ${{github.workspace}}/build + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + export CC=${{ matrix.config.cc }} + export CXX=${{ matrix.config.cxx }} + export AR=${{ matrix.config.ar }} + export WINDRES=${{ matrix.config.windres }} + fi + cmake $GITHUB_WORKSPACE \ + -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} \ + -DCMAKE_C_COMPILER=${{ matrix.config.cc }} \ + -DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} \ + -DCMAKE_CXX_FLAGS="-Werror" -DUNITTEST=1 \ + ${{ matrix.config.cmake_extra_flags }} \ + ${{ github.event.inputs.cmake_flags }} + - name: build + shell: bash + working-directory: ${{github.workspace}}/build + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + export CC=${{ matrix.config.cc }} + export CXX=${{ matrix.config.cxx }} + export AR=${{ matrix.config.ar }} + export WINDRES=${{ matrix.config.windres }} + fi + cmake --build . --config ${{ matrix.config.build_type }} \ + ${{ github.event.inputs.cmake_flags }} + - name: test + continue-on-error: true + shell: bash + working-directory: ${{github.workspace}}/build + run: | + ctest -C ${{ matrix.config.build_type }} \ + --parallel 2 --output-on-failure \ + ${{ github.event.inputs.cmake_flags }} + - name: package + shell: bash + working-directory: ${{github.workspace}}/build + run: | + if [ "$RUNNER_OS" == "Windows" ] || [ "$RUNNER_OS" == "macOS" ]; then + make package + fi + - name: archive binary + uses: actions/upload-artifact@v2 + with: + name: direwolf_${{ matrix.config.os }}_${{ matrix.config.arch }}_${{ github.sha }} + path: | + ${{github.workspace}}/build/direwolf-*.zip + ${{github.workspace}}/build/direwolf.conf + ${{github.workspace}}/build/src/* + ${{github.workspace}}/build/CMakeCache.txt + !${{github.workspace}}/build/src/cmake_install.cmake + !${{github.workspace}}/build/src/CMakeFiles + !${{github.workspace}}/build/src/Makefile diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..7134f213 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ dev ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ dev ] + schedule: + - cron: '25 8 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + - run: | + mkdir build + cd build + cmake -DUNITTEST=1 .. + make + make test + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 61ffc4b4..e44f99b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,15 +167,16 @@ elseif(APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_MACOS_DNSSD") elseif (WIN32) - if(NOT VS2015 AND NOT VS2017) - message(FATAL_ERROR "You must use Microsoft Visual Studio 2015 or 2017 as compiler") + if(C_MSVC) + if (NOT VS2015 AND NOT VS2017 AND NOT VS2019) + message(FATAL_ERROR "You must use Microsoft Visual Studio 2015, 2017 or 2019 as compiler") + else() + # compile with full multicore + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + set(CUSTOM_SHELL_BIN "") + endif() endif() - - # compile with full multicore - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") - - set(CUSTOM_SHELL_BIN "") endif() if (C_CLANG OR C_GCC) diff --git a/cmake/modules/FindCompiler.cmake b/cmake/modules/FindCompiler.cmake index f339a73e..fe036e4b 100644 --- a/cmake/modules/FindCompiler.cmake +++ b/cmake/modules/FindCompiler.cmake @@ -5,7 +5,9 @@ elseif(NOT DEFINED C_GCC AND CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(C_GCC 1) elseif(NOT DEFINED C_MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(C_MSVC 1) - if(MSVC_VERSION GREATER 1910 AND MSVC_VERSION LESS 1919) + if(MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS_EQUAL 1929) + set(VS2019 ON) + elseif(MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS_EQUAL 1919) set(VS2017 ON) elseif(MSVC_VERSION GREATER 1899 AND MSVC_VERSION LESS 1910) set(VS2015 ON) From 04ecdbc6fcc2c73e92cca471dc8e002c3b4a4ccf Mon Sep 17 00:00:00 2001 From: wb2osz Date: Mon, 30 Jan 2023 02:50:17 +0000 Subject: [PATCH 05/29] Complete the new ICHANNEL feature. --- CHANGES.md | 3 ++ src/beacon.c | 14 ++++-- src/config.c | 36 ++++++++++----- src/dtime_now.c | 111 +++++++++++++++++++++++++++++++++++++++++++---- src/dtime_now.h | 13 +++++- src/igate.c | 75 +++++++++++++++++++++----------- src/kiss_frame.c | 5 ++- src/tq.c | 47 ++++++++++++++++++-- 8 files changed, 249 insertions(+), 55 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f979b541..ba28d1d2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,9 @@ ### New Features: ### +- Additional documentation location to slow down growth of main repository. [https://github.com/wb2osz/direwolf-doc](https://github.com/wb2osz/direwolf-doc) + +- New ICHANNEL configuration option to map a KISS client application channel to APRS-IS. Packets from APRS-IS will be presented to client applications as the specified channel. Packets sent, by client applications, to that channel will go to APRS-IS rather than a radio channel. Details in ***Internal-Packet-Routing.pdf***. - New variable speed option for gen_packets. For example, "-v 5,0.1" would generate packets from 5% too slow to 5% too fast with increments of 0.1. Some implementations might have imprecise timing. Use this to test how well TNCs tolerate sloppy timing. diff --git a/src/beacon.c b/src/beacon.c index 4c78198f..69a72701 100644 --- a/src/beacon.c +++ b/src/beacon.c @@ -162,6 +162,7 @@ void beacon_init (struct audio_s *pmodem, struct misc_config_s *pconfig, struct int chan = g_misc_config_p->beacon[j].sendto_chan; if (chan < 0) chan = 0; /* For IGate, use channel 0 call. */ + if (chan >= MAX_CHANS) chan = 0; // For ICHANNEL, use channel 0 call. if (g_modem_config_p->chan_medium[chan] == MEDIUM_RADIO || g_modem_config_p->chan_medium[chan] == MEDIUM_NETTNC) { @@ -621,6 +622,7 @@ static void * beacon_thread (void *arg) // On reboot, the time is in the past. // After time gets set from GPS, all beacons from that interval are sent. // FIXME: This will surely break time slotted scheduling. + // TODO: The correct fix will be using monotonic, rather than clock, time. /* craigerl: if next beacon is scheduled in the past, then set next beacon relative to now (happens when NTP pushes clock AHEAD) */ /* fixme: if NTP sets clock BACK an hour, this thread will sleep for that hour */ @@ -805,11 +807,17 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) assert (bp->sendto_chan >= 0); - strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); + if (g_modem_config_p->chan_medium[bp->sendto_chan] == MEDIUM_IGATE) { // ICHANNEL uses chan 0 mycall. + // TODO: Maybe it should be allowed to have own. + strlcpy (mycall, g_modem_config_p->achan[0].mycall, sizeof(mycall)); + } + else { + strlcpy (mycall, g_modem_config_p->achan[bp->sendto_chan].mycall, sizeof(mycall)); + } if (strlen(mycall) == 0 || strcmp(mycall, "NOCALL") == 0) { text_color_set(DW_COLOR_ERROR); - dw_printf ("MYCALL not set for beacon in config file line %d.\n", bp->lineno); + dw_printf ("MYCALL not set for beacon to chan %d in config file line %d.\n", bp->sendto_chan, bp->lineno); return; } @@ -1046,7 +1054,7 @@ static void beacon_send (int j, dwgps_info_t *gpsinfo) text_color_set(DW_COLOR_XMIT); dw_printf ("[ig] %s\n", beacon_text); - igate_send_rec_packet (0, pp); + igate_send_rec_packet (-1, pp); // Channel -1 to avoid RF>IS filtering. ax25_delete (pp); break; diff --git a/src/config.c b/src/config.c index 5a8eead4..492fa227 100644 --- a/src/config.c +++ b/src/config.c @@ -5594,7 +5594,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (value[0] == 'r' || value[0] == 'R') { int n = atoi(value+1); - if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n); continue; @@ -5604,7 +5605,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { int n = atoi(value+1); - if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; @@ -5615,7 +5617,8 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ } else { int n = atoi(value); - if ( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) { + if (( n < 0 || n >= MAX_CHANS || p_audio_config->chan_medium[n] == MEDIUM_NONE) + && p_audio_config->chan_medium[n] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; @@ -5864,19 +5867,32 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ if (b->sendto_type == SENDTO_XMIT) { - if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) { + if (( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_NONE) + && p_audio_config->chan_medium[b->sendto_chan] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan); return (0); } - if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || - strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || - strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { + if (p_audio_config->chan_medium[b->sendto_chan] == MEDIUM_IGATE) { // Prevent subscript out of bounds. + // Will be using call from chan 0 later. + if ( strcmp(p_audio_config->achan[0].mycall, "") == 0 || + strcmp(p_audio_config->achan[0].mycall, "NOCALL") == 0 || + strcmp(p_audio_config->achan[0].mycall, "N0CALL") == 0 ) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); - return (0); + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", 0); + return (0); + } + } else { + if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || + strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || + strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); + return (0); + } } } diff --git a/src/dtime_now.c b/src/dtime_now.c index af7b98a2..e5b40c6c 100644 --- a/src/dtime_now.c +++ b/src/dtime_now.c @@ -25,9 +25,9 @@ /*------------------------------------------------------------------ * - * Name: dtime_now + * Name: dtime_realtime * - * Purpose: Return current time as double precision. + * Purpose: Return current wall clock time as double precision. * * Input: none * @@ -40,15 +40,99 @@ * part of a second, and having extra calculations everywhere, * simply use double precision floating point to make usage * easier. - * + * + * NOTE: This is not a good way to calculate elapsed time because + * it can jump forward or backware via NTP or other manual setting. + * + * Use the monotonic version for measuring elapsed time. + * + * History: Originally I called this dtime_now. We ran into issues where + * we really cared about elapsed time, rather than wall clock time. + * The wall clock time could be wrong at start up time if there + * is no realtime clock or Internet access. It can then jump + * when GPS time or Internet access becomes available. + * All instances of dtime_now should be replaced by dtime_realtime + * if we want wall clock time, or dtime_monotonic if it is to be + * used for measuring elapsed time, such as between becons. + * *---------------------------------------------------------------*/ +double dtime_realtime (void) +{ + double result; + +#if __WIN32__ + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ + + FILETIME ft; + + GetSystemTimeAsFileTime (&ft); + + result = ((( (double)ft.dwHighDateTime * (256. * 256. * 256. * 256.) + + (double)ft.dwLowDateTime ) / 10000000.) - 11644473600.); +#else + /* tv_sec is seconds from Jan 1, 1970. */ + + struct timespec ts; + +#ifdef __APPLE__ + +// Why didn't I use clock_gettime? +// Not available before Max OSX 10.12? https://github.com/gambit/gambit/issues/293 + + struct timeval tp; + gettimeofday(&tp, NULL); + ts.tv_nsec = tp.tv_usec * 1000; + ts.tv_sec = tp.tv_sec; +#else + clock_gettime (CLOCK_REALTIME, &ts); +#endif + + result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001); + +#endif + +#if DEBUG + text_color_set(DW_COLOR_DEBUG); + dw_printf ("dtime_realtime() returns %.3f\n", result ); +#endif + + return (result); +} + -double dtime_now (void) +/*------------------------------------------------------------------ + * + * Name: dtime_monotonic + * + * Purpose: Return montonically increasing time, which is not influenced + * by the wall clock changing. e.g. leap seconds, NTP adjustments. + * + * Input: none + * + * Returns: Time as double precision, so we can get resolution + * finer than one second. + * + * Description: Use this when calculating elapsed time. + * + *---------------------------------------------------------------*/ + +double dtime_monotonic (void) { double result; #if __WIN32__ + +// FIXME: +// This is still returning wall clock time. +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64 +// GetTickCount64 would be ideal but it requires Vista or Server 2008. +// As far as I know, the current version of direwolf still works on XP. +// +// As a work-around, GetTickCount could be used if we add extra code to deal +// with the wrap around after about 49.7 days. +// Resolution is only about 10 or 16 milliseconds. Is that good enough? + /* 64 bit integer is number of 100 nanosecond intervals from Jan 1, 1601. */ FILETIME ft; @@ -63,12 +147,22 @@ double dtime_now (void) struct timespec ts; #ifdef __APPLE__ + +// FIXME: Does MacOS have a monotonically increasing time? +// https://stackoverflow.com/questions/41509505/clock-gettime-on-macos + struct timeval tp; gettimeofday(&tp, NULL); ts.tv_nsec = tp.tv_usec * 1000; ts.tv_sec = tp.tv_sec; #else - clock_gettime (CLOCK_REALTIME, &ts); + +// This is the only case handled properly. +// Probably the only one that matters. +// It is common to have a Raspberry Pi, without Internet, +// starting up direwolf before GPS/NTP adjusts the time. + + clock_gettime (CLOCK_MONOTONIC, &ts); #endif result = ((double)(ts.tv_sec) + (double)(ts.tv_nsec) * 0.000000001); @@ -84,6 +178,7 @@ double dtime_now (void) } + /*------------------------------------------------------------------ * * Name: timestamp_now @@ -104,7 +199,7 @@ double dtime_now (void) void timestamp_now (char *result, int result_size, int show_ms) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; @@ -150,7 +245,7 @@ void timestamp_now (char *result, int result_size, int show_ms) void timestamp_user_format (char *result, int result_size, char *user_format) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; @@ -191,7 +286,7 @@ void timestamp_user_format (char *result, int result_size, char *user_format) void timestamp_filename (char *result, int result_size) { - double now = dtime_now(); + double now = dtime_realtime(); time_t t = (int)now; struct tm tm; diff --git a/src/dtime_now.h b/src/dtime_now.h index ac22f7d5..411534bf 100644 --- a/src/dtime_now.h +++ b/src/dtime_now.h @@ -1,9 +1,18 @@ -extern double dtime_now (void); +extern double dtime_realtime (void); + +extern double dtime_monotonic (void); + void timestamp_now (char *result, int result_size, int show_ms); void timestamp_user_format (char *result, int result_size, char *user_format); -void timestamp_filename (char *result, int result_size); \ No newline at end of file +void timestamp_filename (char *result, int result_size); + + +// FIXME: remove temp workaround. +// Needs many scattered updates. + +#define dtime_now dtime_realtime diff --git a/src/igate.c b/src/igate.c index 719bef41..73b0bbd3 100644 --- a/src/igate.c +++ b/src/igate.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 2015, 2016, 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 @@ -855,6 +855,9 @@ static void * connnect_thread (void *arg) * Purpose: Send a packet to the IGate server * * Inputs: chan - Radio channel it was received on. + * This is required for the RF>IS filtering. + * Beaconing (sendto=ig, chan=-1) and a client app sending + * to ICHANNEL should bypass the filtering. * * recv_pp - Pointer to packet object. * *** CALLER IS RESPONSIBLE FOR DELETING IT! ** @@ -902,7 +905,12 @@ void igate_send_rec_packet (int chan, packet_t recv_pp) * In that case, the payload will have TCPIP in the path and it will be dropped. */ - if (save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { +// Apply RF>IS filtering only if it same from a radio channel. +// Beacon will be channel -1. +// Client app to ICHANNEL is outside of radio channel range. + + if (chan >= 0 && chan < MAX_CHANS && // in radio channel range + save_digi_config_p->filter_str[chan][MAX_CHANS] != NULL) { if (pfilter(chan, MAX_CHANS, save_digi_config_p->filter_str[chan][MAX_CHANS], recv_pp, 1) != 1) { @@ -1517,32 +1525,36 @@ static void * igate_recv_thread (void *arg) int ichan = save_audio_config_p->igate_vchannel; - // Try to parse it into a packet object. - // This will contain "q constructs" and we might see an address - // with two alphnumeric characters in the SSID so we must use - // the non-strict parsing. - - // Possible problem: Up to 8 digipeaters are allowed in radio format. - // There is a potential of finding a larger number here. - - packet_t pp3 = ax25_from_text((char*)message, 0); // 0 means not strict + // My original poorly thoughtout idea was to parse it into a packet object, + // using the non-strict option, and send to the client app. + // + // A lot of things can go wrong with that approach. + + // (1) Up to 8 digipeaters are allowed in radio format. + // There is a potential of finding a larger number here. + // + // (2) The via path can have names that are not valid in the radio format. + // e.g. qAC, T2HAKATA, N5JXS-F1. + // Non-strict parsing would force uppercase, truncate names too long, + // and drop unacceptable SSIDs. + // + // (3) The source address could be invalid for the RF address format. + // e.g. WHO-IS>APJIW4,TCPIP*,qAC,AE5PL-JF::ZL1JSH-9 :Charles Beadfield/New Zealand{583 + // That is essential information that we absolutely need to preserve. + // + // I think the only correct solution is to apply a third party header + // wrapper so the original contents are preserved. This will be a little + // more work for the application developer. Search for ":}" and use only + // the part after that. At this point, I don't see any value in encoding + // information in the source/destination so I will just use "X>X:}" as a prefix + + char stemp[AX25_MAX_INFO_LEN]; + strlcpy (stemp, "X>X:}", sizeof(stemp)); + strlcat (stemp, (char*)message, sizeof(stemp)); + + packet_t pp3 = ax25_from_text(stemp, 0); // 0 means not strict if (pp3 != NULL) { - // Should we remove the VIA path? - - // For example, we might get something like this from the server. - // Lower case 'q' and non-numeric SSID are not valid for AX.25 over the air. - // K1USN-1>APWW10,TCPIP*,qAC,N5JXS-F1:T#479,100,048,002,500,000,10000000 - - // Should we try to retain all information and pass that along, to the best of our ability, - // to the client app, or should we remove the via path so it looks like this? - // K1USN-1>APWW10:T#479,100,048,002,500,000,10000000 - - // For now, keep it intact and see if it causes problems. Easy to remove like this: - // while (ax25_get_num_repeaters(pp3) > 0) { - // ax25_remove_addr (pp3, AX25_REPEATER_1); - // } - alevel_t alevel; memset (&alevel, 0, sizeof(alevel)); alevel.mark = -2; // FIXME: Do we want some other special case? @@ -1831,8 +1843,19 @@ static void maybe_xmit_packet_from_igate (char *message, int to_chan) * If we recently transmitted a 'message' from some station, * send the position of the message sender when it comes along later. * + * Some refer to this as a courtesy posit report but I don't + * think that is an official term. + * * If we have a position report, look up the sender and see if we should * bypass the normal filtering. + * + * Reference: https://www.aprs-is.net/IGating.aspx + * + * "Passing all message packets also includes passing the sending station's position + * along with the message. When APRS-IS was small, we did this using historical position + * packets. This has become problematic as it introduces historical data on to RF. + * The IGate should note the station(s) it has gated messages to RF for and pass + * the next position packet seen for that station(s) to RF." */ // TODO: Not quite this simple. Should have a function to check for position. diff --git a/src/kiss_frame.c b/src/kiss_frame.c index 862fb714..e304a838 100644 --- a/src/kiss_frame.c +++ b/src/kiss_frame.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2017 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 2017, 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 @@ -611,7 +611,8 @@ void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, struct /* Verify that the radio channel number is valid. */ /* Any sort of medium should be OK here. */ - if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { + if ((chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) + && save_audio_config_p->chan_medium[chan] != MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Invalid transmit channel %d from KISS client app.\n", chan); dw_printf ("\n"); diff --git a/src/tq.c b/src/tq.c index 3d1b056b..c656af54 100644 --- a/src/tq.c +++ b/src/tq.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2014, 2015, 2016, 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 @@ -50,7 +50,8 @@ #include "audio.h" #include "tq.h" #include "dedupe.h" - +#include "igate.h" +#include "dtime_now.h" @@ -195,6 +196,9 @@ void tq_init (struct audio_s *audio_config_p) * * Inputs: chan - Channel, 0 is first. * + * New in 1.7: + * Channel can be assigned to IGate rather than a radio. + * * prio - Priority, use TQ_PRIO_0_HI for digipeated or * TQ_PRIO_1_LO for normal. * @@ -247,6 +251,43 @@ void tq_append (int chan, int prio, packet_t pp) } #endif +// New in 1.7 - A channel can be assigned to the IGate rather than a radio. + +#ifndef DIGITEST // avoid dtest link error + + if (save_audio_config_p->chan_medium[chan] == MEDIUM_IGATE) { + + char ts[100]; // optional time stamp. + + if (strlen(save_audio_config_p->timestamp_format) > 0) { + char tstmp[100]; + timestamp_user_format (tstmp, sizeof(tstmp), save_audio_config_p->timestamp_format); + strlcpy (ts, " ", sizeof(ts)); // space after channel. + strlcat (ts, tstmp, sizeof(ts)); + } + else { + strlcpy (ts, "", sizeof(ts)); + } + + char stemp[256]; // Formated addresses. + ax25_format_addrs (pp, stemp); + unsigned char *pinfo; + int info_len = ax25_get_info (pp, &pinfo); + text_color_set(DW_COLOR_XMIT); + dw_printf ("[%d>is%s] ", chan, ts); + dw_printf ("%s", stemp); /* stations followed by : */ + ax25_safe_print ((char *)pinfo, info_len, ! ax25_is_aprs(pp)); + dw_printf ("\n"); + + igate_send_rec_packet (chan, pp); + ax25_delete(pp); + return; + } +#endif + +// Normal case - put in queue for radio transmission. +// Error if trying to transmit to a radio channel which was not configured. + if (chan < 0 || chan >= MAX_CHANS || save_audio_config_p->chan_medium[chan] == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Request to transmit on invalid radio channel %d.\n", chan); @@ -281,8 +322,6 @@ void tq_append (int chan, int prio, packet_t pp) * The check would allow an unlimited number of other types. * * Limit was 20. Changed to 100 in version 1.2 as a workaround. - * - * Implementing the 6PACK protocol is probably the proper solution. */ if (ax25_is_aprs(pp) && tq_count(chan,prio,"","",0) > 100) { From 8e289025d7b8a4b09efb383e2358594bef4d72a6 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Mon, 30 Jan 2023 03:03:30 +0000 Subject: [PATCH 06/29] Notes for future. --- src/decode_aprs.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/decode_aprs.c b/src/decode_aprs.c index e2c25248..c74f9396 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -858,6 +858,32 @@ static void aprs_ll_pos (decode_aprs_t *A, unsigned char *info, int ilen) strlcpy (A->g_data_type_desc, "Weather Report", sizeof(A->g_data_type_desc)); weather_data (A, p->comment, TRUE); +/* +Here is an interesting case. +The protocol spec states that a position report with symbol _ is a special case +and the information part must contain wxnow.txt format weather data. +But, here we see it being generated like a normal position report. + +N8VIM>BEACON,AB1OC-10*,WIDE2-1:!4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100<0x0d> +Didn't find wind direction in form c999. +Didn't find wind speed in form s999. +Didn't find wind gust in form g999. +Didn't find temperature in form t999. +Weather Report, WEATHER Station (blue) +N 42 40.8500, W 071 33.9900 +, "PHG72604/ Pepperell, MA. WX. 442.9+ PL100" + +It seems, to me, that this is a violation of the protocol spec. +Then, immediately following, we have a positionless weather report in Ultimeter format. + +N8VIM>APN391,AB1OC-10*,WIDE2-1:$ULTW006F00CA01421C52275800008A00000102FA000F04A6000B002A<0x0d><0x0a> +Ultimeter, Kantronics KPC-3 rom versions +wind 6.9 mph, direction 284, temperature 32.2, barometer 29.75, humidity 76 + +aprs.fi merges these two together. Is that anywhere in the protocol spec or +just a heuristic added after noticing a pair of packets like this? +*/ + } else { /* Regular position report. */ @@ -2372,6 +2398,20 @@ static void aprs_status_report (decode_aprs_t *A, char *info, int ilen) * *------------------------------------------------------------------*/ +/* +https://groups.io/g/direwolf/topic/95961245#7357 + +What APRS queries should DireWolf respond to? Well, it should be configurable whether it responds to queries at all, in case some other application is using DireWolf as a dumb TNC (KISS or AGWPE style) and wants to handle the queries itself. + +Assuming query responding is enabled, the following broadcast queries should be supported (if the corresponding data is configured in DireWolf): + +?APRS (I am an APRS station) +?IGATE (I am operating as a I-gate) +?WX (I am providing local weather data in my beacon) + +*/ + + static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quiet) { char *q2; @@ -2524,6 +2564,28 @@ static void aprs_general_query (decode_aprs_t *A, char *info, int ilen, int quie * *------------------------------------------------------------------*/ +/* +https://groups.io/g/direwolf/topic/95961245#7357 + +The following directed queries (sent as bodies of APRS text messages) would also be useful (if corresponding data configured): + +?APRSP (force my current beacon) +?APRST and ?PING (trace my path to requestor) +?APRSD (all stations directly heard [no digipeat hops] by local station) +?APRSO (any Objects/Items originated by this station) +?APRSH (how often or how many times the specified 3rd station was heard by the queried station) +?APRSS (immediately send the Status message if configured) (can DireWolf do Status messages?) + +Lynn KJ4ERJ and I have implemented a non-standard query which might be useful: + +?VER (send the human-readable software version of the queried station) + +Hope this is useful. It's just my $.02. + +Andrew, KA2DDO +author of YAAC +*/ + static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet) { //char query_type[20]; /* Does the query type always need to be exactly 5 characters? */ From 8cb73d2507313b54c274720f5fc4901445436d61 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 5 Mar 2023 15:04:31 +0000 Subject: [PATCH 07/29] More documentation references. --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0bf92daf..3006a1ef 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ Why waste $200 and settle for mediocre receive performance from a 1980's technol ![](tnc-test-cd-results.png) -Dire Wolf now includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless. This was originally developed for satellites and is now seeing widespread use on HF. +Dire Wolf includes [FX.25](https://en.wikipedia.org/wiki/FX.25_Forward_Error_Correction) which adds Forward Error Correction (FEC) in a way that is completely compatible with existing systems. If both ends are capable of FX.25, your information will continue to get through under conditions where regular AX.25 is completely useless. This was originally developed for satellites and is now seeing widespread use on HF. ![](fx25.png) -Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead. +Version 1.7 adds [IL2P](https://en.wikipedia.org/wiki/Improved_Layer_2_Protocol), a different method of FEC with less overhead but it is not compatible with AX.25. @@ -114,12 +114,16 @@ It can also be used as a virtual TNC for other applications such as [APRSIS32](h ## Documentation ## + [Stable Version](https://github.com/wb2osz/direwolf/tree/master/doc) -[Latest Development Version](https://github.com/wb2osz/direwolf/tree/dev/doc) +[Latest Development Version ("dev" branch)](https://github.com/wb2osz/direwolf/tree/dev/doc) + +[Additional Topics](https://github.com/wb2osz/direwolf-doc) -[Power Point presentation](https://github.com/wb2osz/direwolf-presentation) -- Why not give a talk at a local club meeting? +[Power Point presentations](https://github.com/wb2osz/direwolf-presentation) -- Why not give a talk at a local club meeting? +Youtube has many interesting and helpful videos. Searching for [direwolf tnc](https://www.youtube.com/results?search_query=direwolf+tnc) or [direwolf aprs](https://www.youtube.com/results?search_query=direwolf+aprs) will produce the most relevant results. ## Installation ## From 8a978876bd843f5b914196f7847d64cc877f1723 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 5 Mar 2023 15:06:07 +0000 Subject: [PATCH 08/29] Cleanup. --- conf/generic.conf | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/conf/generic.conf b/conf/generic.conf index 887dc229..d9f7b355 100644 --- a/conf/generic.conf +++ b/conf/generic.conf @@ -29,16 +29,21 @@ %C%# Extensive documentation can be found here: %C%# Stable release - https://github.com/wb2osz/direwolf/tree/master/doc %C%# Latest development - https://github.com/wb2osz/direwolf/tree/dev/doc +%C%# Additional topics - https://github.com/wb2osz/direwolf-doc %C%# -%W%# The complete documentation set can also be found in the doc folder. -%L%# The complete documentation set can also be found in +%W%# The basic documentation set can also be found in the doc folder. +%L%# The basic documentation set can also be found in %L%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ %L%# Concise "man" pages are also available for Linux. %M%# /usr/local/share/doc/direwolf/ or /usr/share/doc/direwolf/ %M%# Concise "man" pages are also available for Mac OSX. %C%# +%C%# Questions??? Join the discussion forum: https://groups.io/g/direwolf +%C%# +%C%# %C%# This sample file does not have examples for all of the possibilities. -%C%# Consult the User Guide for more details on configuration options.%C%# +%C%# Consult the User Guide for more details on configuration options +%C%# and other documents for more details for different uses. %C%# %C%# These are the most likely settings you might change: %C%# @@ -231,14 +236,14 @@ %C%# It can be up to 6 letters and digits with an optional ssid. %C%# The APRS specification requires that it be upper case. %C%# -%C%# Example (don't use this unless you are me): MYCALL WB2OSZ-5 +%C%# Example (don't use this unless you are me): MYCALL WB2OSZ-5 %C%# %C% %C%MYCALL N0CALL %C% %C%# %C%# Pick a suitable modem speed based on your situation. -%C%# 1200 Most common for VHF/UHF. Default if not specified. +%C%# 1200 Most common for VHF/UHF. This is the default if not specified. %C%# 2400 QPSK compatible with MFJ-2400, and probably PK232-2400 & KPC-2400. %C%# 300 Low speed for HF SSB. Default tones 1600 & 1800. %C%# EAS Emergency Alert System (EAS) Specific Area Message Encoding (SAME). @@ -263,6 +268,10 @@ %C% %C%#DTMF %C% +%C%# Push to Talk (PTT) can be confusing because there are so many different cases. +%C%# Radio-Interface-Guide.pdf in https://github.com/wb2osz/direwolf-doc +%C%# goes into detail about the various options. +%C% %L%# If using a C-Media CM108/CM119 or similar USB Audio Adapter, %L%# you can use a GPIO pin for PTT control. This is very convenient %L%# because a single USB connection is used for both audio and PTT. @@ -290,6 +299,7 @@ %C%#PTT COM1 RTS %C%#PTT COM1 RTS -DTR %L%#PTT /dev/ttyUSB0 RTS +%L%#PTT /dev/ttyUSB0 RTS -DTR %C% %L%# %L%# On Linux, you can also use general purpose I/O pins if @@ -301,7 +311,7 @@ %L% %L%#PTT GPIO 25 %L% -%C%# The Data Carrier Detect (DCD) signal can be sent to the same places +%C%# The Data Carrier Detect (DCD) signal can be sent to most of the same places %C%# as the PTT signal. This could be used to light up an LED like a normal TNC. %C% %C%#DCD COM1 -DTR @@ -374,7 +384,6 @@ %C%# %C%# 0 - Don't try to repair. %C%# 1 - Attempt to fix single bit error. (default) -%C%# ... see User Guide for more values and in-depth discussion. %C%# %C% %C%#FIX_BITS 0 @@ -399,15 +408,21 @@ %C%# Example: %C%# %C%# This results in a broadcast once every 10 minutes. -%C%# Every half hour, it can travel via two digipeater hops. +%C%# Every half hour, it can travel via one digipeater hop. %C%# The others are kept local. %C%# %C% -%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1,WIDE2-1 +%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" via=WIDE1-1 %C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" %C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W power=50 height=20 gain=4 comment="Chelmsford MA" %C% -%C% +%C%# +%C%# Did you know that APRS comments and messages can contain UTF-8 characters, not only plain ASCII? +%C%# +%C%#PBEACON delay=1 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xe0\xb8\xa7\xe0\xb8\xb4\xe0\xb8\x97\xe0\xb8\xa2\xe0\xb8\xb8\xe0\xb8\xaa\xe0\xb8\xa1\xe0\xb8\xb1\xe0\xb8\x84\xe0\xb8\xa3\xe0\xb9\x80\xe0\xb8\xa5\xe0\xb9\x88\xe0\xb8\x99" +%C%#PBEACON delay=11 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xce\xa1\xce\xb1\xce\xb4\xce\xb9\xce\xbf\xce\xb5\xcf\x81\xce\xb1\xcf\x83\xce\xb9\xcf\x84\xce\xb5\xcf\x87\xce\xbd\xce\xb9\xcf\x83\xce\xbc\xcf\x8c\xcf\x82" +%C%#PBEACON delay=21 every=30 overlay=S symbol="digi" lat=42^37.14N long=071^20.83W comment=" Did you know that APRS comments and messages can contain UTF-8 characters? \xe3\x82\xa2\xe3\x83\x9e\xe3\x83\x81\xe3\x83\xa5\xe3\x82\xa2\xe7\x84\xa1\xe7\xb7\x9a" +%C%# %C%# With UTM coordinates instead of latitude and longitude. %C% %C%#PBEACON delay=1 every=10 overlay=S symbol="digi" zone=19T easting=307477 northing=4720178 @@ -448,12 +463,6 @@ %C%# See User Guide for more explanation of what this means and how %C%# it can be customized for your particular needs. %C% -%C%# Filtering can be used to limit was is digipeated. -%C%# For example, only weather weather reports, received on channel 0, -%C%# will be retransmitted on channel 1. -%C%# -%C% -%C%#FILTER 0 1 t/wn %C% %C%# Traditional connected mode packet radio uses a different %C%# type of digipeating. See User Guide for details. @@ -487,6 +496,7 @@ %C%# without sending it over the air and relying on someone else to %C%# forward it to an IGate server. This is done by using sendto=IG rather %C%# than a radio channel number. Overlay R for receive only, T for two way. +%C%# There is no need to send it as often as you would over the radio. %C% %C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=R lat=42^37.14N long=071^20.83W %C%#PBEACON sendto=IG delay=0:30 every=60:00 symbol="igate" overlay=T lat=42^37.14N long=071^20.83W @@ -502,6 +512,7 @@ %C%# The IGate function will limit the number of packets transmitted %C%# during 1 minute and 5 minute intervals. If a limit would %C%# be exceeded, the packet is dropped and message is displayed in red. +%C%# This might be low for APRS Thursday when there is abnormally high activity. %C% %C%IGTXLIMIT 6 10 %C% From b4b7b1388db94e1c0f1470126edd1a75e9b92afc Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 5 Mar 2023 15:06:48 +0000 Subject: [PATCH 09/29] Improve error message. --- src/audio.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/audio.c b/src/audio.c index 5335db57..82dec22a 100644 --- a/src/audio.c +++ b/src/audio.c @@ -354,6 +354,10 @@ int audio_open (struct audio_s *pa) text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open audio device %s for input\n%s\n", audio_in_name, snd_strerror(err)); + if (err == -EBUSY) { + dw_printf ("This means that some other application is using that device.\n"); + dw_printf ("The solution is to identify that other application and stop it.\n"); + } return (-1); } @@ -459,6 +463,10 @@ int audio_open (struct audio_s *pa) text_color_set(DW_COLOR_ERROR); dw_printf ("Could not open audio device %s for output\n%s\n", audio_out_name, snd_strerror(err)); + if (err == -EBUSY) { + dw_printf ("This means that some other application is using that device.\n"); + dw_printf ("The solution is to identify that other application and stop it.\n"); + } return (-1); } From cac83f2506a480c69cd1339834d29c2b10d062a0 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 5 Mar 2023 15:07:46 +0000 Subject: [PATCH 10/29] Improve error message. --- src/decode_aprs.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/decode_aprs.c b/src/decode_aprs.c index d7cbd72f..8a729428 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -504,8 +504,10 @@ void decode_aprs_print (decode_aprs_t *A) { //dw_printf ("DEBUG decode_aprs_print stemp3=%s mfr=%s\n", stemp, A->g_mfr); if (strlen(A->g_mfr) > 0) { - if (strcmp(A->g_dest, "APRS") == 0) { - strlcat (stemp, "\nUse of \"APRS\" in the destination field is obsolete.", sizeof(stemp)); + if (strcmp(A->g_dest, "APRS") == 0 || strcmp(A->g_dest, "BEACON") == 0) { + strlcat (stemp, "\nUse of \"", sizeof(stemp)); + strlcat (stemp, A->g_dest, sizeof(stemp)); + strlcat (stemp, "\" in the destination field is obsolete.", sizeof(stemp)); strlcat (stemp, " You can help to improve the quality of APRS signals.", sizeof(stemp)); strlcat (stemp, "\nTell the sender (", sizeof(stemp)); strlcat (stemp, A->g_src, sizeof(stemp)); From dbb4777ba78c6abac5bde7f2e418b505de5f8bfc Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 5 Mar 2023 15:10:05 +0000 Subject: [PATCH 11/29] More comments. --- src/digipeater.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/digipeater.c b/src/digipeater.c index 006ee7b7..4daf1890 100644 --- a/src/digipeater.c +++ b/src/digipeater.c @@ -164,7 +164,34 @@ void digipeater (int from_chan, packet_t pp) /* * First pass: Look at packets being digipeated to same channel. * - * We want these to get out quickly. + * We want these to get out quickly, bypassing the usual random wait time. + * + * Some may disagree but I followed what WB4APR had to say about it. + * + * http://www.aprs.org/balloons.html + * + * APRS NETWORK FRATRICIDE: Generally, all APRS digipeaters are supposed to transmit + * immediately and all at the same time. They should NOT wait long enough for each + * one to QRM the channel with the same copy of each packet. NO, APRS digipeaters + * are all supposed to STEP ON EACH OTHER with every packet. This makes sure that + * everyone in range of a digi will hear one and only one copy of each packet. + * and that the packet will digipeat OUTWARD and not backward. The goal is that a + * digipeated packet is cleared out of the local area in ONE packet time and not + * N packet times for every N digipeaters that heard the packet. This means no + * PERSIST times, no DWAIT times and no UIDWAIT times. Notice, this is contrary + * to other packet systems that might want to guarantee delivery (but at the + * expense of throughput). APRS wants to clear the channel quickly to maximize throughput. + * + * http://www.aprs.org/kpc3/kpc3+WIDEn.txt + * + * THIRD: Eliminate the settings that are detrimental to the network. + * + * * UIDWAIT should be OFF. (the default). With it on, your digi is not doing the + * fundamental APRS fratricide that is the primary mechanism for minimizing channel + * loading. All digis that hear the same packet are supposed to DIGI it at the SAME + * time so that all those copies only take up one additional time slot. (but outward + * located digs will hear it without collision (and continue outward propagation) + * */ for (to_chan=0; to_chanfilter_str[from_chan][to_chan]); if (result != NULL) { dedupe_remember (pp, to_chan); - tq_append (to_chan, TQ_PRIO_0_HI, result); + tq_append (to_chan, TQ_PRIO_0_HI, result); // High priority queue. digi_count[from_chan][to_chan]++; } } @@ -207,7 +234,7 @@ void digipeater (int from_chan, packet_t pp) save_digi_config_p->filter_str[from_chan][to_chan]); if (result != NULL) { dedupe_remember (pp, to_chan); - tq_append (to_chan, TQ_PRIO_1_LO, result); + tq_append (to_chan, TQ_PRIO_1_LO, result); // Low priority queue. digi_count[from_chan][to_chan]++; } } From 7573f996c4677b938ac903368dec6ff1869dafc7 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 5 Mar 2023 15:11:00 +0000 Subject: [PATCH 12/29] Improve error message. --- src/direwolf.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/direwolf.c b/src/direwolf.c index f2aeca14..5157e8ef 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021 John Langner, WB2OSZ +// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 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 @@ -283,6 +283,8 @@ int main (int argc, char *argv[]) /* 1 = normal, 0 = no text colors. */ /* 2, 3, ... alternate escape sequences for different terminals. */ +// FIXME: consider case of no space between t and number. + for (j=1; j Date: Sun, 5 Mar 2023 15:12:49 +0000 Subject: [PATCH 13/29] Reduce noise. --- src/il2p_header.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/il2p_header.c b/src/il2p_header.c index 0ab34a01..94fd25ba 100644 --- a/src/il2p_header.c +++ b/src/il2p_header.c @@ -462,8 +462,11 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed) for (int i = 0; i < strlen(addrs[AX25_DESTINATION]); i++) { if (! isupper(addrs[AX25_DESTINATION][i]) && ! isdigit(addrs[AX25_DESTINATION][i])) { if (num_sym_changed == 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]); + // This can pop up sporadically when receiving random noise. + // Would be better to show only when debug is enabled but variable not available here. + // TODO: For now we will just suppress it. + //text_color_set(DW_COLOR_ERROR); + //dw_printf ("IL2P: Invalid character '%c' in destination address '%s'\n", addrs[AX25_DESTINATION][i], addrs[AX25_DESTINATION]); } return (NULL); } @@ -477,8 +480,11 @@ packet_t il2p_decode_header_type_1 (unsigned char *hdr, int num_sym_changed) for (int i = 0; i < strlen(addrs[AX25_SOURCE]); i++) { if (! isupper(addrs[AX25_SOURCE][i]) && ! isdigit(addrs[AX25_SOURCE][i])) { if (num_sym_changed == 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]); + // This can pop up sporadically when receiving random noise. + // Would be better to show only when debug is enabled but variable not available here. + // TODO: For now we will just suppress it. + //text_color_set(DW_COLOR_ERROR); + //dw_printf ("IL2P: Invalid character '%c' in source address '%s'\n", addrs[AX25_SOURCE][i], addrs[AX25_SOURCE]); } return (NULL); } From 11468f26f8c83491bb9782c8ca2e468fb181e9e7 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 5 Mar 2023 15:38:06 +0000 Subject: [PATCH 14/29] Improve error message. --- src/direwolf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/direwolf.c b/src/direwolf.c index 5157e8ef..6215e96b 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -383,7 +383,7 @@ int main (int argc, char *argv[]) #ifndef __WIN32__ if (getuid() == 0 || geteuid() == 0) { text_color_set(DW_COLOR_ERROR); - for (n=0; n<15; n++) { + for (int n=0; n<15; n++) { dw_printf ("\n"); dw_printf ("Dire Wolf requires only privileges available to ordinary users.\n"); dw_printf ("Running this as root is an unnecessary security risk.\n"); From 019ff3bda66611df3b545fe5d6534c2bf01b2bba Mon Sep 17 00:00:00 2001 From: wb2osz Date: Fri, 7 Apr 2023 14:14:32 -0400 Subject: [PATCH 15/29] Update config.c --- src/config.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 4f652939..a71d9e2c 100644 --- a/src/config.c +++ b/src/config.c @@ -2780,6 +2780,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (*t == 'i' || *t == 'I') { from_chan = MAX_CHANS; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: FILTER IG ... on line %d.\n", line); + dw_printf ("Warning! Don't mess with IS>RF filtering unless you are an expert and have an unusual situation.\n"); + dw_printf ("Warning! The default is fine for nearly all situations.\n"); + dw_printf ("Warning! Be sure to read carefully and understand Successful-APRS-Gateway-Operation.pdf .\n"); + dw_printf ("Warning! If you insist, be sure to add \" | i/180 \" so you don't break messaging.\n"); } else { from_chan = isdigit(*t) ? atoi(t) : -999; @@ -2813,6 +2819,12 @@ void config_init (char *fname, struct audio_s *p_audio_config, } if (*t == 'i' || *t == 'I') { to_chan = MAX_CHANS; + text_color_set(DW_COLOR_ERROR); + dw_printf ("Config file: FILTER ... IG ... on line %d.\n", line); + dw_printf ("Warning! Don't mess with RF>IS filtering unless you are an expert and have an unusual situation.\n"); + dw_printf ("Warning! Expected behavior is for everything to go from RF to IS.\n"); + dw_printf ("Warning! The default is fine for nearly all situations.\n"); + dw_printf ("Warning! Be sure to read carefully and understand Successful-APRS-Gateway-Operation.pdf .\n"); } else { to_chan = isdigit(*t) ? atoi(t) : -999; @@ -4578,7 +4590,6 @@ void config_init (char *fname, struct audio_s *p_audio_config, * * In version 1.2 we allow 0 to disable listening. */ -// FIXME: complain if extra parameter e.g. port as in KISSPORT else if (strcasecmp(t, "AGWPORT") == 0) { int n; @@ -4598,6 +4609,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Line %d: Invalid port number for AGW TCPIP Socket Interface. Using %d.\n", line, p_misc_config->agwpe_port); } + t = split(NULL,0); + if (t != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unexpected \"%s\" is ignored.\n", line, t); + dw_printf ("Perhaps you were trying to use feature available only with KISSPORT.\n"); + continue; + } } /* From 75ccf181f95e6e7f1014d4d5599de626d6f66023 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Fri, 7 Apr 2023 16:29:27 -0400 Subject: [PATCH 16/29] Improve config error checking. --- src/config.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config.c b/src/config.c index a71d9e2c..fdaa143f 100644 --- a/src/config.c +++ b/src/config.c @@ -4600,6 +4600,13 @@ void config_init (char *fname, struct audio_s *p_audio_config, continue; } n = atoi(t); + t = split(NULL,0); + if (t != NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Line %d: Unexpected \"%s\" after the port number.\n", line, t); + dw_printf ("Perhaps you were trying to use feature available only with KISSPORT.\n"); + continue; + } if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->agwpe_port = n; } @@ -4609,13 +4616,6 @@ void config_init (char *fname, struct audio_s *p_audio_config, dw_printf ("Line %d: Invalid port number for AGW TCPIP Socket Interface. Using %d.\n", line, p_misc_config->agwpe_port); } - t = split(NULL,0); - if (t != NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Line %d: Unexpected \"%s\" is ignored.\n", line, t); - dw_printf ("Perhaps you were trying to use feature available only with KISSPORT.\n"); - continue; - } } /* From 4c9f9596c05c88888f145780f7262d744443dfac Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Tue, 17 Jan 2023 00:51:40 -0600 Subject: [PATCH 17/29] Add -O option to redirect output to stderr. --- src/atest.c | 2 +- src/cm108.c | 4 ++-- src/decode_aprs.c | 2 +- src/direwolf.c | 18 +++++++++++++----- src/il2p_test.c | 4 ++-- src/kissutil.c | 2 +- src/textcolor.c | 18 ++++++++++++------ src/textcolor.h | 2 +- 8 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/atest.c b/src/atest.c index aec626f2..54a89b68 100644 --- a/src/atest.c +++ b/src/atest.c @@ -217,7 +217,7 @@ int main (int argc, char *argv[]) } #endif - text_color_init(1); + text_color_init(1, 0); text_color_set(DW_COLOR_INFO); /* diff --git a/src/cm108.c b/src/cm108.c index 8c8fc5ed..d55e8438 100644 --- a/src/cm108.c +++ b/src/cm108.c @@ -106,7 +106,7 @@ int main (void) { - text_color_init (0); // Turn off text color. + text_color_init (0, 0); // Turn off text color. #if defined(__OpenBSD__) || defined(__FreeBSD__) dw_printf ("CM108 PTT support is not available for this operating system.\n"); #else @@ -319,7 +319,7 @@ int main (int argc, char **argv) int num_things; int i; - text_color_init (0); // Turn off text color. + text_color_init (0, 0); // Turn off text color. text_color_set(DW_COLOR_INFO); if (argc >=2) { diff --git a/src/decode_aprs.c b/src/decode_aprs.c index 8a729428..10186fce 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -5116,7 +5116,7 @@ int main (int argc, char *argv[]) } // If you don't like the text colors, use 0 instead of 1 here. - text_color_init(1); + text_color_init(1, 0); text_color_set(DW_COLOR_INFO); while (fgets(stuff, sizeof(stuff), stdin) != NULL) diff --git a/src/direwolf.c b/src/direwolf.c index 6215e96b..056fbaee 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -241,6 +241,8 @@ int main (int argc, char *argv[]) char x_opt_mode = ' '; /* "-x N" option for transmitting calibration tones. */ int x_opt_chan = 0; /* Split into 2 parts. Mode e.g. m, a, and optional channel. */ + int O_opt = 0; /* Redirect text io to stderr for use with stdout audio */ + strlcpy(l_opt_logdir, "", sizeof(l_opt_logdir)); strlcpy(L_opt_logfile, "", sizeof(L_opt_logfile)); strlcpy(P_opt, "", sizeof(P_opt)); @@ -269,8 +271,8 @@ int main (int argc, char *argv[]) #endif /* - * Pre-scan the command line options for the text color option. - * We need to set this before any text output. + * Pre-scan the command line options for the text color and stdout redirect options. + * We need to set these before any text output. * Default will be no colors if stdout is not a terminal (i.e. piped into * something else such as "tee") but command line can override this. */ @@ -285,10 +287,12 @@ int main (int argc, char *argv[]) // FIXME: consider case of no space between t and number. - for (j=1; j MAX_T) { int t; for (t = 0; t <= MAX_T; t++) { - text_color_init (t); + text_color_init (t, redirect_output); printf ("-t %d", t); if (t) printf (" [white background] "); printf ("\n"); @@ -377,7 +383,7 @@ int dw_printf (const char *fmt, ...) // TODO: other possible destinations... - fputs (buffer, stdout); + fputs (buffer, g_dw_printf_dest); return (len); } @@ -387,7 +393,7 @@ int dw_printf (const char *fmt, ...) main () { printf ("Initial condition\n"); - text_color_init (1); + text_color_init (1, 0); printf ("After text_color_init\n"); text_color_set(DW_COLOR_INFO); printf ("Info\n"); text_color_set(DW_COLOR_ERROR); printf ("Error\n"); diff --git a/src/textcolor.h b/src/textcolor.h index 4e38c83e..93dfc6b1 100644 --- a/src/textcolor.h +++ b/src/textcolor.h @@ -22,7 +22,7 @@ enum dw_color_e { DW_COLOR_INFO, /* black */ typedef enum dw_color_e dw_color_t; -void text_color_init (int enable_color); +void text_color_init (int enable_color, int redirect_output); void text_color_set (dw_color_t c); void text_color_term (void); From 42eb9832d30bcb1282129dc48beb94574c2d2a5d Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:25:08 -0600 Subject: [PATCH 18/29] Add stdout option for audio output --- src/audio.c | 351 +++++++++++++++++++++++++++++----------------------- src/audio.h | 4 + 2 files changed, 199 insertions(+), 156 deletions(-) diff --git a/src/audio.c b/src/audio.c index 82dec22a..bfeb021d 100644 --- a/src/audio.c +++ b/src/audio.c @@ -129,6 +129,7 @@ static struct adev_s { int outbuf_len; enum audio_in_type_e g_audio_in_type; + enum audio_out_type_e g_audio_out_type; int udp_sock; /* UDP socket for receiving data */ @@ -453,52 +454,63 @@ int audio_open (struct audio_s *pa) } /* - * Output device. Only "soundcard" is supported at this time. + * Output device. Only "soundcard" and "stdout" are supported at this time. */ + if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { + adev[a].g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; + } else { + adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD; + } + + switch (adev[a].g_audio_out_type) { + case AUDIO_OUT_TYPE_STDOUT: + adev[a].outbuf_size_in_bytes = 1024; + break; + case AUDIO_OUT_TYPE_SOUNDCARD: #if USE_ALSA - err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0); + err = snd_pcm_open (&(adev[a].audio_out_handle), audio_out_name, SND_PCM_STREAM_PLAYBACK, 0); - if (err < 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device %s for output\n%s\n", - audio_out_name, snd_strerror(err)); - if (err == -EBUSY) { - dw_printf ("This means that some other application is using that device.\n"); - dw_printf ("The solution is to identify that other application and stop it.\n"); - } - return (-1); - } + if (err < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not open audio device %s for output\n%s\n", + audio_out_name, snd_strerror(err)); + if (err == -EBUSY) { + dw_printf ("This means that some other application is using that device.\n"); + dw_printf ("The solution is to identify that other application and stop it.\n"); + } + return (-1); + } - adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output"); + adev[a].outbuf_size_in_bytes = set_alsa_params (a, adev[a].audio_out_handle, pa, audio_out_name, "output"); - if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { - return (-1); - } + if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { + return (-1); + } #elif USE_SNDIO - adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0); - if (adev[a].sndio_out_handle == NULL) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device %s for output\n", - audio_out_name); - return (-1); - } + adev[a].sndio_out_handle = sio_open (audio_out_name, SIO_PLAY, 0); + if (adev[a].sndio_out_handle == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not open audio device %s for output\n", + audio_out_name); + return (-1); + } - adev[a].outbuf_size_in_bytes = set_sndio_params (a, adev[a].sndio_out_handle, pa, audio_out_name, "output"); + adev[a].outbuf_size_in_bytes = set_sndio_params (a, adev[a].sndio_out_handle, pa, audio_out_name, "output"); - if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { - return (-1); - } + if (adev[a].inbuf_size_in_bytes <= 0 || adev[a].outbuf_size_in_bytes <= 0) { + return (-1); + } - if (!sio_start (adev[a].sndio_out_handle)) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not start audio device %s for output\n", - audio_out_name); - return (-1); - } + if (!sio_start (adev[a].sndio_out_handle)) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not start audio device %s for output\n", + audio_out_name); + return (-1); + } #endif - + } /* * Finally allocate buffer for each direction. */ @@ -1333,13 +1345,36 @@ int audio_put (int a, int c) int audio_flush (int a) { + switch (adev[a].g_audio_out_type) { + case AUDIO_OUT_TYPE_STDOUT:; + int res; + unsigned char *ptr; + int len; + + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; + + while (len > 0) { + res = write(STDOUT_FILENO, ptr, (size_t) len); + if (res <= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nError writing to stdout. Exiting.\n"); + exit (0); + } + ptr += res; + len -= res; + } + adev[a].outbuf_len = 0; + return 0; + + case AUDIO_OUT_TYPE_SOUNDCARD:; #if USE_ALSA - int k; - unsigned char *psound; - int retries = 10; - snd_pcm_status_t *status; + int k; + unsigned char *psound; + int retries = 10; + snd_pcm_status_t *status; - assert (adev[a].audio_out_handle != NULL); + assert (adev[a].audio_out_handle != NULL); /* @@ -1352,159 +1387,160 @@ int audio_flush (int a) */ - snd_pcm_status_alloca(&status); + snd_pcm_status_alloca(&status); - k = snd_pcm_status (adev[a].audio_out_handle, status); - if (k != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k)); - } + k = snd_pcm_status (adev[a].audio_out_handle, status); + if (k != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output get status error.\n%s\n", snd_strerror(k)); + } - if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) { + if ((k = snd_pcm_status_get_state(status)) != SND_PCM_STATE_RUNNING) { - //text_color_set(DW_COLOR_DEBUG); - //dw_printf ("Audio output state = %d. Try to start.\n", k); + //text_color_set(DW_COLOR_DEBUG); + //dw_printf ("Audio output state = %d. Try to start.\n", k); - k = snd_pcm_prepare (adev[a].audio_out_handle); + k = snd_pcm_prepare (adev[a].audio_out_handle); - if (k != 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio output start error.\n%s\n", snd_strerror(k)); - } - } + if (k != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output start error.\n%s\n", snd_strerror(k)); + } + } - psound = adev[a].outbuf_ptr; + psound = adev[a].outbuf_ptr; - while (retries-- > 0) { + while (retries-- > 0) { - k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame); + k = snd_pcm_writei (adev[a].audio_out_handle, psound, adev[a].outbuf_len / adev[a].bytes_per_frame); #if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n", - adev[a].outbuf_len / adev[a].bytes_per_frame, k); - fflush (stdout); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_flush(): snd_pcm_writei %d frames returns %d\n", + adev[a].outbuf_len / adev[a].bytes_per_frame, k); + fflush (stdout); #endif - if (k == -EPIPE) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio output data underrun.\n"); + if (k == -EPIPE) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio output data underrun.\n"); - /* No problemo. Recover and go around again. */ + /* No problemo. Recover and go around again. */ - snd_pcm_recover (adev[a].audio_out_handle, k, 1); - } - else if (k == -ESTRPIPE) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Driver suspended, recovering\n"); - snd_pcm_recover(adev[a].audio_out_handle, k, 1); - } - else if (k == -EBADFD) { - k = snd_pcm_prepare (adev[a].audio_out_handle); - if(k < 0) { - dw_printf ("Error preparing after bad state: %s\n", snd_strerror(k)); - } - } - else if (k < 0) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio write error: %s\n", snd_strerror(k)); + snd_pcm_recover (adev[a].audio_out_handle, k, 1); + } + else if (k == -ESTRPIPE) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Driver suspended, recovering\n"); + snd_pcm_recover(adev[a].audio_out_handle, k, 1); + } + else if (k == -EBADFD) { + k = snd_pcm_prepare (adev[a].audio_out_handle); + if(k < 0) { + dw_printf ("Error preparing after bad state: %s\n", snd_strerror(k)); + } + } + else if (k < 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio write error: %s\n", snd_strerror(k)); - /* Some other error condition. */ - /* Try again. What do we have to lose? */ + /* Some other error condition. */ + /* Try again. What do we have to lose? */ - k = snd_pcm_prepare (adev[a].audio_out_handle); - if(k < 0) { - dw_printf ("Error preparing after error: %s\n", snd_strerror(k)); - } - } - else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio write took %d frames rather than %d.\n", - k, adev[a].outbuf_len / adev[a].bytes_per_frame); - - /* Go around again with the rest of it. */ + k = snd_pcm_prepare (adev[a].audio_out_handle); + if(k < 0) { + dw_printf ("Error preparing after error: %s\n", snd_strerror(k)); + } + } + else if (k != adev[a].outbuf_len / adev[a].bytes_per_frame) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio write took %d frames rather than %d.\n", + k, adev[a].outbuf_len / adev[a].bytes_per_frame); - psound += k * adev[a].bytes_per_frame; - adev[a].outbuf_len -= k * adev[a].bytes_per_frame; - } - else { - /* Success! */ - adev[a].outbuf_len = 0; - return (0); - } - } + /* Go around again with the rest of it. */ + + psound += k * adev[a].bytes_per_frame; + adev[a].outbuf_len -= k * adev[a].bytes_per_frame; + } + else { + /* Success! */ + adev[a].outbuf_len = 0; + return (0); + } + } - text_color_set(DW_COLOR_ERROR); - dw_printf ("Audio write error retry count exceeded.\n"); + text_color_set(DW_COLOR_ERROR); + dw_printf ("Audio write error retry count exceeded.\n"); - adev[a].outbuf_len = 0; - return (-1); + adev[a].outbuf_len = 0; + return (-1); #elif USE_SNDIO - int k; - unsigned char *ptr; - int len; + int k; + unsigned char *ptr; + int len; - ptr = adev[a].outbuf_ptr; - len = adev[a].outbuf_len; + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; - while (len > 0) { - assert (adev[a].sndio_out_handle != NULL); - if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) { - text_color_set(DW_COLOR_ERROR); - perror("Can't write to audio device"); - adev[a].outbuf_len = 0; - return (-1); - } + while (len > 0) { + assert (adev[a].sndio_out_handle != NULL); + if (poll_sndio (adev[a].sndio_out_handle, POLLOUT) < 0) { + text_color_set(DW_COLOR_ERROR); + perror("Can't write to audio device"); + adev[a].outbuf_len = 0; + return (-1); + } - k = sio_write (adev[a].sndio_out_handle, ptr, len); + k = sio_write (adev[a].sndio_out_handle, ptr, len); #if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_flush(): write %d returns %d\n", len, k); - fflush (stdout); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_flush(): write %d returns %d\n", len, k); + fflush (stdout); #endif - ptr += k; - len -= k; - } + ptr += k; + len -= k; + } - adev[a].outbuf_len = 0; - return (0); + adev[a].outbuf_len = 0; + return (0); #else /* OSS */ - int k; - unsigned char *ptr; - int len; + int k; + unsigned char *ptr; + int len; - ptr = adev[a].outbuf_ptr; - len = adev[a].outbuf_len; + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; - while (len > 0) { - assert (adev[a].oss_audio_device_fd > 0); - k = write (adev[a].oss_audio_device_fd, ptr, len); + while (len > 0) { + assert (adev[a].oss_audio_device_fd > 0); + k = write (adev[a].oss_audio_device_fd, ptr, len); #if DEBUGx - text_color_set(DW_COLOR_DEBUG); - dw_printf ("audio_flush(): write %d returns %d\n", len, k); - fflush (stdout); + text_color_set(DW_COLOR_DEBUG); + dw_printf ("audio_flush(): write %d returns %d\n", len, k); + fflush (stdout); #endif - if (k < 0) { - text_color_set(DW_COLOR_ERROR); - perror("Can't write to audio device"); + if (k < 0) { + text_color_set(DW_COLOR_ERROR); + perror("Can't write to audio device"); + adev[a].outbuf_len = 0; + return (-1); + } + if (k < len) { + /* presumably full but didn't block. */ + usleep (10000); + } + ptr += k; + len -= k; + } + adev[a].outbuf_len = 0; - return (-1); - } - if (k < len) { - /* presumably full but didn't block. */ - usleep (10000); - } - ptr += k; - len -= k; + return (0); +#endif } - - adev[a].outbuf_len = 0; return (0); -#endif - } /* end audio_flush */ @@ -1546,9 +1582,12 @@ int audio_flush (int a) void audio_wait (int a) { - audio_flush (a); + if (adev[a].g_audio_out_type == AUDIO_OUT_TYPE_STDOUT) { + return; + } + #if USE_ALSA /* For playback, this should wait for all pending frames */ diff --git a/src/audio.h b/src/audio.h index 87d6c9c7..c4c3b576 100644 --- a/src/audio.h +++ b/src/audio.h @@ -43,6 +43,10 @@ enum audio_in_type_e { AUDIO_IN_TYPE_SDR_UDP, AUDIO_IN_TYPE_STDIN }; +enum audio_out_type_e { + AUDIO_OUT_TYPE_SOUNDCARD, + AUDIO_OUT_TYPE_STDOUT }; + /* For option to try fixing frames with bad CRC. */ typedef enum retry_e { From 818eb475ee4e7e554f2917ff04100151d82f9cf9 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Tue, 17 Jan 2023 18:06:22 -0600 Subject: [PATCH 19/29] Add stdout audio for Windows --- src/audio_win.c | 264 +++++++++++++++++++++++++++++++++--------------- src/textcolor.c | 1 + 2 files changed, 185 insertions(+), 80 deletions(-) diff --git a/src/audio_win.c b/src/audio_win.c index 85a1548b..805ded10 100644 --- a/src/audio_win.c +++ b/src/audio_win.c @@ -133,8 +133,8 @@ static int calcbufsize(int rate, int chans, int bits) static struct adev_s { - enum audio_in_type_e g_audio_in_type; - + enum audio_in_type_e g_audio_in_type; + enum audio_out_type_e g_audio_out_type; /* * UDP socket for receiving audio stream. * Buffer, length, and pointer for UDP or stdin. @@ -146,6 +146,12 @@ static struct adev_s { int stream_len; int stream_next; +/* + * Buffer and index for stdout. + */ + + unsigned char stream_out_data[SDR_UDP_BUF_MAXLEN]; + int stream_out_next; /* For sound output. */ /* out_wavehdr.dwUser is used to keep track of output buffer state. */ @@ -343,28 +349,36 @@ int audio_open (struct audio_s *pa) /* * Select output device. - * Only soundcard at this point. + * Only soundcard and stdout at this point. * Purhaps we'd like to add UDP for an SDR transmitter. */ - if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) { - out_dev_no[a] = atoi(pa->adev[a].adevice_out); - } - else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) { - out_dev_no[a] = atoi(pa->adev[a].adevice_out); - } + if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { + A->g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; + /* Change - to stdout for readability. */ + strlcpy (pa->adev[a].adevice_out, "stdout", sizeof(pa->adev[a].adevice_out)); + } else { + A->g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD; + + if (strlen(pa->adev[a].adevice_out) == 1 && isdigit(pa->adev[a].adevice_out[0])) { + out_dev_no[a] = atoi(pa->adev[a].adevice_out); + } + else if (strlen(pa->adev[a].adevice_out) == 2 && isdigit(pa->adev[a].adevice_out[0]) && isdigit(pa->adev[a].adevice_out[1])) { + out_dev_no[a] = atoi(pa->adev[a].adevice_out); + } - if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { - num_devices = waveOutGetNumDevs(); - for (n=0 ; nadev[a].adevice_out) != NULL) { - out_dev_no[a] = n; + if ((UINT)(out_dev_no[a]) == WAVE_MAPPER && strlen(pa->adev[a].adevice_out) >= 1) { + num_devices = waveOutGetNumDevs(); + for (n=0 ; nadev[a].adevice_out) != NULL) { + out_dev_no[a] = n; + } } } - } - if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out); + if ((UINT)(out_dev_no[a]) == WAVE_MAPPER) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\"%s\" doesn't match any of the output devices.\n", pa->adev[a].adevice_out); + } } } } /* if defined */ @@ -424,7 +438,7 @@ int audio_open (struct audio_s *pa) struct adev_s *A = &(adev[a]); - /* Display stdin or udp:port if appropriate. */ + /* Display stdin or udp:port if appropriate. */ if (A->g_audio_in_type != AUDIO_IN_TYPE_SOUNDCARD) { @@ -498,6 +512,36 @@ int audio_open (struct audio_s *pa) } } +// Add UDP or stdout to end of device list if used. + + for (a=0; aadev[a].defined) { + + struct adev_s *A = &(adev[a]); + + /* Display stdin or udp:port if appropriate. */ + + if (A->g_audio_out_type != AUDIO_OUT_TYPE_SOUNDCARD) { + + int aaa; + for (aaa=0; aaaadev[aaa].defined) { + dw_printf (" %c", a == aaa ? '*' : ' '); + + } + } + dw_printf (" %s ", pa->adev[a].adevice_out); /* should be UDP:nnnn or stdout */ + + if (pa->adev[a].num_channels == 2) { + dw_printf (" (channels %d & %d)", ADEVFIRSTCHAN(a), ADEVFIRSTCHAN(a)+1); + } + else { + dw_printf (" (channel %d)", ADEVFIRSTCHAN(a)); + } + dw_printf ("\n"); + } + } + } /* * Open for each audio device input/output pair. @@ -523,32 +567,47 @@ int audio_open (struct audio_s *pa) /* * Open the audio output device. - * Soundcard is only possibility at this time. + * Soundcard and stdout are only possibility at this time. */ - err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION); - if (err != MMSYSERR_NOERROR) { - text_color_set(DW_COLOR_ERROR); - dw_printf ("Could not open audio device for output.\n"); - return (-1); - } - + switch (A->g_audio_out_type) { + + case AUDIO_OUT_TYPE_SOUNDCARD: + err = waveOutOpen (&(A->audio_out_handle), out_dev_no[a], &wf, (DWORD_PTR)out_callback, a, CALLBACK_FUNCTION); + if (err != MMSYSERR_NOERROR) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Could not open audio device for output.\n"); + return (-1); + } + break; /* * Set up the output buffers. * We use dwUser to indicate it is available for filling. */ - memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr)); + memset ((void*)(A->out_wavehdr), 0, sizeof(A->out_wavehdr)); - for (n = 0; n < NUM_OUT_BUF; n++) { - A->out_wavehdr[n].lpData = malloc(A->outbuf_size); - A->out_wavehdr[n].dwUser = DWU_FILLING; - A->out_wavehdr[n].dwBufferLength = 0; - } - A->out_current = 0; + for (n = 0; n < NUM_OUT_BUF; n++) { + A->out_wavehdr[n].lpData = malloc(A->outbuf_size); + A->out_wavehdr[n].dwUser = DWU_FILLING; + A->out_wavehdr[n].dwBufferLength = 0; + } + A->out_current = 0; + + case AUDIO_OUT_TYPE_STDOUT: + + setmode (STDOUT_FILENO, _O_BINARY); + A->stream_out_next= 0; + break; + + default: + + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error, invalid audio_out_type\n"); + return (-1); + } - /* * Open audio input device. * More possibilities here: soundcard, UDP port, stdin. @@ -942,49 +1001,67 @@ int audio_put (int a, int c) struct adev_s *A; A = &(adev[a]); - + + switch (A->g_audio_out_type) { + + case AUDIO_OUT_TYPE_SOUNDCARD: + /* * Wait if no buffers are available. * Don't use p yet because compiler might might consider dwFlags a loop invariant. */ - int timeout = 10; - while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) { - SLEEP_MS (ONE_BUF_TIME); - timeout--; - if (timeout <= 0) { - text_color_set(DW_COLOR_ERROR); + int timeout = 10; + while ( A->out_wavehdr[A->out_current].dwUser == DWU_PLAYING) { + SLEEP_MS (ONE_BUF_TIME); + timeout--; + if (timeout <= 0) { + text_color_set(DW_COLOR_ERROR); // TODO: open issues 78 & 165. How can we avoid/improve this? - dw_printf ("Audio output failure waiting for buffer.\n"); - dw_printf ("This can occur when we are producing audio output for\n"); - dw_printf ("transmit and the operating system doesn't provide buffer\n"); - dw_printf ("space after waiting and retrying many times.\n"); - //dw_printf ("In recent years, this has been reported only when running the\n"); - //dw_printf ("Windows version with VMWare on a Macintosh.\n"); - ptt_term (); - return (-1); - } - } + dw_printf ("Audio output failure waiting for buffer.\n"); + dw_printf ("This can occur when we are producing audio output for\n"); + dw_printf ("transmit and the operating system doesn't provide buffer\n"); + dw_printf ("space after waiting and retrying many times.\n"); + //dw_printf ("In recent years, this has been reported only when running the\n"); + //dw_printf ("Windows version with VMWare on a Macintosh.\n"); + ptt_term (); + return (-1); + } + } - p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); + p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); - if (p->dwUser == DWU_DONE) { - waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR)); - p->dwBufferLength = 0; - p->dwUser = DWU_FILLING; - } + if (p->dwUser == DWU_DONE) { + waveOutUnprepareHeader (A->audio_out_handle, p, sizeof(WAVEHDR)); + p->dwBufferLength = 0; + p->dwUser = DWU_FILLING; + } + + /* Should never be full at this point. */ - /* Should never be full at this point. */ + assert (p->dwBufferLength >= 0); + assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); - assert (p->dwBufferLength >= 0); - assert (p->dwBufferLength < (DWORD)(A->outbuf_size)); + p->lpData[p->dwBufferLength++] = c; - p->lpData[p->dwBufferLength++] = c; + if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { + return (audio_flush(a)); + } + break; - if (p->dwBufferLength == (DWORD)(A->outbuf_size)) { - return (audio_flush(a)); + case AUDIO_OUT_TYPE_STDOUT: + + A->stream_out_data[A->stream_out_next++] = c; + + assert(A->stream_out_next > 0); + assert(A->stream_out_next <= SDR_UDP_BUF_MAXLEN); + + if (A->stream_out_next == SDR_UDP_BUF_MAXLEN) { + return (audio_flush(a)); + } + break; } return (0); @@ -1015,27 +1092,54 @@ int audio_flush (int a) struct adev_s *A; A = &(adev[a]); - - p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); - if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) { + switch (A->g_audio_out_type) { + case AUDIO_OUT_TYPE_SOUNDCARD: + p = (LPWAVEHDR)(&(A->out_wavehdr[A->out_current])); - p->dwUser = DWU_PLAYING; + if (p->dwUser == DWU_FILLING && p->dwBufferLength > 0) { - waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR)); + p->dwUser = DWU_PLAYING; - e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR)); - if (e != MMSYSERR_NOERROR) { - text_color_set (DW_COLOR_ERROR); - dw_printf ("audio out write error %d\n", e); + waveOutPrepareHeader(A->audio_out_handle, p, sizeof(WAVEHDR)); - /* I don't expect this to ever happen but if it */ - /* does, make the buffer available for filling. */ + e = waveOutWrite(A->audio_out_handle, p, sizeof(WAVEHDR)); + if (e != MMSYSERR_NOERROR) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("audio out write error %d\n", e); - p->dwUser = DWU_DONE; - return (-1); - } - A->out_current = (A->out_current + 1) % NUM_OUT_BUF; + /* I don't expect this to ever happen but if it */ + /* does, make the buffer available for filling. */ + + p->dwUser = DWU_DONE; + return (-1); + } + A->out_current = (A->out_current + 1) % NUM_OUT_BUF; + } + break; + + case AUDIO_OUT_TYPE_STDOUT:; + + int res; + unsigned char *ptr; + unsigned int len; + + ptr = A->stream_out_data; + len = A->stream_out_next; + + while (len > 0) { + res = write(STDOUT_FILENO, ptr, len); + if (res < 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("stdout audio write error %d\n", res); + return (-1); + } + ptr += res; + len -= res; + } + + A->stream_out_next = 0; + break; } return (0); diff --git a/src/textcolor.c b/src/textcolor.c index e21ae1ed..06a3b3ea 100644 --- a/src/textcolor.c +++ b/src/textcolor.c @@ -384,6 +384,7 @@ int dw_printf (const char *fmt, ...) // TODO: other possible destinations... fputs (buffer, g_dw_printf_dest); + fflush (g_dw_printf_dest); return (len); } From 953e8a21783496f829b1a6642f45b698ceac30c5 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Wed, 18 Jan 2023 00:05:09 -0600 Subject: [PATCH 20/29] Document -O dw_printf redirect option in manpage --- man/direwolf.1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/man/direwolf.1 b/man/direwolf.1 index 93f786dc..aee9ee2b 100644 --- a/man/direwolf.1 +++ b/man/direwolf.1 @@ -155,6 +155,9 @@ x = Silence FX.25 information. .BI "-t " "n" Text colors. 0=disabled. 1=default. 2,3,4,... alternatives. Use 9 to test compatibility with your terminal. +.TP +.BI "-O " +Redirects all printed output to stderr so stdout can be used as audio device. .TP .B "-p " From 94a054862a49219c9f65988a836c3d03338c41ae Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Wed, 18 Jan 2023 00:09:45 -0600 Subject: [PATCH 21/29] Add check to ensure -O is used with stdout --- src/audio.c | 5 +++++ src/audio_win.c | 5 +++++ src/textcolor.c | 5 ++++- src/textcolor.h | 2 ++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/audio.c b/src/audio.c index bfeb021d..1884d5ca 100644 --- a/src/audio.c +++ b/src/audio.c @@ -458,6 +458,11 @@ int audio_open (struct audio_s *pa) */ if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { adev[a].g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; + if (!dw_printf_redirected()) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("stdout must only be used with the -O option\n"); + return (-1); + } } else { adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD; } diff --git a/src/audio_win.c b/src/audio_win.c index 805ded10..22346e20 100644 --- a/src/audio_win.c +++ b/src/audio_win.c @@ -354,6 +354,11 @@ int audio_open (struct audio_s *pa) */ if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { A->g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; + if (!dw_printf_redirected()) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("stdout must only be used with the -O option\n"); + return (-1); + } /* Change - to stdout for readability. */ strlcpy (pa->adev[a].adevice_out, "stdout", sizeof(pa->adev[a].adevice_out)); } else { diff --git a/src/textcolor.c b/src/textcolor.c index 06a3b3ea..b05c21d1 100644 --- a/src/textcolor.c +++ b/src/textcolor.c @@ -388,7 +388,10 @@ int dw_printf (const char *fmt, ...) return (len); } - +int dw_printf_redirected () +{ + return g_dw_printf_dest != stdout; +} #if TESTC main () diff --git a/src/textcolor.h b/src/textcolor.h index 93dfc6b1..123430e5 100644 --- a/src/textcolor.h +++ b/src/textcolor.h @@ -55,4 +55,6 @@ int dw_printf (const char *fmt, ...) __attribute__((format(printf,1,2))); /* gnu C lib. */ #endif +int dw_printf_redirected (); + #endif From 2c985c08fc19e2cec3b0931b13c32303c6769623 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Wed, 18 Jan 2023 16:17:02 -0600 Subject: [PATCH 22/29] Update config file examples to mention stdout --- conf/generic.conf | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/conf/generic.conf b/conf/generic.conf index d9f7b355..5b09abed 100644 --- a/conf/generic.conf +++ b/conf/generic.conf @@ -132,10 +132,12 @@ %W%# "stdin" is not an audio device. Don't use this unless you %W%# understand what this means. Read the User Guide. %W%# You can also specify "UDP:" and an optional port for input. -%W%# Something different must be specified for output. +%W%# "-" or "stdout" can be used to pipe audio out to another application. +%W%# The -O option must be specified on the command line to support this. %W% %W%# ADEVICE stdin 0 %W%# ADEVICE UDP:7355 0 +%W%# ADEVICE stdin stdout %W% %W%# The position in the list can change when devices (e.g. USB) are added and removed. %W%# You can also specify devices by using part of the name. @@ -158,10 +160,12 @@ %L%# "stdin" is not an audio device. Don't use this unless you %L%# understand what this means. Read the User Guide. %L%# You can also specify "UDP:" and an optional port for input. -%L%# Something different must be specified for output. +%L%# "-" or "stdout" can be used to pipe audio out to another application. +%L%# The -O option must be specified on the command line to support this. %L% %L%# ADEVICE stdin plughw:1,0 %L%# ADEVICE UDP:7355 default +%L%# ADEVICE stdin stdout %L% %R% ---------- Mac ---------- %R% @@ -183,9 +187,11 @@ %M%# "stdin" is not an audio device. Don't use this unless you %M%# understand what this means. Read the User Guide. %M%# You can also specify "UDP:" and an optional port for input. -%M%# Something different must be specified for output. +%M%# "-" or "stdout" can be used to pipe audio out to another application. +%M%# The -O option must be specified on the command line to support this. %M% %M%# ADEVICE UDP:7355 default +%M%# ADEVICE stdin stdout %M%# %C% %C%# @@ -607,4 +613,4 @@ %C%#TTERR NO_CALL SPEECH No call or object name. %C%#TTERR SATSQ SPEECH Satellite square must be 4 digits. %C%#TTERR SUFFIX_NO_CALL SPEECH Send full call before using suffix. -%C% \ No newline at end of file +%C% From 7ae584dd284c94cae23e0e7634f1a97d47ba95eb Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Wed, 18 Jan 2023 16:54:34 -0600 Subject: [PATCH 23/29] Allow color to be used when stdout is redirected with -O --- src/direwolf.c | 11 ++++++---- src/textcolor.c | 54 ++++++++++++++++++++++++++++--------------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/direwolf.c b/src/direwolf.c index 056fbaee..5712a6f7 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -276,11 +276,16 @@ int main (int argc, char *argv[]) * Default will be no colors if stdout is not a terminal (i.e. piped into * something else such as "tee") but command line can override this. */ + for (j=1; j 0; + t_opt = _isatty(_fileno(O_opt ? stderr : stdout)) > 0; #else - t_opt = isatty(fileno(stdout)); + t_opt = isatty(fileno(O_opt ? stderr : stdout)); #endif /* 1 = normal, 0 = no text colors. */ /* 2, 3, ... alternate escape sequences for different terminals. */ @@ -291,8 +296,6 @@ int main (int argc, char *argv[]) if (strcmp(argv[j], "-t") == 0) { t_opt = atoi (argv[j+1]); //dw_printf ("DEBUG: text color option = %d.\n", t_opt); - } else if (strcmp(argv[j], "-O") == 0) { - O_opt = 1; } } diff --git a/src/textcolor.c b/src/textcolor.c index b05c21d1..453d8287 100644 --- a/src/textcolor.c +++ b/src/textcolor.c @@ -177,7 +177,6 @@ void text_color_init (int enable_color, int redirect_output) { if (redirect_output != 0) { g_dw_printf_dest = stderr; - enable_color = 0; } else { g_dw_printf_dest = stdout; } @@ -195,7 +194,12 @@ void text_color_init (int enable_color, int redirect_output) COORD coord; DWORD nwritten; - h = GetStdHandle(STD_OUTPUT_HANDLE); + if (redirect_output != 0) { + h = GetStdHandle(STD_ERROR_HANDLE); + } else { + h = GetStdHandle(STD_OUTPUT_HANDLE); + } + if (h != NULL && h != INVALID_HANDLE_VALUE) { GetConsoleScreenBufferInfo (h, &csbi); @@ -215,17 +219,17 @@ void text_color_init (int enable_color, int redirect_output) int t; for (t = 0; t <= MAX_T; t++) { text_color_init (t, redirect_output); - printf ("-t %d", t); - if (t) printf (" [white background] "); - printf ("\n"); - printf ("%sBlack ", t_black[t]); - printf ("%sRed ", t_red[t]); - printf ("%sGreen ", t_green[t]); - printf ("%sDark-Green ", t_dark_green[t]); - printf ("%sYellow ", t_yellow[t]); - printf ("%sBlue ", t_blue[t]); - printf ("%sMagenta ", t_magenta[t]); - printf ("%sCyan \n", t_cyan[t]); + fprintf (g_dw_printf_dest,"-t %d", t); + if (t) fprintf (g_dw_printf_dest, " [white background] "); + fprintf (g_dw_printf_dest,"\n"); + fprintf (g_dw_printf_dest,"%sBlack ", t_black[t]); + fprintf (g_dw_printf_dest,"%sRed ", t_red[t]); + fprintf (g_dw_printf_dest,"%sGreen ", t_green[t]); + fprintf (g_dw_printf_dest,"%sDark-Green ", t_dark_green[t]); + fprintf (g_dw_printf_dest,"%sYellow ", t_yellow[t]); + fprintf (g_dw_printf_dest,"%sBlue ", t_blue[t]); + fprintf (g_dw_printf_dest, "%sMagenta ", t_magenta[t]); + fprintf (g_dw_printf_dest, "%sCyan \n", t_cyan[t]); } exit (EXIT_SUCCESS); } @@ -238,9 +242,9 @@ void text_color_init (int enable_color, int redirect_output) if (t < 0) t = 0; if (t > MAX_T) t = MAX_T; - printf ("%s", t_background_white[t]); - printf ("%s", clear_eos); - printf ("%s", t_black[t]); + fprintf (g_dw_printf_dest, "%s", t_background_white[t]); + fprintf (g_dw_printf_dest, "%s", clear_eos); + fprintf (g_dw_printf_dest, "%s", t_black[t]); } #endif } @@ -291,7 +295,11 @@ void text_color_set ( enum dw_color_e c ) break; } - h = GetStdHandle(STD_OUTPUT_HANDLE); + if (dw_printf_redirected()) { + h = GetStdHandle(STD_ERROR_HANDLE); + } else { + h = GetStdHandle(STD_OUTPUT_HANDLE); + } if (h != NULL && h != INVALID_HANDLE_VALUE) { SetConsoleTextAttribute (h, attr); @@ -316,30 +324,30 @@ void text_color_set ( enum dw_color_e c ) default: case DW_COLOR_INFO: - printf ("%s", t_black[t]); + fprintf (g_dw_printf_dest, "%s", t_black[t]); break; case DW_COLOR_ERROR: - printf ("%s", t_red[t]); + fprintf (g_dw_printf_dest, "%s", t_red[t]); break; case DW_COLOR_REC: // Bright green is very difficult to read against a while background. // Let's use dark green instead. release 1.6. //printf ("%s", t_green[t]); - printf ("%s", t_dark_green[t]); + fprintf (g_dw_printf_dest, "%s", t_dark_green[t]); break; case DW_COLOR_DECODED: - printf ("%s", t_blue[t]); + fprintf (g_dw_printf_dest, "%s", t_blue[t]); break; case DW_COLOR_XMIT: - printf ("%s", t_magenta[t]); + fprintf (g_dw_printf_dest, "%s", t_magenta[t]); break; case DW_COLOR_DEBUG: - printf ("%s", t_dark_green[t]); + fprintf (g_dw_printf_dest, "%s", t_dark_green[t]); break; } } From a958b0cffb3501b8c055aa63b9fbe9cc5182ec96 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Thu, 19 Jan 2023 00:33:28 -0600 Subject: [PATCH 24/29] Add UDP audio output for Linux --- src/audio.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++----- src/audio.h | 1 + 2 files changed, 108 insertions(+), 11 deletions(-) diff --git a/src/audio.c b/src/audio.c index 1884d5ca..22206587 100644 --- a/src/audio.c +++ b/src/audio.c @@ -75,6 +75,7 @@ #include #include #include +#include #include @@ -131,7 +132,9 @@ static struct adev_s { enum audio_in_type_e g_audio_in_type; enum audio_out_type_e g_audio_out_type; - int udp_sock; /* UDP socket for receiving data */ + int udp_in_sock; /* UDP socket for receiving data */ + int udp_out_sock; /* UDP socket for sending data */ + struct sockaddr_storage udp_dest_addr; /* Destination address for UDP socket sending */ } adev[MAX_ADEVS]; @@ -239,7 +242,7 @@ int audio_open (struct audio_s *pa) #else adev[a].oss_audio_device_fd = -1; #endif - adev[a].udp_sock = -1; + adev[a].udp_in_sock = -1; } @@ -413,7 +416,7 @@ int audio_open (struct audio_s *pa) //int data_size = 0; //Create UDP Socket - if ((adev[a].udp_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { + if ((adev[a].udp_in_sock=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't create socket, errno %d\n", errno); return -1; @@ -425,7 +428,7 @@ int audio_open (struct audio_s *pa) si_me.sin_addr.s_addr = htonl(INADDR_ANY); //Bind to the socket - if (bind(adev[a].udp_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) { + if (bind(adev[a].udp_in_sock, (const struct sockaddr *) &si_me, sizeof(si_me))==-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Couldn't bind socket, errno %d\n", errno); return -1; @@ -454,7 +457,7 @@ int audio_open (struct audio_s *pa) } /* - * Output device. Only "soundcard" and "stdout" are supported at this time. + * Output device. Soundcard, stdout, and UDP are supported at this time. */ if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { adev[a].g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; @@ -463,6 +466,16 @@ int audio_open (struct audio_s *pa) dw_printf ("stdout must only be used with the -O option\n"); return (-1); } + } else if (strncasecmp(pa->adev[a].adevice_out, "udp:", 4) == 0) { + adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SDR_UDP; + /* User must supply address and port */ + if (strcasecmp(pa->adev[a].adevice_out,"udp:") == 0 || + strlen(pa->adev[a].adevice_out) < 7 || + strstr(pa->adev[a].adevice_out+5, ":") == 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Destination address and port must be supplied for UDP output\n"); + return (-1); + } } else { adev[a].g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD; } @@ -514,7 +527,63 @@ int audio_open (struct audio_s *pa) audio_out_name); return (-1); } + break; #endif + + case AUDIO_OUT_TYPE_SDR_UDP:; + + struct addrinfo ai_out; + struct addrinfo *ai_res; + char udp_outhost[256]; + char *udp_outport; + int res; + + // Initialize structure for addrinfo restrictions + memset((char *) &ai_out, 0, sizeof(ai_out)); + ai_out.ai_socktype = SOCK_DGRAM; + ai_out.ai_protocol = IPPROTO_UDP; + + // Separate out the host and port strings + strncpy(udp_outhost, pa->adev[a].adevice_out+4, 255); + udp_outhost[255] = 0; + udp_outport = strstr(udp_outhost,":"); + *udp_outport++ = 0; + + if (strlen(udp_outport) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("UDP output destination port must be supplied\n"); + return -1; + } + + // Get the sockaddr to represent the host/port provided + res = getaddrinfo(udp_outhost, udp_outport, &ai_out, &ai_res); + if (res != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Error parsing/resolving UDP output address\n"); + return -1; + } + + // IPv4 and IPv6 structs are different sizes + if (ai_res->ai_family == AF_INET6) { + res = sizeof(struct sockaddr_in6); + } else { + res = sizeof(struct sockaddr_in); + } + + //Create UDP Socket for the right address family + if ((adev[a].udp_out_sock=socket(ai_res->ai_family, SOCK_DGRAM, IPPROTO_UDP))==-1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't create socket, errno %d\n", errno); + return -1; + } + + // Save sockaddr needed later to send the data and set buffer size + memcpy(&adev[a].udp_dest_addr, ai_res->ai_addr, res); + adev[a].outbuf_size_in_bytes = SDR_UDP_BUF_MAXLEN; + freeaddrinfo(ai_res); + + break; + } /* * Finally allocate buffer for each direction. @@ -1222,8 +1291,8 @@ int audio_get (int a) while (adev[a].inbuf_next >= adev[a].inbuf_len) { int res; - assert (adev[a].udp_sock > 0); - res = recv(adev[a].udp_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); + assert (adev[a].udp_in_sock > 0); + res = recv(adev[a].udp_in_sock, adev[a].inbuf_ptr, adev[a].inbuf_size_in_bytes, 0); if (res < 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Can't read from udp socket, res=%d", res); @@ -1350,11 +1419,12 @@ int audio_put (int a, int c) int audio_flush (int a) { + int res; + unsigned char *ptr; + int len; + switch (adev[a].g_audio_out_type) { case AUDIO_OUT_TYPE_STDOUT:; - int res; - unsigned char *ptr; - int len; ptr = adev[a].outbuf_ptr; len = adev[a].outbuf_len; @@ -1544,6 +1614,32 @@ int audio_flush (int a) adev[a].outbuf_len = 0; return (0); #endif + + case AUDIO_OUT_TYPE_SDR_UDP:; + + ptr = adev[a].outbuf_ptr; + len = adev[a].outbuf_len; + + while (len > 0) { + + assert (adev[a].udp_out_sock > 0); + + res = sendto(adev[a].udp_out_sock, adev[a].outbuf_ptr, len, 0, (struct sockaddr *)&adev[a].udp_dest_addr, sizeof(struct sockaddr_storage)); + + if (res <= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("\nError %d writing to UDP socket. Exiting.\n", errno); + exit (0); + } + + ptr += res; + len -= res; + + } + + adev[a].outbuf_len = 0; + return 0; + } return (0); } /* end audio_flush */ @@ -1589,7 +1685,7 @@ void audio_wait (int a) { audio_flush (a); - if (adev[a].g_audio_out_type == AUDIO_OUT_TYPE_STDOUT) { + if (adev[a].g_audio_out_type != AUDIO_OUT_TYPE_SOUNDCARD) { return; } diff --git a/src/audio.h b/src/audio.h index c4c3b576..bbe5892b 100644 --- a/src/audio.h +++ b/src/audio.h @@ -45,6 +45,7 @@ enum audio_in_type_e { enum audio_out_type_e { AUDIO_OUT_TYPE_SOUNDCARD, + AUDIO_OUT_TYPE_SDR_UDP, AUDIO_OUT_TYPE_STDOUT }; /* For option to try fixing frames with bad CRC. */ From 4541c3b78b383dbc93dee6a0365457c0e3ad08c9 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Thu, 19 Jan 2023 13:22:18 -0600 Subject: [PATCH 25/29] Make internal name length match string length parsed from config file for audio devs --- src/audio.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/audio.c b/src/audio.c index 22206587..dc4d8109 100644 --- a/src/audio.c +++ b/src/audio.c @@ -226,8 +226,8 @@ int audio_open (struct audio_s *pa) #endif int chan; int a; - char audio_in_name[30]; - char audio_out_name[30]; + char audio_in_name[80]; + char audio_out_name[80]; save_audio_config_p = pa; From a238bf190e98b21f52f60ac8cea05665e7f77ca1 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Thu, 19 Jan 2023 14:38:24 -0600 Subject: [PATCH 26/29] Add UDP audio output for Windows --- src/audio_win.c | 122 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 11 deletions(-) diff --git a/src/audio_win.c b/src/audio_win.c index 22346e20..adba65ee 100644 --- a/src/audio_win.c +++ b/src/audio_win.c @@ -147,11 +147,13 @@ static struct adev_s { int stream_next; /* - * Buffer and index for stdout. + * UDP socket for transmitting audio stream. + * Buffer and index for stdout or UDP. */ - - unsigned char stream_out_data[SDR_UDP_BUF_MAXLEN]; + SOCKET udp_out_sock; + char stream_out_data[SDR_UDP_BUF_MAXLEN]; int stream_out_next; + struct sockaddr_storage udp_dest_addr; /* For sound output. */ /* out_wavehdr.dwUser is used to keep track of output buffer state. */ @@ -292,6 +294,7 @@ int audio_open (struct audio_s *pa) A->udp_sock = INVALID_SOCKET; + A->udp_out_sock = INVALID_SOCKET; in_dev_no[a] = WAVE_MAPPER; /* = ((UINT)-1) in mmsystem.h */ out_dev_no[a] = WAVE_MAPPER; @@ -349,8 +352,7 @@ int audio_open (struct audio_s *pa) /* * Select output device. - * Only soundcard and stdout at this point. - * Purhaps we'd like to add UDP for an SDR transmitter. + * Soundcard, UDP, and stdout supported. */ if (strcasecmp(pa->adev[a].adevice_out, "stdout") == 0 || strcmp(pa->adev[a].adevice_out, "-") == 0) { A->g_audio_out_type = AUDIO_OUT_TYPE_STDOUT; @@ -361,6 +363,16 @@ int audio_open (struct audio_s *pa) } /* Change - to stdout for readability. */ strlcpy (pa->adev[a].adevice_out, "stdout", sizeof(pa->adev[a].adevice_out)); + } else if (strncasecmp(pa->adev[a].adevice_out, "udp:", 4) == 0) { + A->g_audio_out_type = AUDIO_OUT_TYPE_SDR_UDP; + // User must supply address and port + if (strcasecmp(pa->adev[a].adevice_out, "udp:") == 0 || + strlen(pa->adev[a].adevice_out) < 7 || + strstr(pa->adev[a].adevice_out+5, ":") == 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Destination address and port must be supplied for UDP output\n"); + return (-1); + } } else { A->g_audio_out_type = AUDIO_OUT_TYPE_SOUNDCARD; @@ -524,7 +536,7 @@ int audio_open (struct audio_s *pa) struct adev_s *A = &(adev[a]); - /* Display stdin or udp:port if appropriate. */ + /* Display stdout or udp:port if appropriate. */ if (A->g_audio_out_type != AUDIO_OUT_TYPE_SOUNDCARD) { @@ -600,6 +612,71 @@ int audio_open (struct audio_s *pa) } A->out_current = 0; + case AUDIO_OUT_TYPE_SDR_UDP:; + + WSADATA wsadata; + struct addrinfo ai_out; + struct addrinfo *ai_res; + char udp_outhost[256]; + char *udp_outport; + int err, res; + + err = WSAStartup (MAKEWORD(2,2), &wsadata); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("WSAStartup failed: %d\n", err); + return (-1); + } + + if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Could not find a usable version of Winsock.dll\n"); + WSACleanup(); + return (-1); + } + + memset((char *) &ai_out, 0, sizeof(ai_out)); + ai_out.ai_socktype = SOCK_DGRAM; + ai_out.ai_protocol = IPPROTO_UDP; + + strncpy(udp_outhost, pa->adev[a].adevice_out + 4, 255); + udp_outhost[255] = 0; + udp_outport = strstr(udp_outhost, ":"); + *udp_outport++ = 0; + + if (strlen(udp_outport) == 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("UDP output destination port must be supplied\n"); + return -1; + } + + err = getaddrinfo(udp_outhost, udp_outport, &ai_out, &ai_res); + if (err != 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf("Error parsing/resolving UDP output address\n"); + return -1; + } + + if (ai_res->ai_family == AF_INET6) { + res = sizeof(struct sockaddr_in6); + } else { + res = sizeof(struct sockaddr_in); + } + + // Create UDP Socket + + A->udp_out_sock = socket(ai_res->ai_family, SOCK_DGRAM, IPPROTO_UDP); + if (A->udp_out_sock == INVALID_SOCKET) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't create socket, errno %d\n", WSAGetLastError()); + return -1; + } + + memcpy(&A->udp_dest_addr, ai_res->ai_addr, res); + A->stream_out_next = 0; + + break; + case AUDIO_OUT_TYPE_STDOUT: setmode (STDOUT_FILENO, _O_BINARY); @@ -1056,6 +1133,8 @@ int audio_put (int a, int c) } break; + + case AUDIO_OUT_TYPE_SDR_UDP: case AUDIO_OUT_TYPE_STDOUT: A->stream_out_data[A->stream_out_next++] = c; @@ -1095,6 +1174,10 @@ int audio_flush (int a) WAVEHDR *p; MMRESULT e; struct adev_s *A; + int res; + char *ptr; + unsigned int len; + A = &(adev[a]); @@ -1123,11 +1206,7 @@ int audio_flush (int a) } break; - case AUDIO_OUT_TYPE_STDOUT:; - - int res; - unsigned char *ptr; - unsigned int len; + case AUDIO_OUT_TYPE_STDOUT: ptr = A->stream_out_data; len = A->stream_out_next; @@ -1145,6 +1224,27 @@ int audio_flush (int a) A->stream_out_next = 0; break; + + case AUDIO_OUT_TYPE_SDR_UDP: + + ptr = A->stream_out_data; + len = A->stream_out_next; + + while (len > 0) { + res = sendto(A->udp_out_sock, ptr, len, 0, (struct sockaddr *)&A->udp_dest_addr, sizeof(struct sockaddr_storage)); + if (res < 0) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Error %d writing to UDP socket.\n", res); + return (-1); + } + + ptr += res; + len -= res; + } + + A->stream_out_next = 0; + break; + } return (0); From 9b3222bd8475f1d8c403c6e8708e73c4cebd9536 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Thu, 19 Jan 2023 14:40:52 -0600 Subject: [PATCH 27/29] Update changelog --- CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index ba28d1d2..474752fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,10 @@ > > Add: "FX25TX 1" (or 16 or 32 or 64) +- stdout is now supported for audio output via piping to other utilities. To support this, all non-audio output must be redirected to stderr using the new -O option on the command line. + +- udp audio output is also now supported. Use udp:destination:port style output device in the configuration file. + ## Version 1.6 -- October 2020 ## From e0647cf361c7c2cda0d679e28a771037e052519d Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Thu, 19 Jan 2023 14:55:33 -0600 Subject: [PATCH 28/29] Fix crash on null output device --- src/audio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio.c b/src/audio.c index dc4d8109..737247ad 100644 --- a/src/audio.c +++ b/src/audio.c @@ -527,8 +527,8 @@ int audio_open (struct audio_s *pa) audio_out_name); return (-1); } - break; #endif + break; case AUDIO_OUT_TYPE_SDR_UDP:; From 36cd261737cb2abf2f9165afb71d8d70707d3318 Mon Sep 17 00:00:00 2001 From: ars-ka0s <26339355+ars-ka0s@users.noreply.github.com> Date: Thu, 19 Jan 2023 16:02:00 -0600 Subject: [PATCH 29/29] Update config file examples to mention UDP output --- conf/generic.conf | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/conf/generic.conf b/conf/generic.conf index 5b09abed..631824dc 100644 --- a/conf/generic.conf +++ b/conf/generic.conf @@ -134,9 +134,12 @@ %W%# You can also specify "UDP:" and an optional port for input. %W%# "-" or "stdout" can be used to pipe audio out to another application. %W%# The -O option must be specified on the command line to support this. +%W%# For UDP output, specify the destination IP address/hostname and port number +%W%# using "UDP:destination:port" syntax %W% %W%# ADEVICE stdin 0 %W%# ADEVICE UDP:7355 0 +%W%# ADEVICE UDP:7355 UDP:localhost:7356 %W%# ADEVICE stdin stdout %W% %W%# The position in the list can change when devices (e.g. USB) are added and removed. @@ -162,9 +165,12 @@ %L%# You can also specify "UDP:" and an optional port for input. %L%# "-" or "stdout" can be used to pipe audio out to another application. %L%# The -O option must be specified on the command line to support this. +%L%# For UDP output, specify the destination IP address/hostname and port number +%L%# using "UDP:destination:port" syntax %L% %L%# ADEVICE stdin plughw:1,0 %L%# ADEVICE UDP:7355 default +%L%# ADEVICE UDP:7355 UDP:localhost:7356 %L%# ADEVICE stdin stdout %L% %R% ---------- Mac ---------- @@ -189,8 +195,11 @@ %M%# You can also specify "UDP:" and an optional port for input. %M%# "-" or "stdout" can be used to pipe audio out to another application. %M%# The -O option must be specified on the command line to support this. +%M%# For UDP output, specify the destination IP address/hostname and port number +%M%# using "UDP:destination:port" syntax %M% %M%# ADEVICE UDP:7355 default +%M%# ADEVICE UDP:7355 UDP:localhost:7356 %M%# ADEVICE stdin stdout %M%# %C%