mirror of
https://github.com/Hamlib/Hamlib
synced 2026-08-13 17:32:05 -04:00
feat(kenwood): add TH-D75 backend
Register exact TH-D75 identity and regional capabilities. Replace the fixed-offset D74/D75 FO and ME handling with bounded typed record codecs, and make shared Kenwood mode lookup preserve its unsupported sentinel within the declared table bounds. Add malformed-input and round-trip codec coverage while leaving memory writes, arbitrary split, clone names, and asynchronous status unadvertised.
This commit is contained in:
parent
5bc6deefe1
commit
07d6a7ff71
18 changed files with 1616 additions and 379 deletions
|
|
@ -869,6 +869,7 @@ class TestClass:
|
|||
'RIG_MODEL_TCI1X',
|
||||
'RIG_MODEL_THD72A',
|
||||
'RIG_MODEL_THD74',
|
||||
'RIG_MODEL_THD75',
|
||||
'RIG_MODEL_THD7A',
|
||||
'RIG_MODEL_THD7AG',
|
||||
'RIG_MODEL_THETIS',
|
||||
|
|
|
|||
|
|
@ -249,6 +249,7 @@
|
|||
#define RIG_MODEL_SDRCONSOLE RIG_MAKE_MODEL(RIG_KENWOOD, 56)
|
||||
#define RIG_MODEL_QRPLABS_QMX RIG_MAKE_MODEL(RIG_KENWOOD, 57)
|
||||
#define RIG_MODEL_HAMGEEK_USDX RIG_MAKE_MODEL(RIG_KENWOOD, 58)
|
||||
#define RIG_MODEL_THD75 RIG_MAKE_MODEL(RIG_KENWOOD, 59)
|
||||
///@}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ IC10SRC = ts440.c ts940.c ts711.c ts811.c r5000.c
|
|||
|
||||
THSRC = thd7.c thf7.c thg71.c tmd700.c tmv7.c thf6a.c thd72.c tmd710.c thd74.c
|
||||
|
||||
KENWOODSRC = kenwood.c kenwood.h th.c th.h ic10.c ic10.h elecraft.c elecraft.h \
|
||||
KENWOODSRC = kenwood.c kenwood.h th.c th.h thd7x.c thd7x.h ic10.c ic10.h elecraft.c elecraft.h \
|
||||
transfox.c flex.c flex.h level_gran_kenwood.h level_gran_elecraft.h \
|
||||
hamgeek.c hamgeek.h
|
||||
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ static int flex6k_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
|
|||
{
|
||||
struct kenwood_priv_caps *caps = kenwood_caps(rig);
|
||||
char buf[10];
|
||||
char kmode;
|
||||
int kmode;
|
||||
int idx;
|
||||
int err;
|
||||
|
||||
|
|
@ -498,7 +498,7 @@ static int powersdr_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
|
|||
{
|
||||
struct kenwood_priv_caps *caps = kenwood_caps(rig);
|
||||
char buf[64];
|
||||
char kmode;
|
||||
int kmode;
|
||||
int idx;
|
||||
int err;
|
||||
|
||||
|
|
|
|||
|
|
@ -1566,7 +1566,7 @@ static int k3_set_split_mode(RIG *rig, vfo_t vfo, rmode_t tx_mode, pbwidth_t tx_
|
|||
{
|
||||
struct kenwood_priv_caps *caps = kenwood_caps(rig);
|
||||
char buf[32];
|
||||
char kmode;
|
||||
int kmode;
|
||||
int err;
|
||||
char cmd_m[16];
|
||||
const struct kenwood_priv_data *priv = STATE(rig)->priv;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ static const struct kenwood_id_string kenwood_id_string_list[] =
|
|||
{ RIG_MODEL_TMD710, "TM-D710" },
|
||||
{ RIG_MODEL_THD72A, "TH-D72" },
|
||||
{ RIG_MODEL_THD74, "TH-D74" },
|
||||
{ RIG_MODEL_THD75, "TH-D75" },
|
||||
{ RIG_MODEL_TMV7, "TM-V7" },
|
||||
{ RIG_MODEL_TMV71, "TM-V71" },
|
||||
{ RIG_MODEL_THF6A, "TH-F6" },
|
||||
|
|
@ -814,7 +815,7 @@ rmode_t kenwood2rmode(unsigned char mode, const rmode_t mode_table[])
|
|||
return (mode_table[mode]);
|
||||
}
|
||||
|
||||
char rmode2kenwood(rmode_t mode, const rmode_t mode_table[])
|
||||
int rmode2kenwood(rmode_t mode, const rmode_t mode_table[])
|
||||
{
|
||||
rig_debug(RIG_DEBUG_VERBOSE, "%s called, mode=%s\n", __func__,
|
||||
rig_strrmode(mode));
|
||||
|
|
@ -1174,7 +1175,7 @@ int kenwood_open(RIG *rig)
|
|||
it's not supported */
|
||||
}
|
||||
|
||||
if (!RIG_IS_THD74 && !RIG_IS_THD7A && !RIG_IS_TMD700)
|
||||
if (!RIG_IS_THD74 && !RIG_IS_THD75 && !RIG_IS_THD7A && !RIG_IS_TMD700)
|
||||
{
|
||||
int retval;
|
||||
// call get_split to fill in current split and tx_vfo status
|
||||
|
|
@ -2466,7 +2467,7 @@ static int kenwood_set_filter_width(RIG *rig, rmode_t mode, pbwidth_t width)
|
|||
int kenwood_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
|
||||
{
|
||||
char c;
|
||||
char kmode;
|
||||
int kmode;
|
||||
char buf[6];
|
||||
char data_mode = '0';
|
||||
char *data_cmd = "DA";
|
||||
|
|
@ -5294,6 +5295,7 @@ int kenwood_set_trn(RIG *rig, int trn)
|
|||
|
||||
case RIG_MODEL_THD7A:
|
||||
case RIG_MODEL_THD74:
|
||||
case RIG_MODEL_THD75:
|
||||
RETURNFUNC(kenwood_transaction(rig, (trn == RIG_TRN_RIG) ? "AI 1" : "AI 0", buf,
|
||||
sizeof buf));
|
||||
|
||||
|
|
@ -5325,7 +5327,7 @@ int kenwood_get_trn(RIG *rig, int *trn)
|
|||
RETURNFUNC(-RIG_ENAVAIL);
|
||||
}
|
||||
|
||||
if (RIG_IS_THD74 || RIG_IS_THD7A || RIG_IS_TMD700)
|
||||
if (RIG_IS_THD74 || RIG_IS_THD75 || RIG_IS_THD7A || RIG_IS_TMD700)
|
||||
{
|
||||
retval = kenwood_safe_transaction(rig, "AI", trnbuf, 6, 4);
|
||||
}
|
||||
|
|
@ -5339,7 +5341,7 @@ int kenwood_get_trn(RIG *rig, int *trn)
|
|||
RETURNFUNC(retval);
|
||||
}
|
||||
|
||||
if (RIG_IS_THD74 || RIG_IS_THD7A || RIG_IS_TMD700)
|
||||
if (RIG_IS_THD74 || RIG_IS_THD75 || RIG_IS_THD7A || RIG_IS_TMD700)
|
||||
{
|
||||
*trn = trnbuf[3] != '0' ? RIG_TRN_RIG : RIG_TRN_OFF;
|
||||
}
|
||||
|
|
@ -6014,7 +6016,7 @@ int kenwood_get_channel(RIG *rig, vfo_t vfo, channel_t *chan, int read_only)
|
|||
int kenwood_set_channel(RIG *rig, vfo_t vfo, const channel_t *chan)
|
||||
{
|
||||
char buf[128];
|
||||
char mode, tx_mode = 0;
|
||||
int mode, tx_mode = 0;
|
||||
char bank = ' ';
|
||||
int err;
|
||||
int tone = 0;
|
||||
|
|
@ -6531,6 +6533,7 @@ DECLARE_INITRIG_BACKEND(kenwood)
|
|||
rig_register(&thd7a_caps);
|
||||
rig_register(&thd72a_caps);
|
||||
rig_register(&thd74_caps);
|
||||
rig_register(&thd75_caps);
|
||||
rig_register(&thf7e_caps);
|
||||
rig_register(&thg71_caps);
|
||||
rig_register(&tmv7_caps);
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ extern struct confparams kenwood_cfg_params[];
|
|||
#define RIG_IS_K4 (rig->caps->rig_model == RIG_MODEL_K4)
|
||||
#define RIG_IS_THD7A (rig->caps->rig_model == RIG_MODEL_THD7A)
|
||||
#define RIG_IS_THD74 (rig->caps->rig_model == RIG_MODEL_THD74)
|
||||
#define RIG_IS_THD75 (rig->caps->rig_model == RIG_MODEL_THD75)
|
||||
#define RIG_IS_TMD700 (rig->caps->rig_model == RIG_MODEL_TMD700)
|
||||
#define RIG_IS_TS2000 (rig->caps->rig_model == RIG_MODEL_TS2000)
|
||||
#define RIG_IS_TS50 (rig->caps->rig_model == RIG_MODEL_TS50)
|
||||
|
|
@ -208,7 +209,7 @@ int kenwood_safe_transaction(RIG *rig, const char *cmd, char *buf,
|
|||
size_t buf_size, size_t expected);
|
||||
|
||||
rmode_t kenwood2rmode(unsigned char mode, const rmode_t mode_table[]);
|
||||
char rmode2kenwood(rmode_t mode, const rmode_t mode_table[]);
|
||||
int rmode2kenwood(rmode_t mode, const rmode_t mode_table[]);
|
||||
|
||||
int kenwood_init(RIG *rig);
|
||||
int kenwood_cleanup(RIG *rig);
|
||||
|
|
@ -310,6 +311,7 @@ extern struct rig_caps sdrconsole_caps;
|
|||
extern struct rig_caps thd7a_caps;
|
||||
extern struct rig_caps thd72a_caps;
|
||||
extern struct rig_caps thd74_caps;
|
||||
extern struct rig_caps thd75_caps;
|
||||
extern struct rig_caps tmd700_caps;
|
||||
extern struct rig_caps thf7a_caps;
|
||||
extern struct rig_caps thf7e_caps;
|
||||
|
|
|
|||
|
|
@ -598,7 +598,7 @@ int pihspdr_set_channel(RIG *rig, vfo_t vfo, const channel_t *chan)
|
|||
char sqltype;
|
||||
char shift;
|
||||
char buf[128];
|
||||
char mode, tx_mode = 0;
|
||||
int mode, tx_mode = 0;
|
||||
int err;
|
||||
int tone = 0;
|
||||
int tstep;
|
||||
|
|
|
|||
|
|
@ -286,7 +286,8 @@ th_get_freq(RIG *rig, vfo_t vfo, freq_t *freq)
|
|||
int
|
||||
th_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
|
||||
{
|
||||
char kmode, mdbuf[8];
|
||||
char mdbuf[8];
|
||||
int kmode;
|
||||
const struct kenwood_priv_caps *priv = (const struct kenwood_priv_caps *)
|
||||
rig->caps->priv;
|
||||
|
||||
|
|
@ -447,7 +448,8 @@ th_set_vfo(RIG *rig, vfo_t vfo)
|
|||
|
||||
/* No "VMC" cmd on THD72A/THD74 */
|
||||
if (rig->caps->rig_model == RIG_MODEL_THD72A
|
||||
|| rig->caps->rig_model == RIG_MODEL_THD74)
|
||||
|| rig->caps->rig_model == RIG_MODEL_THD74
|
||||
|| rig->caps->rig_model == RIG_MODEL_THD75)
|
||||
{
|
||||
return RIG_OK;
|
||||
}
|
||||
|
|
@ -556,7 +558,8 @@ th_get_vfo_char(RIG *rig, vfo_t *vfo, char *vfoch)
|
|||
|
||||
/* No "VMC" on THD72A/THD74 */
|
||||
if (rig->caps->rig_model == RIG_MODEL_THD72A
|
||||
|| rig->caps->rig_model == RIG_MODEL_THD74)
|
||||
|| rig->caps->rig_model == RIG_MODEL_THD74
|
||||
|| rig->caps->rig_model == RIG_MODEL_THD75)
|
||||
{
|
||||
*vfoch = '0'; /* FIXME: fake */
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
#define THD72_VFO (RIG_VFO_A|RIG_VFO_B)
|
||||
|
||||
static rmode_t thd72_mode_table[3] =
|
||||
static rmode_t thd72_mode_table[KENWOOD_MODE_TABLE_MAX] =
|
||||
{
|
||||
[0] = RIG_MODE_FM, /* normal, but narrow compared to broadcast */
|
||||
[1] = RIG_MODE_FMN, /* what kenwood calls narrow */
|
||||
|
|
|
|||
1030
rigs/kenwood/thd74.c
1030
rigs/kenwood/thd74.c
File diff suppressed because it is too large
Load diff
550
rigs/kenwood/thd7x.c
Normal file
550
rigs/kenwood/thd7x.c
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
/*
|
||||
* Hamlib Kenwood TH-D74/TH-D75 record codec
|
||||
* Copyright (c) 2026 by Hamlib Team
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hamlib/rig.h"
|
||||
#include "thd7x.h"
|
||||
|
||||
#define THD7X_FREQUENCY_MAX UINT64_C(9999999999)
|
||||
|
||||
struct thd7x_cursor
|
||||
{
|
||||
const char *next;
|
||||
size_t remaining;
|
||||
};
|
||||
|
||||
struct thd7x_field
|
||||
{
|
||||
const char *value;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
static int thd7x_next_field(struct thd7x_cursor *cursor, int final,
|
||||
struct thd7x_field *field)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
while (length < cursor->remaining && cursor->next[length] != ',')
|
||||
{
|
||||
length++;
|
||||
}
|
||||
|
||||
if ((!final && length == cursor->remaining)
|
||||
|| (final && length != cursor->remaining))
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
field->value = cursor->next;
|
||||
field->length = length;
|
||||
cursor->next += length + (final ? 0 : 1);
|
||||
cursor->remaining -= length + (final ? 0 : 1);
|
||||
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
static int thd7x_decimal_field(struct thd7x_cursor *cursor, int final,
|
||||
size_t width, uint64_t maximum,
|
||||
uint64_t *value)
|
||||
{
|
||||
struct thd7x_field field;
|
||||
uint64_t parsed = 0;
|
||||
int retval;
|
||||
|
||||
retval = thd7x_next_field(cursor, final, &field);
|
||||
|
||||
if (retval != RIG_OK || field.length != width)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < field.length; i++)
|
||||
{
|
||||
uint64_t digit;
|
||||
|
||||
if (field.value[i] < '0' || field.value[i] > '9')
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
digit = (uint64_t)(field.value[i] - '0');
|
||||
|
||||
if (digit > maximum || parsed > (maximum - digit) / 10)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed = parsed * 10 + digit;
|
||||
}
|
||||
|
||||
*value = parsed;
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
static int thd7x_step_field(struct thd7x_cursor *cursor, uint8_t *step)
|
||||
{
|
||||
struct thd7x_field field;
|
||||
char code;
|
||||
int retval;
|
||||
|
||||
retval = thd7x_next_field(cursor, 0, &field);
|
||||
|
||||
if (retval != RIG_OK || field.length != 1)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
code = field.value[0];
|
||||
|
||||
if (code >= '0' && code <= '9')
|
||||
{
|
||||
*step = (uint8_t)(code - '0');
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
if (code == 'A' || code == 'B')
|
||||
{
|
||||
*step = (uint8_t)(10 + code - 'A');
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
static int thd7x_urcall_field(struct thd7x_cursor *cursor, char *urcall)
|
||||
{
|
||||
struct thd7x_field field;
|
||||
int retval;
|
||||
|
||||
retval = thd7x_next_field(cursor, 0, &field);
|
||||
|
||||
if (retval != RIG_OK || field.length > THD7X_URCALL_MAX)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < field.length; i++)
|
||||
{
|
||||
if (field.value[i] == '\0' || field.value[i] == '\r'
|
||||
|| field.value[i] == '\n')
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(urcall, field.value, field.length);
|
||||
urcall[field.length] = '\0';
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
static int thd7x_start_cursor(const char *input, size_t input_len,
|
||||
const char *prefix,
|
||||
struct thd7x_cursor *cursor)
|
||||
{
|
||||
const size_t prefix_len = 3;
|
||||
|
||||
if (input_len > 0 && input[input_len - 1] == '\r')
|
||||
{
|
||||
input_len--;
|
||||
}
|
||||
|
||||
if (input_len < prefix_len || memcmp(input, prefix, prefix_len) != 0)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
cursor->next = input + prefix_len;
|
||||
cursor->remaining = input_len - prefix_len;
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
static int thd7x_urcall_length(const char *urcall, size_t *length)
|
||||
{
|
||||
for (size_t i = 0; i <= THD7X_URCALL_MAX; i++)
|
||||
{
|
||||
if (urcall[i] == '\0')
|
||||
{
|
||||
*length = i;
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
if (urcall[i] == ',' || urcall[i] == '\r' || urcall[i] == '\n')
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
static char thd7x_step_code(uint8_t step)
|
||||
{
|
||||
return step < 10 ? (char)('0' + step) : (char)('A' + step - 10);
|
||||
}
|
||||
|
||||
static int thd7x_fo_valid(const struct thd7x_fo_record *record,
|
||||
size_t *urcall_len)
|
||||
{
|
||||
if (record->band > 1 || record->frequency_hz > THD7X_FREQUENCY_MAX
|
||||
|| record->offset_hz > THD7X_FREQUENCY_MAX
|
||||
|| record->rx_step > 11 || record->tx_step > 11
|
||||
|| record->mode > 9 || record->fine_enabled > 1
|
||||
|| record->fine_step > 3 || record->tone_enabled > 1
|
||||
|| record->ctcss_enabled > 1 || record->dcs_enabled > 1
|
||||
|| record->cross_enabled > 1 || record->reverse_enabled > 1
|
||||
|| record->shift > 3 || record->tone_index > 41
|
||||
|| record->ctcss_index > 41 || record->dcs_index > 103
|
||||
|| record->cross_selector > 3
|
||||
|| record->digital_squelch_type > 2
|
||||
|| record->digital_squelch_code > 99)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
return thd7x_urcall_length(record->urcall, urcall_len);
|
||||
}
|
||||
|
||||
static int thd7x_me_valid(const struct thd7x_me_record *record,
|
||||
size_t *urcall_len)
|
||||
{
|
||||
if (record->channel > 999 || record->frequency_hz > THD7X_FREQUENCY_MAX
|
||||
|| record->offset_hz > THD7X_FREQUENCY_MAX
|
||||
|| record->rx_step > 11 || record->tx_step > 11
|
||||
|| record->mode > 9 || record->fine_enabled > 1
|
||||
|| record->fine_step > 3 || record->tone_enabled > 1
|
||||
|| record->ctcss_enabled > 1 || record->dcs_enabled > 1
|
||||
|| record->cross_enabled > 1 || record->reverse_enabled > 1
|
||||
|| record->odd_split_enabled > 1 || record->shift > 3
|
||||
|| record->tone_index > 41 || record->ctcss_index > 41
|
||||
|| record->dcs_index > 103 || record->cross_selector > 3
|
||||
|| record->digital_squelch_type > 2
|
||||
|| record->digital_squelch_code > 99
|
||||
|| record->lockout_enabled > 1)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
return thd7x_urcall_length(record->urcall, urcall_len);
|
||||
}
|
||||
|
||||
int thd7x_parse_fo(const char *input, size_t input_len,
|
||||
struct thd7x_fo_record *record)
|
||||
{
|
||||
struct thd7x_fo_record parsed = { 0 };
|
||||
struct thd7x_cursor cursor;
|
||||
uint64_t value;
|
||||
|
||||
if (input == NULL || record == NULL)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
if (thd7x_start_cursor(input, input_len, "FO ", &cursor) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 1, 1, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.band = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 10, THD7X_FREQUENCY_MAX,
|
||||
&parsed.frequency_hz) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 10, THD7X_FREQUENCY_MAX,
|
||||
&parsed.offset_hz) != RIG_OK
|
||||
|| thd7x_step_field(&cursor, &parsed.rx_step) != RIG_OK
|
||||
|| thd7x_step_field(&cursor, &parsed.tx_step) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 1, 9, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.mode = (uint8_t)value;
|
||||
|
||||
#define THD7X_PARSE_FO_FIELD(member, maximum) \
|
||||
if (thd7x_decimal_field(&cursor, 0, 1, maximum, &value) != RIG_OK) \
|
||||
{ \
|
||||
return -RIG_EPROTO; \
|
||||
} \
|
||||
parsed.member = (uint8_t)value
|
||||
|
||||
THD7X_PARSE_FO_FIELD(fine_enabled, 1);
|
||||
THD7X_PARSE_FO_FIELD(fine_step, 3);
|
||||
THD7X_PARSE_FO_FIELD(tone_enabled, 1);
|
||||
THD7X_PARSE_FO_FIELD(ctcss_enabled, 1);
|
||||
THD7X_PARSE_FO_FIELD(dcs_enabled, 1);
|
||||
THD7X_PARSE_FO_FIELD(cross_enabled, 1);
|
||||
THD7X_PARSE_FO_FIELD(reverse_enabled, 1);
|
||||
THD7X_PARSE_FO_FIELD(shift, 3);
|
||||
|
||||
#undef THD7X_PARSE_FO_FIELD
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 2, 41, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.tone_index = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 2, 41, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.ctcss_index = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 3, 103, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.dcs_index = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 1, 3, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.cross_selector = (uint8_t)value;
|
||||
|
||||
if (thd7x_urcall_field(&cursor, parsed.urcall) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 1, 2, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.digital_squelch_type = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 1, 2, 99, &value) != RIG_OK
|
||||
|| cursor.remaining != 0)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.digital_squelch_code = (uint8_t)value;
|
||||
*record = parsed;
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
int thd7x_serialize_fo(const struct thd7x_fo_record *record, char *output,
|
||||
size_t output_size, size_t *output_len)
|
||||
{
|
||||
size_t urcall_len;
|
||||
int length;
|
||||
|
||||
if (record == NULL || output == NULL)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
if (thd7x_fo_valid(record, &urcall_len) != RIG_OK)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
length = snprintf(output, output_size,
|
||||
"FO %u,%010" PRIu64 ",%010" PRIu64
|
||||
",%c,%c,%u,%u,%u,%u,%u,%u,%u,%u,%u"
|
||||
",%02u,%02u,%03u,%u,%.*s,%u,%02u",
|
||||
(unsigned int)record->band, record->frequency_hz,
|
||||
record->offset_hz, thd7x_step_code(record->rx_step),
|
||||
thd7x_step_code(record->tx_step),
|
||||
(unsigned int)record->mode,
|
||||
(unsigned int)record->fine_enabled,
|
||||
(unsigned int)record->fine_step,
|
||||
(unsigned int)record->tone_enabled,
|
||||
(unsigned int)record->ctcss_enabled,
|
||||
(unsigned int)record->dcs_enabled,
|
||||
(unsigned int)record->cross_enabled,
|
||||
(unsigned int)record->reverse_enabled,
|
||||
(unsigned int)record->shift,
|
||||
(unsigned int)record->tone_index,
|
||||
(unsigned int)record->ctcss_index,
|
||||
(unsigned int)record->dcs_index,
|
||||
(unsigned int)record->cross_selector,
|
||||
(int)urcall_len, record->urcall,
|
||||
(unsigned int)record->digital_squelch_type,
|
||||
(unsigned int)record->digital_squelch_code);
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
return -RIG_EINTERNAL;
|
||||
}
|
||||
|
||||
if (output_len != NULL)
|
||||
{
|
||||
*output_len = (size_t)length;
|
||||
}
|
||||
|
||||
return (size_t)length < output_size ? RIG_OK : -RIG_ETRUNC;
|
||||
}
|
||||
|
||||
int thd7x_parse_me(const char *input, size_t input_len,
|
||||
struct thd7x_me_record *record)
|
||||
{
|
||||
struct thd7x_me_record parsed = { 0 };
|
||||
struct thd7x_cursor cursor;
|
||||
uint64_t value;
|
||||
|
||||
if (input == NULL || record == NULL)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
if (thd7x_start_cursor(input, input_len, "ME ", &cursor) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 3, 999, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.channel = (uint16_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 10, THD7X_FREQUENCY_MAX,
|
||||
&parsed.frequency_hz) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 10, THD7X_FREQUENCY_MAX,
|
||||
&parsed.offset_hz) != RIG_OK
|
||||
|| thd7x_step_field(&cursor, &parsed.rx_step) != RIG_OK
|
||||
|| thd7x_step_field(&cursor, &parsed.tx_step) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 1, 9, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.mode = (uint8_t)value;
|
||||
|
||||
#define THD7X_PARSE_ME_FIELD(member, maximum) \
|
||||
if (thd7x_decimal_field(&cursor, 0, 1, maximum, &value) != RIG_OK) \
|
||||
{ \
|
||||
return -RIG_EPROTO; \
|
||||
} \
|
||||
parsed.member = (uint8_t)value
|
||||
|
||||
THD7X_PARSE_ME_FIELD(fine_enabled, 1);
|
||||
THD7X_PARSE_ME_FIELD(fine_step, 3);
|
||||
THD7X_PARSE_ME_FIELD(tone_enabled, 1);
|
||||
THD7X_PARSE_ME_FIELD(ctcss_enabled, 1);
|
||||
THD7X_PARSE_ME_FIELD(dcs_enabled, 1);
|
||||
THD7X_PARSE_ME_FIELD(cross_enabled, 1);
|
||||
THD7X_PARSE_ME_FIELD(reverse_enabled, 1);
|
||||
THD7X_PARSE_ME_FIELD(odd_split_enabled, 1);
|
||||
THD7X_PARSE_ME_FIELD(shift, 3);
|
||||
|
||||
#undef THD7X_PARSE_ME_FIELD
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 2, 41, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.tone_index = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 2, 41, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.ctcss_index = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 3, 103, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.dcs_index = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 1, 3, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.cross_selector = (uint8_t)value;
|
||||
|
||||
if (thd7x_urcall_field(&cursor, parsed.urcall) != RIG_OK
|
||||
|| thd7x_decimal_field(&cursor, 0, 1, 2, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.digital_squelch_type = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 0, 2, 99, &value) != RIG_OK)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.digital_squelch_code = (uint8_t)value;
|
||||
|
||||
if (thd7x_decimal_field(&cursor, 1, 1, 1, &value) != RIG_OK
|
||||
|| cursor.remaining != 0)
|
||||
{
|
||||
return -RIG_EPROTO;
|
||||
}
|
||||
|
||||
parsed.lockout_enabled = (uint8_t)value;
|
||||
*record = parsed;
|
||||
return RIG_OK;
|
||||
}
|
||||
|
||||
int thd7x_serialize_me(const struct thd7x_me_record *record, char *output,
|
||||
size_t output_size, size_t *output_len)
|
||||
{
|
||||
size_t urcall_len;
|
||||
int length;
|
||||
|
||||
if (record == NULL || output == NULL)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
if (thd7x_me_valid(record, &urcall_len) != RIG_OK)
|
||||
{
|
||||
return -RIG_EINVAL;
|
||||
}
|
||||
|
||||
length = snprintf(output, output_size,
|
||||
"ME %03u,%010" PRIu64 ",%010" PRIu64
|
||||
",%c,%c,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u"
|
||||
",%02u,%02u,%03u,%u,%.*s,%u,%02u,%u",
|
||||
(unsigned int)record->channel, record->frequency_hz,
|
||||
record->offset_hz, thd7x_step_code(record->rx_step),
|
||||
thd7x_step_code(record->tx_step),
|
||||
(unsigned int)record->mode,
|
||||
(unsigned int)record->fine_enabled,
|
||||
(unsigned int)record->fine_step,
|
||||
(unsigned int)record->tone_enabled,
|
||||
(unsigned int)record->ctcss_enabled,
|
||||
(unsigned int)record->dcs_enabled,
|
||||
(unsigned int)record->cross_enabled,
|
||||
(unsigned int)record->reverse_enabled,
|
||||
(unsigned int)record->odd_split_enabled,
|
||||
(unsigned int)record->shift,
|
||||
(unsigned int)record->tone_index,
|
||||
(unsigned int)record->ctcss_index,
|
||||
(unsigned int)record->dcs_index,
|
||||
(unsigned int)record->cross_selector,
|
||||
(int)urcall_len, record->urcall,
|
||||
(unsigned int)record->digital_squelch_type,
|
||||
(unsigned int)record->digital_squelch_code,
|
||||
(unsigned int)record->lockout_enabled);
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
return -RIG_EINTERNAL;
|
||||
}
|
||||
|
||||
if (output_len != NULL)
|
||||
{
|
||||
*output_len = (size_t)length;
|
||||
}
|
||||
|
||||
return (size_t)length < output_size ? RIG_OK : -RIG_ETRUNC;
|
||||
}
|
||||
82
rigs/kenwood/thd7x.h
Normal file
82
rigs/kenwood/thd7x.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Hamlib Kenwood TH-D74/TH-D75 record codec
|
||||
* Copyright (c) 2026 by Hamlib Team
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef HAMLIB_KENWOOD_THD7X_H
|
||||
#define HAMLIB_KENWOOD_THD7X_H 1
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define THD7X_URCALL_MAX 8
|
||||
#define THD7X_MAX_COMMAND_LENGTH 80
|
||||
#define THD7X_COMMAND_BUFSIZE (THD7X_MAX_COMMAND_LENGTH + 1)
|
||||
|
||||
struct thd7x_fo_record
|
||||
{
|
||||
uint8_t band;
|
||||
uint64_t frequency_hz;
|
||||
uint64_t offset_hz;
|
||||
uint8_t rx_step;
|
||||
uint8_t tx_step;
|
||||
uint8_t mode;
|
||||
uint8_t fine_enabled;
|
||||
uint8_t fine_step;
|
||||
uint8_t tone_enabled;
|
||||
uint8_t ctcss_enabled;
|
||||
uint8_t dcs_enabled;
|
||||
uint8_t cross_enabled;
|
||||
uint8_t reverse_enabled;
|
||||
uint8_t shift;
|
||||
uint8_t tone_index;
|
||||
uint8_t ctcss_index;
|
||||
uint8_t dcs_index;
|
||||
uint8_t cross_selector;
|
||||
char urcall[THD7X_URCALL_MAX + 1];
|
||||
uint8_t digital_squelch_type;
|
||||
uint8_t digital_squelch_code;
|
||||
};
|
||||
|
||||
struct thd7x_me_record
|
||||
{
|
||||
uint16_t channel;
|
||||
uint64_t frequency_hz;
|
||||
uint64_t offset_hz;
|
||||
uint8_t rx_step;
|
||||
uint8_t tx_step;
|
||||
uint8_t mode;
|
||||
uint8_t fine_enabled;
|
||||
uint8_t fine_step;
|
||||
uint8_t tone_enabled;
|
||||
uint8_t ctcss_enabled;
|
||||
uint8_t dcs_enabled;
|
||||
uint8_t cross_enabled;
|
||||
uint8_t reverse_enabled;
|
||||
uint8_t odd_split_enabled;
|
||||
uint8_t shift;
|
||||
uint8_t tone_index;
|
||||
uint8_t ctcss_index;
|
||||
uint8_t dcs_index;
|
||||
uint8_t cross_selector;
|
||||
char urcall[THD7X_URCALL_MAX + 1];
|
||||
uint8_t digital_squelch_type;
|
||||
uint8_t digital_squelch_code;
|
||||
uint8_t lockout_enabled;
|
||||
};
|
||||
|
||||
int thd7x_parse_fo(const char *input, size_t input_len,
|
||||
struct thd7x_fo_record *record);
|
||||
int thd7x_serialize_fo(const struct thd7x_fo_record *record, char *output,
|
||||
size_t output_size, size_t *output_len);
|
||||
int thd7x_parse_me(const char *input, size_t input_len,
|
||||
struct thd7x_me_record *record);
|
||||
int thd7x_serialize_me(const struct thd7x_me_record *record, char *output,
|
||||
size_t output_size, size_t *output_len);
|
||||
|
||||
#endif
|
||||
|
|
@ -1479,7 +1479,7 @@ int ts2000_set_channel(RIG *rig, vfo_t vfo, const channel_t *chan)
|
|||
{
|
||||
char sqltype = '0';
|
||||
char buf[128];
|
||||
char mode, tx_mode = 0;
|
||||
int mode, tx_mode = 0;
|
||||
char shift = '0';
|
||||
short dcscode = 0;
|
||||
short code = 0;
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ static int sf_fails;
|
|||
static int ts590_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
|
||||
{
|
||||
struct kenwood_priv_caps *caps = kenwood_caps(rig);
|
||||
char kmode = rmode2kenwood(mode, caps->mode_table);
|
||||
int kmode = rmode2kenwood(mode, caps->mode_table);
|
||||
char cmd[32], c;
|
||||
int retval = -RIG_EINTERNAL;
|
||||
int hwidth = 0;
|
||||
|
|
|
|||
|
|
@ -612,7 +612,7 @@ int ts2000_set_channel(RIG *rig, vfo_t vfo, const channel_t *chan)
|
|||
{
|
||||
char sqltype = '0';
|
||||
char buf[128];
|
||||
char mode, tx_mode = 0;
|
||||
int mode, tx_mode = 0;
|
||||
char shift = '0';
|
||||
short dcscode = 0;
|
||||
short code = 0;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ DIRECT_TESTS = \
|
|||
testftx1parsers \
|
||||
testgeministatus \
|
||||
testgs100 \
|
||||
testicomts
|
||||
testicomts \
|
||||
testthd7x
|
||||
|
||||
GENERATED_TEST_WRAPPERS = \
|
||||
test2038.sh \
|
||||
|
|
|
|||
282
tests/testthd7x.c
Normal file
282
tests/testthd7x.c
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* Hamlib Kenwood TH-D74/TH-D75 record codec tests
|
||||
* Copyright (c) 2026 by Hamlib Team
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hamlib/rig.h"
|
||||
#include "../rigs/kenwood/thd7x.h"
|
||||
|
||||
static int failures;
|
||||
|
||||
#define EXPECT_TRUE(name, condition) \
|
||||
do \
|
||||
{ \
|
||||
if (!(condition)) \
|
||||
{ \
|
||||
fprintf(stderr, "%s:%d: %s failed\n", __FILE__, __LINE__, name); \
|
||||
failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EXPECT_INT(name, expected, actual) \
|
||||
do \
|
||||
{ \
|
||||
int expected_value = (expected); \
|
||||
int actual_value = (actual); \
|
||||
if (expected_value != actual_value) \
|
||||
{ \
|
||||
fprintf(stderr, "%s:%d: %s expected %d, got %d\n", \
|
||||
__FILE__, __LINE__, name, expected_value, actual_value); \
|
||||
failures++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void expect_string(const char *name, const char *expected,
|
||||
const char *actual)
|
||||
{
|
||||
if (strcmp(expected, actual) != 0)
|
||||
{
|
||||
fprintf(stderr, "%s: expected \"%s\", got \"%s\"\n", name,
|
||||
expected, actual);
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_analog_fo(void)
|
||||
{
|
||||
static const char command[] =
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00";
|
||||
struct thd7x_fo_record record;
|
||||
char output[THD7X_COMMAND_BUFSIZE];
|
||||
size_t output_len = 0;
|
||||
|
||||
EXPECT_INT("parse analog FO", RIG_OK,
|
||||
thd7x_parse_fo(command, sizeof(command) - 1, &record));
|
||||
EXPECT_INT("FO band", 0, record.band);
|
||||
EXPECT_TRUE("FO frequency", record.frequency_hz == UINT64_C(146520000));
|
||||
EXPECT_TRUE("FO offset", record.offset_hz == UINT64_C(600000));
|
||||
EXPECT_INT("FO RX step", 0, record.rx_step);
|
||||
EXPECT_INT("FO mode", 0, record.mode);
|
||||
EXPECT_INT("FO tone", 1, record.tone_enabled);
|
||||
EXPECT_INT("FO shift", 2, record.shift);
|
||||
EXPECT_INT("FO tone index", 8, record.tone_index);
|
||||
expect_string("FO empty URCALL", "", record.urcall);
|
||||
EXPECT_INT("serialize analog FO", RIG_OK,
|
||||
thd7x_serialize_fo(&record, output, sizeof(output), &output_len));
|
||||
expect_string("analog FO round trip", command, output);
|
||||
EXPECT_TRUE("analog FO length", output_len == strlen(command));
|
||||
}
|
||||
|
||||
static void test_dv_fo(void)
|
||||
{
|
||||
static const char command[] =
|
||||
"FO 1,0440000000,0000000000,A,B,1,0,0,0,0,0,0,0,0,12,12,047,0,CQCQCQ,2,23";
|
||||
struct thd7x_fo_record record;
|
||||
struct thd7x_fo_record reparsed;
|
||||
char output[THD7X_COMMAND_BUFSIZE];
|
||||
|
||||
EXPECT_INT("parse DV FO", RIG_OK,
|
||||
thd7x_parse_fo(command, sizeof(command) - 1, &record));
|
||||
EXPECT_INT("DV FO RX step A", 10, record.rx_step);
|
||||
EXPECT_INT("DV FO TX step B", 11, record.tx_step);
|
||||
EXPECT_INT("DV FO mode", 1, record.mode);
|
||||
expect_string("DV FO URCALL", "CQCQCQ", record.urcall);
|
||||
EXPECT_INT("DV FO squelch type", 2, record.digital_squelch_type);
|
||||
EXPECT_INT("serialize DV FO", RIG_OK,
|
||||
thd7x_serialize_fo(&record, output, sizeof(output), NULL));
|
||||
expect_string("DV FO canonical form", command, output);
|
||||
EXPECT_INT("reparse DV FO", RIG_OK,
|
||||
thd7x_parse_fo(output, strlen(output), &reparsed));
|
||||
EXPECT_INT("reparsed DV FO RX step", record.rx_step, reparsed.rx_step);
|
||||
expect_string("reparsed DV FO URCALL", record.urcall, reparsed.urcall);
|
||||
}
|
||||
|
||||
static void test_analog_me(void)
|
||||
{
|
||||
static const char command[] =
|
||||
"ME 042,0145370000,0000600000,0,0,0,0,1,1,0,0,0,0,0,2,12,12,000,0,CQCQCQ,0,00,1";
|
||||
struct thd7x_me_record record;
|
||||
char output[THD7X_COMMAND_BUFSIZE];
|
||||
|
||||
EXPECT_INT("parse analog ME", RIG_OK,
|
||||
thd7x_parse_me(command, sizeof(command) - 1, &record));
|
||||
EXPECT_INT("ME channel", 42, record.channel);
|
||||
EXPECT_TRUE("ME frequency", record.frequency_hz == UINT64_C(145370000));
|
||||
EXPECT_TRUE("ME offset", record.offset_hz == UINT64_C(600000));
|
||||
EXPECT_INT("ME mode", 0, record.mode);
|
||||
EXPECT_INT("ME fine step", 1, record.fine_step);
|
||||
EXPECT_INT("ME tone index", 12, record.tone_index);
|
||||
EXPECT_INT("ME lockout", 1, record.lockout_enabled);
|
||||
EXPECT_INT("serialize analog ME", RIG_OK,
|
||||
thd7x_serialize_me(&record, output, sizeof(output), NULL));
|
||||
expect_string("analog ME round trip", command, output);
|
||||
}
|
||||
|
||||
static void test_79_byte_dv_me(void)
|
||||
{
|
||||
static const char command[] =
|
||||
"ME 010,0145370000,0000600000,0,0,1,0,1,1,0,0,0,0,0,2,12,12,000,0,CQCQCQ,0,00,0\r";
|
||||
static const char canonical[] =
|
||||
"ME 010,0145370000,0000600000,0,0,1,0,1,1,0,0,0,0,0,2,12,12,000,0,CQCQCQ,0,00,0";
|
||||
struct thd7x_me_record record;
|
||||
struct thd7x_me_record reparsed;
|
||||
char output[THD7X_COMMAND_BUFSIZE];
|
||||
size_t output_len = 0;
|
||||
|
||||
EXPECT_TRUE("DV ME fixture is 79 bytes", sizeof(command) - 1 == 79);
|
||||
EXPECT_INT("parse 79-byte DV ME", RIG_OK,
|
||||
thd7x_parse_me(command, sizeof(command) - 1, &record));
|
||||
EXPECT_INT("DV ME mode", 1, record.mode);
|
||||
expect_string("DV ME URCALL", "CQCQCQ", record.urcall);
|
||||
EXPECT_INT("serialize DV ME", RIG_OK,
|
||||
thd7x_serialize_me(&record, output, sizeof(output), &output_len));
|
||||
expect_string("DV ME strips terminal CR", canonical, output);
|
||||
EXPECT_TRUE("DV ME canonical length", output_len == sizeof(canonical) - 1);
|
||||
EXPECT_INT("reparse DV ME", RIG_OK,
|
||||
thd7x_parse_me(output, output_len, &reparsed));
|
||||
EXPECT_INT("reparsed DV ME channel", record.channel, reparsed.channel);
|
||||
expect_string("reparsed DV ME URCALL", record.urcall, reparsed.urcall);
|
||||
}
|
||||
|
||||
static void test_empty_and_max_urcall(void)
|
||||
{
|
||||
static const char empty_command[] =
|
||||
"ME 999,9999999999,9999999999,B,A,9,1,3,1,1,1,1,1,1,3,41,41,103,3,,2,99,1";
|
||||
static const char max_command[] =
|
||||
"ME 999,9999999999,9999999999,B,A,9,1,3,1,1,1,1,1,1,3,41,41,103,3,ABCDEFGH,2,99,1";
|
||||
struct thd7x_me_record record;
|
||||
char output[THD7X_COMMAND_BUFSIZE];
|
||||
size_t output_len = 0;
|
||||
|
||||
EXPECT_INT("parse empty URCALL", RIG_OK,
|
||||
thd7x_parse_me(empty_command, sizeof(empty_command) - 1,
|
||||
&record));
|
||||
expect_string("preserve empty URCALL", "", record.urcall);
|
||||
EXPECT_INT("serialize empty URCALL", RIG_OK,
|
||||
thd7x_serialize_me(&record, output, sizeof(output), NULL));
|
||||
expect_string("empty URCALL round trip", empty_command, output);
|
||||
|
||||
EXPECT_INT("parse max URCALL", RIG_OK,
|
||||
thd7x_parse_me(max_command, sizeof(max_command) - 1, &record));
|
||||
EXPECT_INT("serialize max command", RIG_OK,
|
||||
thd7x_serialize_me(&record, output, sizeof(output), &output_len));
|
||||
EXPECT_TRUE("maximum command length",
|
||||
output_len == THD7X_MAX_COMMAND_LENGTH);
|
||||
expect_string("max URCALL round trip", max_command, output);
|
||||
}
|
||||
|
||||
static void test_malformed_fo(void)
|
||||
{
|
||||
static const char *const malformed[] = {
|
||||
"FX 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00",
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,",
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00,0",
|
||||
"FO 0,,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00",
|
||||
"FO 0,X146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00",
|
||||
"FO 0,00146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00",
|
||||
"FO 0,0146520000,0000600000,C,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00",
|
||||
"FO 2,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00",
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,42,08,000,0,,0,00",
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,104,0,,0,00",
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,123456789,0,00",
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00\n",
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00\rX"
|
||||
};
|
||||
struct thd7x_fo_record record;
|
||||
|
||||
for (size_t i = 0; i < sizeof(malformed) / sizeof(malformed[0]); i++)
|
||||
{
|
||||
EXPECT_INT("reject malformed FO", -RIG_EPROTO,
|
||||
thd7x_parse_fo(malformed[i], strlen(malformed[i]), &record));
|
||||
}
|
||||
|
||||
EXPECT_INT("reject null FO input", -RIG_EINVAL,
|
||||
thd7x_parse_fo(NULL, 0, &record));
|
||||
}
|
||||
|
||||
static void test_malformed_me(void)
|
||||
{
|
||||
static const char *const malformed[] = {
|
||||
"ME 1000,0145370000,0000600000,0,0,0,0,1,1,0,0,0,0,0,2,12,12,000,0,,0,00,0",
|
||||
"ME 010,0145370000,0000600000,0,0,0,0,1,1,0,0,0,0,0,2,12,12,000,0,,0,00,0,1",
|
||||
"ME 010,0145370000,0000600000,0,0,A,0,1,1,0,0,0,0,0,2,12,12,000,0,,0,00,0",
|
||||
"ME 010,0145370000,0000600000,0,0,0,0,1,1,0,0,0,0,0,2,12,12,000,0,,0,00,"
|
||||
};
|
||||
struct thd7x_me_record record;
|
||||
|
||||
for (size_t i = 0; i < sizeof(malformed) / sizeof(malformed[0]); i++)
|
||||
{
|
||||
EXPECT_INT("reject malformed ME", -RIG_EPROTO,
|
||||
thd7x_parse_me(malformed[i], strlen(malformed[i]), &record));
|
||||
}
|
||||
}
|
||||
|
||||
static void test_length_delimited_input(void)
|
||||
{
|
||||
static const char command[] =
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00";
|
||||
char unterminated[sizeof(command) - 1];
|
||||
struct thd7x_fo_record record;
|
||||
|
||||
memcpy(unterminated, command, sizeof(unterminated));
|
||||
EXPECT_INT("parse unterminated buffer", RIG_OK,
|
||||
thd7x_parse_fo(unterminated, sizeof(unterminated), &record));
|
||||
|
||||
for (size_t i = 0; i < sizeof(unterminated); i++)
|
||||
{
|
||||
EXPECT_INT("reject every truncated prefix", -RIG_EPROTO,
|
||||
thd7x_parse_fo(unterminated, i, &record));
|
||||
}
|
||||
}
|
||||
|
||||
static void test_serializer_errors(void)
|
||||
{
|
||||
static const char command[] =
|
||||
"FO 0,0146520000,0000600000,0,0,0,0,0,1,0,0,0,0,2,08,08,000,0,,0,00";
|
||||
struct thd7x_fo_record record;
|
||||
char output[8];
|
||||
size_t required = 0;
|
||||
|
||||
EXPECT_INT("parse serializer fixture", RIG_OK,
|
||||
thd7x_parse_fo(command, sizeof(command) - 1, &record));
|
||||
EXPECT_INT("report truncated output", -RIG_ETRUNC,
|
||||
thd7x_serialize_fo(&record, output, sizeof(output), &required));
|
||||
EXPECT_TRUE("report required size", required == strlen(command));
|
||||
EXPECT_TRUE("truncated output terminated", output[sizeof(output) - 1] == '\0');
|
||||
|
||||
record.mode = 10;
|
||||
EXPECT_INT("reject invalid serialized value", -RIG_EINVAL,
|
||||
thd7x_serialize_fo(&record, output, sizeof(output), NULL));
|
||||
EXPECT_INT("reject null output", -RIG_EINVAL,
|
||||
thd7x_serialize_fo(&record, NULL, 0, NULL));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
test_analog_fo();
|
||||
test_dv_fo();
|
||||
test_analog_me();
|
||||
test_79_byte_dv_me();
|
||||
test_empty_and_max_urcall();
|
||||
test_malformed_fo();
|
||||
test_malformed_me();
|
||||
test_length_delimited_input();
|
||||
test_serializer_errors();
|
||||
|
||||
if (failures != 0)
|
||||
{
|
||||
fprintf(stderr, "%d TH-D7x record codec test(s) failed\n", failures);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue