/* * Hamlib Interface - event handling * Copyright (c) 2021 by Mikael Nousiainen * Copyright (c) 2000-2010 by Stephane Fillod * Copyright (c) 2000-2003 by Frank Singleton * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ /* Doc todo: Verify assignment to rig group. Consider doc of internal rtns. */ /** * \addtogroup rig * @{ */ /** * \file event.c * \brief Event handling */ #include #include #include #include #include #include #ifdef HAVE_PTHREAD # include #endif #include #include "event.h" #include "misc.h" #include "cache.h" #include "network.h" #define CHECK_RIG_ARG(r) (!(r) || !(r)->caps || !(r)->state.comm_state) #ifdef HAVE_PTHREAD typedef struct rig_poll_routine_args_s { RIG *rig; } rig_poll_routine_args; typedef struct rig_poll_routine_priv_data_s { pthread_t thread_id; rig_poll_routine_args args; } rig_poll_routine_priv_data; // TODO: Where to start/stop rig poll routine? void *rig_poll_routine(void *arg) { rig_poll_routine_args *args = (rig_poll_routine_args *)arg; RIG *rig = args->rig; struct rig_state *rs = &rig->state; int result; int update_occurred; vfo_t vfo = RIG_VFO_NONE, vfo_prev = RIG_VFO_NONE; freq_t freq_main = 0, freq_sub = 0, freq_main_prev = 0, freq_sub_prev = 0; rmode_t mode_main = RIG_MODE_NONE, mode_sub = RIG_MODE_NONE, mode_main_prev = RIG_MODE_NONE, mode_sub_prev = RIG_MODE_NONE; pbwidth_t width_main = 0, width_sub = 0, width_main_prev = 0, width_sub_prev = 0; split_t split, split_prev = -1; rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Starting rig poll routine thread\n", __FILE__, __LINE__); // Rig cache time should be equal to rig poll interval (should be set automatically by rigctld at least) rig_set_cache_timeout_ms(rig, HAMLIB_CACHE_ALL, rs->poll_interval); update_occurred = 0; while (rs->poll_routine_thread_run) { if (rig->caps->get_vfo) { result = rig_get_vfo(rig, &vfo); if (result != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_vfo error %s\n", __FILE__, __LINE__, rigerror(result)); } if (vfo != vfo_prev) { rig_fire_vfo_event(rig, vfo); } if (vfo != vfo_prev) { rig_debug(RIG_DEBUG_CACHE, "%s(%d) vfo=%s was %s\n", __FILE__, __LINE__, rig_strvfo(vfo), rig_strvfo(vfo_prev)); update_occurred = 1; vfo_prev = vfo; } } if (rig->caps->get_freq) { result = rig_get_freq(rig, RIG_VFO_A, &freq_main); if (result != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_freqA error %s\n", __FILE__, __LINE__, rigerror(result)); } result = rig_get_freq(rig, RIG_VFO_B, &freq_sub); if (result != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_freqB error %s\n", __FILE__, __LINE__, rigerror(result)); } if (freq_main != freq_main_prev) { rig_fire_freq_event(rig, RIG_VFO_A, freq_main); } if (freq_sub != freq_sub_prev) { rig_fire_freq_event(rig, RIG_VFO_B, freq_sub); } if (freq_main != freq_main_prev || freq_sub != freq_sub_prev) { rig_debug(RIG_DEBUG_CACHE, "%s(%d) freq_main=%.0f was %.0f, freq_sub=%.0f was %.0f\n", __FILE__, __LINE__, freq_main, freq_main_prev, freq_sub, freq_sub_prev); update_occurred = 1; freq_main_prev = freq_main; freq_sub_prev = freq_sub; } } if (rig->caps->get_mode) { result = rig_get_mode(rig, RIG_VFO_A, &mode_main, &width_main); if (result != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeA error %s\n", __FILE__, __LINE__, rigerror(result)); } result = rig_get_mode(rig, RIG_VFO_B, &mode_sub, &width_sub); if (result != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeB error %s\n", __FILE__, __LINE__, rigerror(result)); } if (mode_main != mode_main_prev || width_main != width_main_prev) { rig_fire_mode_event(rig, RIG_VFO_A, mode_main, width_main); } if (mode_sub != mode_sub_prev || width_sub != width_sub_prev) { rig_fire_mode_event(rig, RIG_VFO_B, mode_sub, width_sub); } if (mode_main != mode_main_prev || mode_sub != mode_sub_prev) { rig_debug(RIG_DEBUG_CACHE, "%s(%d) mode_main=%s was %s, mode_sub=%s was %s\n", __FILE__, __LINE__, rig_strrmode(mode_main), rig_strrmode(mode_main_prev), rig_strrmode(mode_sub), rig_strrmode(mode_sub_prev)); update_occurred = 1; mode_main_prev = mode_main; mode_sub_prev = mode_sub; } if (width_main != width_main_prev || width_sub != width_sub_prev) { rig_debug(RIG_DEBUG_CACHE, "%s(%d) width_main=%ld was %ld, width_sub=%ld was %ld\n", __FILE__, __LINE__, width_main, width_main_prev, width_sub, width_sub_prev); update_occurred = 1; width_main_prev = width_main; width_sub_prev = width_sub; } } if (rig->caps->get_split_vfo) { vfo_t tx_vfo; result = rig_get_split_vfo(rig, RIG_VFO_A, &split, &tx_vfo); if (result != RIG_OK) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig_get_modeA error %s\n", __FILE__, __LINE__, rigerror(result)); } if (split != split_prev) { rig_debug(RIG_DEBUG_CACHE, "%s(%d) split=%d was %d\n", __FILE__, __LINE__, split, split_prev); update_occurred = 1; split_prev = split; } } if (update_occurred) { network_publish_rig_poll_data(rig); } hl_usleep(rs->poll_interval * 1000); } rig_debug(RIG_DEBUG_VERBOSE, "%s(%d): Stopping rig poll routine thread\n", __FILE__, __LINE__); return NULL; } /** * \brief Start rig poll routine * * Start rig poll routine * * \return RIG_OK or < 0 if error */ int rig_poll_routine_start(RIG *rig) { struct rig_state *rs = &rig->state; rig_poll_routine_priv_data *poll_routine_priv; ENTERFUNC; if (rs->poll_interval < 1) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig poll routine disabled, poll interval set to zero\n", __FILE__, __LINE__); RETURNFUNC(RIG_OK); } if (rs->poll_routine_priv_data != NULL) { rig_debug(RIG_DEBUG_ERR, "%s(%d): rig poll routine already running\n", __FILE__, __LINE__); RETURNFUNC(-RIG_EINVAL); } rs->poll_routine_thread_run = 1; rs->poll_routine_priv_data = calloc(1, sizeof(rig_poll_routine_priv_data)); if (rs->poll_routine_priv_data == NULL) { RETURNFUNC(-RIG_ENOMEM); } poll_routine_priv = (rig_poll_routine_priv_data *) rs->poll_routine_priv_data; poll_routine_priv->args.rig = rig; int err = pthread_create(&poll_routine_priv->thread_id, NULL, rig_poll_routine, &poll_routine_priv->args); if (err) { rig_debug(RIG_DEBUG_ERR, "%s(%d) pthread_create error: %s\n", __FILE__, __LINE__, strerror(errno)); RETURNFUNC(-RIG_EINTERNAL); } RETURNFUNC(RIG_OK); } /** * \brief Stop rig poll routine * * Stop rig poll routine * * \return RIG_OK or < 0 if error */ int rig_poll_routine_stop(RIG *rig) { struct rig_state *rs = &rig->state; rig_poll_routine_priv_data *poll_routine_priv; ENTERFUNC; if (rs->poll_interval < 1) { RETURNFUNC(RIG_OK); } if (rs->poll_routine_priv_data == NULL) { RETURNFUNC(-RIG_EINVAL); } rs->poll_routine_thread_run = 0; poll_routine_priv = (rig_poll_routine_priv_data *) rs->poll_routine_priv_data; if (poll_routine_priv->thread_id != 0) { int err = pthread_join(poll_routine_priv->thread_id, NULL); if (err) { rig_debug(RIG_DEBUG_ERR, "%s(%d): pthread_join error %s\n", __FILE__, __LINE__, strerror(errno)); // just ignore it } poll_routine_priv->thread_id = 0; } free(rs->poll_routine_priv_data); rs->poll_routine_priv_data = NULL; RETURNFUNC(RIG_OK); } #endif /** * \brief set the callback for freq events * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * * Install a callback for freq events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). */ int HAMLIB_API rig_set_freq_callback(RIG *rig, freq_cb_t cb, rig_ptr_t arg) { ENTERFUNC; if (CHECK_RIG_ARG(rig)) { RETURNFUNC(-RIG_EINVAL); } rig->callbacks.freq_event = cb; rig->callbacks.freq_arg = arg; RETURNFUNC(RIG_OK); } /** * \brief set the callback for mode events * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * * Install a callback for mode events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). */ int HAMLIB_API rig_set_mode_callback(RIG *rig, mode_cb_t cb, rig_ptr_t arg) { ENTERFUNC; if (CHECK_RIG_ARG(rig)) { RETURNFUNC(-RIG_EINVAL); } rig->callbacks.mode_event = cb; rig->callbacks.mode_arg = arg; RETURNFUNC(RIG_OK); } /** * \brief set the callback for vfo events * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * * Install a callback for vfo events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). */ int HAMLIB_API rig_set_vfo_callback(RIG *rig, vfo_cb_t cb, rig_ptr_t arg) { ENTERFUNC; if (CHECK_RIG_ARG(rig)) { RETURNFUNC(-RIG_EINVAL); } rig->callbacks.vfo_event = cb; rig->callbacks.vfo_arg = arg; RETURNFUNC(RIG_OK); } /** * \brief set the callback for ptt events * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * * Install a callback for ptt events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). */ int HAMLIB_API rig_set_ptt_callback(RIG *rig, ptt_cb_t cb, rig_ptr_t arg) { ENTERFUNC; if (CHECK_RIG_ARG(rig)) { RETURNFUNC(-RIG_EINVAL); } rig->callbacks.ptt_event = cb; rig->callbacks.ptt_arg = arg; RETURNFUNC(RIG_OK); } /** * \brief set the callback for dcd events * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * * Install a callback for dcd events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). */ int HAMLIB_API rig_set_dcd_callback(RIG *rig, dcd_cb_t cb, rig_ptr_t arg) { ENTERFUNC; if (CHECK_RIG_ARG(rig)) { RETURNFUNC(-RIG_EINVAL); } rig->callbacks.dcd_event = cb; rig->callbacks.dcd_arg = arg; RETURNFUNC(RIG_OK); } /** * \brief set the callback for pipelined tuning module * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * used to maintain state during pipelined tuning. * * Install a callback for pipelined tuning module, to be called when the * rig_scan( SCAN_PLT ) loop needs a new frequency, mode and width. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). */ int HAMLIB_API rig_set_pltune_callback(RIG *rig, pltune_cb_t cb, rig_ptr_t arg) { ENTERFUNC; if (CHECK_RIG_ARG(rig)) { RETURNFUNC(-RIG_EINVAL); } rig->callbacks.pltune = cb; rig->callbacks.pltune_arg = arg; RETURNFUNC(RIG_OK); } /** * \brief set the callback for spectrum line reception events * \param rig The rig handle * \param cb The callback to install * \param arg A Pointer to some private data to pass later on to the callback * * Install a callback for spectrum line reception events, to be called when in async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). */ int HAMLIB_API rig_set_spectrum_callback(RIG *rig, spectrum_cb_t cb, rig_ptr_t arg) { ENTERFUNC; if (CHECK_RIG_ARG(rig)) { RETURNFUNC(-RIG_EINVAL); } rig->callbacks.spectrum_event = cb; rig->callbacks.spectrum_arg = arg; RETURNFUNC(RIG_OK); } /** * \brief control the transceive mode * \param rig The rig handle * \param trn The transceive status to set to * * Enable/disable the transceive handling of a rig and kick off async mode. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). * * \sa rig_get_trn() * * \deprecated This functionality has never worked correctly and it is now disabled in favor of new async data handling capabilities. * The command will always return RIG_EDEPRECATED until the command will be removed eventually. */ int HAMLIB_API rig_set_trn(RIG *rig, int trn) { ENTERFUNC; RETURNFUNC(-RIG_EDEPRECATED); } /** * \brief get the current transceive mode * \param rig The rig handle * \param trn The location where to store the current transceive mode * * Retrieves the current status of the transceive mode, i.e. if radio * sends new status automatically when some changes happened on the radio. * * \return RIG_OK if the operation has been successful, otherwise * a negative value if an error occurred (in which case, cause is * set appropriately). * * \sa rig_set_trn() * * \deprecated This functionality has never worked correctly and it is now disabled in favor of new async data handling capabilities. * The command will always return RIG_EDEPRECATED until the command will be removed eventually. */ int HAMLIB_API rig_get_trn(RIG *rig, int *trn) { ENTERFUNC; RETURNFUNC(-RIG_EDEPRECATED); } int rig_fire_freq_event(RIG *rig, vfo_t vfo, freq_t freq) { ENTERFUNC; rig_debug(RIG_DEBUG_TRACE, "Event: freq changed to %"PRIll"Hz on %s\n", (int64_t)freq, rig_strvfo(vfo)); rig_set_cache_freq(rig, vfo, freq); // This doesn't work well for Icom rigs -- no way to tell which VFO we're on // Should work for most other rigs using AI1; mode if (RIG_BACKEND_NUM(rig->caps->rig_model) != RIG_ICOM) { rig->state.use_cached_freq = 1; } network_publish_rig_transceive_data(rig); if (rig->callbacks.freq_event) { rig->callbacks.freq_event(rig, vfo, freq, rig->callbacks.freq_arg); } RETURNFUNC(0); } int rig_fire_mode_event(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) { ENTERFUNC; rig_debug(RIG_DEBUG_TRACE, "Event: mode changed to %s, width %liHz on %s\n", rig_strrmode(mode), width, rig_strvfo(vfo)); rig_set_cache_mode(rig, vfo, mode, width); // This doesn't work well for Icom rigs -- no way to tell which VFO we're on // Should work for most other rigs using AI1; mode if (RIG_BACKEND_NUM(rig->caps->rig_model) != RIG_ICOM) { rig->state.use_cached_mode = 1; } network_publish_rig_transceive_data(rig); if (rig->callbacks.mode_event) { rig->callbacks.mode_event(rig, vfo, mode, width, rig->callbacks.mode_arg); } RETURNFUNC(0); } int rig_fire_vfo_event(RIG *rig, vfo_t vfo) { ENTERFUNC; rig_debug(RIG_DEBUG_TRACE, "Event: vfo changed to %s\n", rig_strvfo(vfo)); rig->state.cache.vfo = vfo; elapsed_ms(&rig->state.cache.time_vfo, HAMLIB_ELAPSED_SET); network_publish_rig_transceive_data(rig); if (rig->callbacks.vfo_event) { rig->callbacks.vfo_event(rig, vfo, rig->callbacks.vfo_arg); } RETURNFUNC(0); } int rig_fire_ptt_event(RIG *rig, vfo_t vfo, ptt_t ptt) { ENTERFUNC; rig_debug(RIG_DEBUG_TRACE, "Event: PTT changed to %i on %s\n", ptt, rig_strvfo(vfo)); rig->state.cache.ptt = ptt; elapsed_ms(&rig->state.cache.time_ptt, HAMLIB_ELAPSED_SET); network_publish_rig_transceive_data(rig); if (rig->callbacks.ptt_event) { rig->callbacks.ptt_event(rig, vfo, ptt, rig->callbacks.ptt_arg); } RETURNFUNC(0); } int rig_fire_dcd_event(RIG *rig, vfo_t vfo, dcd_t dcd) { ENTERFUNC; rig_debug(RIG_DEBUG_TRACE, "Event: DCD changed to %i on %s\n", dcd, rig_strvfo(vfo)); network_publish_rig_transceive_data(rig); if (rig->callbacks.dcd_event) { rig->callbacks.dcd_event(rig, vfo, dcd, rig->callbacks.dcd_arg); } RETURNFUNC(0); } int rig_fire_pltune_event(RIG *rig, vfo_t vfo, freq_t *freq, rmode_t *mode, pbwidth_t *width) { ENTERFUNC; rig_debug(RIG_DEBUG_TRACE, "Event: Pipelined tuning event, vfo=%s\n", rig_strvfo(vfo)); network_publish_rig_transceive_data(rig); if (rig->callbacks.pltune) { rig->callbacks.pltune(rig, vfo, freq, mode, width, rig->callbacks.pltune_arg); } RETURNFUNC(RIG_OK); } static int print_spectrum_line(char *str, size_t length, struct rig_spectrum_line *line) { int data_level_max = line->data_level_max / 2; int aggregate_count = line->spectrum_data_length / 120; int aggregate_value = 0; int i, c; int charlen = strlen("█"); str[0] = '\0'; for (i = 0, c = 0; i < line->spectrum_data_length; i++) { int current = line->spectrum_data[i]; aggregate_value = current > aggregate_value ? current : aggregate_value; if (i > 0 && i % aggregate_count == 0) { if (c + charlen >= length) { break; } int level = aggregate_value * 10 / data_level_max; if (level >= 8) { strcpy(str + c, "█"); c += charlen; } else if (level >= 6) { strcpy(str + c, "▓"); c += charlen; } else if (level >= 4) { strcpy(str + c, "▒"); c += charlen; } else if (level >= 2) { strcpy(str + c, "░"); c += charlen; } else if (level >= 0) { strcpy(str + c, " "); c += 1; } aggregate_value = 0; } } return c; } int rig_fire_spectrum_event(RIG *rig, struct rig_spectrum_line *line) { ENTERFUNC; if (rig_need_debug(RIG_DEBUG_TRACE)) { char spectrum_debug[line->spectrum_data_length * 4]; print_spectrum_line(spectrum_debug, sizeof(spectrum_debug), line); rig_debug(RIG_DEBUG_TRACE, "%s: ASCII Spectrum Scope: %s\n", __func__, spectrum_debug); } network_publish_rig_spectrum_data(rig, line); if (rig->callbacks.spectrum_event) { rig->callbacks.spectrum_event(rig, line, rig->callbacks.spectrum_arg); } RETURNFUNC(RIG_OK); } /** @} */