Hamlib/dorji/dra818.c

728 wiersze
17 KiB
C

/*
* Hamlib Dorji DRA818 backend
* Copyright (c) 2017 by Jeroen Vreeken
*
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <stdbool.h>
#include "hamlib/rig.h"
#include "bandplan.h"
#include "serial.h"
#include "register.h"
#include "tones.h"
#include "dra818.h"
#include "dorji.h"
static const char *dra818_handshake_cmd = "AT+DMOCONNECT\r\n";
static const char *dra818_handshake_res = "+DMOCONNECT:0\r\n";
static const char *dra818_setgroup_res = "+DMOSETGROUP:0\r\n";
static const char *dra818_setvolume_res = "+DMOSETVOLUME:0\r\n";
struct dra818_priv
{
shortfreq_t tx_freq;
shortfreq_t rx_freq;
shortfreq_t bw;
split_t split;
tone_t ctcss_tone;
tone_t ctcss_sql;
tone_t dcs_code;
tone_t dcs_sql;
int sql;
int vol;
};
static int dra818_response(RIG *rig, const char *expected)
{
char response[80];
int r = read_string(&rig->state.rigport, response, sizeof(response), "\n", 1);
if (r != strlen(expected))
{
return -RIG_EIO;
}
if (strcmp(expected, response))
{
rig_debug(RIG_DEBUG_VERBOSE, "dra818: response: %s\n", response);
return -RIG_ERJCTED;
}
return RIG_OK;
}
static void dra818_subaudio(RIG *rig, char *subaudio, tone_t tone, tone_t code)
{
if (code)
{
sprintf(subaudio, "%03dI", code);
return;
}
else if (tone)
{
int i;
for (i = 0; rig->caps->ctcss_list[i]; i++)
{
if (rig->caps->ctcss_list[i] == tone)
{
sprintf(subaudio, "%04d", i + 1);
return;
}
}
}
subaudio[0] = '0';
subaudio[1] = '0';
subaudio[2] = '0';
subaudio[3] = '0';
}
static int dra818_setgroup(RIG *rig)
{
struct dra818_priv *priv = rig->state.priv;
char cmd[80];
char subtx[5] = { 0 };
char subrx[5] = { 0 };
dra818_subaudio(rig, subtx, priv->ctcss_tone, priv->dcs_code);
dra818_subaudio(rig, subrx, priv->ctcss_sql, priv->dcs_sql);
sprintf(cmd, "AT+DMOSETGROUP=%1d,%03d.%04d,%03d.%04d,%4s,%1d,%4s\r\n",
priv->bw == 12500 ? 0 : 1,
(int)(priv->tx_freq / 1000000), (int)((priv->tx_freq % 1000000) / 100),
(int)(priv->rx_freq / 1000000), (int)((priv->rx_freq % 1000000) / 100),
subtx, priv->sql, subrx);
write_block(&rig->state.rigport, cmd, strlen(cmd));
return dra818_response(rig, dra818_setgroup_res);
}
static int dra818_setvolume(RIG *rig)
{
struct dra818_priv *priv = rig->state.priv;
char cmd[80];
sprintf(cmd, "AT+DMOSETVOLUME=%1d\r\n",
priv->vol);
write_block(&rig->state.rigport, cmd, strlen(cmd));
return dra818_response(rig, dra818_setvolume_res);
}
int dra818_init(RIG *rig)
{
rig_debug(RIG_DEBUG_VERBOSE, "%s: dra818_init called\n", __func__);
struct dra818_priv *priv = calloc(sizeof(*priv), 1);
if (!priv)
{
return -RIG_ENOMEM;
}
rig->state.priv = priv;
switch (rig->caps->rig_model)
{
case RIG_MODEL_DORJI_DRA818V:
priv->rx_freq = 145000000;
break;
case RIG_MODEL_DORJI_DRA818U:
priv->rx_freq = 435000000;
break;
}
priv->tx_freq = priv->rx_freq;
priv->bw = 12500;
priv->split = RIG_SPLIT_OFF;
priv->ctcss_tone = 0;
priv->ctcss_sql = 0;
priv->dcs_code = 0;
priv->dcs_sql = 0;
priv->sql = 4;
priv->vol = 6;
return RIG_OK;
}
int dra818_cleanup(RIG *rig)
{
rig_debug(RIG_DEBUG_VERBOSE, "%s: dra818_cleanup called\n", __func__);
free(rig->state.priv);
return RIG_OK;
}
int dra818_open(RIG *rig)
{
int i;
int r;
for (i = 0; i < 3; i++)
{
write_block(&rig->state.rigport, dra818_handshake_cmd,
strlen(dra818_handshake_cmd));
r = dra818_response(rig, dra818_handshake_res);
if (r == RIG_OK)
{
break;
}
}
if (r != RIG_OK)
{
return r;
}
r = dra818_setvolume(rig);
if (r != RIG_OK)
{
return r;
}
return dra818_setgroup(rig);
}
int dra818_set_freq(RIG *rig, vfo_t vfo, freq_t freq)
{
struct dra818_priv *priv = rig->state.priv;
/* Nearest channel */
shortfreq_t sfreq = ((freq + priv->bw / 2) / priv->bw);
sfreq *= priv->bw;
rig_debug(RIG_DEBUG_VERBOSE,
"dra818: requested freq = %"PRIfreq" Hz, set freq = %d Hz\n",
freq, (int)sfreq);
if (vfo == RIG_VFO_RX)
{
priv->rx_freq = sfreq;
if (priv->split == RIG_SPLIT_OFF)
{
priv->tx_freq = sfreq;
}
}
else if (vfo == RIG_VFO_TX)
{
priv->tx_freq = sfreq;
if (priv->split == RIG_SPLIT_OFF)
{
priv->rx_freq = sfreq;
}
}
else
{
return -RIG_EINVAL;
}
return dra818_setgroup(rig);
}
int dra818_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
{
struct dra818_priv *priv = rig->state.priv;
if (width > 12500)
{
priv->bw = 25000;
}
else
{
priv->bw = 12500;
}
rig_debug(RIG_DEBUG_VERBOSE, "dra818: bandwidth: %d\n", (int)priv->bw);
return dra818_setgroup(rig);
}
int dra818_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t *width)
{
struct dra818_priv *priv = rig->state.priv;
*mode = RIG_MODE_FM;
*width = priv->bw;
return RIG_OK;
}
int dra818_get_dcd(RIG *rig, vfo_t vfo, dcd_t *dcd)
{
struct dra818_priv *priv = rig->state.priv;
char cmd[80];
sprintf(cmd, "S+%03d.%04d\r\n",
(int)(priv->rx_freq / 1000000), (int)((priv->rx_freq % 1000000) / 100));
write_block(&rig->state.rigport, cmd, strlen(cmd));
char response[8];
int r = read_string(&rig->state.rigport, response, sizeof(response), "\n", 1);
if (r != 5)
{
return -RIG_EIO;
}
if (response[3] == 1)
{
*dcd = RIG_DCD_OFF;
}
else
{
*dcd = RIG_DCD_ON;
}
return RIG_OK;
}
int dra818_get_freq(RIG *rig, vfo_t vfo, freq_t *freq)
{
struct dra818_priv *priv = rig->state.priv;
switch (vfo)
{
case RIG_VFO_RX:
*freq = priv->rx_freq;
break;
case RIG_VFO_TX:
*freq = priv->tx_freq;
break;
default:
return -RIG_EINVAL;
}
return RIG_OK;
}
int dra818_set_split_vfo(RIG *rig, vfo_t vfo, split_t split, vfo_t tx_vfo)
{
struct dra818_priv *priv = rig->state.priv;
priv->split = split;
if (split == RIG_SPLIT_OFF)
{
priv->tx_freq = priv->rx_freq;
}
return dra818_setgroup(rig);
}
int dra818_get_split_vfo(RIG *rig, vfo_t vfo, split_t *split, vfo_t *tx_vfo)
{
struct dra818_priv *priv = rig->state.priv;
*split = priv->split;
if (priv->split == RIG_SPLIT_ON)
{
*tx_vfo = RIG_VFO_TX;
}
else
{
*tx_vfo = RIG_VFO_RX;
}
return RIG_OK;
}
int dra818_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val)
{
struct dra818_priv *priv = rig->state.priv;
switch (level)
{
case RIG_LEVEL_SQL:
/* SQL range: 0..8 (0=monitor) */
val->f = (priv->sql / 8.0);
break;
case RIG_LEVEL_AF:
/* AF range: 1..8 */
val->f = (priv->vol / 8.0);
break;
default:
return -RIG_EINVAL;
}
return RIG_OK;
}
int dra818_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val)
{
struct dra818_priv *priv = rig->state.priv;
switch (level)
{
case RIG_LEVEL_SQL:
/* SQL range: 0..8 (0=monitor) */
priv->sql = val.f * 8;
if (priv->sql < 0)
{
priv->sql = 0;
}
if (priv->sql > 8)
{
priv->sql = 8;
}
return dra818_setgroup(rig);
case RIG_LEVEL_AF:
/* AF range: 1..8 */
priv->vol = val.f * 8;
if (priv->vol < 1)
{
priv->vol = 1;
}
if (priv->vol > 8)
{
priv->vol = 8;
}
return dra818_setvolume(rig);
default:
break;
}
return -RIG_EINVAL;
}
int dra818_set_dcs_code(RIG *rig, vfo_t vfo, tone_t code)
{
struct dra818_priv *priv = rig->state.priv;
priv->dcs_code = code;
if (code)
{
priv->ctcss_tone = 0;
}
return dra818_setgroup(rig);
}
int dra818_set_ctcss_tone(RIG *rig, vfo_t vfo, tone_t tone)
{
struct dra818_priv *priv = rig->state.priv;
priv->ctcss_tone = tone;
if (tone)
{
priv->dcs_code = 0;
}
return dra818_setgroup(rig);
}
int dra818_set_dcs_sql(RIG *rig, vfo_t vfo, tone_t code)
{
struct dra818_priv *priv = rig->state.priv;
priv->dcs_sql = code;
if (code)
{
priv->ctcss_sql = 0;
}
return dra818_setgroup(rig);
}
int dra818_set_ctcss_sql(RIG *rig, vfo_t vfo, tone_t tone)
{
struct dra818_priv *priv = rig->state.priv;
priv->ctcss_sql = tone;
if (tone)
{
priv->dcs_sql = 0;
}
return dra818_setgroup(rig);
}
int dra818_get_ctcss_sql(RIG *rig, vfo_t vfo, tone_t *tone)
{
struct dra818_priv *priv = rig->state.priv;
*tone = priv->ctcss_sql;
return RIG_OK;
}
int dra818_get_dcs_sql(RIG *rig, vfo_t vfo, tone_t *code)
{
struct dra818_priv *priv = rig->state.priv;
*code = priv->dcs_sql;
return RIG_OK;
}
int dra818_get_dcs_code(RIG *rig, vfo_t vfo, tone_t *code)
{
struct dra818_priv *priv = rig->state.priv;
*code = priv->dcs_code;
return RIG_OK;
}
int dra818_get_ctcss_tone(RIG *rig, vfo_t vfo, tone_t *tone)
{
struct dra818_priv *priv = rig->state.priv;
*tone = priv->ctcss_tone;
return RIG_OK;
}
const struct rig_caps dra818u_caps =
{
.rig_model = RIG_MODEL_DORJI_DRA818U,
.model_name = "DRA818U",
.mfg_name = "Dorji",
.version = "0.1",
.copyright = "LGPL",
.status = RIG_STATUS_UNTESTED,
.rig_type = RIG_TYPE_TRANSCEIVER,
.ptt_type = RIG_PTT_NONE,
.dcd_type = RIG_DCD_RIG,
.port_type = RIG_PORT_SERIAL,
.serial_rate_min = 9600,
.serial_rate_max = 9600,
.serial_data_bits = 8,
.serial_stop_bits = 1,
.serial_parity = RIG_PARITY_NONE,
.serial_handshake = RIG_HANDSHAKE_NONE,
.write_delay = 0,
.post_write_delay = 0,
.timeout = 1000,
.retry = 0,
.has_get_func = RIG_FUNC_TONE | RIG_FUNC_TSQL | RIG_FUNC_SQL,
.has_set_func = RIG_FUNC_TONE | RIG_FUNC_TSQL | RIG_FUNC_SQL,
.has_get_level = RIG_LEVEL_AF | RIG_LEVEL_SQL,
.has_set_level = RIG_LEVEL_AF | RIG_LEVEL_SQL,
.has_get_parm = RIG_PARM_NONE,
.has_set_parm = RIG_PARM_NONE,
.level_gran = {},
.parm_gran = {},
.ctcss_list = /* 38 according to doc, are they all correct? */
(tone_t[])
{
670, 719, 744, 770, 797, 825, 854,
885, 915, 948, 974, 1000, 1035, 1072,
1109, 1148, 1188, 1230, 1273, 1318, 1365,
1413, 1462, 1514, 1567, 1622, 1679, 1738,
1799, 1862, 1928, 2035, 2107, 2181, 2257,
2336, 2418, 2503, 0
},
.dcs_list = common_dcs_list,
.preamp = { RIG_DBLST_END, },
.attenuator = { RIG_DBLST_END, },
.max_rit = Hz(0),
.max_xit = Hz(0),
.max_ifshift = Hz(0),
.targetable_vfo = 0,
.transceive = RIG_TRN_OFF,
.bank_qty = 0,
.chan_desc_sz = 0,
.chan_list = { RIG_CHAN_END, },
.rx_range_list1 = {
{MHz(400), MHz(480), RIG_MODE_FM, -1, -1, RIG_VFO_RX },
RIG_FRNG_END,
},
.rx_range_list2 = {
{MHz(400), MHz(480), RIG_MODE_FM, -1, -1, RIG_VFO_RX },
RIG_FRNG_END,
},
.tx_range_list1 = {
FRQ_RNG_70cm(1, RIG_MODE_FM, W(0.5), W(1), RIG_VFO_TX, RIG_ANT_1),
RIG_FRNG_END,
},
.tx_range_list2 = {
FRQ_RNG_70cm(2, RIG_MODE_FM, W(0.5), W(1), RIG_VFO_TX, RIG_ANT_1),
RIG_FRNG_END,
},
.tuning_steps = {
{RIG_MODE_FM, Hz(12500)},
RIG_TS_END,
},
.filters = {
{RIG_MODE_FM, Hz(12500)},
{RIG_MODE_FM, Hz(25000)},
RIG_FLT_END,
},
.rig_init = dra818_init,
.rig_cleanup = dra818_cleanup,
.rig_open = dra818_open,
.set_freq = dra818_set_freq,
.get_freq = dra818_get_freq,
.set_split_vfo = dra818_set_split_vfo,
.get_split_vfo = dra818_get_split_vfo,
.set_mode = dra818_set_mode,
.get_mode = dra818_get_mode,
.get_dcd = dra818_get_dcd,
.set_level = dra818_set_level,
.get_level = dra818_get_level,
.set_dcs_code = dra818_set_dcs_code,
.set_ctcss_tone = dra818_set_ctcss_tone,
.set_dcs_sql = dra818_set_dcs_sql,
.set_ctcss_sql = dra818_set_ctcss_sql,
.get_dcs_code = dra818_get_dcs_code,
.get_ctcss_tone = dra818_get_ctcss_tone,
.get_dcs_sql = dra818_get_dcs_sql,
.get_ctcss_sql = dra818_get_ctcss_sql,
};
const struct rig_caps dra818v_caps =
{
.rig_model = RIG_MODEL_DORJI_DRA818V,
.model_name = "DRA818V",
.mfg_name = "Dorji",
.version = "0.1",
.copyright = "LGPL",
.status = RIG_STATUS_UNTESTED,
.rig_type = RIG_TYPE_TRANSCEIVER,
.ptt_type = RIG_PTT_NONE,
.dcd_type = RIG_DCD_RIG,
.port_type = RIG_PORT_SERIAL,
.serial_rate_min = 9600,
.serial_rate_max = 9600,
.serial_data_bits = 8,
.serial_stop_bits = 1,
.serial_parity = RIG_PARITY_NONE,
.serial_handshake = RIG_HANDSHAKE_NONE,
.write_delay = 0,
.post_write_delay = 0,
.timeout = 1000,
.retry = 0,
.has_get_func = RIG_FUNC_TONE | RIG_FUNC_TSQL | RIG_FUNC_SQL,
.has_set_func = RIG_FUNC_TONE | RIG_FUNC_TSQL | RIG_FUNC_SQL,
.has_get_level = RIG_LEVEL_AF | RIG_LEVEL_SQL,
.has_set_level = RIG_LEVEL_AF | RIG_LEVEL_SQL,
.has_get_parm = RIG_PARM_NONE,
.has_set_parm = RIG_PARM_NONE,
.level_gran = {},
.parm_gran = {},
.ctcss_list = /* 38 according to doc, are they all correct? */
(tone_t[])
{
670, 719, 744, 770, 797, 825, 854,
885, 915, 948, 974, 1000, 1035, 1072,
1109, 1148, 1188, 1230, 1273, 1318, 1365,
1413, 1462, 1514, 1567, 1622, 1679, 1738,
1799, 1862, 1928, 2035, 2107, 2181, 2257,
2336, 2418, 2503, 0
},
.dcs_list = common_dcs_list,
.preamp = { RIG_DBLST_END, },
.attenuator = { RIG_DBLST_END, },
.max_rit = Hz(0),
.max_xit = Hz(0),
.max_ifshift = Hz(0),
.targetable_vfo = 0,
.transceive = RIG_TRN_OFF,
.bank_qty = 0,
.chan_desc_sz = 0,
.chan_list = { RIG_CHAN_END, },
.rx_range_list1 = {
{MHz(134), MHz(174), RIG_MODE_FM, -1, -1, RIG_VFO_RX },
RIG_FRNG_END,
},
.rx_range_list2 = {
{MHz(134), MHz(174), RIG_MODE_FM, -1, -1, RIG_VFO_RX },
RIG_FRNG_END,
},
.tx_range_list1 = {
FRQ_RNG_2m(1, RIG_MODE_FM, W(0.5), W(1), RIG_VFO_TX, RIG_ANT_1),
RIG_FRNG_END,
},
.tx_range_list2 = {
FRQ_RNG_2m(2, RIG_MODE_FM, W(0.5), W(1), RIG_VFO_TX, RIG_ANT_1),
RIG_FRNG_END,
},
.tuning_steps = {
{RIG_MODE_FM, Hz(12500)},
RIG_TS_END,
},
.filters = {
{RIG_MODE_FM, Hz(12500)},
{RIG_MODE_FM, Hz(25000)},
RIG_FLT_END,
},
.rig_init = dra818_init,
.rig_cleanup = dra818_cleanup,
.rig_open = dra818_open,
.set_freq = dra818_set_freq,
.get_freq = dra818_get_freq,
.set_split_vfo = dra818_set_split_vfo,
.get_split_vfo = dra818_get_split_vfo,
.set_mode = dra818_set_mode,
.get_mode = dra818_get_mode,
.get_dcd = dra818_get_dcd,
.set_level = dra818_set_level,
.get_level = dra818_get_level,
.set_dcs_code = dra818_set_dcs_code,
.set_ctcss_tone = dra818_set_ctcss_tone,
.set_dcs_sql = dra818_set_dcs_sql,
.set_ctcss_sql = dra818_set_ctcss_sql,
.get_dcs_code = dra818_get_dcs_code,
.get_ctcss_tone = dra818_get_ctcss_tone,
.get_dcs_sql = dra818_get_dcs_sql,
.get_ctcss_sql = dra818_get_ctcss_sql,
};