Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Buffering MQTT messages (issue #14) #30

Merged
merged 15 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 6 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ jobs:
- name: Build ${{ matrix.tests }}
run: pio test -e wokwi --without-testing --without-uploading -f ${{ matrix.tests }}
- name: Run ${{ matrix.tests }} with Wokwi CLI
id: wokwi-ci
uses: wokwi/wokwi-ci-action@v1
with:
token: ${{ secrets.WOKWI_CLI_TOKEN }}
path: / # directory with wokwi.toml, relative to repo's root
timeout: 180000
timeout: 240000
expect_text: 'Tests 0 Failures 0 Ignored'
fail_text: ':FAIL:'
serial_log_file: '${{ matrix.tests }}.log'
Expand All @@ -55,4 +56,7 @@ jobs:
with:
name: ${{ matrix.tests }}
path: ${{ matrix.tests }}.log
retention-days: 15
retention-days: 15
- name: Check if Wokwi CLI failed
if: ${{ steps.wokwi-ci.outcome == 'failure' }}
run: exit 1
1 change: 1 addition & 0 deletions conf/conf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ namespace fabomatic
static constexpr unsigned long SERIAL_SPEED_BDS{115200}; /* Serial speed in bauds */
static constexpr bool FORCE_PORTAL{false}; /* True to force portal startup */
static constexpr bool LOAD_EEPROM_DEFAULTS{false}; /* True to force EEPROM settings to defaults */
static constexpr bool ENABLE_BUFFERING{true}; /* True if important MQTT messages should be saved when network is down */

} // namespace conf::debug

Expand Down
84 changes: 84 additions & 0 deletions include/BufferedMsg.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#ifndef BUFFEREDMSG_HPP
#define BUFFEREDMSG_HPP

#include <optional>
#include <queue>
#include <string>
#include <memory>

#include "ArduinoJson.h"
#include "MachineID.hpp"
#include "MQTTtypes.hpp"

namespace fabomatic
{
struct BufferedMsg
{
std::string mqtt_message;
std::string mqtt_topic;
bool wait_for_answer;
BufferedMsg() = default;
BufferedMsg(const std::string &message, const std::string &topic, bool wait) : mqtt_message(message), mqtt_topic(topic), wait_for_answer{wait} {};
BufferedMsg(const BufferedMsg &source) = default;
BufferedMsg(BufferedMsg &source) = default;
};

class Buffer
{
private:
std::deque<BufferedMsg> msg_queue;
bool has_changed{true};
static constexpr auto MAGIC_NUMBER = 1;

public:
auto push_back(const std::string &message, const std::string &topic, bool wait) -> void;
auto push_front(const std::string &message, const std::string &topic, bool wait) -> void;
auto getMessage() -> const BufferedMsg;
auto count() const -> size_t;
auto toJson(JsonDocument &doc, const std::string &element_name) const -> void;
auto hasChanged() const -> bool { return has_changed; };
auto setChanged(bool new_value) -> void { has_changed = new_value; };

static auto fromJsonElement(const JsonObject &json_obj) -> std::optional<Buffer>;
static constexpr auto MAX_MESSAGES = 40;
};

class BufferedQuery final : public ServerMQTT::Query
{
private:
std::string_view mqtt_value;
std::string_view mqtt_topic;
bool wait_for_answer;

public:
BufferedQuery() = delete;
constexpr BufferedQuery(const std::string_view &value,
const std::string_view &topic,
bool wait) : mqtt_value(value),
mqtt_topic(topic),
wait_for_answer{wait} {};

[[nodiscard]] auto payload() const -> const std::string override
{
return std::string(mqtt_value);
};

[[nodiscard]] auto waitForReply() const -> bool override
{
return wait_for_answer;
};

[[nodiscard]] auto buffered() const -> bool override
{
return false;
};

[[nodiscard]] auto topic() const -> std::string
{
return std::string(mqtt_topic);
};
};

} // namespace fabomatic

#endif // BUFFEREDMSG_HPP
21 changes: 19 additions & 2 deletions include/FabBackend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,21 @@
#include "MQTTtypes.hpp"
#include "SavedConfig.hpp"
#include "conf.hpp"
#include "BufferedMsg.hpp"

namespace fabomatic
{
class FabBackend
{
private:
constexpr static auto MAX_MSG_SIZE = 300;
enum class PublishResult : uint8_t
{
ErrorNotPublished,
PublishedWithoutAnswer,
PublishedWithAnswer
};

MQTTClient client{MAX_MSG_SIZE}; // Default is 128, and can be reached with some messages
JsonDocument doc;

Expand All @@ -43,18 +51,24 @@ namespace fabomatic
bool answer_pending{false};
int16_t channel{-1};

Buffer buffer;

auto messageReceived(String &topic, String &payload) -> void;

[[nodiscard]] auto publish(const ServerMQTT::Query &payload) -> bool;
template <typename QueryT>
[[nodiscard]] auto publish(const QueryT &payload) -> PublishResult;

[[nodiscard]] auto waitForAnswer(std::chrono::milliseconds timeout) -> bool;
[[nodiscard]] auto publishWithReply(const ServerMQTT::Query &payload) -> bool;
[[nodiscard]] auto publishWithReply(const ServerMQTT::Query &payload) -> PublishResult;

template <typename RespT, typename QueryT, typename... QueryArgs>
[[nodiscard]] auto processQuery(QueryArgs &&...) -> std::unique_ptr<RespT>;

template <typename QueryT, typename... QueryArgs>
[[nodiscard]] auto processQuery(QueryArgs &&...args) -> bool;

auto loadBuffer(const Buffer &new_buffer) -> void;

public:
FabBackend() = default;

Expand All @@ -67,6 +81,9 @@ namespace fabomatic
[[nodiscard]] auto alive() -> bool;
[[nodiscard]] auto publish(String topic, String payload, bool waitForAnswer) -> bool;
[[nodiscard]] auto isOnline() const -> bool;
[[nodiscard]] auto hasBufferedMsg() const -> bool;
[[nodiscard]] auto transmitBuffer() -> bool;
[[nodiscard]] auto saveBuffer() -> bool;

auto connect() -> bool;
auto connectWiFi() -> bool;
Expand Down
3 changes: 2 additions & 1 deletion include/LCDWrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ namespace fabomatic
auto backlightOn() const -> void;
auto backlightOff() const -> void;
auto prettyPrint(const DisplayBuffer &buffer, const BoardInfo &bi) const -> void;
[[nodiscard]] auto needsUpdate(const BoardInfo &bi) const -> bool;
auto createChar(uint8_t char_idx, const std::array<uint8_t, HEIGHT_PX> &values) -> void;

[[nodiscard]] auto needsUpdate(const BoardInfo &bi) const -> bool;
};
} // namespace fabomatic

Expand Down
10 changes: 9 additions & 1 deletion include/MQTTtypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace fabomatic::ServerMQTT
public:
virtual auto waitForReply() const -> bool = 0;
virtual auto payload() const -> const std::string = 0;
virtual auto buffered() const -> bool = 0;
virtual ~Query() = default;
};

Expand All @@ -29,6 +30,7 @@ namespace fabomatic::ServerMQTT

[[nodiscard]] auto waitForReply() const -> bool override { return true; };
[[nodiscard]] auto payload() const -> const std::string override;
[[nodiscard]] auto buffered() const -> bool override { return false; };
};

class MachineQuery final : public Query
Expand All @@ -37,6 +39,7 @@ namespace fabomatic::ServerMQTT
constexpr MachineQuery() = default;
[[nodiscard]] auto payload() const -> const std::string override;
[[nodiscard]] auto waitForReply() const -> bool override { return true; };
[[nodiscard]] auto buffered() const -> bool override { return false; };
};

class AliveQuery final : public Query
Expand All @@ -45,6 +48,7 @@ namespace fabomatic::ServerMQTT
constexpr AliveQuery() = default;
[[nodiscard]] auto payload() const -> const std::string override;
[[nodiscard]] auto waitForReply() const -> bool override { return false; };
[[nodiscard]] auto buffered() const -> bool override { return false; };
};

class StartUseQuery final : public Query
Expand All @@ -57,6 +61,7 @@ namespace fabomatic::ServerMQTT

[[nodiscard]] auto payload() const -> const std::string override;
[[nodiscard]] auto waitForReply() const -> bool override { return true; };
[[nodiscard]] auto buffered() const -> bool override { return true; };
};

class StopUseQuery final : public Query
Expand All @@ -74,6 +79,7 @@ namespace fabomatic::ServerMQTT
constexpr StopUseQuery(card::uid_t card_uid, std::chrono::seconds duration) : uid(card_uid), duration_s(duration){};
[[nodiscard]] auto payload() const -> const std::string override;
[[nodiscard]] auto waitForReply() const -> bool override { return true; };
[[nodiscard]] auto buffered() const -> bool override { return true; };
};

class InUseQuery final : public Query
Expand All @@ -91,6 +97,7 @@ namespace fabomatic::ServerMQTT
constexpr InUseQuery(card::uid_t card_uid, std::chrono::seconds duration) : uid(card_uid), duration_s(duration){};
[[nodiscard]] auto payload() const -> const std::string override;
[[nodiscard]] auto waitForReply() const -> bool override { return true; };
[[nodiscard]] auto buffered() const -> bool override { return false; };
};

class RegisterMaintenanceQuery final : public Query
Expand All @@ -103,6 +110,7 @@ namespace fabomatic::ServerMQTT

[[nodiscard]] auto payload() const -> const std::string override;
[[nodiscard]] auto waitForReply() const -> bool override { return true; };
[[nodiscard]] auto buffered() const -> bool override { return true; };
};

class Response
Expand Down Expand Up @@ -147,7 +155,7 @@ namespace fabomatic::ServerMQTT
bool is_valid = false; /* True if the machine has a valid ID */
bool maintenance = true; /* True if the machine needs maintenance */
bool allowed = false; /* True if the machine can be used by anybody */
uint16_t logoff{0}; /* Timeout in minutes */
uint16_t logoff{0}; /* Timeout in minutes */
std::string name{""}; /* Name of the machine from server DB */
uint8_t type{0}; /* Type of the machine */
uint16_t grace{0}; /* Grace period in minutes */
Expand Down
11 changes: 7 additions & 4 deletions include/SavedConfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
#include "MachineConfig.hpp"
#include "conf.hpp"
#include "CachedCards.hpp"
#include "BufferedMsg.hpp"

namespace fabomatic
{
class SavedConfig
{
private:
static constexpr auto JSON_DOC_SIZE = 1024;
static_assert(JSON_DOC_SIZE > (conf::common::STR_MAX_LENGTH * 7 + sizeof(bool) + sizeof(size_t) + sizeof(CachedCards)) * 2, "JSON_DOC_SIZE must be larger than SavedConfig size in JSON");
static constexpr auto JSON_DOC_SIZE = 4096;
static_assert(JSON_DOC_SIZE > (conf::common::STR_MAX_LENGTH * 7 + sizeof(bool) + sizeof(size_t) + sizeof(CachedCards) + (sizeof(Buffer) + 40) * 40), "JSON_DOC_SIZE must be larger than SavedConfig size in JSON");
static std::string json_buffer;
static std::mutex buffer_mutex;

Expand All @@ -33,7 +34,7 @@ namespace fabomatic
[[nodiscard]] static auto fromJsonDocument(const std::string &json_text) -> std::optional<SavedConfig>;

public:
static constexpr auto MAGIC_NUMBER = 0x50; // Increment when changing the struct
static constexpr auto MAGIC_NUMBER = 0x51; // Increment when changing the struct

// Magic number to check if the EEPROM is initialized
mutable uint8_t magic_number{0};
Expand Down Expand Up @@ -69,8 +70,10 @@ namespace fabomatic
/// @brief if true, the FORCE_OPEN_PORTAL flag will be ignored
bool disablePortal{false};

Buffer message_buffer;

/// @brief Allow compiler-time construction
constexpr SavedConfig() = default;
SavedConfig() = default;

/// @brief Saves the configuration to EEPROM
/// @return true if successful
Expand Down
22 changes: 15 additions & 7 deletions include/card.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,25 @@ namespace fabomatic::card
}

/// @brief Returns the ESP32 serial number as a string
[[nodiscard]] inline auto esp_serial() -> const std::string
[[nodiscard]] inline auto esp_serial() -> const std::string_view
{
std::stringstream serial{};
std::array<uint8_t, 8> mac{0};
static std::array<char, 13> result; // +1 for null termination

esp_efuse_mac_get_default(mac.data());
for (const auto val : mac)
if (result.empty()) // Compute only once
{
serial << std::setfill('0') << std::setw(2) << std::hex << +val;
std::stringstream serial{};
std::array<uint8_t, 8> mac{0};

esp_efuse_mac_get_default(mac.data());
for (const auto val : mac)
{
serial << std::setfill('0') << std::setw(2) << std::hex << +val;
}
auto res = serial.str().substr(0U, 6 * 2);
std::copy(res.cbegin(), res.cend(), result.begin());
result[result.size() - 1] = '\0';
}
return serial.str().substr(0U, 6 * 2);
return {result.data()};
}
} // namespace fabomatic::card
#endif // CARD_HPP_
10 changes: 5 additions & 5 deletions include/language/en-US.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace fabomatic::strings::en_US
{
static constexpr auto S_LANG_ID = "en-US";

static constexpr auto S_BUSY = "Busy";
static constexpr auto S_CANCELLED = "* CANCELLED *";
static constexpr auto S_CONFIRMED = "* CONFIRMED *";
Expand All @@ -28,17 +28,17 @@ namespace fabomatic::strings::en_US
static constexpr auto S_WORKING = "Working...";
static constexpr auto S_OFFLINE_MODE = "OFFLINE MODE";

static constexpr auto S_BLOCKED_ADMIN_1 = "Blocked";
static constexpr auto S_BLOCKED_ADMIN_2 = "by admin";
static constexpr auto S_BLOCKED_ADMIN_1 = "Blocked by";
static constexpr auto S_BLOCKED_ADMIN_2 = "admins";

static constexpr auto S_VERIFYING_1 = "Verifying";
static constexpr auto S_VERIFYING_2 = "card...";

static constexpr auto S_BLOCKED_MAINTENANCE_1 = "Blocked for";
static constexpr auto S_BLOCKED_MAINTENANCE_2 = "maintenance";

static constexpr auto S_PROMPT_MAINTENANCE_1 = "Maintenance?";
static constexpr auto S_PROMPT_MAINTENANCE_2 = "Record";
static constexpr auto S_PROMPT_MAINTENANCE_1 = "Maintenance";
static constexpr auto S_PROMPT_MAINTENANCE_2 = "record ?";

static constexpr auto S_MAINTENANCE_REGISTERED_1 = "Maintenance";
static constexpr auto S_MAINTENANCE_REGISTERED_2 = "recorded";
Expand Down
2 changes: 1 addition & 1 deletion src/BoardLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ namespace fabomatic
break;
case Status::MaintenanceNeeded:
lcd.setRow(0, strings::S_BLOCKED_MAINTENANCE_1);
lcd.setRow(1, strings::S_BLOCKED_MAINTENANCE_1);
lcd.setRow(1, strings::S_BLOCKED_MAINTENANCE_2);
break;
case Status::MaintenanceQuery:
lcd.setRow(0, strings::S_PROMPT_MAINTENANCE_1);
Expand Down
Loading