From d630070b94362571dceb6eba8ce02947334a6c70 Mon Sep 17 00:00:00 2001 From: Hannes Matuschek Date: Mon, 20 May 2024 11:14:35 +0200 Subject: [PATCH] Implemented message templates for radioddity devices. Addresses #413. --- lib/gd77_codeplug.cc | 8 ++++++ lib/gd77_codeplug.hh | 3 +++ lib/opengd77_codeplug.cc | 5 ---- lib/opengd77_codeplug.hh | 1 - lib/radioddity_codeplug.cc | 55 +++++++++++++++++++++++++++++++------- lib/radioddity_codeplug.hh | 29 ++++++++++++++++++++ lib/rd5r_codeplug.cc | 17 ++++++++++++ lib/rd5r_codeplug.hh | 2 ++ test/gd77_test.cc | 29 ++++++++++++++++++++ test/gd77_test.hh | 1 + test/opengd77_test.cc | 31 +++++++++++++++++++++ test/opengd77_test.hh | 1 + test/rd5r_test.cc | 30 +++++++++++++++++++++ test/rd5r_test.hh | 2 +- test/uv390_test.cc | 4 +-- 15 files changed, 199 insertions(+), 19 deletions(-) diff --git a/lib/gd77_codeplug.cc b/lib/gd77_codeplug.cc index 69f12ebc..4366b2db 100644 --- a/lib/gd77_codeplug.cc +++ b/lib/gd77_codeplug.cc @@ -298,6 +298,14 @@ void GD77Codeplug::clearMessages() { MessageBankElement(data(ADDR_MESSAGE_BANK)).clear(); } +bool +GD77Codeplug::encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { + return MessageBankElement(data(Offset::messages())).encode(ctx, flags, err); +} +bool +GD77Codeplug::decodeMessages(Context &ctx, const ErrorStack &err) { + return MessageBankElement(data(Offset::messages())).decode(ctx, err); +} void GD77Codeplug::clearScanLists() { diff --git a/lib/gd77_codeplug.hh b/lib/gd77_codeplug.hh index c9cac7e7..3ecc5b62 100644 --- a/lib/gd77_codeplug.hh +++ b/lib/gd77_codeplug.hh @@ -262,6 +262,8 @@ public: bool decodeButtonSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearMessages(); + bool encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); + bool decodeMessages(Context &ctx, const ErrorStack &err=ErrorStack()); void clearScanLists(); bool encodeScanLists(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); @@ -310,6 +312,7 @@ protected: struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int buttonSettings() { return 0x000108; } + static constexpr unsigned int messages() { return 0x000128; } /// @endcond }; }; diff --git a/lib/opengd77_codeplug.cc b/lib/opengd77_codeplug.cc index 249b4408..9ebbedb4 100644 --- a/lib/opengd77_codeplug.cc +++ b/lib/opengd77_codeplug.cc @@ -540,11 +540,6 @@ OpenGD77Codeplug::clearButtonSettings() { /// @bug Find button settings within OpenGD77 codeplug. } -void -OpenGD77Codeplug::clearMessages() { - MessageBankElement(data(ADDR_MESSAGE_BANK, IMAGE_MESSAGE_BANK)).clear(); -} - void OpenGD77Codeplug::clearScanLists() { // Scan lists are not touched with OpenGD77 codeplug diff --git a/lib/opengd77_codeplug.hh b/lib/opengd77_codeplug.hh index db31954e..09cd6537 100644 --- a/lib/opengd77_codeplug.hh +++ b/lib/opengd77_codeplug.hh @@ -240,7 +240,6 @@ public: bool decodeGeneralSettings(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()); void clearButtonSettings(); - void clearMessages(); void clearScanLists(); bool encodeScanLists(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); diff --git a/lib/radioddity_codeplug.cc b/lib/radioddity_codeplug.cc index ffe36eb4..31a030e8 100644 --- a/lib/radioddity_codeplug.cc +++ b/lib/radioddity_codeplug.cc @@ -2518,35 +2518,60 @@ RadioddityCodeplug::MessageBankElement::~MessageBankElement() { void RadioddityCodeplug::MessageBankElement::clear() { - setUInt8(0x0000, 0); // set count to 0 + setUInt8(Offset::messageConut(), 0); // set count to 0 memset(_data+0x0001, 0x00, 7); // Fill unused - memset(_data+0x0008, 0x00, 32); // Set message lengths to 0 + memset(_data+Offset::messageLengths(), 0x00, Limit::messages()); // Set message lengths to 0 memset(_data+0x0028, 0x00, 32); // Fill unused - memset(_data+0x0048, 0xff, 32*144); // Clear all messages + memset(_data+Offset::messages(), 0xff, Limit::messages()*Limit::messageLength()); // Clear all messages } unsigned RadioddityCodeplug::MessageBankElement::numMessages() const { - return getUInt8(0x0000); + return getUInt8(Offset::messageConut()); } QString RadioddityCodeplug::MessageBankElement::message(unsigned n) const { if (n >= numMessages()) return QString(); - return readASCII(0x0048+n*144, 144, 0xff); + return readASCII(Offset::messages()+n*Offset::betweenMessages(), Limit::messageLength(), 0xff); } void RadioddityCodeplug::MessageBankElement::appendMessage(const QString msg) { unsigned idx = numMessages(); - if (idx >= 32) + if (idx >= Limit::messages()) return; - unsigned len = std::min(msg.size(), 144); + unsigned int len = std::min((unsigned int)msg.size(), Limit::messageLength()); // increment counter - setUInt8(0x0000, idx+1); + setUInt8(Offset::messageConut(), idx+1); // store length - setUInt8(0x0008+idx, len); + setUInt8(Offset::messageLengths()+idx, len); // store string - writeASCII(0x0048+144*len, msg, 144, 0xff); + writeASCII(Offset::messages()+Offset::betweenMessages()*idx, msg, Limit::messageLength(), 0xff); +} + +bool +RadioddityCodeplug::MessageBankElement::encode(Context &ctx, const Flags &flags, const ErrorStack &err) { + Q_UNUSED(flags); Q_UNUSED(err) + clear(); + unsigned int count = std::min( + Limit::messages(), (unsigned int)ctx.config()->smsExtension()->smsTemplates()->count()); + for (unsigned int i=0; ismsExtension()->smsTemplates()->message(i)->message()); + return true; +} + +bool +RadioddityCodeplug::MessageBankElement::decode(Context &ctx, const ErrorStack &err) { + Q_UNUSED(err); + + for (unsigned int i=0; isetName(QString("Message %1").arg(i+1)); + sms->setMessage(message(i)); + ctx.config()->smsExtension()->smsTemplates()->add(sms); + } + + return true; } @@ -2819,6 +2844,11 @@ RadioddityCodeplug::encodeElements(const Flags &flags, Context &ctx, const Error return false; } + if (! this->encodeMessages(ctx, flags, err)) { + errMsg(err) << "Cannot encode preset messages."; + return false; + } + // Define Contacts if (! this->encodeContacts(ctx.config(), flags, ctx, err)) { errMsg(err) << "Cannot encode contacts."; @@ -2903,6 +2933,11 @@ RadioddityCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { return false; } + if (! this->decodeMessages(ctx, err)) { + errMsg(err) << "Cannot decode preset messages."; + return false; + } + if (! this->createContacts(ctx.config(), ctx, err)) { errMsg(err) << "Cannot create contacts."; return false; diff --git a/lib/radioddity_codeplug.hh b/lib/radioddity_codeplug.hh index c1098751..0bf5ba1f 100644 --- a/lib/radioddity_codeplug.hh +++ b/lib/radioddity_codeplug.hh @@ -1321,6 +1321,8 @@ public: /** Destructor. */ virtual ~MessageBankElement(); + /** Returns the size of the message bank. */ + static constexpr unsigned int size() { return 0x1248; } /** Resets all messages. */ void clear(); @@ -1330,6 +1332,29 @@ public: virtual QString message(unsigned n) const; /** Appends a message to the list. */ virtual void appendMessage(const QString msg); + + /** Encodes all preset messages. */ + virtual bool encode(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); + /** Decodes all preset messages. */ + virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); + + public: + /** Some limits. */ + struct Limit { + static constexpr unsigned int messages() { return 32; } ///< Maximum number of messages. + static constexpr unsigned int messageLength() { return 144; } ///< Maximum length of each message. + }; + + protected: + /** Some internal used offset. */ + struct Offset { + /// @cond DO_NOT_DOCUMENT + static constexpr unsigned int messageConut() { return 0x0000; } + static constexpr unsigned int messageLengths() { return 0x0008; } + static constexpr unsigned int messages() { return 0x0048; } + static constexpr unsigned int betweenMessages() { return Limit::messageLength(); } + /// @endcond + }; }; @@ -1424,6 +1449,10 @@ public: /** Clears the messages. */ virtual void clearMessages() = 0; + /** Encodes preset messages. */ + virtual bool encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()) = 0; + /** Decodes preset messages. */ + virtual bool decodeMessages(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all contacts in the codeplug. */ virtual void clearContacts() = 0; diff --git a/lib/rd5r_codeplug.cc b/lib/rd5r_codeplug.cc index 18b0f728..222fb12a 100644 --- a/lib/rd5r_codeplug.cc +++ b/lib/rd5r_codeplug.cc @@ -282,6 +282,23 @@ void RD5RCodeplug::clearMessages() { MessageBankElement(data(Offset::messages())).clear(); } +bool +RD5RCodeplug::encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { + if (! MessageBankElement(data(Offset::messages())).encode(ctx, flags, err)) { + errMsg(err) << "Cannot encode preset messages."; + return false; + } + return true; +} +bool +RD5RCodeplug::decodeMessages(Context &ctx, const ErrorStack &err) { + if (! MessageBankElement(data(Offset::messages())).decode(ctx, err)) { + errMsg(err) << "Cannot decode preset messages."; + return false; + } + return true; +} + void RD5RCodeplug::clearContacts() { diff --git a/lib/rd5r_codeplug.hh b/lib/rd5r_codeplug.hh index 4352d7bf..421822ae 100644 --- a/lib/rd5r_codeplug.hh +++ b/lib/rd5r_codeplug.hh @@ -170,6 +170,8 @@ public: bool decodeButtonSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearMessages(); + bool encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); + bool decodeMessages(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); diff --git a/test/gd77_test.cc b/test/gd77_test.cc index b5ea5a81..7488848c 100644 --- a/test/gd77_test.cc +++ b/test/gd77_test.cc @@ -62,5 +62,34 @@ GD77Test::testChannelFrequency() { Frequency::fromHz(999999990ULL)); } +void +GD77Test::testSMSTemplates() { + Config config; + config.radioIDs()->add(new DMRRadioID("ID", 1234567)); + SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); + SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); + config.smsExtension()->smsTemplates()->add(sms0); + config.smsExtension()->smsTemplates()->add(sms1); + + ErrorStack err; + GD77Codeplug codeplug; + if (! codeplug.encode(&config, Codeplug::Flags(), err)) { + QFAIL(QString("Cannot encode codeplug for Radioddity GD77: %1") + .arg(err.format()).toStdString().c_str()); + } + + Config decoded; + if (! codeplug.decode(&decoded, err)) { + QFAIL(QString("Cannot decode codeplug for Radioddity GD77: %1") + .arg(err.format()).toStdString().c_str()); + } + + QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); + //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); + QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); + //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); + QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); +} + QTEST_GUILESS_MAIN(GD77Test) diff --git a/test/gd77_test.hh b/test/gd77_test.hh index daf49c03..6a59b1c8 100644 --- a/test/gd77_test.hh +++ b/test/gd77_test.hh @@ -15,6 +15,7 @@ private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); + void testSMSTemplates(); }; #endif // GD77TEST_HH diff --git a/test/opengd77_test.cc b/test/opengd77_test.cc index ba5cc37d..e11d71a9 100644 --- a/test/opengd77_test.cc +++ b/test/opengd77_test.cc @@ -64,5 +64,36 @@ OpenGD77Test::testChannelFrequency() { Frequency::fromHz(999999990ULL)); } +void +OpenGD77Test::testSMSTemplates() { + Config config; + config.radioIDs()->add(new DMRRadioID("ID", 1234567)); + SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); + SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); + config.smsExtension()->smsTemplates()->add(sms0); + config.smsExtension()->smsTemplates()->add(sms1); + + ErrorStack err; + OpenGD77Codeplug codeplug; + if (! codeplug.encode(&config, Codeplug::Flags(), err)) { + QFAIL(QString("Cannot encode codeplug for OpenGD77: %1") + .arg(err.format()).toStdString().c_str()); + } + + Config decoded; + if (! codeplug.decode(&decoded, err)) { + QFAIL(QString("Cannot decode codeplug for OpenGD77: %1") + .arg(err.format()).toStdString().c_str()); + } + + // For now, messages are not encoded + QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); + //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); + QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); + //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); + QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); +} + + QTEST_GUILESS_MAIN(OpenGD77Test) diff --git a/test/opengd77_test.hh b/test/opengd77_test.hh index 4f8aa4fa..5ed8adad 100644 --- a/test/opengd77_test.hh +++ b/test/opengd77_test.hh @@ -18,6 +18,7 @@ private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); + void testSMSTemplates(); protected: QTextStream _stderr; diff --git a/test/rd5r_test.cc b/test/rd5r_test.cc index a7b6ea0e..9d01239d 100644 --- a/test/rd5r_test.cc +++ b/test/rd5r_test.cc @@ -62,5 +62,35 @@ RD5RTest::testChannelFrequency() { 1234567890ULL);*/ } +void +RD5RTest::testSMSTemplates() { + Config config; + config.radioIDs()->add(new DMRRadioID("ID", 1234567)); + SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); + SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); + config.smsExtension()->smsTemplates()->add(sms0); + config.smsExtension()->smsTemplates()->add(sms1); + + ErrorStack err; + RD5RCodeplug codeplug; + if (! codeplug.encode(&config, Codeplug::Flags(), err)) { + QFAIL(QString("Cannot encode codeplug for Radioddity RD5R: %1") + .arg(err.format()).toStdString().c_str()); + } + + Config decoded; + if (! codeplug.decode(&decoded, err)) { + QFAIL(QString("Cannot decode codeplug for Radioddity RD5R: %1") + .arg(err.format()).toStdString().c_str()); + } + + QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); + //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); + QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); + //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); + QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); +} + + QTEST_GUILESS_MAIN(RD5RTest) diff --git a/test/rd5r_test.hh b/test/rd5r_test.hh index 1396d1e7..f0c0a0b9 100644 --- a/test/rd5r_test.hh +++ b/test/rd5r_test.hh @@ -13,8 +13,8 @@ public: private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); - void testChannelFrequency(); + void testSMSTemplates(); }; #endif // RD5RTEST_HH diff --git a/test/uv390_test.cc b/test/uv390_test.cc index 923fb187..5d631b33 100644 --- a/test/uv390_test.cc +++ b/test/uv390_test.cc @@ -73,13 +73,13 @@ UV390Test::testSMSTemplates() { ErrorStack err; UV390Codeplug codeplug; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { - QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") + QFAIL(QString("Cannot encode codeplug for TyT MD-UV390: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { - QFAIL(QString("Cannot decode codeplug for Radioddity GD73: %1") + QFAIL(QString("Cannot decode codeplug for TyT MD-UV390: %1") .arg(err.format()).toStdString().c_str()); }