diff --git a/configure.ac b/configure.ac index 0abd26f45..866ebff41 100644 --- a/configure.ac +++ b/configure.ac @@ -47,7 +47,7 @@ dnl added to AC_CONFIG_FILES near the end of this file. See README.developer dnl Beware of duplication should a backend directory include both rig and dnl rotor definitions, e.g. "dummy". Optional backends will not be listed dnl here but will be added later, e.g. "winradio". -RIG_BACKEND_LIST="rigs/adat rigs/alinco rigs/aor rigs/barrett rigs/codan rigs/dorji rigs/drake rigs/dummy rigs/elad rigs/flexradio rigs/icom rigs/icmarine rigs/jrc rigs/kachina rigs/kenwood rigs/kit rigs/lowe rigs/pcr rigs/prm80 rigs/racal rigs/rft rigs/rs rigs/skanti rigs/tapr rigs/tentec rigs/tuner rigs/uniden rigs/winradio rigs/wj rigs/yaesu rigs/gomspace" +RIG_BACKEND_LIST="rigs/adat rigs/alinco rigs/aor rigs/barrett rigs/codan rigs/dorji rigs/drake rigs/dummy rigs/elad rigs/flexradio rigs/icom rigs/icmarine rigs/jrc rigs/kachina rigs/kenwood rigs/kit rigs/lowe rigs/pcr rigs/prm80 rigs/racal rigs/rft rigs/rs rigs/skanti rigs/tapr rigs/tentec rigs/tuner rigs/uniden rigs/winradio rigs/wj rigs/yaesu rigs/gomspace rigs/mds" ROT_BACKEND_LIST="rotators/amsat rotators/ars rotators/celestron rotators/cnctrk rotators/grbltrk rotators/easycomm rotators/ether6 rotators/fodtrack rotators/gs232a rotators/heathkit rotators/m2 rotators/meade rotators/rotorez rotators/sartek rotators/spid rotators/ts7400 rotators/prosistel rotators/ioptron rotators/satel rotators/radant" # Amplifiers are all in the amplifiers directory AMP_BACKEND_LIST="amplifiers/elecraft amplifiers/gemini" @@ -879,6 +879,7 @@ rigs/winradio/Makefile rigs/wj/Makefile rigs/yaesu/Makefile rigs/gomspace/Makefile +rigs/mds/Makefile tests/Makefile scripts/Makefile android/Makefile diff --git a/include/hamlib/riglist.h b/include/hamlib/riglist.h index a68432246..fd7a1d931 100644 --- a/include/hamlib/riglist.h +++ b/include/hamlib/riglist.h @@ -659,6 +659,12 @@ #define RIG_MODEL_GS100 RIG_MAKE_MODEL(RIG_GOMSPACE, 1) //! @endcond +/* + * MDS Microwave Data Systems https://en.wikipedia.org/wiki/Microwave_Data_Systems + */ +#define RIG_MDS 36 +#define RIG_BACKEND_MDS "MDS" +#define RIG_MODEL_MDS4710 RIG_MAKE_MODEL(RIG_MDS, 1) /* * TODO: RIG_MODEL_KWZ30, KNEISNER +DOERING diff --git a/rigs/mds/Android.mk b/rigs/mds/Android.mk new file mode 100644 index 000000000..1de627344 --- /dev/null +++ b/rigs/mds/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := mds.c +LOCAL_MODULE := mds + +LOCAL_CFLAGS := +LOCAL_C_INCLUDES := android include src +LOCAL_LDLIBS := -lhamlib -Lobj/local/$(TARGET_ARCH_ABI) + +include $(BUILD_STATIC_LIBRARY) diff --git a/rigs/mds/Makefile.am b/rigs/mds/Makefile.am new file mode 100644 index 000000000..ca478ddeb --- /dev/null +++ b/rigs/mds/Makefile.am @@ -0,0 +1,6 @@ +MDSSRC = mds.c + +noinst_LTLIBRARIES = libhamlib-mds.la +libhamlib_mds_la_SOURCES = $(MDSSRC) + +EXTRA_DIST = Android.mk diff --git a/rigs/mds/mds.c b/rigs/mds/mds.c new file mode 100644 index 000000000..0e9cd93fd --- /dev/null +++ b/rigs/mds/mds.c @@ -0,0 +1,659 @@ +/* + * Hamlib MDS 4710/9710 backend - main file + * Copyright (c) 2022 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 "serial.h" +#include "misc.h" +#include "cal.h" +#include "token.h" +#include "register.h" + +#include "mds.h" + +#define MAXCMDLEN 32 + +#define MDS_VFOS (RIG_VFO_A) + +#define MDS_MODES (RIG_MODE_NONE) + +#define MDS_LEVELS (RIG_LEVEL_NONE) + + +const struct rig_caps mds_caps; + +DECLARE_INITRIG_BACKEND(mds) +{ + rig_debug(RIG_DEBUG_VERBOSE, "%s: _init called\n", __func__); + + rig_register(&mds_caps); + rig_debug(RIG_DEBUG_VERBOSE, "%s: _init back from rig_register\n", __func__); + + return RIG_OK; +} + +int mds_transaction(RIG *rig, char *cmd, int expected, char **result) +{ + char cmd_buf[MAXCMDLEN]; + int retval; + struct rig_state *rs = &rig->state; + struct mds_priv_data *priv = rig->state.priv; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: cmd=%s\n", __func__, cmd); + + SNPRINTF(cmd_buf, sizeof(cmd_buf), "%s\n", cmd); + + rig_flush(&rs->rigport); + retval = write_block(&rs->rigport, (unsigned char *) cmd_buf, strlen(cmd_buf)); + + if (retval < 0) + { + return retval; + } + + if (expected == 0) + { + return RIG_OK; + } + else + { + char cmdtrm_str[2]; /* Default Command/Reply termination char */ + cmdtrm_str[0] = 0x0d; + cmdtrm_str[1] = 0x00; + retval = read_string(&rs->rigport, (unsigned char *) priv->ret_data, + sizeof(priv->ret_data), cmdtrm_str, strlen(cmdtrm_str), 0, expected); + + if (retval < 0) + { + rig_debug(RIG_DEBUG_ERR, "%s(%d): error in read_block\n", __func__, __LINE__); + return retval; + } + } + + if (result != NULL) + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: setting result\n", __func__); + *result = &(priv->ret_data[0]); + } + else + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: no result requested\n", __func__); + } + + return RIG_OK; +} + +int mds_init(RIG *rig) +{ + rig_debug(RIG_DEBUG_VERBOSE, "%s version %s\n", __func__, rig->caps->version); + // cppcheck claims leak here but it's freed in cleanup + rig->state.priv = (struct mds_priv_data *)calloc(1, + sizeof(struct mds_priv_data)); + + if (!rig->state.priv) + { + return -RIG_ENOMEM; + } + + return RIG_OK; +} + +/* + * mds_cleanup + * + */ + +int mds_cleanup(RIG *rig) +{ + + rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + + if (!rig) + { + return -RIG_EINVAL; + } + + if (rig->state.priv) + { + free(rig->state.priv); + } + + rig->state.priv = NULL; + + return RIG_OK; +} + +/* + * mds_get_freq + * Assumes rig!=NULL, rig->state.priv!=NULL, freq!=NULL + */ +int mds_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) +{ + int retval; + char *response = NULL; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo)); + *freq = 0; + + retval = mds_transaction(rig, "TX", 16, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: invalid response=%s\n", __func__, response); + return retval; + } + + retval = sscanf(response, "%lg", freq); + + if (retval != 1) + { + rig_debug(RIG_DEBUG_ERR, "%s: Unable to parse response\n", __func__); + return -RIG_EPROTO; + } + + return RIG_OK; +} + +// TC command does not work on 4050 -- not implemented as of 2022-01-12 +/* + * mds_set_freq + * assumes rig!=NULL, rig->state.priv!=NULL + */ +int mds_set_freq(RIG *rig, vfo_t vfo, freq_t freq) +{ + char cmd_buf[MAXCMDLEN]; + int retval; + freq_t tfreq; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s freq=%.0f\n", __func__, + rig_strvfo(vfo), freq); + + retval = rig_get_freq(rig, vfo, &tfreq); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: get_freq failed: %s\n", __func__, + strerror(retval)); + return retval; + } + + if (tfreq == freq) + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: freq not changing\n", __func__); + return RIG_OK; + } + + // If we are not explicitly asking for VFO_B then we'll set the receive side also + if (vfo != RIG_VFO_B) + { + char *response = NULL; + SNPRINTF((char *) cmd_buf, sizeof(cmd_buf), "TX%.4f", freq / 1e6); + retval = mds_transaction(rig, cmd_buf, 0, &response); + + if (retval < 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: TX failed\n", __func__); + return retval; + } + + SNPRINTF((char *) cmd_buf, sizeof(cmd_buf), "RX%.4f", freq / 1e6); + retval = mds_transaction(rig, cmd_buf, 0, &response); + + if (retval < 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: RX failed\n", __func__); + return retval; + } + } + + return RIG_OK; +} + +/* + * mds_set_ptt + * Assumes rig!=NULL + */ +int mds_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt) +{ + int retval; + char cmd_buf[MAXCMDLEN]; + char *response = NULL; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: ptt=%d\n", __func__, ptt); + + SNPRINTF(cmd_buf, sizeof(cmd_buf), "%s", ptt ? "KEY" : "DKEY"); + retval = mds_transaction(rig, cmd_buf, 0, &response); + + if (retval < 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: invalid response=%s\n", __func__, response); + return retval; + } + + if (strncmp(response, "OK", 2) != 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: Expected OK, got '%s'\n", __func__, response); + return -RIG_EINVAL; + } + + rig_debug(RIG_DEBUG_VERBOSE, "%s: cmd:IP result=%s\n", __func__, response); + + return RIG_OK; +} + +/* + * mds_get_ptt + * Assumes rig!=NULL + */ +int mds_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt) +{ + int retval; + char *response = NULL; + char c; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo)); + + retval = mds_transaction(rig, "IP", 0, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: error response?='%s'\n", __func__, response); + return retval; + } + + c = response[0]; + + if (c == '1' || c == '0') + { + *ptt = c - '0'; + } + else + { + rig_debug(RIG_DEBUG_ERR, "%s: error response='%s'\n", __func__, response); + return -RIG_EPROTO; + } + + return RIG_OK; +} + +/* + * mds_set_mode + * Assumes rig!=NULL + * Note that 2050 does not have set or get width + */ +int mds_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) +{ + char cmd_buf[32], ttmode; + int retval; + rmode_t tmode; + pbwidth_t twidth; + + //struct tt588_priv_data *priv = (struct tt588_priv_data *) rig->state.priv; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s mode=%s width=%d\n", __func__, + rig_strvfo(vfo), rig_strrmode(mode), (int)width); + + retval = rig_get_mode(rig, vfo, &tmode, &twidth); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: get_mode failed %s\n", __func__, + strerror(retval)); + } + + if (tmode == mode) + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: already mode %s so not changing\n", __func__, + rig_strrmode(mode)); + return RIG_OK; + } + + switch (mode) + { + case RIG_MODE_USB: + ttmode = 'U'; + break; + + case RIG_MODE_LSB: + ttmode = 'L'; + break; + + case RIG_MODE_CW: + ttmode = 'C'; + break; + + case RIG_MODE_AM: + ttmode = 'A'; + break; + + case RIG_MODE_RTTY: + ttmode = 'F'; + break; + + default: + rig_debug(RIG_DEBUG_ERR, "%s: unsupported mode %s\n", __func__, + rig_strrmode(mode)); + return -RIG_EINVAL; + } + + SNPRINTF((char *) cmd_buf, sizeof(cmd_buf), "TB%c\n", ttmode); + + retval = mds_transaction(rig, cmd_buf, 0, NULL); + + if (retval < 0) + { + return retval; + } + + return RIG_OK; +} + +/* + * mds_get_mode + * Assumes rig!=NULL + * Note that 2050 does not have set or get width + */ +int mds_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width) +{ + char *result = NULL; + int retval; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo)); + + retval = mds_transaction(rig, "IB", 0, &result); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: bad response=%s\n", __func__, result); + return retval; + } + + //dump_hex((unsigned char *)result,strlen(result)); + switch (result[1]) + { + case 'L': + *mode = RIG_MODE_LSB; + break; + + case 'U': + *mode = RIG_MODE_USB; + break; + + case 'A': + *mode = RIG_MODE_AM; + break; + + case 'F': + *mode = RIG_MODE_RTTY; + break; + + case 'C': + *mode = RIG_MODE_CW; + break; + + default: + rig_debug(RIG_DEBUG_ERR, "%s: Unknown mode='%c%c'\n", __func__, result[0], + result[1]); + return -RIG_EPROTO; + } + + *width = 3000; // we'll default this to 3000 for now + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s mode=%s width=%d\n", __func__, + rig_strvfo(vfo), rig_strrmode(*mode), (int)*width); + + return RIG_OK; +} + +#if 0 +int mds_get_vfo(RIG *rig, vfo_t *vfo) +{ + *vfo = RIG_VFO_A; + + if (check_vfo(*vfo) == FALSE) + { + rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __func__, + rig_strvfo(*vfo)); + return -RIG_EINVAL; + } + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(*vfo)); + + return RIG_OK; +} +#endif + +/* + * mds_get_level + */ +int mds_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val) +{ + int retval = 0; + char *response = NULL; + + switch (level) + { + int strength; + int n; + + case RIG_LEVEL_STRENGTH: + retval = mds_transaction(rig, "IAL", 0, &response); + + if (retval < 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: invalid response=%s\n", __func__, + response); + return retval; + } + + n = sscanf(response, "%2d", &strength); + + if (n == 1) + { + val->i = strength; + } + else + { + rig_debug(RIG_DEBUG_ERR, "%s: unable to parse STRENGTH from %s\n", + __func__, response); + return -RIG_EPROTO; + } + + break; + + default: + rig_debug(RIG_DEBUG_ERR, "%s: unsupported level %s\n", __func__, + rig_strlevel(level)); + return -RIG_EINVAL; + } + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s level=%s val=%s\n", __func__, + rig_strvfo(vfo), rig_strlevel(level), response); + + return RIG_OK; +} + +/* + * mds_get_info + */ +const char *mds_get_info(RIG *rig) +{ + char *response = NULL; + int retval; + + rig_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__); + + retval = mds_transaction(rig, "MODEL", 16, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_WARN, "%s: MODEL command failed: %s\n", __func__, + strerror(retval)); + } + else + { + rig_debug(RIG_DEBUG_VERBOSE, "Model: %s\n", response); + } + + response = NULL; + retval = mds_transaction(rig, "SER", 16, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_WARN, "%s: SER command failed: %s\n", __func__, + strerror(retval)); + } + else + { + rig_debug(RIG_DEBUG_VERBOSE, "Serial# %s\n", response); + } + + response = NULL; + retval = mds_transaction(rig, "SREV", 16, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_WARN, "%s: SREV command failed: %s\n", __func__, + strerror(retval)); + } + else + { + rig_debug(RIG_DEBUG_VERBOSE, "Firmware %s\n", response); + } + + response = NULL; + retval = mds_transaction(rig, "SHOW DC", 16, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: SHOW DC failed result=%s\n", __func__, response); + } + else + { + rig_debug(RIG_DEBUG_VERBOSE, "DC %s\n", response); + } + + return response; +} + +int mds_open(RIG *rig) +{ + char *response = NULL; + int retval; + + ENTERFUNC; + mds_get_info(rig); + retval = mds_transaction(rig, "MODEM NONE", 0, &response); + retval = mds_transaction(rig, "PTT 0", 0, &response); + RETURNFUNC(retval); +} + +const struct rig_caps mds_caps = +{ + RIG_MODEL(RIG_MODEL_MDS4710), + .model_name = "4710", + .mfg_name = "MDS", + .version = "20221114.0", + .copyright = "LGPL", + .status = RIG_STATUS_BETA, + .rig_type = RIG_TYPE_TRANSCEIVER, +// .targetable_vfo = RIG_TARGETABLE_FREQ | RIG_TARGETABLE_MODE, + .ptt_type = RIG_PTT_RIG, + .dcd_type = RIG_DCD_NONE, + .port_type = RIG_PORT_SERIAL, + .serial_rate_min = 110, + .serial_rate_max = 38400, + .serial_data_bits = 8, + .serial_stop_bits = 1, + .serial_parity = RIG_PARITY_NONE, + .serial_handshake = RIG_HANDSHAKE_HARDWARE, + .write_delay = 0, + .post_write_delay = 0, + .timeout = 1000, + .retry = 3, + + .has_get_func = RIG_FUNC_NONE, + .has_set_func = RIG_FUNC_NONE, + .has_get_level = MDS_LEVELS, + .has_set_level = RIG_LEVEL_NONE, + .has_get_parm = RIG_PARM_NONE, + .has_set_parm = RIG_PARM_NONE, +// .level_gran = { [LVL_CWPITCH] = { .step = { .i = 10 } } }, +// .ctcss_list = common_ctcss_list, +// .dcs_list = full_dcs_list, +// 2050 does have channels...not implemented yet as no need yet +// .chan_list = { +// { 0, 18, RIG_MTYPE_MEM, DUMMY_MEM_CAP }, +// { 19, 19, RIG_MTYPE_CALL }, +// { 20, NB_CHAN-1, RIG_MTYPE_EDGE }, +// RIG_CHAN_END, +// }, +// .scan_ops = DUMMY_SCAN, +// .vfo_ops = DUMMY_VFO_OP, + .transceive = RIG_TRN_RIG, + .rx_range_list1 = { + {.startf = MHz(380), .endf = MHz(530), .modes = RIG_MODE_ALL, + .low_power = 0, .high_power = 0, MDS_VFOS, RIG_ANT_1 + }, + RIG_FRNG_END, + }, + .rx_range_list2 = {RIG_FRNG_END,}, + .tx_range_list1 = { + {MHz(380), MHz(530), RIG_MODE_ALL, W(.1), W(5), RIG_VFO_A, RIG_ANT_NONE}, + RIG_FRNG_END, + }, +// .tx_range_list2 = {RIG_FRNG_END,} + .tuning_steps = { + // Rem: no support for changing tuning step + {RIG_MODE_ALL, 6250}, + RIG_TS_END, + }, + + .filters = { + {RIG_MODE_ALL, RIG_FLT_ANY}, + RIG_FLT_END + }, + .priv = NULL, + + .rig_init = mds_init, + .rig_open = mds_open, + .rig_cleanup = mds_cleanup, + +// .set_conf = dummy_set_conf, +// .get_conf = dummy_get_conf, + + .set_freq = mds_set_freq, + .get_freq = mds_get_freq, +// .set_mode = mds_set_mode, +// .get_mode = mds_get_mode, + +// .set_level = dummy_set_level, +// .get_level = mds_get_level, + + .get_info = mds_get_info, + .set_ptt = mds_set_ptt, + .get_ptt = mds_get_ptt, +// .get_dcd = dummy_get_dcd, + .hamlib_check_rig_caps = HAMLIB_CHECK_RIG_CAPS +}; diff --git a/rigs/mds/mds.h b/rigs/mds/mds.h new file mode 100644 index 000000000..b9d0cfce1 --- /dev/null +++ b/rigs/mds/mds.h @@ -0,0 +1,9 @@ +#define MDS_DATA_LEN 256 +#define MDS_RET_LEN 256 +struct mds_priv_data { + char cmd_str[MDS_DATA_LEN]; /* command string buffer */ + char ret_data[MDS_RET_LEN]; /* returned data--max value, most are less */ +}; + +extern const struct rig_caps barrett_caps; + diff --git a/src/register.c b/src/register.c index c9aa48418..d49f2b9f2 100644 --- a/src/register.c +++ b/src/register.c @@ -44,7 +44,7 @@ # define PATH_MAX 1024 #endif -#define RIG_BACKEND_MAX 32 +#define RIG_BACKEND_MAX 50 #define DEFINE_INITRIG_BACKEND(backend) \ int MAKE_VERSIONED_FN(PREFIX_INITRIG, ABI_VERSION, backend(void *be_handle)); \ @@ -89,6 +89,7 @@ DEFINE_INITRIG_BACKEND(barrett); DEFINE_INITRIG_BACKEND(elad); DEFINE_INITRIG_BACKEND(codan); DEFINE_INITRIG_BACKEND(gomspace); +DEFINE_INITRIG_BACKEND(mds); //! @endcond #ifdef HAVE_WINRADIO @@ -147,6 +148,7 @@ static struct { RIG_ELAD, RIG_BACKEND_ELAD, RIG_FUNCNAMA(elad) }, { RIG_CODAN, RIG_BACKEND_CODAN, RIG_FUNCNAMA(codan) }, { RIG_GOMSPACE, RIG_BACKEND_GOMSPACE, RIG_FUNCNAM(gomspace) }, + { RIG_MDS, RIG_BACKEND_MDS, RIG_FUNCNAMA(mds) }, { 0, NULL }, /* end */ };