diff --git a/NEWS b/NEWS index 8662ce998..429aca2aa 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ Version 5.x -- future Version 4.6 * 2023-11-XX -- Planned for Nov 2023 + * Add SDR# rig for use with SDR#'s gpredict plugin -- can only get/set freq * Add Apex Shared Loop rotator -- unidirectional only so far * Add client_version to rigctld so client can report it's version for future use/compatility/alternatives * Add --set-conf=tuner_control_pathname=hamlib_tuner_control (default) diff --git a/include/hamlib/riglist.h b/include/hamlib/riglist.h index 6d730595d..3c6418569 100644 --- a/include/hamlib/riglist.h +++ b/include/hamlib/riglist.h @@ -72,6 +72,7 @@ #define RIG_MODEL_DUMMY_NOVFO RIG_MAKE_MODEL(RIG_DUMMY, 6) #define RIG_MODEL_TCI1X RIG_MAKE_MODEL(RIG_DUMMY, 7) #define RIG_MODEL_ACLOG RIG_MAKE_MODEL(RIG_DUMMY, 8) +#define RIG_MODEL_SDRSHARP RIG_MAKE_MODEL(RIG_DUMMY, 9) /* * Yaesu diff --git a/rigs/dummy/Android.mk b/rigs/dummy/Android.mk index 840544658..4108a9db4 100644 --- a/rigs/dummy/Android.mk +++ b/rigs/dummy/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := dummy.c rot_dummy.c netrigctl.c netrotctl.c flrig.c trxmanager.c dummy_common.c +LOCAL_SRC_FILES := dummy.c rot_dummy.c netrigctl.c netrotctl.c flrig.c trxmanager.c dummy_common.c sdrsharp.c LOCAL_MODULE := dummy LOCAL_CFLAGS := diff --git a/rigs/dummy/Makefile.am b/rigs/dummy/Makefile.am index 5a419d3f3..f763ee87e 100644 --- a/rigs/dummy/Makefile.am +++ b/rigs/dummy/Makefile.am @@ -1,4 +1,4 @@ -DUMMYSRC = dummy_common.c dummy_common.h dummy.c dummy.h rot_dummy.c rot_dummy.h netrigctl.c netrotctl.c flrig.c flrig.h trxmanager.c trxmanager.h amp_dummy.c amp_dummy.h netampctl.c tci1x.c aclog.c +DUMMYSRC = dummy_common.c dummy_common.h dummy.c dummy.h rot_dummy.c rot_dummy.h netrigctl.c netrotctl.c flrig.c flrig.h trxmanager.c trxmanager.h amp_dummy.c amp_dummy.h netampctl.c tci1x.c aclog.c sdrsharp.c noinst_LTLIBRARIES = libhamlib-dummy.la libhamlib_dummy_la_SOURCES = $(DUMMYSRC) diff --git a/rigs/dummy/dummy.c b/rigs/dummy/dummy.c index c95390533..d3f7f63b0 100644 --- a/rigs/dummy/dummy.c +++ b/rigs/dummy/dummy.c @@ -2676,6 +2676,7 @@ DECLARE_INITRIG_BACKEND(dummy) rig_register(&trxmanager_caps); rig_register(&dummy_no_vfo_caps); rig_register(&aclog_caps); + rig_register(&sdrsharp_caps); // rig_register(&tci1x_caps); return RIG_OK; } diff --git a/rigs/dummy/dummy.h b/rigs/dummy/dummy.h index dc844a201..b800cfd57 100644 --- a/rigs/dummy/dummy.h +++ b/rigs/dummy/dummy.h @@ -46,6 +46,7 @@ extern const struct rig_caps flrig_caps; extern const struct rig_caps trxmanager_caps; extern const struct rig_caps tci1x_caps; extern const struct rig_caps aclog_caps; +extern const struct rig_caps sdrsharp_caps; int netrigctl_get_vfo_mode(RIG *); diff --git a/rigs/dummy/sdrsharp.c b/rigs/dummy/sdrsharp.c new file mode 100644 index 000000000..0f95cdcef --- /dev/null +++ b/rigs/dummy/sdrsharp.c @@ -0,0 +1,573 @@ +/* +* Hamlib rigctld backend - works with SDR#'s gpredict plugin for example +* Copyright (c) 2023 by Michael Black W9MDB +* +* +* 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 +* +*/ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dummy_common.h" + +#define DEBUG 1 +#define DEBUG_TRACE DEBUG_VERBOSE +#define TRUE 1 +#define FALSE 0 + + +#define MAXCMDLEN 8192 +#define MAXXMLLEN 8192 +#define MAXARGLEN 128 +#define MAXBANDWIDTHLEN 4096 + +#define DEFAULTPATH "127.0.0.1:4532" + +#define SDRSHARP_VFOS (RIG_VFO_A) + +#define SDRSHARP_MODES (RIG_MODE_NONE) + +struct sdrsharp_priv_data +{ + vfo_t curr_vfo; + char bandwidths[MAXBANDWIDTHLEN]; /* pipe delimited set */ + int nbandwidths; + char info[8192]; + ptt_t ptt; + split_t split; + rmode_t curr_modeA; + rmode_t curr_modeB; + freq_t curr_freqA; + freq_t curr_freqB; + pbwidth_t curr_widthA; + pbwidth_t curr_widthB; + int has_get_modeA; /* True if this function is available */ + int has_get_bwA; /* True if this function is available */ + int has_set_bwA; /* True if this function is available */ + float powermeter_scale; /* So we can scale power meter to 0-1 */ + value_t parms[RIG_SETTING_MAX]; + struct ext_list *ext_parms; +}; + +/* +* check_vfo +* No assumptions +*/ +static int check_vfo(vfo_t vfo) +{ + switch (vfo) + { + case RIG_VFO_A: + break; + + case RIG_VFO_TX: + case RIG_VFO_B: + break; + + case RIG_VFO_CURR: + break; // will default to A in which_vfo + + default: + return (FALSE); + } + + return (TRUE); +} + +/* +* read_transaction +* Assumes rig!=NULL, xml!=NULL, xml_len>=MAXXMLLEN +*/ +static int read_transaction(RIG *rig, char *xml, int xml_len) +{ + int retval; + int retry; + char *delims; + char *terminator = "\n"; + struct rig_state *rs = &rig->state; + + ENTERFUNC; + + retry = 2; + delims = "\n"; + xml[0] = 0; + + do + { + char tmp_buf[MAXXMLLEN]; // plenty big for expected sdrsharp responses hopefully + + if (retry < 2) + { + rig_debug(RIG_DEBUG_WARN, "%s: retry needed? retry=%d\n", __func__, retry); + } + + int len = read_string(&rs->rigport, (unsigned char *) tmp_buf, sizeof(tmp_buf), + delims, + strlen(delims), 0, 1); + + if (len > 0) { retry = 3; } + + if (len <= 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: read_string error=%d\n", __func__, len); + continue; + } + + if (strlen(xml) + strlen(tmp_buf) < xml_len - 1) + { + strncat(xml, tmp_buf, xml_len - 1); + } + else + { + rig_debug(RIG_DEBUG_ERR, + "%s: xml buffer overflow!!\nTrying to add len=%d\nTo len=%d\n", __func__, + (int)strlen(tmp_buf), (int)strlen(xml)); + RETURNFUNC(-RIG_EPROTO); + } + } + while (retry-- > 0 && strstr(xml, terminator) == NULL); + + if (retry == 0) + { + rig_debug(RIG_DEBUG_WARN, "%s: retry timeout\n", __func__); + RETURNFUNC(-RIG_ETIMEOUT); + } + + if (strstr(xml, terminator)) + { +// rig_debug(RIG_DEBUG_TRACE, "%s: got %s\n", __func__, terminator); + retval = RIG_OK; + } + else + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: did not get %s\n", __func__, terminator); + retval = -(101 + RIG_EPROTO); + } + + RETURNFUNC(retval); +} + +/* +* write_transaction +* Assumes rig!=NULL, xml!=NULL, xml_len=total size of xml for response +*/ +static int write_transaction(RIG *rig, char *xml, int xml_len) +{ + + int try = rig->caps->retry; + + int retval = -RIG_EPROTO; + + struct rig_state *rs = &rig->state; + + ENTERFUNC; + + // This shouldn't ever happen...but just in case + // We need to avoid an empty write as rigctld replies with blank line + if (xml_len == 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: len==0??\n", __func__); + RETURNFUNC(retval); + } + + // appears we can lose sync if we don't clear things out + // shouldn't be anything for us now anyways + rig_flush(&rig->state.rigport); + + while (try-- >= 0 && retval != RIG_OK) + { + retval = write_block(&rs->rigport, (unsigned char *) xml, strlen(xml)); + + if (retval < 0) + { + RETURNFUNC(-RIG_EIO); + } + } + + RETURNFUNC(retval); +} + +static int sdrsharp_transaction(RIG *rig, char *cmd, char *value, + int value_len) +{ + char xml[MAXXMLLEN]; + int retry = 3; + + ENTERFUNC; + ELAPSED1; + + set_transaction_active(rig); + + if (value) + { + value[0] = 0; + } + + do + { + int retval; + + if (retry != 3) + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: cmd=%s, retry=%d\n", __func__, cmd, retry); + } + + retval = write_transaction(rig, cmd, strlen(cmd)); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: write_transaction error=%d\n", __func__, retval); + + // if we get RIG_EIO the socket has probably disappeared + // so bubble up the error so port can re re-opened + if (retval == -RIG_EIO) { set_transaction_inactive(rig); RETURNFUNC(retval); } + + hl_usleep(50 * 1000); // 50ms sleep if error + } + + if (value) + { + read_transaction(rig, xml, sizeof(xml)); // this might time out -- that's OK + } + + if (value) { strncpy(value, xml, value_len); } + + } + while (((value && strlen(value) == 0)) + && retry--); // we'll do retries if needed + + if (value && strlen(value) == 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: no value returned\n", __func__); + set_transaction_inactive(rig); RETURNFUNC(RIG_EPROTO); + } + + ELAPSED2; + set_transaction_inactive(rig); + RETURNFUNC(RIG_OK); +} + +/* +* sdrsharp_init +* Assumes rig!=NULL +*/ +static int sdrsharp_init(RIG *rig) +{ + struct sdrsharp_priv_data *priv; + + ENTERFUNC; + rig_debug(RIG_DEBUG_TRACE, "%s version %s\n", __func__, rig->caps->version); + + rig->state.priv = (struct sdrsharp_priv_data *)calloc(1, sizeof( + struct sdrsharp_priv_data)); + + if (!rig->state.priv) + { + RETURNFUNC(-RIG_ENOMEM); + } + + priv = rig->state.priv; + + memset(priv, 0, sizeof(struct sdrsharp_priv_data)); + memset(priv->parms, 0, RIG_SETTING_MAX * sizeof(value_t)); + + /* + * set arbitrary initial status + */ + rig->state.current_vfo = RIG_VFO_A; + priv->split = 0; + priv->ptt = 0; + priv->curr_modeA = -1; + priv->curr_modeB = -1; + priv->curr_widthA = -1; + priv->curr_widthB = -1; + + if (!rig->caps) + { + RETURNFUNC(-RIG_EINVAL); + } + + strncpy(rig->state.rigport.pathname, DEFAULTPATH, + sizeof(rig->state.rigport.pathname)); + + RETURNFUNC(RIG_OK); +} + +/* +* sdrsharp_get_freq +* Assumes rig!=NULL, rig->state.priv!=NULL, freq!=NULL +*/ +static int sdrsharp_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) +{ + char value[MAXARGLEN]; + struct sdrsharp_priv_data *priv = (struct sdrsharp_priv_data *) rig->state.priv; + + ENTERFUNC; + rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s\n", __func__, + rig_strvfo(vfo)); + + + if (check_vfo(vfo) == FALSE) + { + rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", + __func__, rig_strvfo(vfo)); + RETURNFUNC(-RIG_EINVAL); + } + + if (vfo == RIG_VFO_CURR) + { + vfo = rig->state.current_vfo; + rig_debug(RIG_DEBUG_TRACE, "%s: get_freq2 vfo=%s\n", + __func__, rig_strvfo(vfo)); + } + + char *cmd = "f\n"; + int retval; + + retval = sdrsharp_transaction(rig, cmd, value, sizeof(value)); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: READBMF failed retval=%s\n", __func__, + rigerror(retval)); + RETURNFUNC(retval); + } + + *freq = 0; + + sscanf(value, "%lf", freq); + + if (*freq == 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: freq==0??\nvalue=%s\n", __func__, + value); + RETURNFUNC(-RIG_EPROTO); + } + else + { + rig_debug(RIG_DEBUG_TRACE, "%s: freq=%.0f\n", __func__, *freq); + } + + if (vfo == RIG_VFO_A) + { + priv->curr_freqA = *freq; + } + else // future support in SDRSHARP maybe? + { + priv->curr_freqB = *freq; + } + + RETURNFUNC(RIG_OK); +} + +/* +* sdrsharp_open +* Assumes rig!=NULL, rig->state.priv!=NULL +*/ +static int sdrsharp_open(RIG *rig) +{ + int retval; + char value[MAXARGLEN]; + + ENTERFUNC; + + freq_t freq; + retval = sdrsharp_get_freq(rig, RIG_VFO_CURR, &freq); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: sdrsharp_get_freq not working!!\n", __func__); + RETURNFUNC(RIG_EPROTO); + } + + rig->state.current_vfo = RIG_VFO_A; + rig_debug(RIG_DEBUG_TRACE, "%s: currvfo=%s value=%s\n", __func__, + rig_strvfo(rig->state.current_vfo), value); + + RETURNFUNC(retval); +} + +/* +* sdrsharp_close +* Assumes rig!=NULL +*/ +static int sdrsharp_close(RIG *rig) +{ + ENTERFUNC; + + RETURNFUNC(RIG_OK); +} + +/* +* sdrsharp_cleanup +* Assumes rig!=NULL, rig->state.priv!=NULL +*/ +static int sdrsharp_cleanup(RIG *rig) +{ + struct sdrsharp_priv_data *priv; + + rig_debug(RIG_DEBUG_TRACE, "%s\n", __func__); + + if (!rig) + { + RETURNFUNC2(-RIG_EINVAL); + } + + priv = (struct sdrsharp_priv_data *)rig->state.priv; + + free(priv->ext_parms); + free(rig->state.priv); + + rig->state.priv = NULL; + + // we really don't need to free this up as it's only done once + // was causing problem when cleanup was followed by rig_open + // model_sdrsharp was not getting refilled + // if we can figure out that one we can re-enable this +#if 0 + int i; + + for (i = 0; modeMap[i].mode_hamlib != 0; ++i) + { + if (modeMap[i].mode_sdrsharp) + { + free(modeMap[i].mode_sdrsharp); + modeMap[i].mode_sdrsharp = NULL; + modeMap[i].mode_hamlib = 0; + } + + } + +#endif + + RETURNFUNC2(RIG_OK); +} + + +/* +* sdrsharp_set_freq +* assumes rig!=NULL, rig->state.priv!=NULL +*/ +static int sdrsharp_set_freq(RIG *rig, vfo_t vfo, freq_t freq) +{ + int retval; + char cmd[MAXARGLEN]; + char value[1024]; + + //struct sdrsharp_priv_data *priv = (struct sdrsharp_priv_data *) rig->state.priv; + + rig_debug(RIG_DEBUG_TRACE, "%s\n", __func__); + rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s freq=%.0f\n", __func__, + rig_strvfo(vfo), freq); + + if (check_vfo(vfo) == FALSE) + { + rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", + __func__, rig_strvfo(vfo)); + RETURNFUNC2(-RIG_EINVAL); + } + + if (vfo == RIG_VFO_CURR) + { + vfo = rig->state.current_vfo; + } + + SNPRINTF(cmd, sizeof(cmd), "F %.0lf\n", freq); + + retval = sdrsharp_transaction(rig, cmd, value, sizeof(value)); + + if (retval != RIG_OK) + { + RETURNFUNC2(retval); + } + + sscanf(value, "RPRT %d", &retval); + + RETURNFUNC2(retval); +} + +/* +* sdrsharp_get_vfo +* assumes rig!=NULL, vfo != NULL +*/ +static int sdrsharp_get_vfo(RIG *rig, vfo_t *vfo) +{ + ENTERFUNC; + + *vfo = RIG_VFO_A; + + RETURNFUNC(RIG_OK); +} + +const struct rig_caps sdrsharp_caps = +{ + RIG_MODEL(RIG_MODEL_SDRSHARP), + .model_name = "SDR#", + .mfg_name = "Hamlib", + .version = "20230127.0", + .copyright = "LGPL", + .status = RIG_STATUS_STABLE, + .rig_type = RIG_TYPE_RECEIVER, + //.targetable_vfo = RIG_TARGETABLE_FREQ | RIG_TARGETABLE_MODE, + .ptt_type = RIG_PTT_RIG, + .port_type = RIG_PORT_NETWORK, + .write_delay = 0, + .post_write_delay = 0, + .timeout = 1000, + .retry = 2, + + .filters = { + {RIG_MODE_ALL, RIG_FLT_ANY}, + RIG_FLT_END + }, + + .rx_range_list1 = {{ + .startf = kHz(1), .endf = GHz(10), .modes = SDRSHARP_MODES, + .low_power = -1, .high_power = -1, SDRSHARP_VFOS, RIG_ANT_1 + }, + RIG_FRNG_END, + }, + .tx_range_list1 = {RIG_FRNG_END,}, + .rx_range_list2 = {{ + .startf = kHz(1), .endf = GHz(10), .modes = SDRSHARP_MODES, + .low_power = -1, .high_power = -1, SDRSHARP_VFOS, RIG_ANT_1 + }, + RIG_FRNG_END, + }, + .tx_range_list2 = {RIG_FRNG_END,}, + .tuning_steps = { {SDRSHARP_MODES, 1}, {SDRSHARP_MODES, RIG_TS_ANY}, RIG_TS_END, }, + .priv = NULL, /* priv */ + + .rig_init = sdrsharp_init, + .rig_open = sdrsharp_open, + .rig_close = sdrsharp_close, + .rig_cleanup = sdrsharp_cleanup, + + .get_vfo = sdrsharp_get_vfo, + .set_freq = sdrsharp_set_freq, + .get_freq = sdrsharp_get_freq, + .hamlib_check_rig_caps = HAMLIB_CHECK_RIG_CAPS +};