Skip to content

Commit

Permalink
Relax GD-73 linking errors (#441)
Browse files Browse the repository at this point in the history
* Turned all linking error into warnings. GD-73 manufacturer CPS generates buggy code plugs. Addresses #414.
* Fixed CTCSS/DCS encoding/decoding for GD-73 radios. Fixes #414.
  • Loading branch information
hmatuschek authored Jul 14, 2024
1 parent 03ec21e commit ec8cde5
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 57 deletions.
125 changes: 69 additions & 56 deletions lib/gd73_codeplug.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "gd73_codeplug.hh"
#include "config.hh"
#include "intermediaterepresentation.hh"
#include "logger.hh"


QVector<Signaling::Code> _ctcss_codes = {
Expand Down Expand Up @@ -960,6 +961,19 @@ GD73Codeplug::GroupListElement::setName(const QString &name) {
writeUnicode(Offset::name(), name, Limit::nameLength(), 0x0000);
}

unsigned int
GD73Codeplug::GroupListElement::members() const {
return getUInt8(Offset::memberCount());
}
bool
GD73Codeplug::GroupListElement::hasMember(unsigned int i) const {
return 0 != getUInt16_le(Offset::members() + i*Offset::betweenMembers());
}
unsigned int
GD73Codeplug::GroupListElement::memberIndex(unsigned int i) const {
return getUInt16_le(Offset::members() + i*Offset::betweenMembers())-1;
}

RXGroupList *
GD73Codeplug::GroupListElement::toGroupList(Context &ctx, const ErrorStack &err) {
Q_UNUSED(ctx); Q_UNUSED(err);
Expand All @@ -968,16 +982,18 @@ GD73Codeplug::GroupListElement::toGroupList(Context &ctx, const ErrorStack &err)

bool
GD73Codeplug::GroupListElement::linkGroupList(RXGroupList *lst, Context &ctx, const ErrorStack &err) {
unsigned int count = std::min((unsigned int)getUInt8(Offset::memberCount()), Limit::memberCount());
Q_UNUSED(err);

unsigned int count = std::min(members(), Limit::memberCount());
for (unsigned int i=0; i<count; i++) {
unsigned int idx = getUInt16_le(Offset::members() + i*Offset::betweenMembers());
if (0 == idx)
if (! hasMember(i))
continue;
if (! ctx.has<DMRContact>(idx-1)) {
errMsg(err) << "Cannot resolve contact at index " << idx-1 << ".";
return false;
}
lst->addContact(ctx.get<DMRContact>(idx-1));
unsigned int idx = memberIndex(i);
if (ctx.has<DMRContact>(idx))
lst->addContact(ctx.get<DMRContact>(idx));
else
logWarn() << "Cannot link group list '" << lst->name()
<< "': Cannot resolve contact at index " << idx << ".";
}
return true;
}
Expand Down Expand Up @@ -1409,6 +1425,8 @@ GD73Codeplug::ChannelElement::toChannel(Context &ctx, const ErrorStack &err) {
}
fm->setBandwidth(bandwidth());
fm->setSquelchDefault();
fm->setRXTone(rxTone());
fm->setTXTone(txTone());
} else if (Type::DMR == type()) {
DMRChannel *dmr = new DMRChannel(); ch = dmr;
switch (admit()) {
Expand All @@ -1432,34 +1450,32 @@ GD73Codeplug::ChannelElement::toChannel(Context &ctx, const ErrorStack &err) {

bool
GD73Codeplug::ChannelElement::linkChannel(Channel *ch, Context &ctx, const ErrorStack &err) {
Q_UNUSED(err);

if (Type::DMR == type()) {
DMRChannel *dmr = ch->as<DMRChannel>();
if (hasTXContact()) {
if (! ctx.has<DMRContact>(txContactIndex())) {
errMsg(err) << "Cannot link channel '" << name() << "', cannot resolve contact index "
<< txContactIndex() << ".";
return false;
}
dmr->setTXContactObj(ctx.get<DMRContact>(txContactIndex()));
if (ctx.has<DMRContact>(txContactIndex()))
dmr->setTXContactObj(ctx.get<DMRContact>(txContactIndex()));
else
logWarn() << "Cannot link channel '" << name() << "', cannot resolve contact index "
<< txContactIndex() << ".";
}
if ((! groupListAllMatch()) && (! groupListMatchesContact())) {
if (! ctx.has<RXGroupList>(groupListIndex())) {
errMsg(err) << "Cannot link channel '" << name() << "', cannot resolve group list index "
<< groupListIndex() << ".";
return false;
}
dmr->setGroupListObj(ctx.get<RXGroupList>(groupListIndex()));
if (ctx.has<RXGroupList>(groupListIndex()))
dmr->setGroupListObj(ctx.get<RXGroupList>(groupListIndex()));
else
logWarn() << "Cannot link channel '" << name() << "', cannot resolve group list index "
<< groupListIndex() << ".";
}
}

if (hasScanListIndex()) {
if (! ctx.has<ScanList>(scanListIndex())) {
errMsg(err) << "Cannot link channel '" << name() << "', cannot resolve scanlist index "
<< scanListIndex() << ".";
return false;
}
ch->setScanList(ctx.get<ScanList>(scanListIndex()));
if (ctx.has<ScanList>(scanListIndex()))
ch->setScanList(ctx.get<ScanList>(scanListIndex()));
else
logWarn() << "Cannot link channel '" << name() << "', cannot resolve scanlist index "
<< scanListIndex() << ".";
}

return true;
Expand Down Expand Up @@ -1503,6 +1519,8 @@ GD73Codeplug::ChannelElement::encode(Channel *ch, Context &ctx, const ErrorStack
case FMChannel::Admit::Free: setAdmit(Admit::Free); break;
}
setBandwidth(fm->bandwidth());
setRXTone(fm->rxTone());
setTXTone(fm->txTone());
}

return true;
Expand Down Expand Up @@ -1607,12 +1625,11 @@ GD73Codeplug::ZoneElement::linkZone(Zone *zone, Context &ctx, const ErrorStack &
unsigned int index = getUInt16_le(Offset::channelIndices() + i*Offset::betweenChannelIndices());
if (0 == index)
continue;
if (! ctx.has<Channel>(index-1)) {
errMsg(err) << "Cannot link zone '" << zone->name() << "': Channel at index " << (index-1)
<< " not known.";
return false;
}
zone->A()->add(ctx.get<Channel>(index-1));
if (ctx.has<Channel>(index-1))
zone->A()->add(ctx.get<Channel>(index-1));
else
logWarn() << "Cannot link zone '" << zone->name() << "': Channel at index " << (index-1)
<< " not known.";
}
return true;
}
Expand Down Expand Up @@ -1872,34 +1889,31 @@ GD73Codeplug::ScanListElement::toScanList(Context &ctx, const ErrorStack &err) {
bool
GD73Codeplug::ScanListElement::linkScanList(ScanList *lst, Context &ctx, const ErrorStack &err) {
if ((ChannelMode::Fixed == primaryChannelMode()) && hasPrimaryChannelIndex()) {
if (! ctx.has<Channel>(primaryChannelIndex())) {
errMsg(err) << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve primary channel index " << primaryChannelIndex() << ".";
return false;
}
lst->setPrimaryChannel(ctx.get<Channel>(primaryChannelIndex()));
if (ctx.has<Channel>(primaryChannelIndex()))
lst->setPrimaryChannel(ctx.get<Channel>(primaryChannelIndex()));
else
logWarn() << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve primary channel index " << primaryChannelIndex() << ".";
} else if (ChannelMode::Selected == primaryChannelMode()) {
lst->setPrimaryChannel(SelectedChannel::get());
}

if ((ChannelMode::Fixed == secondaryChannelMode()) && hasSecondaryChannelIndex()) {
if (! ctx.has<Channel>(secondaryChannelIndex())) {
errMsg(err) << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve secondary channel index " << secondaryChannelIndex() << ".";
return false;
}
lst->setSecondaryChannel(ctx.get<Channel>(secondaryChannelIndex()));
if (ctx.has<Channel>(secondaryChannelIndex()))
lst->setSecondaryChannel(ctx.get<Channel>(secondaryChannelIndex()));
else
logWarn() << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve secondary channel index " << secondaryChannelIndex() << ".";
} else if (ChannelMode::Selected == secondaryChannelMode()) {
lst->setSecondaryChannel(SelectedChannel::get());
}

if ((ChannelMode::Fixed == revertChannelMode()) && hasRevertChannelIndex()) {
if (! ctx.has<Channel>(revertChannelIndex())) {
errMsg(err) << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve revert channel index " << revertChannelIndex() << ".";
return false;
}
lst->setRevertChannel(ctx.get<Channel>(revertChannelIndex()));
if (ctx.has<Channel>(revertChannelIndex()))
lst->setRevertChannel(ctx.get<Channel>(revertChannelIndex()));
else
logWarn() << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve revert channel index " << revertChannelIndex() << ".";
} else if (ChannelMode::Selected == revertChannelMode()) {
lst->setRevertChannel(SelectedChannel::get());
}
Expand All @@ -1909,12 +1923,11 @@ GD73Codeplug::ScanListElement::linkScanList(ScanList *lst, Context &ctx, const E
unsigned int index = getUInt16_le(Offset::members() + i*Offset::betweenMembers());
if (0 == index)
continue;
if (! ctx.has<Channel>(index-1)) {
errMsg(err) << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve member index" << index-1 << ".";
return false;
}
lst->addChannel(ctx.get<Channel>(index-1));
if (ctx.has<Channel>(index-1))
lst->addChannel(ctx.get<Channel>(index-1));
else
logWarn() << "Cannot link scan list '" << lst->name()
<< "': Cannot resolve member index" << index-1 << ".";
}

return true;
Expand Down
7 changes: 7 additions & 0 deletions lib/gd73_codeplug.hh
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,13 @@ public:
/** Sets the name of the group list. */
void setName(const QString &name);

/** Returns the number of entries in the group list. */
unsigned int members() const;
/** Returns @c true, if the i-th member is set. */
bool hasMember(unsigned int i) const;
/** Returns the i-th member index. */
unsigned int memberIndex(unsigned int i) const;

/** Decodes the group list. */
RXGroupList *toGroupList(Context &ctx, const ErrorStack &err=ErrorStack());
/** Links the given RX group list. */
Expand Down
5 changes: 4 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ qt5_wrap_cpp(rd5r_MOC_SOURCES rd5r_test.hh)
add_executable(rd5r_test rd5r_test.cc ${rd5r_MOC_SOURCES} ${testlib_RCC_SOURCES})
target_link_libraries(rd5r_test ${LIBS} libdmrconf libdmrconfigtest)

qt5_wrap_cpp(gd73_MOC_SOURCES gd73_test.hh)
add_executable(gd73_test gd73_test.cc ${gd73_MOC_SOURCES} ${testlib_RCC_SOURCES})
target_link_libraries(gd73_test ${LIBS} libdmrconf libdmrconfigtest)

qt5_wrap_cpp(gd77_MOC_SOURCES gd77_test.hh)
add_executable(gd77_test gd77_test.cc ${gd77_MOC_SOURCES} ${testlib_RCC_SOURCES})
target_link_libraries(gd77_test ${LIBS} libdmrconf libdmrconfigtest)


# Unit tests for OpenGD77 firmware
qt5_wrap_cpp(opengd77_MOC_SOURCES opengd77_test.hh)
add_executable(opengd77_test opengd77_test.cc ${opengd77_MOC_SOURCES} ${testlib_RCC_SOURCES})
Expand Down
154 changes: 154 additions & 0 deletions test/gd73_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#include "gd73_test.hh"
#include "config.hh"
#include "gd73.hh"
#include "gd73_codeplug.hh"
#include "errorstack.hh"
#include <iostream>
#include <QTest>

GD73Test::GD73Test(QObject *parent)
: UnitTestBase(parent)
{
// pass...
}

void
GD73Test::testBasicConfigEncoding() {
ErrorStack err;
GD73Codeplug codeplug;
if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) {
QFAIL(QString("Cannot encode codeplug for Radioddity GD77: %1")
.arg(err.format()).toStdString().c_str());
}
}

void
GD73Test::testBasicConfigDecoding() {
ErrorStack err;
GD73Codeplug codeplug;
if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) {
QFAIL(QString("Cannot encode codeplug for Radioddity GD77: %1")
.arg(err.format()).toStdString().c_str());
}

Config config;
if (! codeplug.decode(&config, err)) {
QFAIL(QString("Cannot decode codeplug for Radioddity GD77: %1")
.arg(err.format()).toStdString().c_str());
}
}

void
GD73Test::testChannelFrequency() {
ErrorStack err;
GD73Codeplug codeplug;
if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) {
QFAIL(QString("Cannot encode codeplug for Radioddity GD77: {}")
.arg(err.format()).toStdString().c_str());
}

Config config;
if (! codeplug.decode(&config, err)) {
QFAIL(QString("Cannot decode codeplug for Radioddity GD77: {}")
.arg(err.format()).toStdString().c_str());
}

QCOMPARE(config.channelList()->channel(0)->rxFrequency(),
Frequency::fromHz(123456780ULL));
QCOMPARE(config.channelList()->channel(0)->txFrequency(),
Frequency::fromHz(999999990ULL));
}


void
GD73Test::testFMSignaling() {

Config config;
config.radioIDs()->add(new DMRRadioID("ID", 1234567));

{
FMChannel *ch = new FMChannel();
ch->setName("Channel 1");
ch->setRXFrequency(Frequency::fromMHz(144.0));
ch->setTXFrequency(Frequency::fromMHz(144.6));
ch->setRXTone(Signaling::CTCSS_67_0Hz);
ch->setTXTone(Signaling::SIGNALING_NONE);
config.channelList()->add(ch);
}

{
FMChannel *ch = new FMChannel();
ch->setName("Channel 2");
ch->setRXFrequency(Frequency::fromMHz(144.0));
ch->setTXFrequency(Frequency::fromMHz(144.6));
ch->setRXTone(Signaling::SIGNALING_NONE);
ch->setTXTone(Signaling::CTCSS_67_0Hz);
config.channelList()->add(ch);
}

{
FMChannel *ch = new FMChannel();
ch->setName("Channel 3");
ch->setRXFrequency(Frequency::fromMHz(144.0));
ch->setTXFrequency(Frequency::fromMHz(144.6));
ch->setRXTone(Signaling::DCS_023N);
ch->setTXTone(Signaling::SIGNALING_NONE);
config.channelList()->add(ch);
}

{
FMChannel *ch = new FMChannel();
ch->setName("Channel 4");
ch->setRXFrequency(Frequency::fromMHz(144.0));
ch->setTXFrequency(Frequency::fromMHz(144.6));
ch->setRXTone(Signaling::SIGNALING_NONE);
ch->setTXTone(Signaling::DCS_023N);
config.channelList()->add(ch);
}

{
FMChannel *ch = new FMChannel();
ch->setName("Channel 5");
ch->setRXFrequency(Frequency::fromMHz(144.0));
ch->setTXFrequency(Frequency::fromMHz(144.6));
ch->setRXTone(Signaling::DCS_023I);
ch->setTXTone(Signaling::SIGNALING_NONE);
config.channelList()->add(ch);
}

{
FMChannel *ch = new FMChannel();
ch->setName("Channel 6");
ch->setRXFrequency(Frequency::fromMHz(144.0));
ch->setTXFrequency(Frequency::fromMHz(144.6));
ch->setRXTone(Signaling::SIGNALING_NONE);
ch->setTXTone(Signaling::DCS_023I);
config.channelList()->add(ch);
}

ErrorStack err;
GD73Codeplug codeplug;
if (! codeplug.encode(&config, Codeplug::Flags(), err)) {
QFAIL(QString("Cannot encode codeplug for Radioddity GD73: {}")
.arg(err.format()).toStdString().c_str());
}

Config compare;
if (! codeplug.decode(&compare, err)) {
QFAIL(QString("Cannot decode codeplug for Radioddity GD73: {}")
.arg(err.format()).toStdString().c_str());
}

QCOMPARE(compare.channelList()->count(), 6);
for (int i=0; i<compare.channelList()->count(); i++) {
QVERIFY(compare.channelList()->channel(i)->is<FMChannel>());
QCOMPARE((unsigned int)compare.channelList()->channel(i)->as<FMChannel>()->rxTone(),
(unsigned int)config.channelList()->channel(i)->as<FMChannel>()->rxTone());
QCOMPARE((unsigned int)compare.channelList()->channel(i)->as<FMChannel>()->txTone(),
(unsigned int)config.channelList()->channel(i)->as<FMChannel>()->txTone());
}
}


QTEST_GUILESS_MAIN(GD73Test)

Loading

0 comments on commit ec8cde5

Please sign in to comment.