Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5c9341f
(#387): Starting to breakout GPAP protocol functionality
TheBakedPotato Mar 1, 2026
8bec1d5
(#387): Added more constructors/deserialization methods
TheBakedPotato Mar 4, 2026
da65224
(#387): Compiles with creating a new 'default' ProcotolMessage in the…
TheBakedPotato Mar 7, 2026
649a458
(#387): Improved layout of class members and methods
TheBakedPotato Mar 7, 2026
131188b
(#387): Constructors/operators support const types as well now
TheBakedPotato Mar 9, 2026
f11dc66
(#387): Starting to parse the AlarmMessageId
TheBakedPotato Mar 9, 2026
0a45be7
(#387): Parsing Alarm type designator
TheBakedPotato Mar 13, 2026
cb37421
(#387): Cleaning up the constructors
TheBakedPotato Mar 16, 2026
82a4ed5
(#387): Leveraging new 'protocol' namespace/module to deserialize GPA…
TheBakedPotato Mar 20, 2026
5ec0813
(#387): ProtocolMessage members are public to be more conventional ta…
TheBakedPotato Mar 20, 2026
28004ea
(#387): Writing AlarmCommand contents to the serial
TheBakedPotato Mar 22, 2026
df726ae
(#387): Properly deserializing AlarmCommand contents
TheBakedPotato Mar 23, 2026
8b0e47b
(#387): AlarmMessageId now being parsed
TheBakedPotato Mar 27, 2026
e21892b
(#387): The AlarmCommand is now deserialized through its own "Builder"
TheBakedPotato Mar 27, 2026
75555c1
(#387): Adding the other commands and deserializing the AlarmMessage
TheBakedPotato Mar 28, 2026
dbe19f6
(#387): Incorporating the new command types into the rest of the system
TheBakedPotato Mar 28, 2026
e039cfa
(#387): Moving the GPAP protocol stuff into its own library
TheBakedPotato Mar 30, 2026
f01e30f
(#387): Added in the other message types
TheBakedPotato Mar 30, 2026
24be0d0
(#387): Added missing std::move when returning AlarmCommand
TheBakedPotato Mar 31, 2026
b221ce4
(#387): Removed manually added the null terminator to the type design…
TheBakedPotato Mar 31, 2026
a8de85e
(#387): PossibleParameter added as an 'optional' type to alarm messag…
TheBakedPotato Mar 31, 2026
06228f3
(#387): Removing personal test code
TheBakedPotato Mar 31, 2026
01752df
(#387): Better using the PossibleParameter type
TheBakedPotato Apr 2, 2026
352c733
(#387): MessageID and Type Designator can now appear anywhere
TheBakedPotato Apr 2, 2026
bd0c199
(#387): Corrected defintions of the Move Constructors and move assign…
TheBakedPotato Apr 3, 2026
d9163c9
(#387): Fixed logic for deserializing AlarmMessage with the component…
TheBakedPotato Apr 3, 2026
ec4cbd5
(#387): Chore: Added GPL license to top of all files
TheBakedPotato Apr 3, 2026
056c553
(#387): GPAP source now in `src/` directory, adding README
TheBakedPotato Apr 3, 2026
9b21870
(#387): Added content to the README explaining design choices
TheBakedPotato Apr 3, 2026
7f035c5
(#387): Removed dependency on an Arduino specific library
TheBakedPotato Apr 4, 2026
8a7702d
(#387): Converted uses of `size_t` to `std::size_t`
TheBakedPotato Apr 4, 2026
20d075b
(#387): Adding unit tests
TheBakedPotato Apr 4, 2026
bdaf1dc
(#387): Message length is being updated in the builder
TheBakedPotato Apr 6, 2026
b1d3d1f
(#387): Leveraging Printable class again, setup mocking
TheBakedPotato Apr 6, 2026
8ae9540
(#387): Testing has been updated to leverage new mocking types
TheBakedPotato Apr 6, 2026
54d2173
(#387): Completed unit test
TheBakedPotato Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Firmware/GPAD_API/GPAD_API/GPAD_API.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "";
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -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
Expand Down
28 changes: 19 additions & 9 deletions Firmware/GPAD_API/GPAD_API/GPAD_HAL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <SPI.h>
#include "WiFiManagerOTA.h"
#include "GPAD_menu.h"
#include "GPAPMessage.h"

using namespace gpad_hal;

Expand Down Expand Up @@ -426,25 +427,32 @@ void interpretBuffer(char *buf, int rlen, Stream *serialport, PubSubClient *clie
printError(serialport);
return;
}
Command command = static_cast<Command>(buf[0]);
char commandChar = static_cast<char>(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
Expand All @@ -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
Expand Down Expand Up @@ -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"));
Expand Down
9 changes: 0 additions & 9 deletions Firmware/GPAD_API/GPAD_API/GPAD_HAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
16 changes: 16 additions & 0 deletions Firmware/GPAD_API/lib/GPAP/README
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 23 additions & 0 deletions Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
94 changes: 94 additions & 0 deletions Firmware/GPAD_API/lib/GPAP/src/AlarmMessage.h
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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<AlarmMessageId>;
using PossibleTypeDesignator = PossibleParameter<AlarmTypeDesignator>;

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
42 changes: 42 additions & 0 deletions Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "AlarmContent.h"

#include <iterator>

#ifndef PIO_UNIT_TESTING
#include <Print.h>
#else
#include <MockPrint.h>
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;
}
72 changes: 72 additions & 0 deletions Firmware/GPAD_API/lib/GPAP/src/AlarmMessage/AlarmContent.h
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.

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 <algorithm>
#include <array>

#ifndef PIO_UNIT_TESTING
#include <Printable.h>
#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<char, AlarmContent::MAX_LENGTH>;

private:
std::size_t messageLength;
std::array<char, AlarmContent::MAX_LENGTH> 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
Loading