/* * GomSpace backend and the GS100 radio control module * * Created in 2022 by Richard Linhart OK1CTR OK1CTR@gmail.com * and used during VZLUSAT-2 satellite mission. * * 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 * */ /* Includes ------------------------------------------------------------------*/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include "hamlib/rig.h" #include "serial.h" #include "parallel.h" #include "cm108.h" #include "gpio.h" #include "misc.h" #include "tones.h" #include "idx_builtin.h" #include "register.h" #include "gs100.h" /* Private Defines -----------------------------------------------------------*/ // If defined, commands are software simulated only #undef _LOCAL_SIMULATION_ // If true, configuration table switching is minimized #define PARAM_MEM_MINIMAL 1 // Buffer size for serial transfers #define BUFSZ 256 // Last character on each line received from RIG #define GOM_STOPSET "\n" // Exact character sequence on the RIG's command prompt #define GOM_PROMPT "\x1B[1;32mnanocom-ax\x1B[1;30m # \x1B[0m\x1B[0m" // Maximum number of lines parsed from GS100 response #define GOM_MAXLINES 20 // RIG's parametric table number for receive #define GOM_CONFIG_TAB_RX 1 // RIG's parametric table number for transmit #define GOM_CONFIG_TAB_TX 5 /* Private Typedefs ----------------------------------------------------------*/ /** * GS100 rig private data structure */ struct gs100_priv_data { freq_t freq_rx; ///< currently just for backup and TRX emulation freq_t freq_tx; ///< currently just for backup and TRX emulation int param_mem; ///< last value of configuration table selection }; /* Imported Functions --------------------------------------------------------*/ struct ext_list *alloc_init_ext(const struct confparams *cfp); struct ext_list *find_ext(struct ext_list *elp, token_t token); /* Private function prototypes -----------------------------------------------*/ /** * Set variable in the GS100 configuration table */ static int gomx_set(RIG *rig, int table, char *varname, char *varvalue); /** * Get variable from the GS100 configuration table */ static int gomx_get(RIG *rig, int table, char *varname, char *varvalue); /** * Sends a message to the GS100 and parses response lines */ static int gomx_transaction(RIG *rig, char *message, char *response); /* Functions -----------------------------------------------------------------*/ /* GS100 transceiver control init */ static int gs100_init(RIG *rig) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; ENTERFUNC; if (!rig || !rig->caps) { RETURNFUNC(-RIG_EINVAL); } priv = (struct gs100_priv_data *)malloc(sizeof(struct gs100_priv_data)); if (!priv) { RETURNFUNC(-RIG_ENOMEM); } rig->state.priv = (void *)priv; #ifdef _LOCAL_SIMULATION_ rig->state.rigport.type.rig = RIG_PORT_NONE; // just simulation priv->freq_rx = rig->caps->rx_range_list1->startf; priv->freq_tx = rig->caps->tx_range_list1->startf; #endif priv->param_mem = -1; // means undefined last selection RETURNFUNC(RIG_OK); } /* GS100 transceiver control deinit */ static int gs100_cleanup(RIG *rig) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; ENTERFUNC; if (rig->state.priv) { free(rig->state.priv); } rig->state.priv = NULL; RETURNFUNC(RIG_OK); } /* GS100 transceiver open */ static int gs100_open(RIG *rig) { ENTERFUNC; if (rig->caps->rig_model == RIG_MODEL_GS100) { rig_debug(RIG_DEBUG_VERBOSE, "%s: OPENING'\n", __func__); } RETURNFUNC(RIG_OK); } /* GS100 transceiver close */ static int gs100_close(RIG *rig) { ENTERFUNC; if (rig->caps->rig_model == RIG_MODEL_GS100) { rig_debug(RIG_DEBUG_VERBOSE, "%s: CLOSING'\n", __func__); } RETURNFUNC(RIG_OK); } /* GS100 transceiver set configuration */ static int gs100_set_conf(RIG *rig, token_t token, const char *val) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; ENTERFUNC; priv = (struct gs100_priv_data *)rig->state.priv; switch (token) { case 0: break; case 1: break; default: RETURNFUNC(-RIG_EINVAL); } RETURNFUNC(RIG_OK); } /* GS100 transceiver get configuration */ static int gs100_get_conf(RIG *rig, token_t token, char *val) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; ENTERFUNC; priv = (struct gs100_priv_data *)rig->state.priv; switch (token) { case 0: break; default: RETURNFUNC(-RIG_EINVAL); } RETURNFUNC(RIG_OK); } /* GS100 transceiver set receiver frequency */ static int gs100_set_freq(RIG *rig, vfo_t vfo, freq_t freq) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; char fstr[20], value[20]; int retval; ENTERFUNC; // reporting sprintf_freq(fstr, sizeof(fstr), freq); rig_debug(RIG_DEBUG_VERBOSE, "%s: fstr = '%s'\n", __func__, fstr); #ifdef _LOCAL_SIMULATION_ priv->freq_rx = freq; #endif // range check - GS100 don't do it! if (freq < rig->caps->rx_range_list1->startf || freq > rig->caps->rx_range_list1->endf) RETURNFUNC(-RIG_EDOM); // perform set command sprintf(value, "%1.0lf", freq); retval = gomx_set(rig, GOM_CONFIG_TAB_RX, "freq", value); if (retval != RIG_OK) RETURNFUNC(retval); RETURNFUNC(retval); } /* GS100 transceiver get receiver frequency */ static int gs100_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; char resp[20]; int retval; freq_t f; ENTERFUNC; // perform the get command retval = gomx_get(rig, GOM_CONFIG_TAB_RX, "freq", resp); if (retval != RIG_OK) RETURNFUNC(retval); #ifdef _LOCAL_SIMULATION_ *freq = priv->freq_rx; #else retval = sscanf(resp, "%lf", &f); #endif // relevance check if (retval != 1) RETURNFUNC(-RIG_EPROTO); if (f < rig->caps->rx_range_list1->startf || f > rig->caps->rx_range_list1->endf) RETURNFUNC(-RIG_EDOM); *freq = f; RETURNFUNC(RIG_OK); } /* GS100 transceiver set transmitter frequency */ static int gs100_set_tx_freq(RIG *rig, vfo_t vfo, freq_t freq) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; char fstr[20], value[20]; int retval; ENTERFUNC; // reporting sprintf_freq(fstr, sizeof(fstr), freq); rig_debug(RIG_DEBUG_VERBOSE, "%s: fstr = '%s'\n", __func__, fstr); #ifdef _LOCAL_SIMULATION_ priv->freq_tx = freq; #endif // range check - GS100 don't do it! if (freq < rig->caps->tx_range_list1->startf || freq > rig->caps->tx_range_list1->endf) RETURNFUNC(-RIG_EDOM); // perform set command sprintf(value, "%1.0lf", freq); retval = gomx_set(rig, GOM_CONFIG_TAB_TX, "freq", value); if (retval != RIG_OK) RETURNFUNC(retval); RETURNFUNC(RIG_OK); } /* GS100 transceiver get transmitter frequency */ static int gs100_get_tx_freq(RIG *rig, vfo_t vfo, freq_t *freq) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; char resp[20]; int retval; freq_t f; ENTERFUNC; // perform the get command retval = gomx_get(rig, GOM_CONFIG_TAB_TX, "freq", resp); if (retval != RIG_OK) RETURNFUNC(retval); #ifdef _LOCAL_SIMULATION_ *freq = priv->freq_tx; #else retval = sscanf(resp, "%lf", &f); #endif // relevance check if (retval != 1) RETURNFUNC(-RIG_EPROTO); if (f < rig->caps->tx_range_list1->startf || f > rig->caps->tx_range_list1->endf) RETURNFUNC(-RIG_EDOM); *freq = f; RETURNFUNC(RIG_OK); } /* GS100 transceiver get info */ static const char *gs100_get_info(RIG *rig) { return("Gomspace Ground Station Transceiver GS100"); } /* The HAMLIB RIG Capabilities Structure Definition --------------------------*/ #define GS100_FUNC (0) #define GS100_LEVEL (0) #define GS100_PARM (0) #define GS100_VFO_OPS RIG_OP_TUNE #define GS100_VFOS (RIG_VFO_A) #define GS100_MODES (RIG_MODE_PKTFM) struct rig_caps GS100_caps = { RIG_MODEL(RIG_MODEL_GS100), .model_name = "GS100", .mfg_name = "GOMSPACE", .version = "20211117.0", .copyright = "LGPL", .status = RIG_STATUS_ALPHA, .rig_type = RIG_TYPE_TRANSCEIVER, .targetable_vfo = 0, .ptt_type = RIG_PTT_NONE, .dcd_type = RIG_DCD_NONE, .port_type = RIG_PORT_SERIAL, .serial_rate_min = 115200, .serial_rate_max = 500000, .serial_data_bits = 8, .serial_stop_bits = 1, .serial_parity = RIG_PARITY_NONE, .serial_handshake = RIG_HANDSHAKE_NONE, .write_delay = 1, .post_write_delay = 1, .timeout = 250, .retry = 0, .has_get_func = GS100_FUNC, .has_set_func = GS100_FUNC, .has_get_level = GS100_LEVEL, .has_set_level = GS100_LEVEL, .has_get_parm = GS100_PARM, .has_set_parm = GS100_PARM, .vfo_ops = GS100_VFO_OPS, .rx_range_list1 = { { .startf = MHz(430), .endf = MHz(440), .modes = GS100_MODES, .low_power = -1, .high_power = -1, GS100_VFOS, .label = "GS100#1" }, RIG_FRNG_END, }, .tx_range_list1 = { { .startf = MHz(430), .endf = MHz(440), .modes = GS100_MODES, .low_power = -1, .high_power = -1, GS100_VFOS, .label = "GS100#1" }, RIG_FRNG_END, }, .tuning_steps = { {GS100_MODES, Hz(10)}, RIG_TS_END, }, .priv = NULL, .rig_init = gs100_init, .rig_cleanup = gs100_cleanup, .rig_open = gs100_open, .rig_close = gs100_close, .set_conf = gs100_set_conf, .get_conf = gs100_get_conf, .set_freq = gs100_set_freq, .get_freq = gs100_get_freq, .set_split_freq = gs100_set_tx_freq, .get_split_freq = gs100_get_tx_freq, .get_info = gs100_get_info, }; /* Private functions ---------------------------------------------------------*/ /* Set variable in the GS100 configuration table */ static int gomx_set(RIG *rig, int table, char *varname, char *varvalue) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; int retval; char msg[BUFSZ], resp[BUFSZ]; assert(rig != NULL); assert(varname != NULL); assert(varvalue != NULL); rig_debug(RIG_DEBUG_TRACE, "%s: table=%d, '%s' = '%s'\n", __func__, table, varname, varvalue); if (!PARAM_MEM_MINIMAL || table != priv->param_mem) { // select the configuration table priv->param_mem = table; sprintf(msg, "param mem %d\n", table); retval = gomx_transaction(rig, msg, resp); if (retval != RIG_OK) return(retval); } // set the variable sprintf(msg, "param set %s %s\n", varname, varvalue); retval = gomx_transaction(rig, msg, resp); if (retval != RIG_OK) return(retval); // check response if (strlen(resp) > 0) return(-RIG_EPROTO); return(RIG_OK); } /* Get variable from the GS100 configuration table */ static int gomx_get(RIG *rig, int table, char *varname, char *varvalue) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; int retval; char msg[BUFSZ], resp[BUFSZ], *c; assert(rig != NULL); assert(varname != NULL); assert(varvalue != NULL); rig_debug(RIG_DEBUG_TRACE, "%s: table=%d, '%s'\n", __func__, table, varname); if (!PARAM_MEM_MINIMAL || table != priv->param_mem) { // select the configuration table priv->param_mem = table; sprintf(msg, "param mem %d\n", table); retval = gomx_transaction(rig, msg, resp); if (retval != RIG_OK) return(retval); } // get the variable sprintf(msg, "param get %s\n", varname); retval = gomx_transaction(rig, msg, resp); if (retval != RIG_OK) return(retval); // check response and extract the value if ((c = strchr(resp, '=')) == NULL) return(-RIG_EPROTO); if (sscanf(c + 1, "%s", varvalue) != 1) return(-RIG_EPROTO); return(RIG_OK); } /* Sends a message to the GS100 and parses response lines */ static int gomx_transaction(RIG *rig, char *message, char *response) { __attribute__((unused)) struct gs100_priv_data *priv = (struct gs100_priv_data *)rig->state.priv; struct rig_state *rs; int retval, n = 0; char buf[BUFSZ]; assert(rig != NULL); assert(message != NULL); assert(response != NULL); rig_debug(RIG_DEBUG_TRACE, "%s: msg='%s'\n", __func__, message == NULL ? "NULL" : message); // access to private variables rs = &rig->state; priv = (struct gs100_priv_data *)rs->priv; // send message to the transceiver rig_flush(&rs->rigport); retval = write_block(&rs->rigport, (uint8_t*)message, strlen(message)); if (retval != RIG_OK) return(retval); while (1) { // read the response line retval = read_string(&rs->rigport, (unsigned char*)buf, BUFSZ, (const char*)GOM_STOPSET, 0, strlen(GOM_STOPSET), 0); if (retval < 0) return(retval); if (retval == 0) return(-RIG_ETIMEOUT); n++; // prompt is always the last line if (strcmp(buf, GOM_PROMPT) == 0) break; // before last line would be the response if (n > 1) strcpy(response, buf); else *response = '\0'; // don't return command echo if (n > GOM_MAXLINES) return(-RIG_EPROTO); } // report the response rig_debug(RIG_DEBUG_VERBOSE, "%s: returning response='%s'\n", __func__, response == NULL ? "NULL" : response); return(RIG_OK); } /* System Integration Functions ----------------------------------------------*/ /* Init RIG backend function */ DECLARE_INITRIG_BACKEND(gomspace) { rig_debug(RIG_DEBUG_VERBOSE, "%s: _init called\n", __func__); rig_register(&GS100_caps); return(RIG_OK); } /* Probe RIG backend function */ DECLARE_PROBERIG_BACKEND(gomspace) { return(RIG_MODEL_GS100); } /*----------------------------------------------------------------------------*/