From 4cd66126a94928495fc99255fa08ecce8dd74f75 Mon Sep 17 00:00:00 2001 From: Hannes Matuschek Date: Wed, 15 Mar 2023 10:54:59 +0100 Subject: [PATCH] Fixed decoding of roaming channels for AT-D878UV, AT-D878UV II, AT-D578UV, DMR-6X2UV. Fixes #319. --- lib/d878uv_codeplug.cc | 73 +++++++++-------- lib/d878uv_codeplug.hh | 57 +++++++++++--- lib/dmr6x2uv_codeplug.cc | 4 +- test/d878uv_test.cc | 37 +++++++++ test/d878uv_test.hh | 3 + test/data/roaming_channel_test.yaml | 118 ++++++++++++++++++++++++++++ test/resources.qrc | 1 + 7 files changed, 247 insertions(+), 46 deletions(-) create mode 100644 test/data/roaming_channel_test.yaml diff --git a/lib/d878uv_codeplug.cc b/lib/d878uv_codeplug.cc index 8b0b71d4..ed2d1695 100644 --- a/lib/d878uv_codeplug.cc +++ b/lib/d878uv_codeplug.cc @@ -280,7 +280,7 @@ D878UVCodeplug::RoamingChannelElement::RoamingChannelElement(uint8_t *ptr, unsig } D878UVCodeplug::RoamingChannelElement::RoamingChannelElement(uint8_t *ptr) - : Element(ptr, 0x0020) + : Element(ptr, RoamingChannelElement::size()) { // pass... } @@ -292,41 +292,41 @@ D878UVCodeplug::RoamingChannelElement::clear() { unsigned D878UVCodeplug::RoamingChannelElement::rxFrequency() const { - return getBCD8_be(Offsets::RXFrequency)*10; + return getBCD8_be(Offset::rxFrequency())*10; } void D878UVCodeplug::RoamingChannelElement::setRXFrequency(unsigned hz) { - setBCD8_be(Offsets::RXFrequency, hz/10); + setBCD8_be(Offset::rxFrequency(), hz/10); } unsigned D878UVCodeplug::RoamingChannelElement::txFrequency() const { - return getBCD8_be(Offsets::TXFrequency)*10; + return getBCD8_be(Offset::txFrequency())*10; } void D878UVCodeplug::RoamingChannelElement::setTXFrequency(unsigned hz) { - setBCD8_be(Offsets::TXFrequency, hz/10); + setBCD8_be(Offset::txFrequency(), hz/10); } bool D878UVCodeplug::RoamingChannelElement::hasColorCode() const { - return ColorCodeValue::Disabled == getUInt8(Offsets::ColorCode); + return ColorCodeValue::Disabled == getUInt8(Offset::colorCode()); } unsigned D878UVCodeplug::RoamingChannelElement::colorCode() const { - return std::min(15u, (unsigned)getUInt8(Offsets::ColorCode)); + return std::min(15u, (unsigned)getUInt8(Offset::colorCode())); } void D878UVCodeplug::RoamingChannelElement::setColorCode(unsigned cc) { - setUInt8(Offsets::ColorCode, cc); + setUInt8(Offset::colorCode(), cc); } void D878UVCodeplug::RoamingChannelElement::disableColorCode() { - setUInt8(Offsets::ColorCode, ColorCodeValue::Disabled); + setUInt8(Offset::colorCode(), ColorCodeValue::Disabled); } DMRChannel::TimeSlot D878UVCodeplug::RoamingChannelElement::timeSlot() const { - switch (getUInt8(Offsets::TimeSlot)) { + switch (getUInt8(Offset::timeSlot())) { case TimeSlotValue::TS1: return DMRChannel::TimeSlot::TS1; case TimeSlotValue::TS2: return DMRChannel::TimeSlot::TS2; } @@ -335,18 +335,18 @@ D878UVCodeplug::RoamingChannelElement::timeSlot() const { void D878UVCodeplug::RoamingChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { switch (ts) { - case DMRChannel::TimeSlot::TS1: setUInt8(Offsets::TimeSlot, TimeSlotValue::TS1); break; - case DMRChannel::TimeSlot::TS2: setUInt8(Offsets::TimeSlot, TimeSlotValue::TS2); break; + case DMRChannel::TimeSlot::TS1: setUInt8(Offset::timeSlot(), TimeSlotValue::TS1); break; + case DMRChannel::TimeSlot::TS2: setUInt8(Offset::timeSlot(), TimeSlotValue::TS2); break; } } QString D878UVCodeplug::RoamingChannelElement::name() const { - return readASCII(Offsets::Name, Offsets::NameLength, 0x00); + return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void D878UVCodeplug::RoamingChannelElement::setName(const QString &name) { - writeASCII(Offsets::Name, name, Offsets::NameLength, 0x00); + writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool @@ -391,7 +391,7 @@ D878UVCodeplug::RoamingZoneElement::RoamingZoneElement(uint8_t *ptr, unsigned si } D878UVCodeplug::RoamingZoneElement::RoamingZoneElement(uint8_t *ptr) - : Element(ptr, 0x0080) + : Element(ptr, RoamingZoneElement::size()) { // pass... } @@ -399,7 +399,7 @@ D878UVCodeplug::RoamingZoneElement::RoamingZoneElement(uint8_t *ptr) void D878UVCodeplug::RoamingZoneElement::clear() { memset(_data, 0x00, _size); - memset(_data+0x000, 0xff, NUM_CH_PER_ROAMINGZONE); + memset(_data+Offset::members(), 0xff, Limit::numMembers()); } bool @@ -408,49 +408,60 @@ D878UVCodeplug::RoamingZoneElement::hasMember(unsigned n) const { } unsigned D878UVCodeplug::RoamingZoneElement::member(unsigned n) const { - return getUInt8(0x0000 + n); + return getUInt8(Offset::members() + n*Offset::betweenMembers()); } void D878UVCodeplug::RoamingZoneElement::setMember(unsigned n, unsigned idx) { - setUInt8(0x0000 + n, idx); + if (n >= Limit::numMembers()) + return; + setUInt8(Offset::members() + n*Offset::betweenMembers(), idx); } void D878UVCodeplug::RoamingZoneElement::clearMember(unsigned n) { - setMember(n, 0xff); + if (n >= Limit::numMembers()) + return; + setMember(Offset::members() + n*Offset::betweenMembers(), 0xff); } QString D878UVCodeplug::RoamingZoneElement::name() const { - return readASCII(0x0040, 16, 0x00); + return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void D878UVCodeplug::RoamingZoneElement::setName(const QString &name) { - writeASCII(0x0040, name, 16, 0x00); + writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool -D878UVCodeplug::RoamingZoneElement::fromRoamingZone( - RoamingZone *zone, Context &ctx) +D878UVCodeplug::RoamingZoneElement::fromRoamingZone(RoamingZone *zone, Context &ctx, const ErrorStack& err) { + Q_UNUSED(err) + clear(); setName(zone->name()); - for (int i=0; icount()); i++) { + for (unsigned int i=0; icount()); i++) { setMember(i, ctx.index(zone->channel(i))); } return true; } RoamingZone * -D878UVCodeplug::RoamingZoneElement::toRoamingZone() const { +D878UVCodeplug::RoamingZoneElement::toRoamingZone(Context &ctx, const ErrorStack &err) const { + Q_UNUSED(ctx); Q_UNUSED(err); return new RoamingZone(name()); } bool -D878UVCodeplug::RoamingZoneElement::linkRoamingZone(RoamingZone *zone, Context &ctx) +D878UVCodeplug::RoamingZoneElement::linkRoamingZone(RoamingZone *zone, Context &ctx, const ErrorStack &err) { - for (uint8_t i=0; (i(i)) - zone->addChannel(ctx.get(i)); + for (uint8_t i=0; (i(member(i))) { + zone->addChannel(ctx.get(member(i))); + } else { + errMsg(err) << "Cannot link roaming zone '" << zone->name() + << "': Roaming channel index " << member(i) << " is not defined."; + return false; + } } return true; } @@ -2579,9 +2590,9 @@ D878UVCodeplug::createRoaming(Context &ctx, const ErrorStack &err) { continue; uint32_t addr = ADDR_ROAMING_ZONE_0 + i*ROAMING_ZONE_OFFSET; RoamingZoneElement z(data(addr)); - RoamingZone *zone = z.toRoamingZone(); + RoamingZone *zone = z.toRoamingZone(ctx, err); ctx.config()->roamingZones()->add(zone); ctx.add(zone, i); - z.linkRoamingZone(zone, ctx); + z.linkRoamingZone(zone, ctx, err); } return true; diff --git a/lib/d878uv_codeplug.hh b/lib/d878uv_codeplug.hh index 5ae2d11e..441af277 100644 --- a/lib/d878uv_codeplug.hh +++ b/lib/d878uv_codeplug.hh @@ -1125,16 +1125,6 @@ public: RoamingChannelElement(uint8_t *ptr, unsigned size); protected: - /** Address offsets within the element. */ - enum Offsets { - RXFrequency = 0x0000, - TXFrequency = 0x0004, - ColorCode = 0x0008, - TimeSlot = 0x0009, - Name = 0x000a, - NameLength = 16 - }; - /** Special values for the color code. */ enum ColorCodeValue { Disabled = 16 @@ -1150,6 +1140,9 @@ public: /** Constructor. */ RoamingChannelElement(uint8_t *ptr); + /** The size of the element. */ + static constexpr unsigned int size() { return 0x0020; } + /** Resets the roaming channel. */ void clear(); @@ -1185,6 +1178,24 @@ public: virtual bool fromChannel(const RoamingChannel *ch); /** Constructs a @c RoamingChannel instance for this roaming channel. */ virtual RoamingChannel *toChannel(Context &ctx); + + public: + /** Some limits. */ + struct Limit { + static constexpr unsigned int nameLength() { return 16; } ///< Maximum name length. + }; + + protected: + /** Some internal offsets within the element. */ + struct Offset { + /// @cond DO_NOT_DOCUMENT + static constexpr unsigned int rxFrequency() { return 0x0000; } + static constexpr unsigned int txFrequency() { return 0x0004; } + static constexpr unsigned int colorCode() { return 0x0008; } + static constexpr unsigned int timeSlot() { return 0x0009; } + static constexpr unsigned int name() { return 0x000a; } + /// @endcond + }; }; /** Represents a roaming zone within the binary codeplug. @@ -1201,6 +1212,9 @@ public: /** Constructor. */ RoamingZoneElement(uint8_t *ptr); + /** The size of the element. */ + static constexpr unsigned int size() { return 0x0080; } + /** Clears the roaming zone. */ void clear(); @@ -1219,11 +1233,28 @@ public: virtual void setName(const QString &name); /** Assembles a binary representation of the given RoamingZone instance.*/ - virtual bool fromRoamingZone(RoamingZone *zone, Context& ctx); + virtual bool fromRoamingZone(RoamingZone *zone, Context& ctx, const ErrorStack &err=ErrorStack()); /** Constructs a @c RoamingZone instance from this configuration. */ - virtual RoamingZone *toRoamingZone() const; + virtual RoamingZone *toRoamingZone(Context& ctx, const ErrorStack &err=ErrorStack()) const; /** Links the given RoamingZone. */ - virtual bool linkRoamingZone(RoamingZone *zone, Context& ctx); + virtual bool linkRoamingZone(RoamingZone *zone, Context& ctx, const ErrorStack& err=ErrorStack()); + + public: + /** Some limits. */ + struct Limit { + static constexpr unsigned int nameLength() { return 16; } ///< Maximum name length. + static constexpr unsigned int numMembers() { return 64; } ///< Maximum number of roaming channel in zone. + }; + + protected: + /** Some internal offsets within the element. */ + struct Offset { + /// @cond DO_NOT_DOCUMENT + static constexpr unsigned int members() { return 0x0000; } + static constexpr unsigned int betweenMembers() { return 0x0001; } + static constexpr unsigned int name() { return 0x0040; } + /// @endcond + }; }; /** Represents an AES encryption key. diff --git a/lib/dmr6x2uv_codeplug.cc b/lib/dmr6x2uv_codeplug.cc index 0f02535e..a7ae1738 100644 --- a/lib/dmr6x2uv_codeplug.cc +++ b/lib/dmr6x2uv_codeplug.cc @@ -1032,9 +1032,9 @@ DMR6X2UVCodeplug::createRoaming(Context &ctx, const ErrorStack &err) { continue; uint32_t addr = ADDR_ROAMING_ZONE_0 + i*ROAMING_ZONE_OFFSET; D878UVCodeplug::RoamingZoneElement z(data(addr)); - RoamingZone *zone = z.toRoamingZone(); + RoamingZone *zone = z.toRoamingZone(ctx, err); ctx.config()->roamingZones()->add(zone); ctx.add(zone, i); - z.linkRoamingZone(zone, ctx); + z.linkRoamingZone(zone, ctx, err); } return true; diff --git a/test/d878uv_test.cc b/test/d878uv_test.cc index b0c0ebd8..668933a4 100644 --- a/test/d878uv_test.cc +++ b/test/d878uv_test.cc @@ -19,6 +19,10 @@ D878UVTest::initTestCase() { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } + if (! _roamingConfig.readYAML(":/data/roaming_channel_test.yaml", err)) { + QFAIL(QString("Cannot open codeplug file: %1") + .arg(err.format()).toStdString().c_str()); + } } void @@ -55,5 +59,38 @@ D878UVTest::testBasicConfigDecoding() { } } +void +D878UVTest::testRoaming() { + ErrorStack err; + Codeplug::Flags flags; flags.updateCodePlug=false; + D878UVCodeplug codeplug; + if (! codeplug.encode(&_roamingConfig, flags, err)) { + QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: {}") + .arg(err.format()).toStdString().c_str()); + } + + Config config; + if (! codeplug.decode(&config, err)) { + QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: {}") + .arg(err.format()).toStdString().c_str()); + } + + QCOMPARE(config.roamingChannels()->count(), 3); + QCOMPARE(config.roamingZones()->count(), 2); + QCOMPARE(config.roamingZones()->count(), 2); + + QCOMPARE(config.roamingZones()->get(0)->as()->count(), 2); + QCOMPARE(config.roamingZones()->get(0)->as()->channel(0), + config.roamingChannels()->get(0)->as()); + QCOMPARE(config.roamingZones()->get(0)->as()->channel(1), + config.roamingChannels()->get(1)->as()); + + QCOMPARE(config.roamingZones()->get(1)->as()->count(), 2); + QCOMPARE(config.roamingZones()->get(1)->as()->channel(0), + config.roamingChannels()->get(0)->as()); + QCOMPARE(config.roamingZones()->get(1)->as()->channel(1), + config.roamingChannels()->get(2)->as()); +} + QTEST_GUILESS_MAIN(D878UVTest) diff --git a/test/d878uv_test.hh b/test/d878uv_test.hh index 98b8b9b3..8a33528d 100644 --- a/test/d878uv_test.hh +++ b/test/d878uv_test.hh @@ -19,8 +19,11 @@ private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); + void testRoaming(); + protected: Config _basicConfig; + Config _roamingConfig; }; #endif // D878UVTEST_HH diff --git a/test/data/roaming_channel_test.yaml b/test/data/roaming_channel_test.yaml new file mode 100644 index 00000000..9731b89b --- /dev/null +++ b/test/data/roaming_channel_test.yaml @@ -0,0 +1,118 @@ +--- +version: 0.9.0 +settings: + introLine1: DM3MAT + introLine2: qDMR + micLevel: 3 + speech: false + power: High + squelch: 1 + vox: 0 + tot: 0 + defaultID: id1 +radioIDs: + - dmr: {id: id1, name: DM3MAT, number: 2621370} +contacts: + - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} + - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} + - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} + - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} + - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} + - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} +groupLists: + - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} + - {id: grp2, name: DL, contacts: [cont4]} + - {id: grp3, name: HamRadioVillage, contacts: [cont6]} +channels: + - dmr: + id: ch1 + name: L9 DB0LDS + rxFrequency: 439.5625 + txFrequency: 431.9625 + rxOnly: false + admit: Always + colorCode: 1 + timeSlot: TS2 + groupList: grp1 + contact: cont1 + power: High + timeout: 0 + vox: 0 + - dmr: + id: ch2 + name: BB DB0LDS + rxFrequency: 439.5625 + txFrequency: 431.9625 + rxOnly: false + admit: Always + colorCode: 1 + timeSlot: TS2 + groupList: grp1 + contact: cont3 + aprs: aprs1 + power: High + timeout: 0 + vox: 0 + - dmr: + id: ch3 + name: DL DB0LDS + rxFrequency: 439.5625 + txFrequency: 431.9625 + rxOnly: false + admit: Always + colorCode: 1 + timeSlot: TS1 + groupList: grp2 + contact: cont4 + power: High + timeout: 0 + vox: 0 + - dmr: + id: ch4 + name: HRV DB0LDS + rxFrequency: 439.5625 + txFrequency: 431.9625 + rxOnly: false + admit: Always + colorCode: 1 + timeSlot: TS1 + groupList: grp3 + contact: cont6 + power: High + timeout: 0 + vox: 0 +zones: + - id: zone1 + name: Zu Hause + A: [ch1, ch2, ch4] + B: [ch3] +roamingChannels: + - id: rc1 + name: R DB0LDS + rxFrequency: 439.5625 + txFrequency: 431.96249999999998 + colorCode: 1 + - id: rc2 + name: R DM0TZN + rxFrequency: 438.82499999999999 + txFrequency: 431.22500000000002 + colorCode: 1 + - id: rc3 + name: R DM0TT + rxFrequency: 439.08749999999998 + txFrequency: 431.48750000000001 + colorCode: 1 +roamingZones: + - id: roam1 + name: RZ1 + channels: [rc1, rc2] + - id: roam2 + name: RZ2 + channels: [rc1, rc3] +positioning: + - dmr: + id: aprs1 + name: GPS System + period: 300 + contact: cont5 +... diff --git a/test/resources.qrc b/test/resources.qrc index fb2bb4b2..ad90c015 100644 --- a/test/resources.qrc +++ b/test/resources.qrc @@ -2,5 +2,6 @@ data/config_test.yaml data/channel_frequency_test.yaml + data/roaming_channel_test.yaml