From 0ccf0b480c8b24d84f087d86401a27b93be0e719 Mon Sep 17 00:00:00 2001 From: Mike Black W9MDB Date: Mon, 27 Dec 2021 11:48:05 -0600 Subject: [PATCH] Add CODAN Envoy and NGS rigs --- configure.ac | 3 +- rigs/codan/Makefile.am | 6 + rigs/codan/README.codan | 11 + rigs/codan/codan.c | 520 ++++++++++++++++++++++++++++++++++++++++ rigs/codan/codan.h | 60 +++++ src/register.c | 2 + 6 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 rigs/codan/Makefile.am create mode 100644 rigs/codan/README.codan create mode 100644 rigs/codan/codan.c create mode 100644 rigs/codan/codan.h diff --git a/configure.ac b/configure.ac index 0f04b931f..259ae4a8f 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/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" +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" ROT_BACKEND_LIST="rotators/amsat rotators/ars rotators/celestron rotators/cnctrk 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" @@ -842,6 +842,7 @@ rigs/adat/Makefile rigs/alinco/Makefile rigs/aor/Makefile rigs/barrett/Makefile +rigs/codan/Makefile rigs/dorji/Makefile rigs/drake/Makefile rigs/dummy/Makefile diff --git a/rigs/codan/Makefile.am b/rigs/codan/Makefile.am new file mode 100644 index 000000000..e0e751d84 --- /dev/null +++ b/rigs/codan/Makefile.am @@ -0,0 +1,6 @@ +CODANSRC = codan.c codan.h + +noinst_LTLIBRARIES = libhamlib-codan.la +libhamlib_codan_la_SOURCES = $(CODANSRC) + +EXTRA_DIST = README.codan Android.mk diff --git a/rigs/codan/README.codan b/rigs/codan/README.codan new file mode 100644 index 000000000..0e57bd27c --- /dev/null +++ b/rigs/codan/README.codan @@ -0,0 +1,11 @@ +CODAN NGT and Envoy Radio Control Process. + +User Programming Responsibilities + +1. Set Admin password to '' (empty, or NO password) +2. Enable Free TX mode by the sales option available from CODAN USA +3. Must know the serial port connected to "CICS" in the radio +programming. This is most often the GP (general purpose) port which +is a 15-pin D-Sub connector on a flying lead on the back of the radio. +(NGT and Envoy models). Programming must be set to select "CICS" as +the device which is addressed via the GP port. diff --git a/rigs/codan/codan.c b/rigs/codan/codan.c new file mode 100644 index 000000000..3dbd629be --- /dev/null +++ b/rigs/codan/codan.c @@ -0,0 +1,520 @@ +/* + * Hamlib CODAN backend - main file + * Copyright (c) 2021 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 + * + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include "serial.h" +#include "misc.h" +#include "cal.h" +#include "token.h" +#include "register.h" + +#include "codan.h" + +#define MAXCMDLEN 32 + +#define CODAN_VFOS (RIG_VFO_A|RIG_VFO_B) + +#define CODAN_MODES (RIG_MODE_USB) + +int codan_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt); +static int codan_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt); +int codan_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width); +int codan_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, + pbwidth_t *width); + +int codan_transaction(RIG *rig, char *cmd, int expected, char **result) +{ + char cmd_buf[MAXCMDLEN]; + int retval, cmd_len; + struct rig_state *rs = &rig->state; + struct codan_priv_data *priv = rig->state.priv; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: cmd=%s\n", __func__, cmd); + + cmd_len = snprintf(cmd_buf, sizeof(cmd_buf), "%s%s", cmd, EOM); + + rig_flush(&rs->rigport); + retval = write_block(&rs->rigport, (unsigned char *) cmd_buf, cmd_len); + + if (retval < 0) + { + return retval; + } + + if (expected == 0) + { + // response format is reponse...0x0d0x0a + retval = read_string(&rs->rigport, (unsigned char *) priv->ret_data, + sizeof(priv->ret_data), + "\x0a", 1, 0, 1); + rig_debug(RIG_DEBUG_VERBOSE, "%s: result=%s, resultlen=%d\n", __func__, + priv->ret_data, (int)strlen(priv->ret_data)); + + if (retval < 0) + { + return retval; + } + } + else + { + retval = read_string(&rs->rigport, (unsigned char *) priv->ret_data, + sizeof(priv->ret_data), + "\x0a", 1, 0, 1); + + if (retval < 0) + { + return retval; + } + + if (strncmp(priv->ret_data, "LEVELS:", 7) == 0) + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: %s\n", __func__, priv->ret_data); + retval = read_string(&rs->rigport, (unsigned char *) priv->ret_data, + sizeof(priv->ret_data), + "\x0a", 1, 0, 1); + rig_debug(RIG_DEBUG_VERBOSE, "%s: %s\n", __func__, priv->ret_data); + } + } + + rig_debug(RIG_DEBUG_VERBOSE, "%s: retval=%d\n", __func__, retval); + rig_debug(RIG_DEBUG_VERBOSE, "%s: %s\n", __func__, priv->ret_data); + + if (result != NULL) + { + *result = &(priv->ret_data[0]); + rig_debug(RIG_DEBUG_VERBOSE, "%s: returning result=%s\n", __func__, + *result); + } + else + { + rig_debug(RIG_DEBUG_VERBOSE, "%s: no result requested\n", __func__); + } + + return RIG_OK; +} + +int codan_init(RIG *rig) +{ + char *results = NULL; + 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 codan_priv_data *)calloc(1, + sizeof(struct codan_priv_data)); + + if (!rig->state.priv) + { + return -RIG_ENOMEM; + } + + codan_transaction(rig, "login admin ''\r", 0, NULL); + codan_transaction(rig, "login\r", 1, &results); + + return RIG_OK; +} + +int codan_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; +} + + +/* + * codan_get_mode + * Assumes rig!=NULL + */ +int codan_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width) +{ + char *result = NULL; + char modeA[8], modeB[8]; + int widthA, widthB; + int retval; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo)); + + retval = codan_transaction(rig, "mode\r", 0, &result); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: bad response=%s\n", __func__, result); + return retval; + } + + rig_debug(RIG_DEBUG_VERBOSE, "%s: result=%s", __func__, result); + int n = sscanf(result, "MODE: %[A-Z], %[A-Z], %d, %d", modeA, modeB, &widthA, + &widthB); + + if (n != 4) + { + rig_debug(RIG_DEBUG_ERR, "%s: sscanf expected 4, got %d, %s\n", __func__, n, + result); + return -RIG_EPROTO; + } + + if (strncmp(modeA, "USB", 3) == 0) { *mode = RIG_MODE_USB; } + else if (strncmp(modeA, "LSB", 3) == 0) { *mode = RIG_MODE_LSB; } + else + { + rig_debug(RIG_DEBUG_ERR, "%s: Unknown mode=%s'\n", __func__, modeA) + return -RIG_EPROTO; + } + + *width = widthA; // 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; +} + +/* + * codan_set_mode + * Assumes rig!=NULL + */ +int codan_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) +{ + char cmd_buf[32], *ttmode; + char *response = NULL; + int retval; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s mode=%s width=%d\n", __func__, + rig_strvfo(vfo), rig_strrmode(mode), (int)width); + + switch (mode) + { + case RIG_MODE_USB: + ttmode = "USBW"; + break; + + case RIG_MODE_LSB: + ttmode = "LSBW"; + break; + + default: + rig_debug(RIG_DEBUG_ERR, "%s: unsupported mode %s\n", __func__, + rig_strrmode(mode)); + return -RIG_EINVAL; + } + + sprintf((char *) cmd_buf, "mode %s\r", ttmode); + + retval = codan_transaction(rig, cmd_buf, 0, &response); + + if (retval < 0) + { + return retval; + } + + return RIG_OK; +} + + +/* + * codan_set_freq + * assumes rig!=NULL, rig->state.priv!=NULL + */ +int codan_set_freq(RIG *rig, vfo_t vfo, freq_t freq) +{ + char cmd_buf[MAXCMDLEN]; + int retval; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s freq=%.0f\n", __func__, + rig_strvfo(vfo), freq); + + // Purportedly can't do split so we just set VFOB=VFOA + sprintf(cmd_buf, "connect tcvr rf %.0f %.0f\r", freq, freq); + + char *response = NULL; + retval = codan_transaction(rig, cmd_buf, 0, &response); + + if (retval < 0) + { + return retval; + } + + return retval; +} + +/* + * codan_get_freq + * Assumes rig!=NULL, rig->state.priv!=NULL, freq!=NULL + */ +int codan_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 = codan_transaction(rig, "freq\r", 0, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: invalid response=%s\n", __func__, response); + return retval; + } + + retval = sscanf(response, "FREQ: %lg", freq); + + if (retval != 1) + { + rig_debug(RIG_DEBUG_ERR, "%s: Unable to parse response\n", __func__); + return -RIG_EPROTO; + } + + return RIG_OK; +} + +/* + * codan_get_ptt + * Assumes rig!=NULL + */ +int codan_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt) +{ + int retval; + char *response = NULL; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __func__, rig_strvfo(vfo)); + + retval = codan_transaction(rig, "connect tcvr rf ptt", 0, &response); + + if (retval != RIG_OK) + { + rig_debug(RIG_DEBUG_ERR, "%s: error response?='%s'\n", __func__, response); + return retval; + } + + char *p = strstr(response, "Ptt"); + + if (p) + { + if (strcmp(p, "Ptt=Off") == 0) { *ptt = 0; } + else { *ptt = 1; } + } + else + { + rig_debug(RIG_DEBUG_ERR, "%s: unable to find Ptt in %s\n", __func__, response); + return -RIG_EPROTO; + } + + return RIG_OK; +} + +/* + * codan_set_ptt + * Assumes rig!=NULL + */ +int codan_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt) +{ + int retval; + char cmd_buf[MAXCMDLEN]; + char *response; + + rig_debug(RIG_DEBUG_VERBOSE, "%s: ptt=%d\n", __func__, ptt); + + sprintf(cmd_buf, "connect tcvr rf ptt %s\r", ptt == 0 ? "off" : "on"); + response = NULL; + retval = codan_transaction(rig, cmd_buf, 0, &response); + + if (retval < 0) + { + rig_debug(RIG_DEBUG_ERR, "%s: invalid response=%s\n", __func__, response); + return retval; + } + + rig_debug(RIG_DEBUG_VERBOSE, "%s: cmd result=%s\n", __func__, response); + + return RIG_OK; +} + + + + +const struct rig_caps envoy_caps = +{ + RIG_MODEL(RIG_MODEL_CODAN_ENVOY), + .model_name = "Envoy", + .mfg_name = "CODAN", + .version = BACKEND_VER ".0", + .copyright = "LGPL", + .status = RIG_STATUS_ALPHA, + .rig_type = RIG_TYPE_TRANSCEIVER, + .targetable_vfo = RIG_TARGETABLE_FREQ, + .ptt_type = RIG_PTT_RIG, + .dcd_type = RIG_DCD_NONE, + .port_type = RIG_PORT_SERIAL, + .serial_rate_min = 9600, + .serial_rate_max = 115200, + .serial_data_bits = 8, + .serial_stop_bits = 1, + .serial_parity = RIG_PARITY_NONE, + .serial_handshake = RIG_HANDSHAKE_NONE, + .write_delay = 0, + .post_write_delay = 50, + .timeout = 1000, + .retry = 3, + + .has_get_func = RIG_FUNC_NONE, + .has_set_func = RIG_FUNC_NONE, + .has_get_level = RIG_LEVEL_NONE, + .has_set_level = RIG_LEVEL_NONE, + .has_get_parm = RIG_PARM_NONE, + .has_set_parm = RIG_PARM_NONE, + .transceive = RIG_TRN_RIG, + .rx_range_list1 = {{ + .startf = kHz(1600), .endf = MHz(30), .modes = CODAN_MODES, + .low_power = -1, .high_power = -1, CODAN_VFOS, RIG_ANT_1 + }, + RIG_FRNG_END, + }, + .rx_range_list2 = {RIG_FRNG_END,}, + .tx_range_list1 = {RIG_FRNG_END,}, + .tx_range_list2 = {RIG_FRNG_END,}, + .tuning_steps = { {CODAN_MODES, 1}, {CODAN_MODES, RIG_TS_ANY}, RIG_TS_END, }, + .filters = { + {RIG_MODE_SSB, kHz(2.4)}, + {RIG_MODE_SSB, kHz(0.5)}, + {RIG_MODE_SSB, kHz(2.7)}, + {RIG_MODE_SSB, kHz(3.0)}, + RIG_FLT_END, + }, + .priv = NULL, + + .rig_init = codan_init, + .rig_cleanup = codan_cleanup, + + .set_freq = codan_set_freq, + .get_freq = codan_get_freq, + .set_mode = codan_set_mode, + .get_mode = codan_get_mode, + + .set_ptt = codan_set_ptt, + .get_ptt = codan_get_ptt, +}; + +const struct rig_caps ngs_caps = +{ + RIG_MODEL(RIG_MODEL_CODAN_NGT), + .model_name = "NGT", + .mfg_name = "CODAN", + .version = BACKEND_VER ".0", + .copyright = "LGPL", + .status = RIG_STATUS_ALPHA, + .rig_type = RIG_TYPE_TRANSCEIVER, + .targetable_vfo = RIG_TARGETABLE_FREQ, + .ptt_type = RIG_PTT_RIG, + .dcd_type = RIG_DCD_NONE, + .port_type = RIG_PORT_SERIAL, + .serial_rate_min = 9600, + .serial_rate_max = 115200, + .serial_data_bits = 8, + .serial_stop_bits = 1, + .serial_parity = RIG_PARITY_NONE, + .serial_handshake = RIG_HANDSHAKE_NONE, + .write_delay = 0, + .post_write_delay = 50, + .timeout = 1000, + .retry = 3, + + .has_get_func = RIG_FUNC_NONE, + .has_set_func = RIG_FUNC_NONE, + .has_get_level = RIG_LEVEL_NONE, + .has_set_level = RIG_LEVEL_NONE, + .has_get_parm = RIG_PARM_NONE, + .has_set_parm = RIG_PARM_NONE, + .transceive = RIG_TRN_RIG, + .rx_range_list1 = {{ + .startf = kHz(1600), .endf = MHz(30), .modes = CODAN_MODES, + .low_power = -1, .high_power = -1, CODAN_VFOS, RIG_ANT_1 + }, + RIG_FRNG_END, + }, + .rx_range_list2 = {RIG_FRNG_END,}, + .tx_range_list1 = {RIG_FRNG_END,}, + .tx_range_list2 = {RIG_FRNG_END,}, + .tuning_steps = { {CODAN_MODES, 1}, {CODAN_MODES, RIG_TS_ANY}, RIG_TS_END, }, + .filters = { + {RIG_MODE_SSB, kHz(2.4)}, + {RIG_MODE_SSB, kHz(0.5)}, + {RIG_MODE_SSB, kHz(2.7)}, + {RIG_MODE_SSB, kHz(3.0)}, + RIG_FLT_END, + }, + .priv = NULL, + + .rig_init = codan_init, + .rig_cleanup = codan_cleanup, + + .set_freq = codan_set_freq, + .get_freq = codan_get_freq, + .set_mode = codan_set_mode, + .get_mode = codan_get_mode, + + .set_ptt = codan_set_ptt, + .get_ptt = codan_get_ptt, +}; + + + +DECLARE_INITRIG_BACKEND(codan) +{ + rig_debug(RIG_DEBUG_VERBOSE, "%s: _init called\n", __func__); + + rig_register(&envoy_caps); + rig_register(&ngt_caps); + rig_debug(RIG_DEBUG_VERBOSE, "%s: _init back from rig_register\n", __func__); + + return RIG_OK; +} + + + + + + + + + + diff --git a/rigs/codan/codan.h b/rigs/codan/codan.h new file mode 100644 index 000000000..f4d425a19 --- /dev/null +++ b/rigs/codan/codan.h @@ -0,0 +1,60 @@ +/* + * Hamlib Barrett backend - main header + * Copyright (c) 2017 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 + * + */ + +#ifndef _CODAN_H +#define _CODAN_H 1 + +#include "hamlib/rig.h" + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#define BACKEND_VER "20181111" + +#define EOM "\x0d" +#define TRUE 1 +#define FALSE 0 +// For the current implemented command set 64 is long enough +// This will need a lot more room for some channel commands like IDFA which return all channels +// But that would 9999*41 or 406KB so didn't do that right now +#define CODAN_DATA_LEN 64 + +extern const struct rig_caps envoy_caps; +extern const struct rig_caps ngt_caps; + +struct codan_priv_data { + char cmd_str[CODAN_DATA_LEN]; /* command string buffer */ + char ret_data[CODAN_DATA_LEN]; /* returned data--max value, most are less */ +}; + +extern int codan_transaction(RIG *rig, char *cmd, int expected, char **result); + +extern int codan_init(RIG *rig); +extern int codan_cleanup(RIG *rig); +extern int codan_set_freq(RIG *rig, vfo_t vfo, freq_t freq); +extern int codan_get_freq(RIG *rig, vfo_t vfo, freq_t *freq); +extern int codan_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width); +extern int codan_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, + pbwidth_t *width); +extern int codan_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt); + +#endif /* _CODAN_H */ diff --git a/src/register.c b/src/register.c index cbe087649..9dd05e03e 100644 --- a/src/register.c +++ b/src/register.c @@ -90,6 +90,7 @@ DEFINE_INITRIG_BACKEND(adat); DEFINE_INITRIG_BACKEND(dorji); DEFINE_INITRIG_BACKEND(barrett); DEFINE_INITRIG_BACKEND(elad); +DEFINE_INITRIG_BACKEND(codan); //! @endcond #ifdef HAVE_WINRADIO @@ -146,6 +147,7 @@ static struct { RIG_DORJI, RIG_BACKEND_DORJI, RIG_FUNCNAMA(dorji) }, { RIG_BARRETT, RIG_BACKEND_BARRETT, RIG_FUNCNAMA(barrett) }, { RIG_ELAD, RIG_BACKEND_ELAD, RIG_FUNCNAMA(elad) }, + { RIG_CODAN, RIG_BACKEND_CODAN, RIG_FUNCNAMA(codan) }, { 0, NULL }, /* end */ };