diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_API.ino b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino index 53cf5a60..a8d8251d 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_API.ino +++ b/Firmware/GPAD_API/GPAD_API/GPAD_API.ino @@ -132,7 +132,7 @@ const int LED_COUNT = sizeof(LED_PINS) / sizeof(LED_PINS[0]); // const int SWITCH_COUNT = sizeof(SWITCH_PINS) / sizeof(SWITCH_PINS[0]); // MQTT Broker -//#define USE_HIVEMQ +// #define USE_HIVEMQ #ifdef USE_HIVEMQ const char *mqtt_broker_name = "broker.hivemq.com"; const char *mqtt_user = ""; @@ -245,7 +245,7 @@ void reconnect() { n++; Serial.print("Attempting MQTT connection at: "); - Serial.print(millis() ); + Serial.print(millis()); Serial.print("..... "); if (client.connect(COMPANY_NAME, mqtt_user, mqtt_password)) { @@ -520,12 +520,13 @@ void loop() { #if defined HMWK || defined KRAKE -if (!client.loop()) { - Serial.print(mqtt_broker_name); - Serial.print(" lost MQTT at: "); - Serial.println(millis()); + if (!client.loop()) + { + Serial.print(mqtt_broker_name); + Serial.print(" lost MQTT at: "); + Serial.println(millis()); reconnect(); -} + } publishOnLineMsg(); wink(); // The builtin LED diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp index 3860a916..bb4362bd 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp +++ b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp @@ -25,6 +25,7 @@ #include #include "WiFiManagerOTA.h" #include "GPAD_menu.h" +#include "GPAPMessage.h" using namespace gpad_hal; @@ -426,25 +427,32 @@ void interpretBuffer(char *buf, int rlen, Stream *serialport, PubSubClient *clie printError(serialport); return; } - Command command = static_cast(buf[0]); - char commandChar = static_cast(command); + + const auto protocolMessage = gpap_message::GPAPMessage::deserialize(buf, rlen); serialport->print(F("Command: ")); - serialport->printf("%c\n", commandChar); - switch (command) + serialport->printf("%c\n", protocolMessage.getMessageType()); + + switch (protocolMessage.getMessageType()) + { + case gpap_message::MessageType::MUTE: { - case Command::MUTE: serialport->println(F("Muting Case!")); currentlyMuted = true; break; - case Command::UNMUTE: + } + case gpap_message::MessageType::UNMUTE: + { serialport->println(F("UnMuting Case!")); currentlyMuted = false; break; - case Command::HELP: // help + } + case gpap_message::MessageType::HELP: + { printInstructions(serialport); break; - case Command::ALARM: + } + case gpap_message::MessageType::ALARM: { // In the case of an alarm state, the rest of the buffer is a message. // we will read up to 60 characters from this buffer for display on our @@ -465,7 +473,7 @@ void interpretBuffer(char *buf, int rlen, Stream *serialport, PubSubClient *clie break; } - case Command::INFO: // Information. Firmware Version, Mute Status, + case gpap_message::MessageType::INFO: { // Firmware Version // 81+23 = Maximum string length @@ -550,9 +558,11 @@ void interpretBuffer(char *buf, int rlen, Stream *serialport, PubSubClient *clie break; // end of 'i' } default: + { serialport->println(F("Unknown Command")); break; } + } serialport->print(F("currentlyMuted : ")); serialport->println(currentlyMuted); serialport->println(F("interpret Done")); diff --git a/Firmware/GPAD_API/GPAD_API/GPAD_HAL.h b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.h index e2e62bb7..e912ab2d 100644 --- a/Firmware/GPAD_API/GPAD_API/GPAD_HAL.h +++ b/Firmware/GPAD_API/GPAD_API/GPAD_HAL.h @@ -130,15 +130,6 @@ namespace gpad_hal static const uint8_t API_MINOR_VERSION = 1; static const uint8_t API_PATCH_VERSION = 0; - enum class Command : char - { - MUTE = 's', - UNMUTE = 'u', - HELP = 'h', - ALARM = 'a', - INFO = 'i', - }; - /** * SemanticVersion stores a version following the "semantic versioning" convention * defined here: https://semver.org/ diff --git a/Firmware/GPAD_API/lib/GPAP/README b/Firmware/GPAD_API/lib/GPAP/README new file mode 100644 index 00000000..d1bae677 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/README @@ -0,0 +1,16 @@ +This library provides the types needed to deserialize messages as laid out in the [GPAP](https://github.com/PubInv/gpap) protocol. +At the time of the library's creation it leverages functionality of C++11 that is "more advanced" than other parts of this project. +Building the main class, `GPAPMessage`, heavily leverages the concept of "Return Value Optimization", or [RVO](https://sigcpp.github.io/2020/06/08/return-value-optimization) +(also known as [Copy elision](https://en.wikipedia.org/wiki/Copy_elision)). +Without RVO, when an instance is returned from a function that instances destructor is called and then a _new_ constructor is called +copying the data from the initial instance. **With** RVO, the initial instance's destructor is never called. Only a single constructor is called. +The article linked earlier for RVO has wonderful code explains highlighting the difference. + +In C++17 and later, RVO (or copy elision) happens more implicitly than C++11 (the current C++ version at the time of writing). Since C++11 is being +used we do have to be more explicit and leverage [`std::move()`](https://en.cppreference.com/w/cpp/utility/move.html). While it is _technically_ a function, +it is more an idication to the compiler saying "this value can be moved efficiently, don't copy it". `std::move()` is used through out the building of the +`GPAPMessage`, specifically the `AlarmMessage`, since a "builder pattern" is leveraged to constructor the types from a buffer of bytes. + +To facilitate being explicit about RVO and using `std::move()` the various `AlarmMessage*` types have defined move constructors and move assignment operators. +This does result in a substantial amount of "boiler plate" code but it's all quite straightforward. None of the types perform any sort of allocation, or hold +onto dynamically allocated memory, the move assignments and move constructors are just stating the data from one instance is moving to the new instance. \ No newline at end of file diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.cpp b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.cpp new file mode 100644 index 00000000..6fa66b20 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.cpp @@ -0,0 +1,23 @@ +#include "AlarmMessage.h" + +using namespace gpap_message::alarm; + +const AlarmContent &AlarmMessage::getAlarmContent() const noexcept +{ + return this->content; +} + +AlarmMessage::Level AlarmMessage::getAlarmLevel() const noexcept +{ + return this->level; +} + +const AlarmMessage::PossibleTypeDesignator &AlarmMessage::getTypeDesignator() const noexcept +{ + return this->typeDesignator; +} + +const AlarmMessage::PossibleMessageId &AlarmMessage::getMessageId() const noexcept +{ + return this->messageId; +} diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.h b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.h new file mode 100644 index 00000000..384448da --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.h @@ -0,0 +1,94 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#ifndef _ALARM_MESSAGE_H +#define _ALARM_MESSAGE_H + +#include "AlarmMessage/AlarmMessageId.h" +#include "AlarmMessage/AlarmTypeDesignator.h" +#include "AlarmMessage/AlarmContent.h" +#include "AlarmMessage/PossibleParameter.h" + +namespace gpap_message::alarm +{ + class AlarmMessage final + { + public: + enum class Level : char + { + Level0 = '0', + Level1 = '1', + Level2 = '2', + Level3 = '3', + Level4 = '4', + Level5 = '5', + }; + + using PossibleMessageId = PossibleParameter; + using PossibleTypeDesignator = PossibleParameter; + + private: + Level level; + AlarmContent content; + PossibleMessageId messageId; + PossibleTypeDesignator typeDesignator; + + public: + explicit AlarmMessage(const AlarmMessage::Level alarmLevel, + const AlarmContent alarmContent, + const PossibleMessageId messageId, + const PossibleTypeDesignator typeDesignator) noexcept + : level(alarmLevel), + content(std::move(alarmContent)), + messageId(std::move(messageId)), + typeDesignator(std::move(typeDesignator)) + { + } + AlarmMessage(const AlarmMessage &&other) noexcept + : level(other.level), + content(std::move(other.content)), + messageId(std::move(other.messageId)), + typeDesignator(std::move(other.typeDesignator)) + { + } + AlarmMessage &operator=(const AlarmMessage &&other) noexcept + { + if (this != &other) + { + this->level = other.level; + this->content = std::move(other.content); + this->messageId = std::move(other.messageId); + this->typeDesignator = std::move(other.typeDesignator); + } + return *this; + } + + ~AlarmMessage() {} + + AlarmMessage() = delete; + AlarmMessage(AlarmMessage &other) = delete; + AlarmMessage(const AlarmMessage &other) = delete; + + const AlarmContent &getAlarmContent() const noexcept; + Level getAlarmLevel() const noexcept; + const PossibleTypeDesignator &getTypeDesignator() const noexcept; + const PossibleMessageId &getMessageId() const noexcept; + }; + +} // namespace alarm + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.cpp b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.cpp new file mode 100644 index 00000000..82bc4780 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.cpp @@ -0,0 +1,42 @@ +#include "AlarmContent.h" + +#include + +#ifndef PIO_UNIT_TESTING +#include +#else +#include +using Print = MockPrint; +#endif + +using namespace gpap_message::alarm; + +AlarmContent::AlarmContent(const std::size_t messageLength, const Buffer message) + : messageLength(messageLength), message(std::move(message)) +{ + if (this->messageLength > AlarmContent::MAX_LENGTH) + { + throw; + } +} + +AlarmContent::~AlarmContent() {} + +std::size_t AlarmContent::printTo(Print &print) const +{ + auto beginIterator = this->message.cbegin(); + const auto endIterator = std::next(beginIterator, this->messageLength); + + auto bytesWritten = 0; + + std::for_each( + beginIterator, + endIterator, + [&bytesWritten, &print](const char &currElement) + { + bytesWritten += print.print(currElement); + }); + + bytesWritten += print.print('\0'); + return bytesWritten; +} diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.h b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.h new file mode 100644 index 00000000..db908795 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.h @@ -0,0 +1,72 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#ifndef _ALARM_CONTENT_H +#define _ALARM_CONTENT_H + +#include +#include + +#ifndef PIO_UNIT_TESTING +#include +#else +#include "MockPrintable.h" +#include "MockPrint.h" +using Printable = MockPrintable; +using Print = MockPrint; +#endif + +namespace gpap_message::alarm +{ + + class AlarmContent final : public Printable + { + public: + static const std::size_t MAX_LENGTH = 80; + + using Buffer = std::array; + + private: + std::size_t messageLength; + std::array message; + + public: + explicit AlarmContent(const std::size_t messageLength, const Buffer message); + + AlarmContent(const AlarmContent &&other) noexcept + : messageLength(other.messageLength), message(std::move(other.message)) {} + AlarmContent &operator=(const AlarmContent &&other) noexcept + { + if (this != &other) + { + this->messageLength = other.messageLength; + this->message = std::move(other.message); + } + return *this; + } + + AlarmContent() = delete; + AlarmContent(AlarmContent &other) = delete; + AlarmContent(const AlarmContent &other) = delete; + + virtual ~AlarmContent(); + + std::size_t printTo(Print &print) const; + }; +} + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmMessageId.cpp b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmMessageId.cpp new file mode 100644 index 00000000..680486eb --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmMessageId.cpp @@ -0,0 +1,95 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#include "AlarmMessageId.h" + +#include +#include + +#ifndef PIO_UNIT_TESTING +#include +#else +#include +using Print = MockPrint; +#endif + +using namespace gpap_message::alarm; + +AlarmMessageId::AlarmMessageId( + const std::size_t idLength, + const Buffer id) + : idLength(idLength), id(std::move(AlarmMessageId::validateId(idLength, id))) {} + +std::array AlarmMessageId::validateId( + const std::size_t idLength, + const Buffer id) +{ + std::array validatedId = {}; + auto validatedIdIterator = validatedId.begin(); + + // The recorded real-length of the message ID cannot me more than the max + // number of elements + if (idLength > id.size()) + { + throw; + } + + auto startIterator = id.cbegin(); + auto endIterator = id.cbegin() + idLength; + + const bool allHex = std::all_of( + startIterator, + endIterator, + [&validatedIdIterator](char hexChar) + { + *validatedIdIterator = hexChar; + validatedIdIterator = std::next(validatedIdIterator, 1); + + return std::isxdigit(static_cast(hexChar)); + }); + + // If all the characters are NOT hex characters we need to throw an error and + // cancel the creation of this instance. + if (!allHex) + { + throw; + } + + *validatedIdIterator = '\0'; + return std::move(validatedId); +} + +AlarmMessageId::~AlarmMessageId() {} + +std::size_t AlarmMessageId::printTo(Print &print) const +{ + auto beginIterator = this->id.cbegin(); + const auto endIterator = std::next(beginIterator, this->idLength); + + auto bytesWritten = 0; + + std::for_each( + beginIterator, + endIterator, + [&bytesWritten, &print](const char &character) + { + bytesWritten += print.print(character); + }); + + bytesWritten += print.print('\0'); + return bytesWritten; +} diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmMessageId.h b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmMessageId.h new file mode 100644 index 00000000..c53934cd --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmMessageId.h @@ -0,0 +1,78 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#ifndef _ALARM_MESSAGE_ID_H +#define _ALARM_MESSAGE_ID_H + +#include + +#ifndef PIO_UNIT_TESTING +#include +#else +#include "MockPrintable.h" +#include "MockPrint.h" +using Printable = MockPrintable; +using Print = MockPrint; +#endif + +namespace gpap_message::alarm +{ + class AlarmMessageId final : public Printable + { + public: + // TODO: Find out what this value is SUPPOSED to be. + static const std::size_t MAX_LENGTH = 10; + + using Buffer = std::array; + + private: + static const std::size_t TOTAL_MAX_LENGTH = AlarmMessageId::MAX_LENGTH + 1; + + std::size_t idLength; + std::array id; + + public: + explicit AlarmMessageId(const std::size_t idLength, const Buffer id); + + AlarmMessageId(const AlarmMessageId &&other) noexcept + : id(std::move(other.id)), idLength(other.idLength) {} + AlarmMessageId &operator=(const AlarmMessageId &&other) noexcept + { + if (this != &other) + { + this->idLength = other.idLength; + this->id = std::move(other.id); + } + return *this; + } + + AlarmMessageId() = delete; + AlarmMessageId(AlarmMessageId &other) = delete; + AlarmMessageId(const AlarmMessageId &other) = delete; + + virtual ~AlarmMessageId(); + + private: + static std::array + validateId(const std::size_t idLength, const Buffer); + + public: + std::size_t printTo(Print &print) const; + }; +} + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmTypeDesignator.cpp b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmTypeDesignator.cpp new file mode 100644 index 00000000..4c92dc64 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmTypeDesignator.cpp @@ -0,0 +1,74 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#include "AlarmTypeDesignator.h" + +#include +#include + +#ifndef PIO_UNIT_TESTING +#include +#else +#include +using Print = MockPrint; +#endif + +using namespace gpap_message::alarm; + +AlarmTypeDesignator::AlarmTypeDesignator(const Buffer designator) : designator(std::move(designator)) +{ + auto designatorIterator = designator.begin(); + + const bool allDigits = + std::all_of(this->designator.cbegin(), this->designator.cend(), + [](char inputCharacter) + { + return std::isdigit(static_cast(inputCharacter)); + }); + + // if the characters are not all digits we want to throw + if (!allDigits) + { + throw; + } +} + +AlarmTypeDesignator::~AlarmTypeDesignator() {} + +const AlarmTypeDesignator::Buffer &AlarmTypeDesignator::getValue() const +{ + return this->designator; +} + +std::size_t AlarmTypeDesignator::printTo(Print &print) const +{ + auto beginIterator = this->designator.cbegin(); + auto const endIterator = this->designator.cend(); + + auto bytesWritten = 0; + + std::for_each( + beginIterator, + endIterator, + [&bytesWritten, &print](const char &currElement) + { + bytesWritten += print.print(currElement); + }); + + bytesWritten += print.print('\0'); + return bytesWritten; +} diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmTypeDesignator.h b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmTypeDesignator.h new file mode 100644 index 00000000..953e78e9 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmTypeDesignator.h @@ -0,0 +1,69 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#ifndef _ALARM_TYPE_DESIGNATOR_H +#define _ALARM_TYPE_DESIGNATOR_H + +#include + +#ifndef PIO_UNIT_TESTING +#include +#else +#include "MockPrintable.h" +#include "MockPrint.h" +using Printable = MockPrintable; +using Print = MockPrint; +#endif + +namespace gpap_message::alarm +{ + class AlarmTypeDesignator final : public Printable + { + public: + static const std::size_t DESIGNATOR_LENGTH = 3; + using Buffer = std::array; + + private: + std::array designator; + + public: + explicit AlarmTypeDesignator(const Buffer designator); + + AlarmTypeDesignator(const AlarmTypeDesignator &&other) noexcept + : designator(std::move(other.designator)) {} + AlarmTypeDesignator &operator=(const AlarmTypeDesignator &&other) noexcept + { + if (this != &other) + { + this->designator = std::move(other.designator); + } + return *this; + } + + AlarmTypeDesignator() = delete; + AlarmTypeDesignator(AlarmTypeDesignator &other) = delete; + AlarmTypeDesignator(const AlarmTypeDesignator &other) = delete; + + virtual ~AlarmTypeDesignator(); + + public: + const Buffer &getValue() const; + std::size_t printTo(Print &print) const; + }; +} + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/PossibleParameter.h b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/PossibleParameter.h new file mode 100644 index 00000000..aa10e46f --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/PossibleParameter.h @@ -0,0 +1,92 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +/* + In the AlarmMessage, a couple of the fields are optional. The "message ID" and the "types designator". + Treating the concrete type of AlarmMessageId and AlarmTypeDesignator as both having a possible value + and also being empty can lead to many headaches and mistakes. To address this, an "optional" type has + been added to clearly indicate if a field in the AlarmMessage is present or not. + + Note: In C++17 there is a native "optional" type called `std::optional`. At the time of creation for + this library, it was compiled using C++11 so an "optional" type had to be manually implemented. + https://en.cppreference.com/w/cpp/utility/optional.html +*/ + +#ifndef _POSSIBLE_PARAMETER_H +#define _POSSIBLE_PARAMETER_H + +namespace gpap_message::alarm +{ + template + struct PossibleParameter final + { + enum class State + { + Some, + None, + }; + + union + { + T contents; + std::nullptr_t empty; + }; + + State state; + + explicit PossibleParameter() : state(State::None), empty(nullptr) {} + explicit PossibleParameter(T contents) : state(State::Some), contents(std::move(contents)) {} + + PossibleParameter(const PossibleParameter &&other) noexcept + : state(other.state) + { + switch (this->state) + { + case State::Some: + this->contents = std::move(other.contents); + break; + case State::None: + this->empty = nullptr; + break; + } + } + PossibleParameter &operator=(const PossibleParameter &&other) noexcept + { + if (this != &other) + { + this->state = other.state; + switch (this->state) + { + case State::Some: + this->contents = std::move(other.contents); + break; + case State::None: + this->empty = nullptr; + break; + } + } + return *this; + } + + PossibleParameter(PossibleParameter &other) = delete; + PossibleParameter(const PossibleParameter &other) = delete; + + ~PossibleParameter() {} + }; +} + +#endif \ No newline at end of file diff --git a/Firmware/GPAD_API/lib/GPAP/src/Deserialize/AlarmMessageBuilder/AlarmMessageBuilder.cpp b/Firmware/GPAD_API/lib/GPAP/src/Deserialize/AlarmMessageBuilder/AlarmMessageBuilder.cpp new file mode 100644 index 00000000..14b65880 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/Deserialize/AlarmMessageBuilder/AlarmMessageBuilder.cpp @@ -0,0 +1,228 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#include "AlarmMessageBuilder.h" + +using namespace gpap_message::alarm; +using namespace gpap_message::deserialize; + +AlarmMessageBuilder::AlarmMessageBuilder() + : level(AlarmMessage::Level::Level1), idLength(0), idBuffer({}), + designatorLength(0), designatorBuffer({}), messageLength(0), + messageBuffer({}) +{ +} + +std::size_t AlarmMessageBuilder::deserializeLevel(const char *const buffer, const std::size_t numBytes) +{ + if (numBytes == 0) + { + throw; + } + + this->level = static_cast(buffer[0]); + + return 1; +} + +std::size_t AlarmMessageBuilder::deserializeId(const char *const buffer, const std::size_t numBytes) +{ + if (numBytes == 0 || (buffer[0] != AlarmMessageBuilder::ID_START_CHARACTER)) + { + return 0; + } + + auto idLength = 0; + auto foundEnd = false; + + // Going 1 more than the max since the end character, ], can be after the max + // length of the ID + while ((idLength < (AlarmMessageId::MAX_LENGTH + 1)) && + (idLength < numBytes) && !foundEnd) + { + // Need to offset the index by 1 since we have to account for the starting + // character, [ + auto bufferIndex = idLength + 1; + if (buffer[bufferIndex] == AlarmMessageBuilder::ID_END_CHARACTER) + { + foundEnd = true; + } + else if (idLength < AlarmMessageId::MAX_LENGTH) + { + this->idBuffer.at(idLength) = buffer[bufferIndex]; + ++idLength; + } + } + + // If the terminating character, }, was not found then it is an invalid string + if (!foundEnd) + { + throw; + } + else + { + this->idLength = idLength; + + // Add 2 to the index value since that will account for the deliminating + // characters, '[' and ']' + return idLength + 2; + } +} + +std::size_t +AlarmMessageBuilder::deserializeTypeDesignator(const char *const buffer, const std::size_t numBytes) +{ + if ((numBytes == 0) || + buffer[0] != AlarmMessageBuilder::DESIGNATOR_START_CHARACTER) + { + return 0; + } + + const auto currBuffer = buffer + 1; + + auto designatorLength = 0; + auto foundEnd = false; + while ((designatorLength < (AlarmTypeDesignator::DESIGNATOR_LENGTH + 1)) && + (designatorLength < numBytes) && !foundEnd) + { + // Need to offset the index by 1 since we have to account for the starting + // character, { + if (currBuffer[designatorLength] == AlarmMessageBuilder::DESIGNATOR_END_CHARACTER) + { + foundEnd = true; + } + else if (designatorLength < AlarmTypeDesignator::DESIGNATOR_LENGTH) + { + this->designatorBuffer.at(designatorLength) = currBuffer[designatorLength]; + ++designatorLength; + } + } + + if (!foundEnd || + (designatorLength != AlarmTypeDesignator::DESIGNATOR_LENGTH && + designatorLength != 0)) + { + throw; + } + else + { + this->designatorLength = designatorLength; + + // Add 2 to the index value since that will account for the deliminating + // characters, '{' and '}' + return designatorLength + 2; + } +} + +std::size_t AlarmMessageBuilder::deserializeMessage(const char *const buffer, const std::size_t numBytes) +{ + auto messageLength = 0; + for (; messageLength < numBytes; ++messageLength) + { + if (AlarmMessageBuilder::isReservedCharacter(buffer[messageLength])) + { + messageLength - 1; + break; + } + + this->messageBuffer.at(messageLength) = buffer[messageLength]; + } + + this->messageLength = messageLength; + return messageLength; +} + +AlarmMessage AlarmMessageBuilder::buildAlarmMessage(const char *const buffer, const std::size_t numBytes) +{ + AlarmMessageBuilder builder = AlarmMessageBuilder(); + + auto totalBytes = builder.deserializeLevel(buffer, numBytes); + + auto foundId = false; + auto foundDesignator = false; + auto foundMessage = false; + + while ((totalBytes < numBytes) && !(foundId && foundDesignator && foundMessage)) + { + auto currBuffer = buffer + totalBytes; + auto currNumBytes = numBytes - totalBytes; + if (currBuffer[0] == AlarmMessageBuilder::ID_START_CHARACTER && !foundId) + { + totalBytes += builder.deserializeId(currBuffer, currNumBytes); + + foundId = true; + } + else if (currBuffer[0] == AlarmMessageBuilder::DESIGNATOR_START_CHARACTER && !foundDesignator) + { + totalBytes += builder.deserializeTypeDesignator(currBuffer, currNumBytes); + + foundDesignator = true; + } + else if (!foundMessage) + { + totalBytes += builder.deserializeMessage(currBuffer, currNumBytes); + + foundMessage = true; + } + } + + // The use of the lambda function here, and for creating the `PossibleParameter allows + // for conditionally setting the variable value messageId. If messageId was assigned outside of a lambda it + // could not be const. The compiler will inline the lambda since it is called right away so there is no + // "inefficiency" due to leveraging this + const AlarmMessage::PossibleMessageId messageId = [](const std::size_t numBytes, const AlarmMessageId::Buffer buffer) + { + if (numBytes == 0) + { + return std::move(AlarmMessage::PossibleMessageId()); + } + + const AlarmMessageId messageId(numBytes, std::move(buffer)); + const AlarmMessage::PossibleMessageId possibleMessageId(std::move(messageId)); + + return possibleMessageId; + }(builder.idLength, std::move(builder.idBuffer)); + + const AlarmMessage::PossibleTypeDesignator typeDesignator = [](const std::size_t numBytes, const AlarmTypeDesignator::Buffer buffer) + { + if (numBytes == 0) + { + return AlarmMessage::PossibleTypeDesignator(); + } + + const AlarmTypeDesignator typeDesignator(std::move(buffer)); + const AlarmMessage::PossibleTypeDesignator possibleTypeDesignator(std::move(typeDesignator)); + + return possibleTypeDesignator; + }(builder.designatorLength, std::move(builder.designatorBuffer)); + + const auto content = + AlarmContent(builder.messageLength, std::move(builder.messageBuffer)); + + return AlarmMessage(builder.level, + std::move(content), + std::move(messageId), + std::move(typeDesignator)); +} + +bool AlarmMessageBuilder::isReservedCharacter(const char character) +{ + return (character == AlarmMessageBuilder::DESIGNATOR_START_CHARACTER || + character == AlarmMessageBuilder::DESIGNATOR_END_CHARACTER || + character == AlarmMessageBuilder::ID_START_CHARACTER || + character == AlarmMessageBuilder::ID_END_CHARACTER); +} diff --git a/Firmware/GPAD_API/lib/GPAP/src/Deserialize/AlarmMessageBuilder/AlarmMessageBuilder.h b/Firmware/GPAD_API/lib/GPAP/src/Deserialize/AlarmMessageBuilder/AlarmMessageBuilder.h new file mode 100644 index 00000000..47ddd36e --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/Deserialize/AlarmMessageBuilder/AlarmMessageBuilder.h @@ -0,0 +1,88 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#ifndef _ALARM_MESSAGE_BUILDER_H +#define _ALARM_MESSAGE_BUILDER_H + +#include "AlarmMessage.h" + +namespace gpap_message::deserialize +{ + + class AlarmMessageBuilder final + { + + private: + static const char ID_START_CHARACTER = '{'; + static const char ID_END_CHARACTER = '}'; + + static const char DESIGNATOR_START_CHARACTER = '['; + static const char DESIGNATOR_END_CHARACTER = ']'; + + private: + alarm::AlarmMessage::Level level; + + std::size_t idLength; + alarm::AlarmMessageId::Buffer idBuffer; + + std::size_t designatorLength; + alarm::AlarmTypeDesignator::Buffer designatorBuffer; + + std::size_t messageLength; + alarm::AlarmContent::Buffer messageBuffer; + + private: + explicit AlarmMessageBuilder(); + + std::size_t deserializeLevel(const char *const buffer, std::size_t numBytes); + std::size_t deserializeId(const char *const buffer, const std::size_t numBytes); + std::size_t deserializeTypeDesignator(const char *const buffer, const std::size_t numBytes); + std::size_t deserializeMessage(const char *const buffer, const std::size_t numBytes); + + public: + static alarm::AlarmMessage buildAlarmMessage(const char *const buffer, const std::size_t numBytes); + static bool isReservedCharacter(const char character); + + AlarmMessageBuilder(const AlarmMessageBuilder &&other) noexcept + : level(other.level), + idLength(other.idLength), + idBuffer(std::move(other.idBuffer)), + designatorLength(other.designatorLength), + designatorBuffer(std::move(other.designatorBuffer)), + messageLength(other.messageLength), + messageBuffer(std::move(other.messageBuffer)) {}; + AlarmMessageBuilder &operator=(const AlarmMessageBuilder &&other) noexcept + { + if (this != &other) + { + this->level = other.level; + this->idLength = other.idLength; + this->idBuffer = std::move(other.idBuffer); + this->designatorLength = other.designatorLength; + this->designatorBuffer = std::move(other.designatorBuffer); + this->messageLength = other.messageLength; + this->messageBuffer = std::move(other.messageBuffer); + } + return *this; + } + + AlarmMessageBuilder(AlarmMessageBuilder &other) = delete; + AlarmMessageBuilder operator=(AlarmMessageBuilder &other) = delete; + }; +} + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/GPAPMessage.cpp b/Firmware/GPAD_API/lib/GPAP/src/GPAPMessage.cpp new file mode 100644 index 00000000..ef20397a --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/GPAPMessage.cpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#include "GPAPMessage.h" +#include "Deserialize/AlarmMessageBuilder/AlarmMessageBuilder.h" + +using namespace gpap_message; +GPAPMessage GPAPMessage::deserialize(const char *const buffer, const std::size_t numBytes) +{ + // Can't determined the message type if there are no bytes + if (numBytes == 0) + { + throw; + } + + // TODO: This should be wrapped in a try/catch if there isn't a 0th element + const auto messageType = static_cast(buffer[0]); + + switch (messageType) + { + case MessageType::ALARM: + if ((numBytes - 1) == 0) + { + throw; + } + + return GPAPMessage(std::move(deserialize::AlarmMessageBuilder::buildAlarmMessage(buffer + 1, numBytes - 1))); + + case MessageType::INFO: + return GPAPMessage(InfoMessage()); + + case MessageType::MUTE: + return GPAPMessage(MuteMessage()); + + case MessageType::UNMUTE: + return GPAPMessage(UnmuteMessage()); + + case MessageType::HELP: + return GPAPMessage(HelpMessage()); + } +} + +MessageType GPAPMessage::getMessageType() const noexcept +{ + return this->messageType; +} + +const alarm::AlarmMessage &GPAPMessage::getAlarmMessage() const +{ + // Cannot access a field of the union when it's the incorrect type + if (this->messageType != MessageType::ALARM) + { + throw; + } + + return this->alarm; +} diff --git a/Firmware/GPAD_API/lib/GPAP/src/GPAPMessage.h b/Firmware/GPAD_API/lib/GPAP/src/GPAPMessage.h new file mode 100644 index 00000000..6e766dac --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/GPAPMessage.h @@ -0,0 +1,129 @@ +/* + Copyright (C) 2026 Public Invention + + This program includes free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + See the GNU Affero General Public License for more details. + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + This program 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. +*/ + +#ifndef _GPAP_MESSAGE_H +#define _GPAP_MESSAGE_H + +#include "AlarmMessage.h" +#include "HelpMessage.h" +#include "InfoMessage.h" +#include "MuteMessage.h" +#include "UnmuteMessage.h" + +namespace gpap_message +{ + + enum class MessageType : char + { + MUTE = 's', + UNMUTE = 'u', + HELP = 'h', + ALARM = 'a', + INFO = 'i', + }; + struct GPAPMessage final + { + public: + static const std::size_t BUFFER_LENGTH = 131; + + private: + union + { + alarm::AlarmMessage alarm; + InfoMessage info; + MuteMessage mute; + UnmuteMessage unmute; + HelpMessage help; + }; + + MessageType messageType; + + using GPAPBuffer = std::array; + + public: + explicit GPAPMessage(const alarm::AlarmMessage alarmMessage) noexcept + : messageType(MessageType::ALARM), alarm(std::move(alarmMessage)) {} + explicit GPAPMessage(const InfoMessage infoMessage) noexcept + : messageType(MessageType::INFO), info(std::move(infoMessage)) {} + explicit GPAPMessage(const MuteMessage muteMessage) noexcept + : messageType(MessageType::MUTE), mute(std::move(muteMessage)) {} + explicit GPAPMessage(const UnmuteMessage unmuteCommand) noexcept + : messageType(MessageType::UNMUTE), unmute(std::move(unmuteCommand)) {} + explicit GPAPMessage(const HelpMessage helpCommand) noexcept + : messageType(MessageType::HELP), help(std::move(helpCommand)) {} + GPAPMessage(const GPAPMessage &&other) noexcept + : messageType(other.messageType) + { + switch (this->messageType) + { + case MessageType::ALARM: + this->alarm = std::move(other.alarm); + break; + case MessageType::INFO: + this->info = std::move(other.info); + break; + case MessageType::MUTE: + this->mute = std::move(other.mute); + break; + case MessageType::UNMUTE: + this->unmute = std::move(other.unmute); + break; + case MessageType::HELP: + this->help = std::move(other.help); + break; + } + } + GPAPMessage &operator=(const GPAPMessage &&other) noexcept + { + if (this != &other) + { + this->messageType = other.messageType; + switch (this->messageType) + { + case MessageType::ALARM: + this->alarm = std::move(other.alarm); + break; + case MessageType::INFO: + this->info = std::move(other.info); + break; + case MessageType::MUTE: + this->mute = std::move(other.mute); + break; + case MessageType::UNMUTE: + this->unmute = std::move(other.unmute); + break; + case MessageType::HELP: + this->help = std::move(other.help); + break; + } + } + return *this; + } + + ~GPAPMessage() {} + + GPAPMessage() = delete; + GPAPMessage(GPAPMessage &other) = delete; + + static GPAPMessage deserialize(const char *const buffer, const std::size_t numBytes); + + MessageType getMessageType() const noexcept; + const alarm::AlarmMessage &getAlarmMessage() const; + }; +} + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/HelpMessage.h b/Firmware/GPAD_API/lib/GPAP/src/HelpMessage.h new file mode 100644 index 00000000..8e79c1c2 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/HelpMessage.h @@ -0,0 +1,11 @@ +#ifndef _HELP_MESSAGE_H +#define _HELP_MESSAGE_H + +namespace gpap_message +{ + class HelpMessage final + { + }; +} + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/InfoMessage.h b/Firmware/GPAD_API/lib/GPAP/src/InfoMessage.h new file mode 100644 index 00000000..a60f6a2c --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/InfoMessage.h @@ -0,0 +1,11 @@ +#ifndef _INFO_MESSAGE_H +#define _INFO_MESSAGE_H + +namespace gpap_message +{ + class InfoMessage final + { + }; +} // namespace gpap_message + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/MuteMessage.h b/Firmware/GPAD_API/lib/GPAP/src/MuteMessage.h new file mode 100644 index 00000000..2a2ff183 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/MuteMessage.h @@ -0,0 +1,11 @@ +#ifndef _MUTE_MESSAGE_H +#define _MUTE_MESSAGE_H + +namespace gpap_message +{ + class MuteMessage final + { + }; +} // namespace gpap_message + +#endif diff --git a/Firmware/GPAD_API/lib/GPAP/src/UnmuteMessage.h b/Firmware/GPAD_API/lib/GPAP/src/UnmuteMessage.h new file mode 100644 index 00000000..fbd6aa29 --- /dev/null +++ b/Firmware/GPAD_API/lib/GPAP/src/UnmuteMessage.h @@ -0,0 +1,11 @@ +#ifndef _UNMUTE_MESSAGE_H +#define _UNMUTE_MESSAGE_H + +namespace gpap_message +{ + class UnmuteMessage final + { + }; +} + +#endif diff --git a/Firmware/GPAD_API/lib/mocking/MockPrint.h b/Firmware/GPAD_API/lib/mocking/MockPrint.h new file mode 100644 index 00000000..f2cd8a99 --- /dev/null +++ b/Firmware/GPAD_API/lib/mocking/MockPrint.h @@ -0,0 +1,17 @@ +#ifndef _MOCK_PRINT_H +#define _MOCK_PRINT_H + +#include +#include + +class MockPrint +{ +public: + virtual std::size_t write(uint8_t) = 0; + std::size_t print(char character) + { + return this->write((uint8_t)character); + } +}; + +#endif \ No newline at end of file diff --git a/Firmware/GPAD_API/lib/mocking/MockPrintable.h b/Firmware/GPAD_API/lib/mocking/MockPrintable.h new file mode 100644 index 00000000..2c6c0aa0 --- /dev/null +++ b/Firmware/GPAD_API/lib/mocking/MockPrintable.h @@ -0,0 +1,15 @@ +#ifndef _MOCK_PRINTABLE_H +#define _MOCK_PRINTABLE_H + +#include + +class MockPrint; + +class MockPrintable +{ +public: + virtual ~MockPrintable() {} + virtual std::size_t printTo(MockPrint &p) const = 0; +}; + +#endif diff --git a/Firmware/GPAD_API/platformio.ini b/Firmware/GPAD_API/platformio.ini index 2214a04f..d66ef455 100644 --- a/Firmware/GPAD_API/platformio.ini +++ b/Firmware/GPAD_API/platformio.ini @@ -10,6 +10,11 @@ [platformio] src_dir = GPAD_API +default_envs = esp32dev + +[env:native] +platform = native +build_flags = -std=gnu++11 [env:esp32dev] extra_scripts = @@ -33,3 +38,6 @@ lib_deps = tzapu/WiFiManager@^2.0.17 ayushsharma82/ElegantOTA@^3.1.7 marcoschwartz/LiquidCrystal_I2C@^1.1.4 +lib_ignore = + mocking +test_ignore = native/* diff --git a/Firmware/GPAD_API/test/native/test_GPAP/test_gpap_message.cpp b/Firmware/GPAD_API/test/native/test_GPAP/test_gpap_message.cpp new file mode 100644 index 00000000..a5915dbb --- /dev/null +++ b/Firmware/GPAD_API/test/native/test_GPAP/test_gpap_message.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include + +#ifndef PIO_UNIT_TESTING +#define PIO_UNIT_TESTING +#endif + +#include "GPAPMessage.h" +#include "MockPrint.h" + +class VectorMockPrint : public MockPrint +{ +private: + std::vector contents; + +public: + std::size_t write(uint8_t character) override + { + this->contents.push_back(static_cast(character)); + this->contents.shrink_to_fit(); + return 1; + } + + const char *data() + { + return this->contents.data(); + } + + void clear() + { + this->contents.clear(); + } +}; + +struct AlarmMessageData final +{ + const gpap_message::alarm::AlarmMessage::Level alarmLevel; + const std::string alarmContentStr; + const std::string typeDesignatorStr; + const std::string messageIdStr; + + AlarmMessageData( + const gpap_message::alarm::AlarmMessage::Level alarmLevel, + const std::string alarmContentStr, + const std::string typeDesignatorStr, + const std::string messageIdStr) + : alarmLevel(alarmLevel), + alarmContentStr(alarmContentStr), + typeDesignatorStr(typeDesignatorStr), + messageIdStr(messageIdStr) {} +}; + +void alarm_test_helper(const AlarmMessageData &alarmMessageData, const std::string &fullMessageString) +{ + using namespace gpap_message; + + const auto gpapMessage = GPAPMessage::deserialize(fullMessageString.c_str(), fullMessageString.length()); + const alarm::AlarmMessage &alarmMessage = gpapMessage.getAlarmMessage(); + + TEST_ASSERT_EQUAL(MessageType::ALARM, gpapMessage.getMessageType()); + TEST_ASSERT_EQUAL(alarmMessageData.alarmLevel, alarmMessage.getAlarmLevel()); + + auto printedData = VectorMockPrint(); + auto alarmContentBytesWritten = alarmMessage.getAlarmContent().printTo(printedData); + + // Have to add an additional 1 since AlarmContent handles printing the null terminator as well + TEST_ASSERT_EQUAL(alarmMessageData.alarmContentStr.size() + 1, alarmContentBytesWritten); + TEST_ASSERT_EQUAL(strcmp(alarmMessageData.alarmContentStr.c_str(), printedData.data()), 0); + printedData.clear(); + + if (alarmMessage.getTypeDesignator().state == alarm::PossibleParameter::State::Some) + { + auto typeDesignatorBytesWritten = alarmMessage.getTypeDesignator().contents.printTo(printedData); + // Have to add an additional 1 since AlarmContent handles printing the null terminator as well + TEST_ASSERT_EQUAL(alarmMessageData.typeDesignatorStr.size() + 1, typeDesignatorBytesWritten); + TEST_ASSERT_EQUAL(strcmp(alarmMessageData.typeDesignatorStr.c_str(), printedData.data()), 0); + printedData.clear(); + } + else + { + TEST_ASSERT_TRUE(alarmMessageData.typeDesignatorStr.empty()); + } + + if (alarmMessage.getMessageId().state == alarm::PossibleParameter::State::Some) + { + auto messageIdBytesWritten = alarmMessage.getMessageId().contents.printTo(printedData); + TEST_ASSERT_EQUAL(alarmMessageData.messageIdStr.size() + 1, messageIdBytesWritten); + TEST_ASSERT_EQUAL(strcmp(alarmMessageData.messageIdStr.c_str(), printedData.data()), 0); + } + else + { + TEST_ASSERT_TRUE(alarmMessageData.messageIdStr.empty()); + } +} + +void test_alarm_message_order_content_designator_id() +{ + static const std::string alarmMessageStr = "a5Test message[444]{a4ab}"; + static const std::string alarmContentStr = "Test message"; + static const std::string typeDesignatorStr = "444"; + static const std::string messageIdStr = "a4ab"; + + const auto alarmData = AlarmMessageData( + gpap_message::alarm::AlarmMessage::Level::Level5, + alarmContentStr, + typeDesignatorStr, + messageIdStr); + + alarm_test_helper(alarmData, alarmMessageStr); +} + +void test_alarm_message_order_designator_content_id() +{ + static const std::string alarmMessageStr = "a2[432]Testing more messages{abcdef}"; + static const std::string alarmContentStr = "Testing more messages"; + static const std::string typeDesignatorStr = "432"; + static const std::string messageIdStr = "abcdef"; + + const auto alarmData = AlarmMessageData( + gpap_message::alarm::AlarmMessage::Level::Level2, + alarmContentStr, + typeDesignatorStr, + messageIdStr); + + alarm_test_helper(alarmData, alarmMessageStr); +} + +void test_alarm_message_only_content() +{ + static const std::string alarmMessageStr = "a0Content only alarm"; + static const std::string alarmContentStr = "Content only alarm"; + static const std::string typeDesignatorStr = ""; + static const std::string messageIdStr = ""; + + const auto alarmData = AlarmMessageData( + gpap_message::alarm::AlarmMessage::Level::Level0, + alarmContentStr, + typeDesignatorStr, + messageIdStr); + + alarm_test_helper(alarmData, alarmMessageStr); +} + +void test_alarm_message_only_id() +{ + static const std::string alarmMessageStr = "a1{98765}"; + static const std::string alarmContentStr = ""; + static const std::string typeDesignatorStr = ""; + static const std::string messageIdStr = "98765"; + + const auto alarmData = AlarmMessageData( + gpap_message::alarm::AlarmMessage::Level::Level1, + alarmContentStr, + typeDesignatorStr, + messageIdStr); + + alarm_test_helper(alarmData, alarmMessageStr); +} + +void test_alarm_message_only_type_designator() +{ + static const std::string alarmMessageStr = "a3[000]"; + static const std::string alarmContentStr = ""; + static const std::string typeDesignatorStr = "000"; + static const std::string messageIdStr = ""; + + const auto alarmData = AlarmMessageData( + gpap_message::alarm::AlarmMessage::Level::Level3, + alarmContentStr, + typeDesignatorStr, + messageIdStr); + + alarm_test_helper(alarmData, alarmMessageStr); +} + +int main(int argc, char **argv) +{ + UNITY_BEGIN(); + + RUN_TEST(test_alarm_message_order_content_designator_id); + RUN_TEST(test_alarm_message_order_designator_content_id); + RUN_TEST(test_alarm_message_only_content); + RUN_TEST(test_alarm_message_only_id); + RUN_TEST(test_alarm_message_only_type_designator); + + UNITY_END(); +}