/* * Hamlib FLRig backend - main file * 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 * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include /* String function definitions */ #include /* UNIX standard function definitions */ #include #include #include #include #include #include #include #include #include "flrig.h" #define DEBUG 1 #define MAXCMDLEN 8192 #define DEFAULTPATH "localhost:12345" #define FLRIG_VFOS (RIG_VFO_A|RIG_VFO_B|RIG_VFO_TX) #define FLRIG_MODES (RIG_MODE_AM | RIG_MODE_CW | RIG_MODE_RTTY | \ RIG_MODE_SSB | RIG_MODE_FM) #define RIG_DEBUG_TRACE RIG_DEBUG_VERBOSE static int flrig_init(RIG *rig); //int flrig_cleanup(RIG *rig); static int flrig_set_freq(RIG *rig, vfo_t vfo, freq_t freq); static int flrig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq); static int flrig_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt); static int flrig_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width); static int flrig_get_vfo(RIG *rig, vfo_t *vfo); static int flrig_set_vfo(RIG *rig, vfo_t vfo); static int flrig_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt); static int flrig_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt); static int flrig_set_split_freq(RIG *rig, vfo_t vfo, freq_t tx_freq); static int flrig_get_split_freq(RIG *rig, vfo_t vfo, freq_t *tx_freq); static int flrig_set_split_vfo(RIG *rig, vfo_t vfo, split_t split, vfo_t tx_vfo); static int flrig_get_split_vfo(RIG *rig, vfo_t vfo, split_t *split, vfo_t *tx_vfo); struct flrig_priv_data { vfo_t vfo_curr; }; const struct rig_caps flrig_caps = { .rig_model = RIG_MODEL_FLRIG, .model_name = "FLRig", .mfg_name = "FLRig", .version = BACKEND_VER, .copyright = "LGPL", .status = RIG_STATUS_ALPHA, .rig_type = RIG_TYPE_TRANSCEIVER, .targetable_vfo = 0, .ptt_type = RIG_PTT_RIG, .port_type = RIG_PORT_NETWORK, .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_RAWSTR | RIG_LEVEL_STRENGTH), .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, // .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, // }, // .vfo_ops = DUMMY_VFO_OP, .transceive = RIG_TRN_RIG, // .attenuator = { 10, 20, 30, RIG_DBLST_END, }, // .preamp = { 10, RIG_DBLST_END, }, // .rx_range_list1 = {{.start = kHz(150),.end = MHz(1500),.modes = FLRIG_MODES, // .low_power = -1,.high_power = -1, FLRIG_VFOS, RIG_ANT_1 | RIG_ANT_2}, // RIG_FRNG_END,}, // .tx_range_list1 = {RIG_FRNG_END,}, // .rx_range_list2 = {{.start = kHz(150),.end = MHz(1500),.modes = FLRIG_MODES, // .low_power = -1,.high_power = -1, FLRIG_VFOS, RIG_ANT_1 | RIG_ANT_2}, // RIG_FRNG_END,}, // .tx_range_list2 = {RIG_FRNG_END,}, // .tuning_steps = { {DUMMY_MODES,1}, {DUMMY_MODES,RIG_TS_ANY}, RIG_TS_END, }, // .filters = { // {RIG_MODE_SSB | RIG_MODE_CW | RIG_MODE_RTTY, kHz(2.4)}, // {RIG_MODE_CW, Hz(500)}, // {RIG_MODE_AM, kHz(8)}, // {RIG_MODE_AM, kHz(2.4)}, // {RIG_MODE_FM, kHz(15)}, // {RIG_MODE_FM, kHz(8)}, // {RIG_MODE_WFM, kHz(230)}, // RIG_FLT_END, // }, // .max_rit = 9990, // .max_xit = 9990, // .max_ifshift = 10000, .priv = NULL, /* priv */ // .extlevels = dummy_ext_levels, // .extparms = dummy_ext_parms, // .cfgparams = dummy_cfg_params, .rig_init = flrig_init, // .rig_cleanup = dummy_cleanup, // .rig_open = dummy_open, // .rig_close = dummy_close, // .set_conf = dummy_set_conf, // .get_conf = dummy_get_conf, .set_freq = flrig_set_freq, .get_freq = flrig_get_freq, .set_mode = flrig_set_mode, // .get_mode = dummy_get_mode, .set_vfo = flrig_set_vfo, .get_vfo = flrig_get_vfo, // .set_powerstat = dummy_set_powerstat, // .get_powerstat = dummy_get_powerstat, // .set_level = dummy_set_level, // .get_level = dummy_get_level, // .set_func = dummy_set_func, // .get_func = dummy_get_func, // .set_parm = dummy_set_parm, // .get_parm = dummy_get_parm, // .set_ext_level = dummy_set_ext_level, // .get_ext_level = dummy_get_ext_level, // .set_ext_parm = dummy_set_ext_parm, // .get_ext_parm = dummy_get_ext_parm, // .get_info = dummy_get_info, .set_ptt = flrig_set_ptt, .get_ptt = flrig_get_ptt, .set_split_freq = flrig_set_split_freq, .get_split_freq = flrig_get_split_freq, // .set_split_mode = flrig_set_split_mode, // .get_split_mode = flrig_get_split_mode, .set_split_vfo = flrig_set_split_vfo, .get_split_vfo = flrig_get_split_vfo, // .set_ant = dummy_set_ant, // .get_ant = dummy_get_ant, // .set_bank = dummy_set_bank, // .set_mem = dummy_set_mem, // .get_mem = dummy_get_mem, // .vfo_op = dummy_vfo_op, // .set_trn = dummy_set_trn, // .get_trn = dummy_get_trn, }; DECLARE_INITRIG_BACKEND(flrig) { rig_debug(RIG_DEBUG_TRACE, "flrig: _init called\n"); rig_register(&flrig_caps); return RIG_OK; } static int check_vfo(vfo_t vfo) { switch (vfo) { // Omni VII only has A & B case RIG_VFO_A: break; case RIG_VFO_B: break; case RIG_VFO_TX: break; case RIG_VFO_CURR: break; // will default to A in which_vfo default: return FALSE; } return TRUE; } static int vfo_curr(RIG *rig, vfo_t vfo) { int retval = 0; struct flrig_priv_data *priv = (struct flrig_priv_data *) rig->state.priv; retval = (vfo == priv->vfo_curr); return retval; } // Rather than use some huge XML library we only need a few things // So we'll hand craft them static char *xml_build(char *cmd, char *value) { char xml[4096]; char tmp[32]; static char xmlpost[4096]; // Standard 50ms sleep borrowed from ts200.c settings // Tested with ANAN 100 usleep(50 * 1000); sprintf(xmlpost, "POST /RPC2 HTTP/1.1\n" "User-Agent: XMLRPC++ 0.8\n" "Host: 127.0.0.1:12345\n" "Content-type: text/xml\n"); sprintf(xml, "\n"); strcat(xml, ""); strcat(xml, cmd); strcat(xml, ""); if (value && strlen(value) > 0) { strcat(xml, value); } strcat(xml, "\n"); strcat(xmlpost, "Content-length: "); sprintf(tmp, "%d\n\n", (int)strlen(xml)); strcat(xmlpost, tmp); strcat(xmlpost, xml); rig_debug(RIG_DEBUG_VERBOSE, "XML:\n%s", xmlpost); return xmlpost; } // This is a very crude xml parse specific to what we need from FLRig // This will not handle array returns for example yet...only simple values // It simply grabs the first element before the first closing tag // This works for strings, doubles, and I4-type values char *xml_parse2(char *xml, char *value, int valueLen) { char *pstart = strchr(xml, '<'); while (pstart[0] == '<' && pstart[1] != '/') { char *p2 = strchr(pstart, '>') + 1; pstart = strchr(p2, '<'); strncpy(value, p2, pstart - p2); value[pstart - p2] = 0; } return value; } static char *xml_parse(char *xml, char *value, int value_len) { /* first off we should have an OK on the 1st line */ if (strstr(xml, " 200 OK") == NULL) { return NULL; } // find the xml skipping the other stuff above it char *pxml = strstr(xml, "state; int retval; char cmd_buf[16384]; // plenty big for expected flrig responses char delim[1]; delim[0] = 0x0a; xml[0] = cmd_buf[0] = 0; do { retval = read_string(&rs->rigport, cmd_buf, sizeof(cmd_buf), delim, sizeof(delim)); if (strlen(xml) > 8192 - retval) { return -RIG_EINVAL; } if (retval > 0) { strcat(xml, cmd_buf); } } while (retval > 0 && strstr(cmd_buf, "") == NULL); return RIG_OK; } int flrig_init(RIG *rig) { rig_debug(RIG_DEBUG_TRACE, "%s\n", __FUNCTION__); struct flrig_priv_data *priv = (struct flrig_priv_data *)malloc(sizeof(struct flrig_priv_data)); if (!priv) { return -RIG_ENOMEM; } memset(priv, 0, sizeof(struct flrig_priv_data)); /* * set arbitrary initial status */ priv->vfo_curr = RIG_VFO_A; rig->state.priv = (rig_ptr_t) priv; if (!rig || !rig->caps) { return -RIG_EINVAL; } strncpy(rig->state.rigport.pathname, DEFAULTPATH, sizeof(rig->state.rigport.pathname)); return RIG_OK; } /* * flrig_get_freq * Assumes rig!=NULL, rig->state.priv!=NULL, freq!=NULL */ int flrig_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s\n", __FUNCTION__, rig_strvfo(vfo)); rs = &rig->state; if (check_vfo(vfo) == FALSE) { rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __FUNCTION__, rig_strvfo(vfo)); return -RIG_EINVAL; } if (vfo == RIG_VFO_CURR) { vfo = RIG_VFO_A; } flrig_set_vfo(rig, vfo); pxml = xml_build("rig.get_vfo", NULL); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); xml_parse(xml, value, sizeof(value)); *freq = atof(value); rig_debug(RIG_DEBUG_VERBOSE, "%s: '%s'\n", __FUNCTION__, value); return RIG_OK; } /* * flrig_set_freq * assumes rig!=NULL, rig->state.priv!=NULL */ int flrig_set_freq(RIG *rig, vfo_t vfo, freq_t freq) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s freq=%.1f\n", __FUNCTION__, rig_strvfo(vfo), freq); rs = &rig->state; if (check_vfo(vfo) == FALSE) { rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __FUNCTION__, rig_strvfo(vfo)); return -RIG_EINVAL; } if (vfo == RIG_VFO_CURR) { if ((retval = flrig_get_vfo(rig, &vfo)) != RIG_OK) { return retval; } rig_debug(RIG_DEBUG_VERBOSE, "%s: set_freq2 vfo=%s\n", __FUNCTION__, rig_strvfo(vfo)); } if (!vfo_curr(rig, vfo)) { flrig_set_vfo(rig, vfo); } sprintf(value, "%.6f", freq); pxml = xml_build("rig.set_vfo", value); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); //don't care about the response right now //xml_parse(xml, value, sizeof(value)); flrig_set_vfo(rig, vfo); return RIG_OK; } /* * flrig_set_ptt * Assumes rig!=NULL */ int flrig_set_ptt(RIG *rig, vfo_t vfo, ptt_t ptt) { int retval; char cmd_buf[MAXCMDLEN]; char *pxml; char xml[8192]; struct rig_state *rs; rig_debug(RIG_DEBUG_TRACE, "%s: ptt=%d\n", __FUNCTION__, ptt); rs = &rig->state; if (check_vfo(vfo) == FALSE) { rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __FUNCTION__, rig_strvfo(vfo)); return -RIG_EINVAL; } if (vfo == RIG_VFO_CURR) { vfo = RIG_VFO_A; } if (!vfo_curr(rig, vfo)) { flrig_set_vfo(rig, vfo); } sprintf(cmd_buf, "%d", ptt); pxml = xml_build("rig.set_ptt", cmd_buf); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); xml_parse(xml, cmd_buf, sizeof(cmd_buf)); return RIG_OK; } int flrig_get_ptt(RIG *rig, vfo_t vfo, ptt_t *ptt) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s\n", __FUNCTION__, rig_strvfo(vfo)); rs = &rig->state; pxml = xml_build("rig.get_ptt", NULL); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); xml_parse(xml, value, sizeof(value)); *ptt = atoi(value); rig_debug(RIG_DEBUG_VERBOSE, "%s: '%s'\n", __FUNCTION__, value); return RIG_OK; } /* * flrig_set_mode * Assumes rig!=NULL */ int flrig_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width) { char cmd_buf[32], ttmode; int cmd_len, retval; struct rig_state *rs; //struct tt588_priv_data *priv = (struct tt588_priv_data *) rig->state.priv; rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s mode=%d width=%d\n", __FUNCTION__, rig_strvfo(vfo), mode, width); rs = &rig->state; if (check_vfo(vfo) == FALSE) { rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __FUNCTION__, rig_strvfo(vfo)); return -RIG_EINVAL; } 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_FSK: ttmode = 'F'; break; default: rig_debug(RIG_DEBUG_ERR, "%s: unsupported mode %d\n", __FUNCTION__, mode); return -RIG_EINVAL; } cmd_len = sprintf((char *) cmd_buf, "XB%c" EOM, ttmode); retval = write_block(&rs->rigport, cmd_buf, cmd_len); if (retval < 0) { return retval; } return RIG_OK; } #if 0 static int flrig_flush(RIG *rig, vfo_t vfo) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s\n", __FUNCTION__, rig_strvfo(vfo)); rs = &rig->state; sprintf(value, "%s", vfo == RIG_VFO_A ? "A" : "B"); pxml = xml_build("rig.flush", value); network_flush(&rs->rigport); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); return RIG_OK; } #endif int flrig_set_vfo(RIG *rig, vfo_t vfo) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s\n", __FUNCTION__, rig_strvfo(vfo)); rs = &rig->state; if (check_vfo(vfo) == FALSE) { rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __FUNCTION__, rig_strvfo(vfo)); return -RIG_EINVAL; } if (vfo == RIG_VFO_CURR) { struct flrig_priv_data *priv = (struct flrig_priv_data *) rig->state.priv; vfo = priv->vfo_curr; } sprintf(value, "%s", vfo == RIG_VFO_A ? "A" : "B"); pxml = xml_build("rig.set_AB", value); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); //xml_parse(xml,value,sizeof(value)); vfo_t vfotmp; flrig_get_vfo(rig, &vfotmp); return RIG_OK; } int flrig_get_vfo(RIG *rig, vfo_t *vfo) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s\n", __FUNCTION__); rs = &rig->state; pxml = xml_build("rig.get_AB", NULL); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); xml_parse(xml, value, sizeof(value)); switch (value[0]) { case 'A': *vfo = RIG_VFO_A; break; case 'B': *vfo = RIG_VFO_B; break; default: *vfo = RIG_VFO_CURR; return -RIG_EINVAL; } if (check_vfo(*vfo) == FALSE) { rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __FUNCTION__, rig_strvfo(*vfo)); return -RIG_EINVAL; } struct flrig_priv_data *priv = (struct flrig_priv_data *) rig->state.priv; priv->vfo_curr = *vfo; rig_debug(RIG_DEBUG_VERBOSE, "%s: vfo=%s\n", __FUNCTION__, rig_strvfo(*vfo)); return RIG_OK; } /* * flrig_set_split_freq * Note that split doesn't work for FLRig models that don't have reply codes * Like most Yaesu rigs. The commands will overrun FLRig in those cases. * Rigs that do have replies for all cat commands will work with split */ int flrig_set_split_freq(RIG *rig, vfo_t vfo, freq_t tx_freq) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s freq=%.1f\n", __FUNCTION__, rig_strvfo(vfo), tx_freq); rs = &rig->state; if (vfo == RIG_VFO_SUB) { vfo = RIG_VFO_B; } if (check_vfo(vfo) == FALSE) { rig_debug(RIG_DEBUG_ERR, "%s: unsupported VFO %s\n", __FUNCTION__, rig_strvfo(vfo)); return -RIG_EINVAL; } if (!vfo_curr(rig, vfo)) { flrig_set_vfo(rig, vfo); } sprintf(value, "%.6f", tx_freq); pxml = xml_build("rig.set_vfo", value); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); xml_parse(xml, value, sizeof(value)); return RIG_OK; } /* * flrig_get_split_freq * assumes rig!=NULL, tx_freq!=NULL */ int flrig_get_split_freq(RIG *rig, vfo_t vfo, freq_t *tx_freq) { rig_debug(RIG_DEBUG_TRACE, "%s: vfo=%s\n", __FUNCTION__, rig_strvfo(vfo)); return flrig_get_freq(rig, vfo, tx_freq); } /* * flrig_set_split_vfo * assumes rig!=NULL, tx_freq!=NULL */ int flrig_set_split_vfo(RIG *rig, vfo_t vfo, split_t split, vfo_t tx_vfo) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s: tx_vfo=%s\n", __FUNCTION__, rig_strvfo(tx_vfo)); rs = &rig->state; if (tx_vfo == RIG_VFO_SUB) { tx_vfo = RIG_VFO_B; } if (!vfo_curr(rig, vfo)) { flrig_set_vfo(rig, vfo); } sprintf(value, "%d", split); pxml = xml_build("rig.set_split", value); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); //xml_parse(xml, value, sizeof(value)); //flrig_set_vfo(rig,RIG_VFO_A); return RIG_OK; } /* * flrig_get_split_vfo * assumes rig!=NULL, tx_freq!=NULL */ int flrig_get_split_vfo(RIG *rig, vfo_t vfo, split_t *split, vfo_t *tx_vfo) { char value[MAXCMDLEN]; char *pxml; int retval; struct rig_state *rs; char xml[8192]; rig_debug(RIG_DEBUG_TRACE, "%s\n", __FUNCTION__); rs = &rig->state; //flrig_set_vfo(rig,RIG_VFO_B); pxml = xml_build("rig.get_split", NULL); retval = write_block(&rs->rigport, pxml, strlen(pxml)); if (retval < 0) { return retval; } read_transaction(rig, xml, sizeof(xml)); xml_parse(xml, value, sizeof(value)); //flrig_set_vfo(rig,RIG_VFO_A); *tx_vfo = RIG_VFO_B; *split = atoi(value); return RIG_OK; } /* .set_split_freq = flrig_set_split_freq, .get_split_freq = flrig_get_split_freq, .set_split_mode = flrig_set_split_mode, .get_split_mode = flrig_get_split_mode, */