diff --git a/NEWS b/NEWS index 938f29fb9..251a56f42 100644 --- a/NEWS +++ b/NEWS @@ -1,11 +1,15 @@ Hamlib -- History of visible changes. Copyright (C) 2000-2003 Frank Singleton -Copyright (C) 2000-2018 Stephane Fillod, and others -Copyright (C) 2000-2020 Michael Black W9MDB, and others +Copyright (C) 2000-2021 Stephane Fillod, and others +Copyright (C) 2000-2021 Michael Black W9MDB, and others Please send Hamlib bug reports to hamlib-developer@lists.sourceforge.net +Version 4.2 + 2021-??-?? + * Major rework for PRM80 + Version 4.1 2021-01-31 * rigctld and rigs should be more robust for disconnect problems diff --git a/rigs/prm80/prm80.c b/rigs/prm80/prm80.c index 36db7fe25..93df5af26 100644 --- a/rigs/prm80/prm80.c +++ b/rigs/prm80/prm80.c @@ -1,6 +1,6 @@ /* * Hamlib PRM80 backend - main file - * Copyright (c) 2010 by Stephane Fillod + * Copyright (c) 2010,2021 by Stephane Fillod * * * This library is free software; you can redistribute it and/or @@ -41,46 +41,23 @@ #define LF "\x0a" -#define PROMPT ">" - #define BUFSZ 64 -/* V3 commands - * retrieved from https://sourceforge.net/projects/prm80/ -MessageVersion: DB "PRM8060 V3.0", 0 +// Channel number min and max +#define CHAN_MIN 0 +#define CHAN_MAX 99 -MessageAide: DB "H",0Dh,0Ah - DB " Commandes disponibles :",0Dh,0Ah - DB " [0] = Reset.",0Dh,0Ah - DB " [1] a [5] = Show 80c552 port state P1 to P5.",0Dh,0Ah - DB " [A] = Set serial communication to 1200 bps.",0Dh,0Ah - DB " [B] = Set serial communication to 4800 bps.",0Dh,0Ah - DB " [C] = Print channels list.",0Dh,0Ah - DB " [D] = Set system byte.",0Dh,0Ah - DB " [E] = Show system state (Mode-Chan-Chanstate-Sql-Vol-Lock-RX freq-TX freq).",0Dh,0Ah - DB " [F] = Set squelch.",0Dh,0Ah - DB " [H] = Print this help page.",0Dh,0Ah - DB " [I] = Erase and init RAM and EEPROM.",0Dh,0Ah - DB " [K] = Set lock byte.",0Dh,0Ah - DB " [L] = Print latch state.",0Dh,0Ah - DB " [M] = Edit external RAM manualy.",0Dh,0Ah - DB " [N] = Set current channel.",0Dh,0Ah - DB " [O] = Set volume.",0Dh,0Ah - DB " [P] = Edit/Add channel.",0Dh,0Ah - DB " [Q] = Set channels number.",0Dh,0Ah - DB " [R] = Set synthetiser frequencies.",0Dh,0Ah - DB " [U] = Print 80c552 internal RAM.",0Dh,0Ah - DB " [S] = Copy EEPROM to external RAM.",0Dh,0Ah - DB " [T] = Set current channel state.",0Dh,0Ah - DB " [V] = Print firmware version.",0Dh,0Ah - DB " [X] = Copy external RAM to EEPROM.",0Dh,0Ah - DB " [Y] = Print first 2 kb from the EEPROM I2C 24c16.",0Dh,0Ah - DB " [Z] = Print external RAM ($0000 to $07FF).",0Dh,0Ah,0 -*/ +#define RX_IF_OFFSET MHz(21.4) + +// The rig's PLL only deals with freq in Hz divided by this value +#define FREQ_DIV 12500. /* V4 commands - * retrieved from https://sourceforge.net/projects/prm80/ - * + * retrieved from https://github.com/f4fez/prm80 + * and https://github.com/f4fez/prm80/blob/master/doc/Computer_commands_V4.md + * It used to be from https://sourceforge.net/projects/prm80/ + * and https://sourceforge.net/p/prm80/wiki/Computer%20commands%20V4/ + MessageVersion: IF TARGET EQ 8060 DB "PRM8060 V4.0" @@ -134,86 +111,173 @@ MessageAide: DB "H",0Dh,0Ah */ /* - * prm80_transaction - * We assume that rig!=NULL, rig->state!= NULL, data!=NULL, data_len!=NULL - * Otherwise, you'll get a nice seg fault. You've been warned! - * TODO: error case handling + * Mode byte, which holds the state of system basic features: + b0: Squelch mode is displayed on LCD if true. Channel mode if false. + b1: Power level (High or Low mode) + b2: Squelch open (Read only) + b3: TX mode (Read only) + b4: PLL locked (Read only) + b5: Long key push (Internal) + b6: Key bounce (Internal) + b7: Force LCD refresh when set. Automaticaly cleared. + + Channel state byte: + b0: Shift enable when true + b1: Reverse mode when true + b2: Positive shift when true. Negative if false + b3: Scanning locked out channel if set + b4-7: na. + + Lock byte, which disables user controls when connected to a computer + b0: Keys disabled when true + b1: TX disabled when true + b2: Volume button disabled when true + b3: RX disabled when true + b4-b7: na. + + * ********************************************************************* */ -static int prm80_transaction(RIG *rig, const char *cmd, int cmd_len, char *data, - int *data_len) + +/* + * TODO make read_colon_prompt_and_send() more generic to read + * a prompt terminated by "$" (without space afterwards) + */ +#define read_dollar_prompt_and_send read_colon_prompt_and_send + +/* + * Read a prompt terminated by ": ", then write an optional string s. + */ +static int read_colon_prompt_and_send(hamlib_port_t *rigport, + char *data, int *data_len, const char *s) { - int retval, i; - struct rig_state *rs; + char buf[BUFSZ]; + char spacebuf[4]; + int buflen, retval; - rs = &rig->state; - - rig_flush(&rs->rigport); - - retval = write_block(&rs->rigport, cmd, cmd_len); - - if (retval != RIG_OK) + /* no data wanted? flush it anyway by reading it */ + if (data == NULL) { - return retval; + data = buf; } - /* no data wanted, but flush it anyway */ - if (!data || !data_len) - { - char retbuf[BUFSZ + 1]; + buflen = (data_len == NULL) ? sizeof(buf) : *data_len; - retval = read_string(&rs->rigport, retbuf, BUFSZ, LF, strlen(LF)); - - if (retval < 0) - { - return retval; - } - -#if 0 - - /* - * Does transceiver sends back ">" ? - */ - if (strstr(retbuf, PROMPT)) - { - return RIG_OK; - } - else - { - return -RIG_ERJCTED; - } - -#else - return RIG_OK; -#endif - } - - retval = read_string(&rs->rigport, data, BUFSZ, LF, strlen(LF)); - - if (retval == -RIG_ETIMEOUT) - { - retval = 0; - } + retval = read_string(rigport, data, buflen, ":", 1); if (retval < 0) { return retval; } - /* Clear possible MSB, because of 7S1 */ - for (i = 0; i < retval; i++) + // Place an end of string + data[(retval < buflen) ? retval : (buflen - 1)] = '\0'; + + if (data_len != NULL) { - data[i] &= 0x7f; + *data_len = retval; } - *data_len = retval; + // Read one (dummy) space character after the colon + retval = read_block(rigport, spacebuf, 1); - /* chomp CR/LF from string */ - if (*data_len >= 2 && data[*data_len - 1] == '\x0a') + if (retval < 0 && retval != -RIG_ETIMEOUT) { - *data_len -= 2; + return retval; } - data[*data_len] = '\0'; + // Here is the answer to the prompt + retval = write_block(rigport, s, strlen(s)); + + return retval; +} + +/* + * After each executed command, the rig generally sends "\r\n>" + */ +static int prm80_wait_for_prompt(hamlib_port_t *rigport) +{ + char buf[BUFSZ * 2]; + int retval; + + // Read up to the '>' prompt and discard content. + retval = read_string(rigport, buf, sizeof(buf), ">", 1); + + if (retval < 0) + { + return retval; + } + + return RIG_OK; +} + +/* + * + * \param cmd is string of generally one letter (or digit) + * \param arg1 is an optional string string sent + * \param wait_prompt boolean when non-nul, will wait for "\r\n>" afterwards + */ +static int prm80_transaction(RIG *rig, const char *cmd, + const char *arg1, int wait_prompt) +{ + int retval; + struct rig_state *rs = &rig->state; + + // Get rid of possible prompt sent by the rig + rig_flush(&rs->rigport); + + // Start with the command + retval = write_block(&rs->rigport, cmd, strlen(cmd)); + + if (retval != RIG_OK) + { + return retval; + } + + if (arg1 != NULL) + { + retval = read_colon_prompt_and_send(&rs->rigport, NULL, NULL, arg1); + + if (retval < 0) + { + return retval; + } + } + + if (wait_prompt) + { + prm80_wait_for_prompt(&rs->rigport); + } + + return RIG_OK; +} + +int prm80_init(RIG *rig) +{ + if (!rig) + { + return -RIG_EINVAL; + } + + rig->state.priv = (void *)calloc(1, sizeof(struct prm80_priv_data)); + + if (!rig->state.priv) + { + /* whoops! memory shortage! */ + return -RIG_ENOMEM; + } + + return RIG_OK; +} + +int prm80_cleanup(RIG *rig) +{ + if (rig == NULL) + { + return -RIG_EINVAL; + } + + free(rig->state.priv); + rig->state.priv = NULL; return RIG_OK; } @@ -228,9 +292,9 @@ int prm80_reset(RIG *rig, reset_t reset) int retval; /* - * master reset ? + * Reset CPU */ - retval = prm80_transaction(rig, "0", 1, NULL, NULL); + retval = prm80_transaction(rig, "0", NULL, 1); if (retval != RIG_OK) { @@ -242,33 +306,107 @@ int prm80_reset(RIG *rig, reset_t reset) /* - * prm80_set_freq - * Assumes rig!=NULL + * Set RX and TX freq + * + * See https://github.com/f4fez/prm80/blob/master/doc/Computer_control.md + * "Adding a new channel" regarding freq format. */ -int prm80_set_freq(RIG *rig, vfo_t vfo, freq_t freq) +int prm80_set_rx_tx_freq(RIG *rig, freq_t rx_freq, freq_t tx_freq) { - char freqbuf[BUFSZ]; - char data[BUFSZ]; - int freq_len; - int rc; struct rig_state *rs = &rig->state; + char rx_freq_buf[BUFSZ]; + char tx_freq_buf[BUFSZ]; + int rc; - /* wild guess */ - freq_len = sprintf(freqbuf, "R%04X%04X", - (unsigned)(freq / 12500.), - (unsigned)(freq / 12500.)); + // for RX, compute the PLL word without the IF + sprintf(rx_freq_buf, "%04X", + (unsigned)((rx_freq - RX_IF_OFFSET) / FREQ_DIV)); + sprintf(tx_freq_buf, "%04X", + (unsigned)(tx_freq / FREQ_DIV)); + + // The protocol is like this : + // "RX frequency : " XXXX + // CRLF"TX frequency : " XXXX + + rc = prm80_transaction(rig, "R", rx_freq_buf, 0); + + if (rc != RIG_OK) + { + return rc; + } + + // There's a second line to process after prm80_transaction() + rc = read_colon_prompt_and_send(&rs->rigport, NULL, NULL, tx_freq_buf); + + if (rc != RIG_OK) + { + return rc; + } + + // quid timeout in trx waiting for freq ? + + // NB: the [R] command does not update the checksum of the RAM! + + prm80_wait_for_prompt(&rs->rigport); - rc = prm80_transaction(rig, freqbuf, freq_len, NULL, NULL); - read_string(&rs->rigport, data, BUFSZ, LF, strlen(LF)); return rc; } /* - * prm80_get_freq - * Assumes rig!=NULL + * Set (RX) freq + */ +int prm80_set_freq(RIG *rig, vfo_t vfo, freq_t freq) +{ + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; + freq_t tx_freq; + int rc; + + if (priv->split == RIG_SPLIT_OFF) + { + tx_freq = freq; + } + else + { + tx_freq = (priv->tx_freq == 0.) ? freq : priv->tx_freq; + } + + rc = prm80_set_rx_tx_freq(rig, freq, tx_freq); + + if (rc == RIG_OK) + { + priv->rx_freq = freq; + } + + return rc; +} + +/* + * Set TX freq depending on emulated split state + */ +int prm80_set_split_freq(RIG *rig, vfo_t vfo, freq_t tx_freq) +{ + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; + freq_t rx_freq; + int rc; + + rx_freq = (priv->rx_freq == 0.) ? tx_freq : priv->rx_freq; + + rc = prm80_set_rx_tx_freq(rig, rx_freq, tx_freq); + + if (rc == RIG_OK) + { + priv->tx_freq = tx_freq; + } + + return rc; +} + +/* + * Get RX freq depending on emulated split state */ int prm80_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) { + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; int ret; channel_t chan; @@ -283,29 +421,96 @@ int prm80_get_freq(RIG *rig, vfo_t vfo, freq_t *freq) } *freq = chan.freq; + priv->tx_freq = chan.tx_freq; return RIG_OK; } +/* + * Enable/disable Split + * + * Rem: don't care about vfo + */ +int prm80_set_split_vfo(RIG *rig, vfo_t vfo, split_t split, vfo_t tx_vfo) +{ + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; + + priv->split = split; + + return RIG_OK; +} + +/* + * Get Split + */ +int prm80_get_split_vfo(RIG *rig, vfo_t vfo, split_t *split, vfo_t *tx_vfo) +{ + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; + + *split = priv->split; + *tx_vfo = RIG_VFO_CURR; + + return RIG_OK; +} + +/* + * Get TX freq + */ +int prm80_get_split_freq(RIG *rig, vfo_t vfo, freq_t *tx_freq) +{ + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; + int ret; + channel_t chan; + + memset(&chan, 0, sizeof(chan)); + chan.vfo = RIG_VFO_CURR; + + ret = prm80_get_channel(rig, vfo, &chan, 0); + + if (ret != RIG_OK) + { + return ret; + } + + *tx_freq = chan.tx_freq; + priv->rx_freq = chan.freq; + + return RIG_OK; +} + +/* + * Basic helper to ease some generic applications + */ +int prm80_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width) +{ + // Can only do FM + *mode = RIG_MODE_FM; + *width = rig_passband_normal(rig, *mode); + + return RIG_OK; +} + + /* * prm80_set_mem * Assumes rig!=NULL */ int prm80_set_mem(RIG *rig, vfo_t vfo, int ch) { - int cmd_len; - char cmdbuf[BUFSZ]; + char chbuf[BUFSZ]; /* [N] = Set current channel. */ - if (ch < 0 || ch > 99) + if (ch < CHAN_MIN || ch > CHAN_MAX) { return -RIG_EINVAL; } - cmd_len = sprintf(cmdbuf, "N%02d", ch); + sprintf(chbuf, "%02u", (unsigned)ch); - return prm80_transaction(rig, cmdbuf, cmd_len, NULL, NULL); + // Send command, no answer expected from rig except ">" prompt + + return prm80_transaction(rig, "N", chbuf, 1); } /* @@ -352,9 +557,11 @@ static int hhtoi(const char *p) */ int prm80_get_channel(RIG *rig, vfo_t vfo, channel_t *chan, int read_only) { + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; + struct rig_state *rs = &rig->state; char statebuf[BUFSZ]; - int statebuf_len = BUFSZ; - int ret, chanstate; + char *p; + int ret, chanstate, mode_byte, lock_byte; if (chan->vfo == RIG_VFO_MEM) { @@ -366,30 +573,75 @@ int prm80_get_channel(RIG *rig, vfo_t vfo, channel_t *chan, int read_only) } } - /* [E] = Show system state (Mode-Chan-Chanstate-Sql-Vol-Lock-RX freq-TX freq). */ - ret = prm80_transaction(rig, "E", 1, statebuf, &statebuf_len); + // Get rid of possible prompt sent by the rig + rig_flush(&rs->rigport); - if (ret != RIG_OK) + /* [E] = Show system state */ + ret = write_block(&rs->rigport, "E", 1); + + if (ret < 0) { RETURNFUNC(ret); } - if (statebuf_len < 20) + // The response length is fixed + ret = read_block(&rs->rigport, statebuf, 20); + + if (ret < 0) { - rig_debug(RIG_DEBUG_ERR, "%s: statebuf_len < 20, statebuf='%s'\n", __func__, - statebuf); + return ret; + } + + if (ret >= 0) + { + statebuf[ret] = '\0'; + } + + if (ret < 20) + { + rig_debug(RIG_DEBUG_ERR, "%s: len=%d < 20, statebuf='%s'\n", __func__, + ret, statebuf); RETURNFUNC(-RIG_EPROTO); } - /* Example: 1240080AFF0033F02D40 */ - if (hhtoi(statebuf) != 0x12) - rig_debug(RIG_DEBUG_WARN, "%s: Unknown mode 0x%c%c\n", - __func__, statebuf[0], statebuf[1]); + p = strchr(statebuf, '>'); + + if (p) + { + int left_to_read = (p - statebuf) + 1; + memmove(statebuf, p + 1, 20 - left_to_read); + ret = read_block(&rs->rigport, statebuf + 20 - left_to_read, left_to_read); + + if (ret >= 0) + { + statebuf[20] = '\0'; + } + + rig_debug(RIG_DEBUG_WARN, "%s: len=%d, statebuf='%s'\n", __func__, ret, + statebuf); + } + + /* (Mode-Chan-Chanstate-Sql-Vol-Lock-RX freq-TX freq). */ + /* Examples: 1240080AFF0033F02D40 or 14000C00FD0079708020 */ + + /* Current mode: + ; b0: Squelch b1: power + ; b2: Squelch open b3: TX + ; b4: PLL locked b5: Long press memorize + ; b6: Debouncing in effect b7: LCD refresh + */ + mode_byte = hhtoi(statebuf); chan->mode = RIG_MODE_FM; chan->width = rig_passband_normal(rig, chan->mode); chan->channel_num = hhtoi(statebuf + 2); + chan->tx_mode = chan->mode; + chan->tx_width = chan->width; + /* Chan state: + ; b0: shift enabled b1: reverse + ; b2: shift + b3: lock out + */ chanstate = hhtoi(statebuf + 4) & 0x0f; /* is it rptr_shift or split mode ? */ chan->rptr_shift = (chanstate & 0x01) == 0 ? RIG_RPT_SHIFT_NONE : @@ -400,39 +652,160 @@ int prm80_get_channel(RIG *rig, vfo_t vfo, channel_t *chan, int read_only) // cppcheck-suppress * chan->levels[LVL_SQL].f = ((float)(hhtoi(statebuf + 6) >> 4)) / 15.; chan->levels[LVL_AF].f = ((float)(hhtoi(statebuf + 8) >> 4)) / 15.; - /* same as chanstate bit 1 */ - chan->flags = hhtoi(statebuf + 10) == 0 ? 0 : RIG_CHFLAG_SKIP; - chan->freq = ((hhtoi(statebuf + 12) << 8) + hhtoi(statebuf + 14)) * 12500; - chan->tx_freq = ((hhtoi(statebuf + 16) << 8) + hhtoi(statebuf + 18)) * 12500; - chan->rptr_offs = chan->tx_freq - chan->freq; + chan->levels[LVL_RFPOWER].f = (mode_byte & 0x02) ? 1.0 : 0.0; + + chan->funcs |= (chanstate & 0x02) ? RIG_FUNC_REV : 0; + + lock_byte = hhtoi(statebuf + 10) & 0x0f; + chan->funcs = (lock_byte != 0) ? RIG_FUNC_LOCK : 0; + + chan->freq = ((hhtoi(statebuf + 12) << 8) + hhtoi(statebuf + 14)) * FREQ_DIV + + RX_IF_OFFSET; + chan->tx_freq = ((hhtoi(statebuf + 16) << 8) + hhtoi(statebuf + 18)) * FREQ_DIV; + + if (chan->rptr_shift != RIG_RPT_SHIFT_NONE) + { + chan->rptr_offs = chan->tx_freq - chan->freq; + chan->split = RIG_SPLIT_OFF; + } + else + { + chan->rptr_offs = 0; + chan->split = priv->split; // RIG_SPLIT_ON; ? + } if (!read_only) { // Set rig to channel values - rig_debug(RIG_DEBUG_ERR, - "%s: please contact hamlib mailing list to implement this, rxfreq=%.0f, txfreq=%.0f\n", - __func__, chan->freq, chan->tx_freq); - rig_debug(RIG_DEBUG_ERR, + rig_debug(RIG_DEBUG_WARN, + "%s: please contact hamlib mailing list to implement this\n", __func__); + rig_debug(RIG_DEBUG_WARN, "%s: need to know if rig updates when channel read or not\n", __func__); //return -RIG_ENIMPL; } + prm80_wait_for_prompt(&rs->rigport); + return RIG_OK; } /* - * prm80_set_channel - * Assumes rig!=NULL + * prm80_set_channel handles RIG_VFO_MEM and RIG_VFO_CURR */ int prm80_set_channel(RIG *rig, vfo_t vfo, const channel_t *chan) { - char statebuf[BUFSZ]; - int statebuf_len = BUFSZ; - int ret; + struct prm80_priv_data *priv = (struct prm80_priv_data *)rig->state.priv; + struct rig_state *rs = &rig->state; + char buf[BUFSZ]; + int ret, chanstate; + freq_t tx_freq; if (chan->vfo == RIG_VFO_MEM) { - ret = prm80_set_mem(rig, RIG_VFO_CURR, chan->channel_num); + // setting channel without calling set_mem() + + if (chan->channel_num < CHAN_MIN || chan->channel_num > CHAN_MAX) + { + return -RIG_EINVAL; + } + + /* [P] = Edit/Add channel */ + /* Example + Channel to set : 00 + PLL value to load : $8020 + Channel state : $00 + + TODO: handle the possible query from the rig: + "This channel number doesn't exist. Add new channel (Y/N) ? " + TODO implement correctly read_dollar_prompt_and_send (dollar prompt) + */ + + sprintf(buf, "%02u", (unsigned)chan->channel_num); + + ret = prm80_transaction(rig, "P", buf, 0); + + if (ret != RIG_OK) + { + return ret; + } + + // Set the RX frequency as PLL word + sprintf(buf, "%04X", (unsigned)((chan->freq - RX_IF_OFFSET) / FREQ_DIV)); + ret = read_dollar_prompt_and_send(&rs->rigport, NULL, NULL, buf); + + if (ret != RIG_OK) + { + return ret; + } + + // the channel status byte. + switch (chan->rptr_shift) + { + case RIG_RPT_SHIFT_NONE : chanstate = 0x00; break; + + case RIG_RPT_SHIFT_MINUS : chanstate = 0x03; break; + + case RIG_RPT_SHIFT_PLUS : chanstate = 0x05; break; + + default: chanstate = 0x00; break; + } + + chanstate |= (chan->flags & RIG_CHFLAG_SKIP) ? 0x08 : 0; + + sprintf(buf, "%02X", chanstate); + ret = read_dollar_prompt_and_send(&rs->rigport, NULL, NULL, buf); + + if (ret != RIG_OK) + { + return ret; + } + + prm80_wait_for_prompt(&rs->rigport); + } + else + { + // assume here chan->vfo == RIG_VFO_CURR + // that is the "RAM" VFO not backed by memory + + tx_freq = (chan->split == RIG_SPLIT_ON) ? chan->tx_freq : chan->freq; + + ret = prm80_set_rx_tx_freq(rig, chan->freq, tx_freq); + + if (ret != RIG_OK) + { + return ret; + } + + priv->split = chan->split; + priv->rx_freq = chan->freq; + priv->tx_freq = tx_freq; + + ret = prm80_set_level(rig, vfo, RIG_LEVEL_SQL, chan->levels[LVL_SQL]); + + if (ret != RIG_OK) + { + return ret; + } + + ret = prm80_set_level(rig, vfo, RIG_LEVEL_AF, chan->levels[LVL_AF]); + + if (ret != RIG_OK) + { + return ret; + } + +#if 0 + // Not implemented yet.. + ret = prm80_set_level(rig, vfo, RIG_LEVEL_RFPOWER, chan->levels[LVL_RFPOWER]); + + if (ret != RIG_OK) + { + return ret; + } + +#endif + + ret = prm80_set_func(rig, vfo, RIG_FUNC_LOCK, chan->funcs & RIG_FUNC_LOCK); if (ret != RIG_OK) { @@ -440,52 +813,91 @@ int prm80_set_channel(RIG *rig, vfo_t vfo, const channel_t *chan) } } - /* [T] = Set current channel state. (Mode-Chan-Chanstate-Sql-Vol-Lock-RX freq-TX freq) ? */ - /* Example: 1240080AFF0033F02D40 ? */ - statebuf_len = sprintf(statebuf, "T%02X%02X%02X%02X%02X%02X%04X%04X", - 0x12, - chan->channel_num, - (chan->flags & RIG_CHFLAG_SKIP) ? 0x08 : 0, /* TODO: tx shift */ - (unsigned)(chan->levels[LVL_SQL].f * 15), - (unsigned)(chan->levels[LVL_AF].f * 15), - (chan->flags & RIG_CHFLAG_SKIP) ? 0x01 : 0x00, /* Lock */ - (unsigned)(chan->freq / 12500.), - (unsigned)(chan->tx_freq / 12500.) - ); + return RIG_OK; +} - ret = prm80_transaction(rig, statebuf, statebuf_len, NULL, NULL); + +// TODO FUNC_REV ? +int prm80_set_func(RIG *rig, vfo_t vfo, setting_t func, int status) +{ + int ret; + + if (func & RIG_FUNC_LOCK) + { + /* Lock keys/TX/Vol */ + ret = prm80_transaction(rig, "K", (status != 0) ? "03" : "00", 1); + } + else + { + ret = -RIG_EINVAL; + } + + return ret; +} + +int prm80_get_func(RIG *rig, vfo_t vfo, setting_t func, int *status) +{ + int ret; + channel_t chan; + + memset(&chan, 0, sizeof(chan)); + chan.vfo = RIG_VFO_CURR; + + ret = prm80_get_channel(rig, vfo, &chan, 0); if (ret != RIG_OK) { return ret; } + *status = (chan.funcs & func); + return RIG_OK; } - /* * prm80_set_level * Assumes rig!=NULL */ int prm80_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val) { - int cmd_len; - char cmdbuf[BUFSZ]; + char buf[BUFSZ]; + + // do some clamping, all levels are float values. + if (val.f < 0.0) + { + val.f = 0.0; + } + else if (val.f > 1.0) + { + val.f = 1.0; + } switch (level) { case RIG_LEVEL_AF: - cmd_len = sprintf(cmdbuf, "O%02u", (unsigned)(val.f * 15)); + sprintf(buf, "%02u", (unsigned)(val.f * 15)); - return prm80_transaction(rig, cmdbuf, cmd_len, NULL, NULL); + return prm80_transaction(rig, "O", buf, 1); case RIG_LEVEL_SQL: - cmd_len = sprintf(cmdbuf, "F%02u", (unsigned)(val.f * 15)); + sprintf(buf, "%02u", (unsigned)(val.f * 15)); - return prm80_transaction(rig, cmdbuf, cmd_len, NULL, NULL); + return prm80_transaction(rig, "F", buf, 1); case RIG_LEVEL_RFPOWER: + // TODO : modify the Mode byte b1 ? +#if 0 + /* Current mode: + ; b0: Squelch b1: power + ; b2: Squelch open b3: TX + ; b4: PLL locked b5: Long press memorize + ; b6: Debouncing in effect b7: LCD refresh + */ + // TODO perform a "Read-Modify-Write" of the mode_byte + mode_byte = 0x10; + mode_byte |= (chan->levels[LVL_RFPOWER].f == 0.) ? 0 : 0x02; +#endif return -RIG_ENIMPL; default: @@ -528,6 +940,11 @@ int prm80_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val) break; + case RIG_LEVEL_RFPOWER: + val->f = chan.levels[LVL_RFPOWER].f; + + break; + default: rig_debug(RIG_DEBUG_ERR, "%s: unsupported set_level %s\n", __func__, rig_strlevel(level)); @@ -537,26 +954,47 @@ int prm80_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val) return RIG_OK; } +// TODO vfo_op : MCL FROM_VFO .. + /* * prm80_get_info * Assumes rig!=NULL */ const char *prm80_get_info(RIG *rig) { - static char buf[BUFSZ]; - int ret, buf_len = BUFSZ; + static char s_buf[BUFSZ]; + struct rig_state *rs = &rig->state; + char *p; + int ret; + + // Get rid of possible prompt sent by the rig + rig_flush(&rs->rigport); /* [V] = Print firmware version. */ - ret = prm80_transaction(rig, "V", 1, buf, &buf_len); + ret = write_block(&rs->rigport, "V", 1); if (ret < 0) { return NULL; } - return buf; -} + ret = read_string(&rs->rigport, s_buf, BUFSZ, ">", 1); + if (ret < 0) + { + return NULL; + } + + p = strchr(s_buf, '\r'); + + if (p) + { + // chomp + *p = '\0'; + } + + return s_buf; +} /* diff --git a/rigs/prm80/prm80.h b/rigs/prm80/prm80.h index 71e96a138..fbaf50fbb 100644 --- a/rigs/prm80/prm80.h +++ b/rigs/prm80/prm80.h @@ -1,6 +1,6 @@ /* * Hamlib PRM80 backend - main header - * Copyright (c) 2010 by Stephane Fillod + * Copyright (c) 2010,2021 by Stephane Fillod * * * This library is free software; you can redistribute it and/or @@ -24,28 +24,41 @@ #include -#define BACKEND_VER "20101027" +#define BACKEND_VER "20210217" #define PRM80_MEM_CAP { \ .freq = 1, \ - .mode = 1, \ .rptr_shift = 1, \ - .rptr_offs = 1, \ - .flags = 1, /* lockout*/ \ - .levels = RIG_LEVEL_SQL|RIG_LEVEL_AF, \ + .flags = 1, /* lockout */ \ } +struct prm80_priv_data +{ + freq_t rx_freq; /* last RX freq set */ + freq_t tx_freq; /* last TX freq set */ + split_t split; /* emulated split on/off */ +}; + +int prm80_init(RIG *rig); +int prm80_cleanup(RIG *rig); int prm80_reset(RIG *rig, reset_t reset); int prm80_set_freq(RIG *rig, vfo_t vfo, freq_t freq); int prm80_get_freq(RIG *rig, vfo_t vfo, freq_t *freq); +int prm80_set_split_vfo(RIG *rig, vfo_t vfo, split_t split, vfo_t tx_vfo); +int prm80_get_split_vfo(RIG *rig, vfo_t vfo, split_t *split, vfo_t *tx_vfo); +int prm80_set_split_freq(RIG *rig, vfo_t vfo, freq_t tx_freq); +int prm80_get_split_freq(RIG *rig, vfo_t vfo, freq_t *tx_freq); +int prm80_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width); +int prm80_set_func(RIG *rig, vfo_t vfo, setting_t func, int status); +int prm80_get_func(RIG *rig, vfo_t vfo, setting_t func, int *status); int prm80_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val); int prm80_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val); -int prm80_set_mem (RIG *rig, vfo_t vfo, int ch); -int prm80_get_mem (RIG *rig, vfo_t vfo, int *ch); -int prm80_set_channel(RIG * rig, vfo_t vfo, const channel_t * chan); -int prm80_get_channel(RIG * rig, vfo_t vfo, channel_t * chan, int read_only); +int prm80_set_mem(RIG *rig, vfo_t vfo, int ch); +int prm80_get_mem(RIG *rig, vfo_t vfo, int *ch); +int prm80_set_channel(RIG *rig, vfo_t vfo, const channel_t *chan); +int prm80_get_channel(RIG *rig, vfo_t vfo, channel_t *chan, int read_only); -const char* prm80_get_info(RIG *rig); +const char *prm80_get_info(RIG *rig); extern const struct rig_caps prm8060_caps; diff --git a/rigs/prm80/prm8060.c b/rigs/prm80/prm8060.c index 9f60d09d5..ff690e883 100644 --- a/rigs/prm80/prm8060.c +++ b/rigs/prm80/prm8060.c @@ -1,6 +1,6 @@ /* * Hamlib PRM80 backend - PRM8060 description - * Copyright (c) 2010 by Stephane Fillod + * Copyright (c) 2010,2021 by Stephane Fillod * * * This library is free software; you can redistribute it and/or @@ -33,12 +33,13 @@ #define PRM8060_ALL_MODES (RIG_MODE_FM) -#define PRM8060_FUNC (RIG_FUNC_NONE) +#define PRM8060_FUNC (RIG_FUNC_REV|RIG_FUNC_LOCK) -#define PRM8060_LEVEL_ALL (RIG_LEVEL_AF|RIG_LEVEL_SQL) +#define PRM8060_LEVEL_ALL (RIG_LEVEL_AF|RIG_LEVEL_SQL /* |RIG_LEVEL_RFPOWER */) #define PRM8060_PARM_ALL (RIG_PARM_NONE) +// RIG_OP_FROM_VFO RIG_OP_MCL ?? #define PRM8060_VFO_OPS (RIG_OP_NONE) #define PRM8060_VFO (RIG_VFO_MEM) @@ -46,7 +47,7 @@ /* * PRM 8060 rig capabilities. * http://prm80.sourceforge.net/ - * + * https://github.com/f4fez/prm80 */ const struct rig_caps prm8060_caps = { @@ -55,7 +56,7 @@ const struct rig_caps prm8060_caps = .mfg_name = "Philips/Simoco", .version = BACKEND_VER ".0", .copyright = "LGPL", - .status = RIG_STATUS_ALPHA, + .status = RIG_STATUS_BETA, .rig_type = RIG_TYPE_TRANSCEIVER, .ptt_type = RIG_PTT_NONE, .dcd_type = RIG_DCD_NONE, @@ -64,16 +65,16 @@ const struct rig_caps prm8060_caps = .serial_rate_max = 4800, .serial_data_bits = 7, .serial_stop_bits = 1, - .serial_parity = RIG_PARITY_SPACE, + .serial_parity = RIG_PARITY_EVEN, .serial_handshake = RIG_HANDSHAKE_NONE, .write_delay = 0, .post_write_delay = 0, - .timeout = 2000, - .retry = 3, + .timeout = 1000, + .retry = 0, .has_get_func = PRM8060_FUNC, .has_set_func = PRM8060_FUNC, - .has_get_level = PRM8060_LEVEL_ALL, + .has_get_level = PRM8060_LEVEL_ALL | RIG_LEVEL_RFPOWER, .has_set_level = RIG_LEVEL_SET(PRM8060_LEVEL_ALL), .has_get_parm = PRM8060_PARM_ALL, .has_set_parm = RIG_PARM_SET(PRM8060_PARM_ALL), @@ -115,17 +116,25 @@ const struct rig_caps prm8060_caps = }, /* mode/filter list, remember: order matters! */ .filters = { - /* rough guesses */ {PRM8060_ALL_MODES, kHz(12.5)}, RIG_FLT_END, }, + .rig_init = prm80_init, + .rig_cleanup = prm80_cleanup, + .get_mode = prm80_get_mode, .set_freq = prm80_set_freq, .get_freq = prm80_get_freq, + .set_split_vfo = prm80_set_split_vfo, + .get_split_vfo = prm80_get_split_vfo, + .set_split_freq = prm80_set_split_freq, + .get_split_freq = prm80_get_split_freq, .set_channel = prm80_set_channel, .get_channel = prm80_get_channel, .set_mem = prm80_set_mem, .get_mem = prm80_get_mem, + .set_func = prm80_set_func, + .get_func = prm80_get_func, .set_level = prm80_set_level, .get_level = prm80_get_level, .reset = prm80_reset,