From 05db2b8b33a21ada19f7169e9159c5a11a443d97 Mon Sep 17 00:00:00 2001 From: HentaiHeavenVR Date: Wed, 29 Nov 2023 12:19:32 +0100 Subject: [PATCH] Rework config (#134) --- .../configuration/captive-portal-config.ts | 32 ++- .../serialization/configuration/config.ts | 8 +- .../serialization/configuration/rfconfig.ts | 41 ++- include/config/BackendConfig.h | 19 ++ include/config/CaptivePortalConfig.h | 20 ++ include/{ => config}/Config.h | 37 +-- include/config/ConfigBase.h | 19 ++ include/config/RFConfig.h | 18 ++ include/config/RootConfig.h | 24 ++ include/config/WiFiConfig.h | 23 ++ include/config/WiFiCredentials.h | 25 ++ include/config/internal/utils.h | 27 ++ include/http/HTTPRequestManager.h | 10 +- .../serialization/_fbs/ConfigFile_generated.h | 154 +++++++---- include/util/HexUtils.h | 40 ++- include/util/JsonRoot.h | 99 ------- schemas/ConfigFile.fbs | 4 +- src/CaptivePortal.cpp | 2 +- src/CommandHandler.cpp | 2 +- src/EStopManager.cpp | 2 +- src/GatewayConnectionManager.cpp | 2 +- src/SerialInputHandler.cpp | 59 +--- src/config/BackendConfig.cpp | 62 +++++ src/config/CaptivePortalConfig.cpp | 69 +++++ src/{ => config}/Config.cpp | 259 ++++-------------- src/config/RFConfig.cpp | 67 +++++ src/config/RootConfig.cpp | 92 +++++++ src/config/WiFiConfig.cpp | 117 ++++++++ src/config/WiFiCredentials.cpp | 139 ++++++++++ src/event_handlers/WiFiScan.cpp | 2 +- src/main.cpp | 2 +- src/wifi/WiFiManager.cpp | 2 +- 32 files changed, 1016 insertions(+), 462 deletions(-) create mode 100644 include/config/BackendConfig.h create mode 100644 include/config/CaptivePortalConfig.h rename include/{ => config}/Config.h (75%) create mode 100644 include/config/ConfigBase.h create mode 100644 include/config/RFConfig.h create mode 100644 include/config/RootConfig.h create mode 100644 include/config/WiFiConfig.h create mode 100644 include/config/WiFiCredentials.h create mode 100644 include/config/internal/utils.h delete mode 100644 include/util/JsonRoot.h create mode 100644 src/config/BackendConfig.cpp create mode 100644 src/config/CaptivePortalConfig.cpp rename src/{ => config}/Config.cpp (50%) create mode 100644 src/config/RFConfig.cpp create mode 100644 src/config/RootConfig.cpp create mode 100644 src/config/WiFiConfig.cpp create mode 100644 src/config/WiFiCredentials.cpp diff --git a/WebUI/src/lib/_fbs/open-shock/serialization/configuration/captive-portal-config.ts b/WebUI/src/lib/_fbs/open-shock/serialization/configuration/captive-portal-config.ts index d9134e50..5c8b161c 100644 --- a/WebUI/src/lib/_fbs/open-shock/serialization/configuration/captive-portal-config.ts +++ b/WebUI/src/lib/_fbs/open-shock/serialization/configuration/captive-portal-config.ts @@ -11,18 +11,36 @@ export class CaptivePortalConfig { return this; } +static getRootAsCaptivePortalConfig(bb:flatbuffers.ByteBuffer, obj?:CaptivePortalConfig):CaptivePortalConfig { + return (obj || new CaptivePortalConfig()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsCaptivePortalConfig(bb:flatbuffers.ByteBuffer, obj?:CaptivePortalConfig):CaptivePortalConfig { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new CaptivePortalConfig()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + alwaysEnabled():boolean { - return !!this.bb!.readInt8(this.bb_pos); + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false; } -static sizeOf():number { - return 1; +static startCaptivePortalConfig(builder:flatbuffers.Builder) { + builder.startObject(1); } -static createCaptivePortalConfig(builder:flatbuffers.Builder, always_enabled: boolean):flatbuffers.Offset { - builder.prep(1, 1); - builder.writeInt8(Number(Boolean(always_enabled))); - return builder.offset(); +static addAlwaysEnabled(builder:flatbuffers.Builder, alwaysEnabled:boolean) { + builder.addFieldInt8(0, +alwaysEnabled, +false); } +static endCaptivePortalConfig(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createCaptivePortalConfig(builder:flatbuffers.Builder, alwaysEnabled:boolean):flatbuffers.Offset { + CaptivePortalConfig.startCaptivePortalConfig(builder); + CaptivePortalConfig.addAlwaysEnabled(builder, alwaysEnabled); + return CaptivePortalConfig.endCaptivePortalConfig(builder); +} } diff --git a/WebUI/src/lib/_fbs/open-shock/serialization/configuration/config.ts b/WebUI/src/lib/_fbs/open-shock/serialization/configuration/config.ts index 36d11e38..f8b88f9b 100644 --- a/WebUI/src/lib/_fbs/open-shock/serialization/configuration/config.ts +++ b/WebUI/src/lib/_fbs/open-shock/serialization/configuration/config.ts @@ -28,7 +28,7 @@ static getSizePrefixedRootAsConfig(bb:flatbuffers.ByteBuffer, obj?:Config):Confi rf(obj?:RFConfig):RFConfig|null { const offset = this.bb!.__offset(this.bb_pos, 4); - return offset ? (obj || new RFConfig()).__init(this.bb_pos + offset, this.bb!) : null; + return offset ? (obj || new RFConfig()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; } wifi(obj?:WiFiConfig):WiFiConfig|null { @@ -38,7 +38,7 @@ wifi(obj?:WiFiConfig):WiFiConfig|null { captivePortal(obj?:CaptivePortalConfig):CaptivePortalConfig|null { const offset = this.bb!.__offset(this.bb_pos, 8); - return offset ? (obj || new CaptivePortalConfig()).__init(this.bb_pos + offset, this.bb!) : null; + return offset ? (obj || new CaptivePortalConfig()).__init(this.bb!.__indirect(this.bb_pos + offset), this.bb!) : null; } backend(obj?:BackendConfig):BackendConfig|null { @@ -51,7 +51,7 @@ static startConfig(builder:flatbuffers.Builder) { } static addRf(builder:flatbuffers.Builder, rfOffset:flatbuffers.Offset) { - builder.addFieldStruct(0, rfOffset, 0); + builder.addFieldOffset(0, rfOffset, 0); } static addWifi(builder:flatbuffers.Builder, wifiOffset:flatbuffers.Offset) { @@ -59,7 +59,7 @@ static addWifi(builder:flatbuffers.Builder, wifiOffset:flatbuffers.Offset) { } static addCaptivePortal(builder:flatbuffers.Builder, captivePortalOffset:flatbuffers.Offset) { - builder.addFieldStruct(2, captivePortalOffset, 0); + builder.addFieldOffset(2, captivePortalOffset, 0); } static addBackend(builder:flatbuffers.Builder, backendOffset:flatbuffers.Offset) { diff --git a/WebUI/src/lib/_fbs/open-shock/serialization/configuration/rfconfig.ts b/WebUI/src/lib/_fbs/open-shock/serialization/configuration/rfconfig.ts index c0ff31a2..8c1e725a 100644 --- a/WebUI/src/lib/_fbs/open-shock/serialization/configuration/rfconfig.ts +++ b/WebUI/src/lib/_fbs/open-shock/serialization/configuration/rfconfig.ts @@ -11,23 +11,46 @@ export class RFConfig { return this; } +static getRootAsRFConfig(bb:flatbuffers.ByteBuffer, obj?:RFConfig):RFConfig { + return (obj || new RFConfig()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + +static getSizePrefixedRootAsRFConfig(bb:flatbuffers.ByteBuffer, obj?:RFConfig):RFConfig { + bb.setPosition(bb.position() + flatbuffers.SIZE_PREFIX_LENGTH); + return (obj || new RFConfig()).__init(bb.readInt32(bb.position()) + bb.position(), bb); +} + txPin():number { - return this.bb!.readUint8(this.bb_pos); + const offset = this.bb!.__offset(this.bb_pos, 4); + return offset ? this.bb!.readUint8(this.bb_pos + offset) : 0; } keepaliveEnabled():boolean { - return !!this.bb!.readInt8(this.bb_pos + 1); + const offset = this.bb!.__offset(this.bb_pos, 6); + return offset ? !!this.bb!.readInt8(this.bb_pos + offset) : false; } -static sizeOf():number { - return 2; +static startRFConfig(builder:flatbuffers.Builder) { + builder.startObject(2); } -static createRFConfig(builder:flatbuffers.Builder, tx_pin: number, keepalive_enabled: boolean):flatbuffers.Offset { - builder.prep(1, 2); - builder.writeInt8(Number(Boolean(keepalive_enabled))); - builder.writeInt8(tx_pin); - return builder.offset(); +static addTxPin(builder:flatbuffers.Builder, txPin:number) { + builder.addFieldInt8(0, txPin, 0); } +static addKeepaliveEnabled(builder:flatbuffers.Builder, keepaliveEnabled:boolean) { + builder.addFieldInt8(1, +keepaliveEnabled, +false); +} + +static endRFConfig(builder:flatbuffers.Builder):flatbuffers.Offset { + const offset = builder.endObject(); + return offset; +} + +static createRFConfig(builder:flatbuffers.Builder, txPin:number, keepaliveEnabled:boolean):flatbuffers.Offset { + RFConfig.startRFConfig(builder); + RFConfig.addTxPin(builder, txPin); + RFConfig.addKeepaliveEnabled(builder, keepaliveEnabled); + return RFConfig.endRFConfig(builder); +} } diff --git a/include/config/BackendConfig.h b/include/config/BackendConfig.h new file mode 100644 index 00000000..3c417fb9 --- /dev/null +++ b/include/config/BackendConfig.h @@ -0,0 +1,19 @@ +#pragma once + +#include "config/ConfigBase.h" + +#include + +namespace OpenShock::Config { + struct BackendConfig : public ConfigBase { + std::string authToken; + + void ToDefault() override; + + bool FromFlatbuffers(const Serialization::Configuration::BackendConfig* config) override; + flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const override; + + bool FromJSON(const cJSON* json) override; + cJSON* ToJSON() const override; + }; +} // namespace OpenShock::Config diff --git a/include/config/CaptivePortalConfig.h b/include/config/CaptivePortalConfig.h new file mode 100644 index 00000000..ebcd3f60 --- /dev/null +++ b/include/config/CaptivePortalConfig.h @@ -0,0 +1,20 @@ +#pragma once + +#include "config/ConfigBase.h" + +namespace OpenShock::Config { + struct CaptivePortalConfig : public ConfigBase { + CaptivePortalConfig(); + CaptivePortalConfig(bool alwaysEnabled); + + bool alwaysEnabled; + + void ToDefault() override; + + bool FromFlatbuffers(const Serialization::Configuration::CaptivePortalConfig* config) override; + flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const override; + + bool FromJSON(const cJSON* json) override; + cJSON* ToJSON() const override; + }; +} // namespace OpenShock::Config diff --git a/include/Config.h b/include/config/Config.h similarity index 75% rename from include/Config.h rename to include/config/Config.h index 40349629..d821ee2c 100644 --- a/include/Config.h +++ b/include/config/Config.h @@ -1,37 +1,24 @@ #pragma once -#include "serialization/_fbs/ConfigFile_generated.h" +#include "config/BackendConfig.h" +#include "config/CaptivePortalConfig.h" +#include "config/RFConfig.h" +#include "config/WiFiConfig.h" +#include "config/WiFiCredentials.h" #include #include #include namespace OpenShock::Config { - // This is a copy of the flatbuffers schema defined in schemas/ConfigFile.fbs - struct RFConfig { - std::uint8_t txPin; - bool keepAliveEnabled; - }; - struct WiFiCredentials { - std::uint8_t id; - std::string ssid; - std::uint8_t bssid[6]; - std::string password; - }; - struct WiFiConfig { - std::string apSsid; - std::string hostname; - std::vector credentials; - }; - struct CaptivePortalConfig { - bool alwaysEnabled; - }; - struct BackendConfig { - std::string authToken; - }; - void Init(); + /* Get the config file translated to JSON. */ + std::string GetAsJSON(); + + /* Save the config file from JSON. */ + bool SaveFromJSON(const std::string& json); + /** * @brief Resets the config file to the factory default values. * @@ -68,4 +55,6 @@ namespace OpenShock::Config { const std::string& GetBackendAuthToken(); bool SetBackendAuthToken(const std::string& token); bool ClearBackendAuthToken(); + + bool SaveChanges(); } // namespace OpenShock::Config diff --git a/include/config/ConfigBase.h b/include/config/ConfigBase.h new file mode 100644 index 00000000..7133444a --- /dev/null +++ b/include/config/ConfigBase.h @@ -0,0 +1,19 @@ +#pragma once + +#include "serialization/_fbs/ConfigFile_generated.h" + +#include + +namespace OpenShock::Config { + template + struct ConfigBase { + virtual void ToDefault() = 0; + + virtual bool FromFlatbuffers(const T* config) = 0; + virtual flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const = 0; + + virtual bool FromJSON(const cJSON* json) = 0; + virtual cJSON* ToJSON() const = 0; + }; + +} // namespace OpenShock::Config diff --git a/include/config/RFConfig.h b/include/config/RFConfig.h new file mode 100644 index 00000000..85083078 --- /dev/null +++ b/include/config/RFConfig.h @@ -0,0 +1,18 @@ +#pragma once + +#include "config/ConfigBase.h" + +namespace OpenShock::Config { + struct RFConfig : public ConfigBase { + std::uint8_t txPin; + bool keepAliveEnabled; + + void ToDefault() override; + + bool FromFlatbuffers(const Serialization::Configuration::RFConfig* config) override; + flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const override; + + bool FromJSON(const cJSON* json) override; + cJSON* ToJSON() const override; + }; +} // namespace OpenShock::Config diff --git a/include/config/RootConfig.h b/include/config/RootConfig.h new file mode 100644 index 00000000..68299ed6 --- /dev/null +++ b/include/config/RootConfig.h @@ -0,0 +1,24 @@ +#pragma once + +#include "config/RFConfig.h" +#include "config/WiFiConfig.h" +#include "config/CaptivePortalConfig.h" +#include "config/BackendConfig.h" +#include "config/ConfigBase.h" + +namespace OpenShock::Config { + struct RootConfig : public ConfigBase { + OpenShock::Config::RFConfig rf; + OpenShock::Config::WiFiConfig wifi; + OpenShock::Config::CaptivePortalConfig captivePortal; + OpenShock::Config::BackendConfig backend; + + void ToDefault() override; + + bool FromFlatbuffers(const Serialization::Configuration::Config* config) override; + flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const override; + + bool FromJSON(const cJSON* json) override; + cJSON* ToJSON() const override; + }; +} // namespace OpenShock::Config diff --git a/include/config/WiFiConfig.h b/include/config/WiFiConfig.h new file mode 100644 index 00000000..46f7add7 --- /dev/null +++ b/include/config/WiFiConfig.h @@ -0,0 +1,23 @@ +#pragma once + +#include "config/WiFiCredentials.h" +#include "config/ConfigBase.h" + +#include +#include + +namespace OpenShock::Config { + struct WiFiConfig : public ConfigBase { + std::string accessPointSSID; + std::string hostname; + std::vector credentialsList; + + void ToDefault() override; + + bool FromFlatbuffers(const Serialization::Configuration::WiFiConfig* config) override; + flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const override; + + bool FromJSON(const cJSON* json) override; + cJSON* ToJSON() const override; + }; +} // namespace OpenShock::Config diff --git a/include/config/WiFiCredentials.h b/include/config/WiFiCredentials.h new file mode 100644 index 00000000..f4f150b3 --- /dev/null +++ b/include/config/WiFiCredentials.h @@ -0,0 +1,25 @@ +#pragma once + +#include "config/ConfigBase.h" + +#include + +namespace OpenShock::Config { + struct WiFiCredentials : public ConfigBase { + WiFiCredentials(); + WiFiCredentials(std::uint8_t id, const std::string& ssid, const std::uint8_t (&bssid)[6], const std::string& password); + + std::uint8_t id; + std::string ssid; + std::uint8_t bssid[6]; + std::string password; + + void ToDefault() override; + + bool FromFlatbuffers(const Serialization::Configuration::WiFiCredentials* config) override; + flatbuffers::Offset ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const override; + + bool FromJSON(const cJSON* json) override; + cJSON* ToJSON() const override; + }; +} // namespace OpenShock::Config diff --git a/include/config/internal/utils.h b/include/config/internal/utils.h new file mode 100644 index 00000000..189cd46e --- /dev/null +++ b/include/config/internal/utils.h @@ -0,0 +1,27 @@ +#pragma once + +#include "config/ConfigBase.h" + +#include + +namespace OpenShock::Config::Internal::Utils { + inline void FromFbsStr(std::string& str, const flatbuffers::String* fbsStr, const char* defaultStr) { + if (fbsStr != nullptr) { + str = fbsStr->c_str(); + } else { + str = defaultStr; + } + } + template // T inherits from ConfigBase + void FromFbsVec(std::vector& vec, const flatbuffers::Vector>* fbsVec) { + vec.clear(); + if (fbsVec != nullptr) { + for (auto fbsItem : *fbsVec) { + T item; + if (item.FromFlatbuffers(fbsItem)) { + vec.push_back(std::move(item)); + } + } + } + } +} // namespace OpenShock::Config::Internal::Utils diff --git a/include/http/HTTPRequestManager.h b/include/http/HTTPRequestManager.h index 93751a3b..820efd34 100644 --- a/include/http/HTTPRequestManager.h +++ b/include/http/HTTPRequestManager.h @@ -1,9 +1,9 @@ #pragma once -#include "util/JsonRoot.h" - #include +#include + #include #include #include @@ -37,8 +37,8 @@ namespace OpenShock::HTTP { return {response.result, response.code, {}}; } - OpenShock::JsonRoot json = OpenShock::JsonRoot::Parse(response.data); - if (!json.isValid()) { + cJSON* json = cJSON_ParseWithLength(response.data.c_str(), response.data.length()); + if (json == nullptr) { return {RequestResult::ParseFailed, response.code, {}}; } @@ -47,6 +47,8 @@ namespace OpenShock::HTTP { return {RequestResult::ParseFailed, response.code, {}}; } + cJSON_Delete(json); + return {response.result, response.code, data}; } } // namespace OpenShock::HTTP diff --git a/include/serialization/_fbs/ConfigFile_generated.h b/include/serialization/_fbs/ConfigFile_generated.h index 3f693163..c9281c8e 100644 --- a/include/serialization/_fbs/ConfigFile_generated.h +++ b/include/serialization/_fbs/ConfigFile_generated.h @@ -20,6 +20,7 @@ namespace Configuration { struct BSSID; struct RFConfig; +struct RFConfigBuilder; struct WiFiCredentials; struct WiFiCredentialsBuilder; @@ -28,6 +29,7 @@ struct WiFiConfig; struct WiFiConfigBuilder; struct CaptivePortalConfig; +struct CaptivePortalConfigBuilder; struct BackendConfig; struct BackendConfigBuilder; @@ -60,60 +62,64 @@ struct BSSID::Traits { using type = BSSID; }; -FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(1) RFConfig FLATBUFFERS_FINAL_CLASS { - private: - uint8_t tx_pin_; - uint8_t keepalive_enabled_; - - public: +struct RFConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef RFConfigBuilder Builder; struct Traits; static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { return "OpenShock.Serialization.Configuration.RFConfig"; } - RFConfig() - : tx_pin_(0), - keepalive_enabled_(0) { - } - RFConfig(uint8_t _tx_pin, bool _keepalive_enabled) - : tx_pin_(::flatbuffers::EndianScalar(_tx_pin)), - keepalive_enabled_(::flatbuffers::EndianScalar(static_cast(_keepalive_enabled))) { - } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_TX_PIN = 4, + VT_KEEPALIVE_ENABLED = 6 + }; uint8_t tx_pin() const { - return ::flatbuffers::EndianScalar(tx_pin_); + return GetField(VT_TX_PIN, 0); } bool keepalive_enabled() const { - return ::flatbuffers::EndianScalar(keepalive_enabled_) != 0; + return GetField(VT_KEEPALIVE_ENABLED, 0) != 0; + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_TX_PIN, 1) && + VerifyField(verifier, VT_KEEPALIVE_ENABLED, 1) && + verifier.EndTable(); } }; -FLATBUFFERS_STRUCT_END(RFConfig, 2); - -struct RFConfig::Traits { - using type = RFConfig; -}; - -FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(1) CaptivePortalConfig FLATBUFFERS_FINAL_CLASS { - private: - uint8_t always_enabled_; - public: - struct Traits; - static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { - return "OpenShock.Serialization.Configuration.CaptivePortalConfig"; +struct RFConfigBuilder { + typedef RFConfig Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_tx_pin(uint8_t tx_pin) { + fbb_.AddElement(RFConfig::VT_TX_PIN, tx_pin, 0); } - CaptivePortalConfig() - : always_enabled_(0) { + void add_keepalive_enabled(bool keepalive_enabled) { + fbb_.AddElement(RFConfig::VT_KEEPALIVE_ENABLED, static_cast(keepalive_enabled), 0); } - CaptivePortalConfig(bool _always_enabled) - : always_enabled_(::flatbuffers::EndianScalar(static_cast(_always_enabled))) { + explicit RFConfigBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); } - bool always_enabled() const { - return ::flatbuffers::EndianScalar(always_enabled_) != 0; + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; } }; -FLATBUFFERS_STRUCT_END(CaptivePortalConfig, 1); -struct CaptivePortalConfig::Traits { - using type = CaptivePortalConfig; +inline ::flatbuffers::Offset CreateRFConfig( + ::flatbuffers::FlatBufferBuilder &_fbb, + uint8_t tx_pin = 0, + bool keepalive_enabled = false) { + RFConfigBuilder builder_(_fbb); + builder_.add_keepalive_enabled(keepalive_enabled); + builder_.add_tx_pin(tx_pin); + return builder_.Finish(); +} + +struct RFConfig::Traits { + using type = RFConfig; + static auto constexpr Create = CreateRFConfig; }; struct WiFiCredentials FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { @@ -303,6 +309,56 @@ inline ::flatbuffers::Offset CreateWiFiConfigDirect( credentials__); } +struct CaptivePortalConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { + typedef CaptivePortalConfigBuilder Builder; + struct Traits; + static FLATBUFFERS_CONSTEXPR_CPP11 const char *GetFullyQualifiedName() { + return "OpenShock.Serialization.Configuration.CaptivePortalConfig"; + } + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_ALWAYS_ENABLED = 4 + }; + bool always_enabled() const { + return GetField(VT_ALWAYS_ENABLED, 0) != 0; + } + bool Verify(::flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyField(verifier, VT_ALWAYS_ENABLED, 1) && + verifier.EndTable(); + } +}; + +struct CaptivePortalConfigBuilder { + typedef CaptivePortalConfig Table; + ::flatbuffers::FlatBufferBuilder &fbb_; + ::flatbuffers::uoffset_t start_; + void add_always_enabled(bool always_enabled) { + fbb_.AddElement(CaptivePortalConfig::VT_ALWAYS_ENABLED, static_cast(always_enabled), 0); + } + explicit CaptivePortalConfigBuilder(::flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + ::flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = ::flatbuffers::Offset(end); + return o; + } +}; + +inline ::flatbuffers::Offset CreateCaptivePortalConfig( + ::flatbuffers::FlatBufferBuilder &_fbb, + bool always_enabled = false) { + CaptivePortalConfigBuilder builder_(_fbb); + builder_.add_always_enabled(always_enabled); + return builder_.Finish(); +} + +struct CaptivePortalConfig::Traits { + using type = CaptivePortalConfig; + static auto constexpr Create = CreateCaptivePortalConfig; +}; + struct BackendConfig FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { typedef BackendConfigBuilder Builder; struct Traits; @@ -390,23 +446,25 @@ struct Config FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { VT_BACKEND = 10 }; const OpenShock::Serialization::Configuration::RFConfig *rf() const { - return GetStruct(VT_RF); + return GetPointer(VT_RF); } const OpenShock::Serialization::Configuration::WiFiConfig *wifi() const { return GetPointer(VT_WIFI); } const OpenShock::Serialization::Configuration::CaptivePortalConfig *captive_portal() const { - return GetStruct(VT_CAPTIVE_PORTAL); + return GetPointer(VT_CAPTIVE_PORTAL); } const OpenShock::Serialization::Configuration::BackendConfig *backend() const { return GetPointer(VT_BACKEND); } bool Verify(::flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && - VerifyField(verifier, VT_RF, 1) && + VerifyOffset(verifier, VT_RF) && + verifier.VerifyTable(rf()) && VerifyOffset(verifier, VT_WIFI) && verifier.VerifyTable(wifi()) && - VerifyField(verifier, VT_CAPTIVE_PORTAL, 1) && + VerifyOffset(verifier, VT_CAPTIVE_PORTAL) && + verifier.VerifyTable(captive_portal()) && VerifyOffset(verifier, VT_BACKEND) && verifier.VerifyTable(backend()) && verifier.EndTable(); @@ -417,14 +475,14 @@ struct ConfigBuilder { typedef Config Table; ::flatbuffers::FlatBufferBuilder &fbb_; ::flatbuffers::uoffset_t start_; - void add_rf(const OpenShock::Serialization::Configuration::RFConfig *rf) { - fbb_.AddStruct(Config::VT_RF, rf); + void add_rf(::flatbuffers::Offset rf) { + fbb_.AddOffset(Config::VT_RF, rf); } void add_wifi(::flatbuffers::Offset wifi) { fbb_.AddOffset(Config::VT_WIFI, wifi); } - void add_captive_portal(const OpenShock::Serialization::Configuration::CaptivePortalConfig *captive_portal) { - fbb_.AddStruct(Config::VT_CAPTIVE_PORTAL, captive_portal); + void add_captive_portal(::flatbuffers::Offset captive_portal) { + fbb_.AddOffset(Config::VT_CAPTIVE_PORTAL, captive_portal); } void add_backend(::flatbuffers::Offset backend) { fbb_.AddOffset(Config::VT_BACKEND, backend); @@ -442,9 +500,9 @@ struct ConfigBuilder { inline ::flatbuffers::Offset CreateConfig( ::flatbuffers::FlatBufferBuilder &_fbb, - const OpenShock::Serialization::Configuration::RFConfig *rf = nullptr, + ::flatbuffers::Offset rf = 0, ::flatbuffers::Offset wifi = 0, - const OpenShock::Serialization::Configuration::CaptivePortalConfig *captive_portal = nullptr, + ::flatbuffers::Offset captive_portal = 0, ::flatbuffers::Offset backend = 0) { ConfigBuilder builder_(_fbb); builder_.add_backend(backend); diff --git a/include/util/HexUtils.h b/include/util/HexUtils.h index 8760dedf..ef4d7d0e 100644 --- a/include/util/HexUtils.h +++ b/include/util/HexUtils.h @@ -4,6 +4,7 @@ #include #include +#include namespace OpenShock::HexUtils { /// @brief Converts a single byte to a hex pair, and writes it to the output buffer. @@ -98,19 +99,34 @@ namespace OpenShock::HexUtils { return true; } - /// @brief Converts a hex string to a byte array. - /// @param data The hex string to convert. - /// @param output The output buffer to write to. - /// @return Whether the conversion was successful. - /// @remark To use this you must specify the size of the array in the template parameter. - template - constexpr bool TryParseHexMac(nonstd::span data, nonstd::span output) noexcept { - static_assert((N + 1) % 3 == 0, "Invalid MAC-Style hex string length."); - for (std::size_t i = 0; i < output.size(); ++i) { - if (!TryParseHexPair(data[i * 3], data[i * 3 + 1], output[i])) { - return false; + constexpr std::size_t TryParseHexMac(const char* str, std::size_t strLen, std::uint8_t* out, std::size_t outLen) noexcept { + std::size_t parsedLength = (strLen + 1) / 3; + + if ((parsedLength * 3) - 1 != strLen) { + return 0; // Invalid MAC-Style hex string length. + } + + if (parsedLength > outLen) { + return 0; // Output buffer is too small. + } + + for (std::size_t i = 0; i < parsedLength - 1; ++i) { + if (!TryParseHexPair(str[i * 3], str[i * 3 + 1], out[i])) { + return 0; // Invalid hex pair. + } + if (str[i * 3 + 2] != ':') { + return 0; // Invalid separator. } } - return true; + + if (!TryParseHexPair(str[(parsedLength - 1) * 3], str[(parsedLength - 1) * 3 + 1], out[parsedLength - 1])) { + return 0; // Invalid hex pair. + } + + return parsedLength; + } + + inline std::size_t TryParseHexMac(const char* str, std::uint8_t* out, std::size_t outLen) noexcept { + return TryParseHexMac(str, strlen(str), out, outLen); } } // namespace OpenShock::HexUtils diff --git a/include/util/JsonRoot.h b/include/util/JsonRoot.h deleted file mode 100644 index 9e98bb37..00000000 --- a/include/util/JsonRoot.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -namespace OpenShock { - class JsonRoot { - JsonRoot(cJSON* root) : _root(root) { } - public: - static JsonRoot Parse(const char* data) { - return JsonRoot(cJSON_Parse(data)); - } - static JsonRoot Parse(const char* data, std::size_t length) { - return JsonRoot(cJSON_ParseWithLength(data, length)); - } - static JsonRoot Parse(const std::string& data) { - return JsonRoot(cJSON_Parse(data.c_str())); - } - static JsonRoot Parse(const String& data) { - return JsonRoot(cJSON_Parse(data.c_str())); - } - static JsonRoot CreateNull() { - return JsonRoot(cJSON_CreateNull()); - } - static JsonRoot CreateObject() { - return JsonRoot(cJSON_CreateObject()); - } - static JsonRoot CreateArray() { - return JsonRoot(cJSON_CreateArray()); - } - - JsonRoot() : _root(nullptr) { } - JsonRoot(JsonRoot&& other) : _root(other._root) { - other._root = nullptr; - } - JsonRoot(const JsonRoot& other) : _root(nullptr) { - if (other._root != nullptr) { - _root = cJSON_Duplicate(other._root, true); - } - } - ~JsonRoot() { - if (_root != nullptr) { - cJSON_Delete(_root); - } - } - - bool isValid() const { - return _root != nullptr; - } - bool isObject() const { - return isValid() && cJSON_IsObject(_root); - } - bool isArray() const { - return isValid() && cJSON_IsArray(_root); - } - - const char* GetErrorMessage() const { - const char* error = cJSON_GetErrorPtr(); - if (error == nullptr) { - return "Unknown error"; - } - - return error; - } - - JsonRoot& operator=(JsonRoot&& other) { - if (this != &other) { - if (_root) { - cJSON_Delete(_root); - } - _root = other._root; - other._root = nullptr; - } - return *this; - } - JsonRoot& operator=(const JsonRoot& other) { - if (this != &other) { - if (_root) { - cJSON_Delete(_root); - } - _root = cJSON_Duplicate(other._root, true); - } - return *this; - } - - cJSON* operator->() const { - return _root; - } - operator cJSON*() const { - return _root; - } - private: - cJSON* _root; - }; -} diff --git a/schemas/ConfigFile.fbs b/schemas/ConfigFile.fbs index c13159c1..d17967e5 100644 --- a/schemas/ConfigFile.fbs +++ b/schemas/ConfigFile.fbs @@ -4,7 +4,7 @@ struct BSSID { array:[ubyte:6]; } -struct RFConfig { +table RFConfig { tx_pin:uint8; keepalive_enabled:bool; } @@ -22,7 +22,7 @@ table WiFiConfig { credentials:[WiFiCredentials]; } -struct CaptivePortalConfig { +table CaptivePortalConfig { always_enabled:bool; } diff --git a/src/CaptivePortal.cpp b/src/CaptivePortal.cpp index 54331f9d..ca5605a9 100644 --- a/src/CaptivePortal.cpp +++ b/src/CaptivePortal.cpp @@ -2,7 +2,7 @@ #include "CaptivePortalInstance.h" #include "CommandHandler.h" -#include "Config.h" +#include "config/Config.h" #include "GatewayConnectionManager.h" #include "Logging.h" diff --git a/src/CommandHandler.cpp b/src/CommandHandler.cpp index 1c0b4cf2..59509a22 100644 --- a/src/CommandHandler.cpp +++ b/src/CommandHandler.cpp @@ -1,7 +1,7 @@ #include "CommandHandler.h" #include "Board.h" -#include "Config.h" +#include "config/Config.h" #include "Constants.h" #include "Logging.h" #include "radio/RFTransmitter.h" diff --git a/src/EStopManager.cpp b/src/EStopManager.cpp index 6edb8331..43ea87c1 100644 --- a/src/EStopManager.cpp +++ b/src/EStopManager.cpp @@ -1,7 +1,7 @@ #include "EStopManager.h" #include "CommandHandler.h" -#include "Config.h" +#include "config/Config.h" #include "Logging.h" #include "Time.h" #include "VisualStateManager.h" diff --git a/src/GatewayConnectionManager.cpp b/src/GatewayConnectionManager.cpp index ff104850..752e3139 100644 --- a/src/GatewayConnectionManager.cpp +++ b/src/GatewayConnectionManager.cpp @@ -2,7 +2,7 @@ #include "VisualStateManager.h" -#include "Config.h" +#include "config/Config.h" #include "GatewayClient.h" #include "http/JsonAPI.h" #include "Logging.h" diff --git a/src/SerialInputHandler.cpp b/src/SerialInputHandler.cpp index 1bd3ef8a..8dbffc7d 100644 --- a/src/SerialInputHandler.cpp +++ b/src/SerialInputHandler.cpp @@ -1,9 +1,8 @@ #include "SerialInputHandler.h" #include "CommandHandler.h" -#include "Config.h" +#include "config/Config.h" #include "Logging.h" -#include "util/JsonRoot.h" #include "wifi/WiFiManager.h" #include @@ -220,24 +219,17 @@ void _handleAuthtokenCommand(char* arg, std::size_t argLength) { void _handleNetworksCommand(char* arg, std::size_t argLength) { cJSON* network = nullptr; - OpenShock::JsonRoot root; + cJSON* root; if (arg == nullptr || argLength <= 0) { - root = OpenShock::JsonRoot::CreateArray(); - if (!root.isValid()) { + root = cJSON_CreateArray(); + if (root == nullptr) { SERPR_ERROR("Failed to create JSON array"); return; } for (auto& creds : Config::GetWiFiCredentials()) { - network = cJSON_CreateObject(); - if (network == nullptr) { - SERPR_ERROR("Failed to create JSON object"); - return; - } - - cJSON_AddStringToObject(network, "ssid", creds.ssid.c_str()); - cJSON_AddStringToObject(network, "password", creds.password.c_str()); + network = creds.ToJSON(); cJSON_AddItemToArray(root, network); } @@ -254,12 +246,13 @@ void _handleNetworksCommand(char* arg, std::size_t argLength) { return; } - root = OpenShock::JsonRoot::Parse(arg, argLength); - if (!root.isValid()) { - SERPR_ERROR("Failed to parse JSON: %s", root.GetErrorMessage()); + root = cJSON_ParseWithLength(arg, argLength); + if (root == nullptr) { + SERPR_ERROR("Failed to parse JSON: %s", cJSON_GetErrorPtr()); return; } - if (!root.isArray()) { + + if (!cJSON_IsArray(root)) { SERPR_ERROR("Invalid argument (not an array)"); return; } @@ -267,38 +260,14 @@ void _handleNetworksCommand(char* arg, std::size_t argLength) { std::uint8_t id = 1; std::vector creds; cJSON_ArrayForEach(network, root) { - if (!cJSON_IsObject(network)) { - SERPR_ERROR("Invalid argument (array entry is not an object)"); - return; - } - - const cJSON* ssid = cJSON_GetObjectItemCaseSensitive(network, "ssid"); - const cJSON* password = cJSON_GetObjectItemCaseSensitive(network, "password"); + Config::WiFiCredentials cred; - if (!cJSON_IsString(ssid) || !cJSON_IsString(password)) { - SERPR_ERROR("Invalid argument (ssid or password is not a string)"); - return; - } - - const char* ssidStr = ssid->valuestring; - const char* passwordStr = password->valuestring; - - if (ssidStr == nullptr || passwordStr == nullptr) { - SERPR_ERROR("Invalid argument (ssid or password is null)"); - return; - } - if (ssidStr[0] == '\0' || passwordStr[0] == '\0') { - SERPR_ERROR("Invalid argument (ssid or password is empty)"); + if (!cred.FromJSON(network)) { + SERPR_ERROR("Failed to parse network"); return; } - Config::WiFiCredentials cred { - .id = id++, - .ssid = ssidStr, - .bssid = {0, 0, 0, 0, 0, 0}, - .password = passwordStr, - }; - ESP_LOGI(TAG, "Adding network to config %s", ssidStr); + ESP_LOGI(TAG, "Adding network to config %s", cred.ssid.c_str()); creds.push_back(std::move(cred)); } diff --git a/src/config/BackendConfig.cpp b/src/config/BackendConfig.cpp new file mode 100644 index 00000000..5867c007 --- /dev/null +++ b/src/config/BackendConfig.cpp @@ -0,0 +1,62 @@ +#include "config/BackendConfig.h" + +#include "config/internal/utils.h" +#include "Logging.h" + +const char* const TAG = "Config::BackendConfig"; + +using namespace OpenShock::Config; + +void BackendConfig::ToDefault() { + authToken = ""; +} + +bool BackendConfig::FromFlatbuffers(const Serialization::Configuration::BackendConfig* config) { + if (config == nullptr) { + ESP_LOGE(TAG, "config is null"); + return false; + } + + Internal::Utils::FromFbsStr(authToken, config->auth_token(), ""); + + return true; +} + +flatbuffers::Offset BackendConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const { + return Serialization::Configuration::CreateBackendConfig(builder, builder.CreateString(authToken)); +} + +bool BackendConfig::FromJSON(const cJSON* json) { + if (json == nullptr) { + ESP_LOGE(TAG, "json is null"); + return false; + } + + if (!cJSON_IsObject(json)) { + ESP_LOGE(TAG, "json is not an object"); + return false; + } + + const cJSON* authTokenJson = cJSON_GetObjectItemCaseSensitive(json, "authToken"); + if (authTokenJson == nullptr) { + ESP_LOGE(TAG, "authToken is null"); + return false; + } + + if (!cJSON_IsString(authTokenJson)) { + ESP_LOGE(TAG, "authToken is not a string"); + return false; + } + + authToken = authTokenJson->valuestring; + + return true; +} + +cJSON* BackendConfig::ToJSON() const { + cJSON* root = cJSON_CreateObject(); + + cJSON_AddStringToObject(root, "authToken", authToken.c_str()); + + return root; +} diff --git a/src/config/CaptivePortalConfig.cpp b/src/config/CaptivePortalConfig.cpp new file mode 100644 index 00000000..db3f5746 --- /dev/null +++ b/src/config/CaptivePortalConfig.cpp @@ -0,0 +1,69 @@ +#include "config/CaptivePortalConfig.h" + +#include "Logging.h" + +const char* const TAG = "Config::CaptivePortalConfig"; + +using namespace OpenShock::Config; + +CaptivePortalConfig::CaptivePortalConfig() { + ToDefault(); +} + +CaptivePortalConfig::CaptivePortalConfig(bool alwaysEnabled) { + this->alwaysEnabled = alwaysEnabled; +} + +void CaptivePortalConfig::ToDefault() { + alwaysEnabled = false; +} + +bool CaptivePortalConfig::FromFlatbuffers(const Serialization::Configuration::CaptivePortalConfig* config) { + if (config == nullptr) { + ESP_LOGE(TAG, "config is null"); + return false; + } + + alwaysEnabled = config->always_enabled(); + + return true; +} + +flatbuffers::Offset CaptivePortalConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const { + return Serialization::Configuration::CreateCaptivePortalConfig(builder, alwaysEnabled); +} + +bool CaptivePortalConfig::FromJSON(const cJSON* json) { + if (json == nullptr) { + ESP_LOGE(TAG, "json is null"); + return false; + } + + if (!cJSON_IsObject(json)) { + ESP_LOGE(TAG, "json is not an object"); + return false; + } + + const cJSON* alwaysEnabledJson = cJSON_GetObjectItemCaseSensitive(json, "alwaysEnabled"); + if (alwaysEnabledJson == nullptr) { + ESP_LOGE(TAG, "alwaysEnabled is null"); + return false; + } + + if (!cJSON_IsBool(alwaysEnabledJson)) { + ESP_LOGE(TAG, "alwaysEnabled is not a bool"); + return false; + } + + alwaysEnabled = cJSON_IsTrue(alwaysEnabledJson); + + return true; +} + +cJSON* CaptivePortalConfig::ToJSON() const { + cJSON* root = cJSON_CreateObject(); + + cJSON_AddBoolToObject(root, "alwaysEnabled", alwaysEnabled); + + return root; +} diff --git a/src/Config.cpp b/src/config/Config.cpp similarity index 50% rename from src/Config.cpp rename to src/config/Config.cpp index 58f3703b..e4abcc2c 100644 --- a/src/Config.cpp +++ b/src/config/Config.cpp @@ -1,151 +1,19 @@ -#include "Config.h" +#include "config/Config.h" +#include "config/RootConfig.h" #include "Constants.h" #include "Logging.h" #include "util/HexUtils.h" #include +#include + const char* const TAG = "Config"; using namespace OpenShock; -struct MainConfig { - Config::RFConfig rf; - Config::WiFiConfig wifi; - Config::CaptivePortalConfig captivePortal; - Config::BackendConfig backend; -}; - -static MainConfig _mainConfig; - -bool ReadFbsConfig(const Serialization::Configuration::RFConfig* fbsConfig) { - if (fbsConfig == nullptr) { - ESP_LOGE(TAG, "Config::RF is null"); - return false; - } - - _mainConfig.rf = { - .txPin = fbsConfig->tx_pin(), - .keepAliveEnabled = fbsConfig->keepalive_enabled(), - }; - - return true; -} -bool ReadFbsConfig(const Serialization::Configuration::WiFiConfig* cfg) { - if (cfg == nullptr) { - ESP_LOGE(TAG, "Config::WiFi is null"); - return false; - } - - auto fbsApSsid = cfg->ap_ssid(); - auto fbsHostname = cfg->hostname(); - auto fbsCredsVec = cfg->credentials(); - - if (fbsApSsid == nullptr || fbsHostname == nullptr || fbsCredsVec == nullptr) { - ESP_LOGE(TAG, "Config::WiFi::apSsid, Config::WiFi::hostname or Config::WiFi::credentials is null"); - return false; - } - - std::vector credentials; - credentials.reserve(fbsCredsVec->size()); - - for (auto it = fbsCredsVec->begin(); it != fbsCredsVec->end(); ++it) { - auto fbsCreds = *it; - - if (fbsCreds == nullptr) { - ESP_LOGE(TAG, "Config::WiFi::credentials contains null entry"); - continue; - } - - auto id = fbsCreds->id(); - if (id == 0 || id > 32) { - ESP_LOGE(TAG, "Config::WiFi::credentials entry %u has invalid ID (must be 1-32)", id); - continue; - } - - auto ssid = fbsCreds->ssid(); - if (ssid == nullptr) { - ESP_LOGE(TAG, "Config::WiFi::credentials entry has null SSID"); - continue; - } - - auto bssid = fbsCreds->bssid(); - if (bssid == nullptr) { - ESP_LOGE(TAG, "Config::WiFi::credentials entry has null BSSID"); - continue; - } - - auto password = fbsCreds->password(); - if (password == nullptr) { - ESP_LOGE(TAG, "Config::WiFi::credentials entry has null password"); - continue; - } - - auto bssidArray = bssid->array(); - if (bssidArray == nullptr) { - ESP_LOGE(TAG, "Config::WiFi::credentials entry has null BSSID array"); - continue; - } - - Config::WiFiCredentials creds { - .id = id, - .ssid = ssid->str(), - .password = password->str(), - }; - - std::memcpy(creds.bssid, bssidArray->data(), 6); - - credentials.push_back(std::move(creds)); - } - - _mainConfig.wifi = { - .apSsid = cfg->ap_ssid()->str(), - .hostname = cfg->hostname()->str(), - .credentials = std::move(credentials), - }; - - return true; -} -bool ReadFbsConfig(const Serialization::Configuration::CaptivePortalConfig* cfg) { - if (cfg == nullptr) { - ESP_LOGE(TAG, "Config::CaptivePortal is null"); - return false; - } - - _mainConfig.captivePortal = { - .alwaysEnabled = cfg->always_enabled(), - }; - - return true; -} -bool ReadFbsConfig(const Serialization::Configuration::BackendConfig* cfg) { - if (cfg == nullptr) { - ESP_LOGE(TAG, "Config::Backend is null"); - return false; - } - - auto authToken = cfg->auth_token(); - - if (authToken == nullptr) { - ESP_LOGE(TAG, "Config::Backend::authToken is null"); - return false; - } - - _mainConfig.backend = { - .authToken = authToken->str(), - }; - - return true; -} -bool ReadFbsConfig(const Serialization::Configuration::Config* cfg) { - if (cfg == nullptr) { - ESP_LOGE(TAG, "Config is null"); - return false; - } - - return ReadFbsConfig(cfg->rf()) && ReadFbsConfig(cfg->wifi()) && ReadFbsConfig(cfg->captive_portal()) && ReadFbsConfig(cfg->backend()); -} +Config::RootConfig _mainConfig; bool _tryLoadConfig() { File file = LittleFS.open("/config", "rb"); @@ -186,7 +54,7 @@ bool _tryLoadConfig() { } // Read config - if (!ReadFbsConfig(fbsConfig)) { + if (!_mainConfig.FromFlatbuffers(fbsConfig)) { ESP_LOGE(TAG, "Failed to read config file"); return false; } @@ -208,24 +76,7 @@ bool _trySaveConfig() { // Serialize flatbuffers::FlatBufferBuilder builder(1024); - auto rfConfig = Serialization::Configuration::RFConfig(_rf.txPin, _rf.keepAliveEnabled); - - std::vector> wifiCredentials; - for (const auto& cred : _wifi.credentials) { - auto bssid = Serialization::Configuration::BSSID(cred.bssid); - - wifiCredentials.push_back(Serialization::Configuration::CreateWiFiCredentials(builder, cred.id, builder.CreateString(cred.ssid), &bssid, builder.CreateString(cred.password))); - } - - auto wifiConfig = Serialization::Configuration::CreateWiFiConfig(builder, builder.CreateString(_wifi.apSsid), builder.CreateString(_wifi.hostname), builder.CreateVector(wifiCredentials)); - - auto backendConfig = Serialization::Configuration::CreateBackendConfig(builder, builder.CreateString(""), builder.CreateString(_backend.authToken)); - - auto captivePortalConfig = Serialization::Configuration::CaptivePortalConfig(_captivePortal.alwaysEnabled); - - auto fbsConfig = Serialization::Configuration::CreateConfig(builder, &rfConfig, wifiConfig, &captivePortalConfig, backendConfig); - - builder.Finish(fbsConfig); + builder.Finish(_mainConfig.ToFlatbuffers(builder)); // Write file if (file.write(builder.GetBufferPointer(), builder.GetSize()) != builder.GetSize()) { @@ -245,33 +96,45 @@ void Config::Init() { ESP_LOGW(TAG, "Failed to load config, writing default config"); - _mainConfig = { - .rf = { -#ifdef OPENSHOCK_TX_PIN - .txPin = OPENSHOCK_TX_PIN, -#else - .txPin = Constants::GPIO_INVALID, -#endif - .keepAliveEnabled = true, - }, - .wifi = { - .apSsid = "", - .hostname = "", - .credentials = {}, - }, - .captivePortal = { - .alwaysEnabled = false, - }, - .backend = { - .authToken = "", - }, - }; + _mainConfig.ToDefault(); if (!_trySaveConfig()) { ESP_PANIC(TAG, "Failed to save default config. Recommend formatting microcontroller and re-flashing firmware"); } } +std::string Config::GetAsJSON() { + cJSON* root = _mainConfig.ToJSON(); + + char* json = cJSON_PrintUnformatted(root); + + std::string result(json); + + free(json); + + cJSON_Delete(root); + + return result; +} +bool Config::SaveFromJSON(const std::string& json) { + cJSON* root = cJSON_Parse(json.c_str()); + if (root == nullptr) { + ESP_LOGE(TAG, "Failed to parse JSON: %s", cJSON_GetErrorPtr()); + return false; + } + + bool result = _mainConfig.FromJSON(root); + + cJSON_Delete(root); + + if (!result) { + ESP_LOGE(TAG, "Failed to read JSON"); + return false; + } + + return _trySaveConfig(); +} + void Config::FactoryReset() { if (!LittleFS.remove("/config") && LittleFS.exists("/config")) { ESP_PANIC(TAG, "Failed to remove existing config file for factory reset. Reccomend formatting microcontroller and re-flashing firmware"); @@ -287,7 +150,7 @@ const Config::WiFiConfig& Config::GetWiFiConfig() { } const std::vector& Config::GetWiFiCredentials() { - return _mainConfig.wifi.credentials; + return _mainConfig.wifi.credentialsList; } const Config::CaptivePortalConfig& Config::GetCaptivePortalConfig() { @@ -298,17 +161,17 @@ const Config::BackendConfig& Config::GetBackendConfig() { return _mainConfig.backend; } -bool Config::SetRFConfig(const RFConfig& config) { +bool Config::SetRFConfig(const Config::RFConfig& config) { _mainConfig.rf = config; return _trySaveConfig(); } -bool Config::SetWiFiConfig(const WiFiConfig& config) { +bool Config::SetWiFiConfig(const Config::WiFiConfig& config) { _mainConfig.wifi = config; return _trySaveConfig(); } -bool Config::SetWiFiCredentials(const std::vector& credentials) { +bool Config::SetWiFiCredentials(const std::vector& credentials) { for (auto& cred : credentials) { if (cred.id == 0 || cred.id > 32) { ESP_LOGE(TAG, "Cannot set WiFi credentials: credential ID %u is invalid (must be 1-32)", cred.id); @@ -316,16 +179,16 @@ bool Config::SetWiFiCredentials(const std::vector& credentials) } } - _mainConfig.wifi.credentials = credentials; + _mainConfig.wifi.credentialsList = credentials; return _trySaveConfig(); } -bool Config::SetCaptivePortalConfig(const CaptivePortalConfig& config) { +bool Config::SetCaptivePortalConfig(const Config::CaptivePortalConfig& config) { _mainConfig.captivePortal = config; return _trySaveConfig(); } -bool Config::SetBackendConfig(const BackendConfig& config) { +bool Config::SetBackendConfig(const Config::BackendConfig& config) { _mainConfig.backend = config; return _trySaveConfig(); } @@ -345,7 +208,7 @@ std::uint8_t Config::AddWiFiCredentials(const std::string& ssid, const std::uint // Bitmask representing available credential IDs (0-31) std::uint32_t bits = 0; - for (auto& creds : _mainConfig.wifi.credentials) { + for (auto& creds : _mainConfig.wifi.credentialsList) { if (creds.ssid == ssid) { creds.password = password; @@ -369,15 +232,9 @@ std::uint8_t Config::AddWiFiCredentials(const std::string& ssid, const std::uint return 0; } - WiFiCredentials creds { - .id = id, - .ssid = ssid, - .password = password, - }; - - memcpy(creds.bssid, bssid, 6); + WiFiCredentials creds(id, ssid, bssid, password); - _mainConfig.wifi.credentials.push_back(creds); + _mainConfig.wifi.credentialsList.push_back(creds); } _trySaveConfig(); @@ -386,7 +243,7 @@ std::uint8_t Config::AddWiFiCredentials(const std::string& ssid, const std::uint } bool Config::TryGetWiFiCredentialsByID(std::uint8_t id, Config::WiFiCredentials& credentials) { - for (auto& creds : _mainConfig.wifi.credentials) { + for (auto& creds : _mainConfig.wifi.credentialsList) { if (creds.id == id) { credentials = creds; return true; @@ -397,7 +254,7 @@ bool Config::TryGetWiFiCredentialsByID(std::uint8_t id, Config::WiFiCredentials& } bool Config::TryGetWiFiCredentialsBySSID(const char* ssid, Config::WiFiCredentials& credentials) { - for (auto& creds : _mainConfig.wifi.credentials) { + for (auto& creds : _mainConfig.wifi.credentialsList) { if (creds.ssid == ssid) { credentials = creds; return true; @@ -408,7 +265,7 @@ bool Config::TryGetWiFiCredentialsBySSID(const char* ssid, Config::WiFiCredentia } bool Config::TryGetWiFiCredentialsByBSSID(const std::uint8_t (&bssid)[6], Config::WiFiCredentials& credentials) { - for (auto& creds : _mainConfig.wifi.credentials) { + for (auto& creds : _mainConfig.wifi.credentialsList) { if (memcmp(creds.bssid, bssid, 6) == 0) { credentials = creds; return true; @@ -419,7 +276,7 @@ bool Config::TryGetWiFiCredentialsByBSSID(const std::uint8_t (&bssid)[6], Config } std::uint8_t Config::GetWiFiCredentialsIDbySSID(const char* ssid) { - for (auto& creds : _mainConfig.wifi.credentials) { + for (auto& creds : _mainConfig.wifi.credentialsList) { if (creds.ssid == ssid) { return creds.id; } @@ -429,7 +286,7 @@ std::uint8_t Config::GetWiFiCredentialsIDbySSID(const char* ssid) { } std::uint8_t Config::GetWiFiCredentialsIDbyBSSID(const std::uint8_t (&bssid)[6]) { - for (auto& creds : _mainConfig.wifi.credentials) { + for (auto& creds : _mainConfig.wifi.credentialsList) { if (memcmp(creds.bssid, bssid, 6) == 0) { return creds.id; } @@ -448,9 +305,9 @@ std::uint8_t Config::GetWiFiCredentialsIDbyBSSIDorSSID(const std::uint8_t (&bssi } bool Config::RemoveWiFiCredentials(std::uint8_t id) { - for (auto it = _mainConfig.wifi.credentials.begin(); it != _mainConfig.wifi.credentials.end(); ++it) { + for (auto it = _mainConfig.wifi.credentialsList.begin(); it != _mainConfig.wifi.credentialsList.end(); ++it) { if (it->id == id) { - _mainConfig.wifi.credentials.erase(it); + _mainConfig.wifi.credentialsList.erase(it); _trySaveConfig(); return true; } @@ -460,7 +317,7 @@ bool Config::RemoveWiFiCredentials(std::uint8_t id) { } void Config::ClearWiFiCredentials() { - _mainConfig.wifi.credentials.clear(); + _mainConfig.wifi.credentialsList.clear(); _trySaveConfig(); } diff --git a/src/config/RFConfig.cpp b/src/config/RFConfig.cpp new file mode 100644 index 00000000..f6f321b6 --- /dev/null +++ b/src/config/RFConfig.cpp @@ -0,0 +1,67 @@ +#include "config/RFConfig.h" + +#include "Logging.h" + +const char* const TAG = "Config::RFConfig"; + +using namespace OpenShock::Config; + +void RFConfig::ToDefault() { + txPin = 0U; + keepAliveEnabled = true; +} + +bool RFConfig::FromFlatbuffers(const Serialization::Configuration::RFConfig* config) { + if (config == nullptr) { + ESP_LOGE(TAG, "config is null"); + return false; + } + + txPin = config->tx_pin(); + keepAliveEnabled = config->keepalive_enabled(); + + return true; +} + +flatbuffers::Offset RFConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const { + return Serialization::Configuration::CreateRFConfig(builder, txPin, keepAliveEnabled); +} + +bool RFConfig::FromJSON(const cJSON* json) { + if (json == nullptr) { + ESP_LOGE(TAG, "json is null"); + return false; + } + + if (!cJSON_IsObject(json)) { + ESP_LOGE(TAG, "json is not an object"); + return false; + } + + const cJSON* txPinJson = cJSON_GetObjectItemCaseSensitive(json, "txPin"); + if (!cJSON_IsNumber(txPinJson)) { + ESP_LOGE(TAG, "value at 'txPin' is not a number"); + return false; + } + + txPin = txPinJson->valueint; + + const cJSON* keepAliveEnabledJson = cJSON_GetObjectItemCaseSensitive(json, "keepAliveEnabled"); + if (!cJSON_IsBool(keepAliveEnabledJson)) { + ESP_LOGE(TAG, "value at 'keepAliveEnabled' is not a bool"); + return false; + } + + keepAliveEnabled = cJSON_IsTrue(keepAliveEnabledJson); + + return true; +} + +cJSON* RFConfig::ToJSON() const { + cJSON* root = cJSON_CreateObject(); + + cJSON_AddNumberToObject(root, "txPin", txPin); + cJSON_AddBoolToObject(root, "keepAliveEnabled", keepAliveEnabled); + + return root; +} diff --git a/src/config/RootConfig.cpp b/src/config/RootConfig.cpp new file mode 100644 index 00000000..02f08482 --- /dev/null +++ b/src/config/RootConfig.cpp @@ -0,0 +1,92 @@ +#include "config/RootConfig.h" + +#include "Logging.h" + +const char* const TAG = "Config::RootConfig"; + +using namespace OpenShock::Config; + +void RootConfig::ToDefault() { + rf.ToDefault(); + wifi.ToDefault(); + captivePortal.ToDefault(); + backend.ToDefault(); +} + +bool RootConfig::FromFlatbuffers(const Serialization::Configuration::Config* config) { + if (config == nullptr) { + ESP_LOGE(TAG, "config is null"); + return false; + } + + if (!rf.FromFlatbuffers(config->rf())) { + ESP_LOGE(TAG, "Unable to load rf config"); + return false; + } + + if (!wifi.FromFlatbuffers(config->wifi())) { + ESP_LOGE(TAG, "Unable to load wifi config"); + return false; + } + + if (!captivePortal.FromFlatbuffers(config->captive_portal())) { + ESP_LOGE(TAG, "Unable to load captive portal config"); + return false; + } + + if (!backend.FromFlatbuffers(config->backend())) { + ESP_LOGE(TAG, "Unable to load backend config"); + return false; + } + + return true; +} + +flatbuffers::Offset RootConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const { + return Serialization::Configuration::CreateConfig(builder, rf.ToFlatbuffers(builder), wifi.ToFlatbuffers(builder), captivePortal.ToFlatbuffers(builder), backend.ToFlatbuffers(builder)); +} + +bool RootConfig::FromJSON(const cJSON* json) { + if (json == nullptr) { + ESP_LOGE(TAG, "json is null"); + return false; + } + + if (!cJSON_IsObject(json)) { + ESP_LOGE(TAG, "json is not an object"); + return false; + } + + if (!rf.FromJSON(cJSON_GetObjectItemCaseSensitive(json, "rf"))) { + ESP_LOGE(TAG, "Unable to load rf config"); + return false; + } + + if (!wifi.FromJSON(cJSON_GetObjectItemCaseSensitive(json, "wifi"))) { + ESP_LOGE(TAG, "Unable to load wifi config"); + return false; + } + + if (!captivePortal.FromJSON(cJSON_GetObjectItemCaseSensitive(json, "captivePortal"))) { + ESP_LOGE(TAG, "Unable to load captive portal config"); + return false; + } + + if (!backend.FromJSON(cJSON_GetObjectItemCaseSensitive(json, "backend"))) { + ESP_LOGE(TAG, "Unable to load backend config"); + return false; + } + + return true; +} + +cJSON* RootConfig::ToJSON() const { + cJSON* root = cJSON_CreateObject(); + + cJSON_AddItemToObject(root, "rf", rf.ToJSON()); + cJSON_AddItemToObject(root, "wifi", wifi.ToJSON()); + cJSON_AddItemToObject(root, "captivePortal", captivePortal.ToJSON()); + cJSON_AddItemToObject(root, "backend", backend.ToJSON()); + + return root; +} diff --git a/src/config/WiFiConfig.cpp b/src/config/WiFiConfig.cpp new file mode 100644 index 00000000..08428ec7 --- /dev/null +++ b/src/config/WiFiConfig.cpp @@ -0,0 +1,117 @@ +#include "config/WiFiConfig.h" + +#include "config/internal/utils.h" +#include "Logging.h" + +const char* const TAG = "Config::WiFiConfig"; + +using namespace OpenShock::Config; + +void WiFiConfig::ToDefault() { + accessPointSSID = OPENSHOCK_FW_AP_PREFIX; + hostname = OPENSHOCK_FW_HOSTNAME; + credentialsList.clear(); +} + +bool WiFiConfig::FromFlatbuffers(const Serialization::Configuration::WiFiConfig* config) { + if (config == nullptr) { + ESP_LOGE(TAG, "config is null"); + return false; + } + + Internal::Utils::FromFbsStr(accessPointSSID, config->ap_ssid(), OPENSHOCK_FW_AP_PREFIX); + Internal::Utils::FromFbsStr(hostname, config->hostname(), OPENSHOCK_FW_HOSTNAME); + Internal::Utils::FromFbsVec(credentialsList, config->credentials()); + + return true; +} + +flatbuffers::Offset WiFiConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const { + std::vector> fbsCredentialsList; + for (auto& credentials : credentialsList) { + fbsCredentialsList.push_back(credentials.ToFlatbuffers(builder)); + } + + return Serialization::Configuration::CreateWiFiConfig(builder, builder.CreateString(accessPointSSID), builder.CreateString(hostname), builder.CreateVector(fbsCredentialsList)); +} + +bool WiFiConfig::FromJSON(const cJSON* json) { + if (json == nullptr) { + ESP_LOGE(TAG, "json is null"); + return false; + } + + if (!cJSON_IsObject(json)) { + ESP_LOGE(TAG, "json is not an object"); + return false; + } + + const cJSON* accessPointSSIDJson = cJSON_GetObjectItemCaseSensitive(json, "accessPointSSID"); + if (accessPointSSIDJson == nullptr) { + ESP_LOGE(TAG, "accessPointSSID is null"); + return false; + } + + if (!cJSON_IsString(accessPointSSIDJson)) { + ESP_LOGE(TAG, "accessPointSSID is not a string"); + return false; + } + + accessPointSSID = accessPointSSIDJson->valuestring; + + const cJSON* hostnameJson = cJSON_GetObjectItemCaseSensitive(json, "hostname"); + if (hostnameJson == nullptr) { + ESP_LOGE(TAG, "hostname is null"); + return false; + } + + if (!cJSON_IsString(hostnameJson)) { + ESP_LOGE(TAG, "hostname is not a string"); + return false; + } + + hostname = hostnameJson->valuestring; + + const cJSON* credentialsListJson = cJSON_GetObjectItemCaseSensitive(json, "credentials"); + if (credentialsListJson == nullptr) { + ESP_LOGE(TAG, "credentials is null"); + return false; + } + + if (!cJSON_IsArray(credentialsListJson)) { + ESP_LOGE(TAG, "credentials is not an array"); + return false; + } + + credentialsList.clear(); + + const cJSON* credentialsJson = nullptr; + cJSON_ArrayForEach(credentialsJson, credentialsListJson) { + WiFiCredentials wifiCredential; + if (!wifiCredential.FromJSON(credentialsJson)) { + ESP_LOGE(TAG, "Failed to parse WiFiCredential"); + return false; + } + + credentialsList.push_back(std::move(wifiCredential)); + } + + return true; +} + +cJSON* WiFiConfig::ToJSON() const { + cJSON* root = cJSON_CreateObject(); + + cJSON_AddStringToObject(root, "accessPointSSID", accessPointSSID.c_str()); + cJSON_AddStringToObject(root, "hostname", hostname.c_str()); + + cJSON* credentialsListJson = cJSON_CreateArray(); + + for (auto& credentials : credentialsList) { + cJSON_AddItemToArray(credentialsListJson, credentials.ToJSON()); + } + + cJSON_AddItemToObject(root, "credentials", credentialsListJson); + + return root; +} diff --git a/src/config/WiFiCredentials.cpp b/src/config/WiFiCredentials.cpp new file mode 100644 index 00000000..2f03f720 --- /dev/null +++ b/src/config/WiFiCredentials.cpp @@ -0,0 +1,139 @@ +#include "config/WiFiCredentials.h" + +#include "config/internal/utils.h" +#include "Logging.h" +#include "util/HexUtils.h" + +const char* const TAG = "Config::WiFiCredentials"; + +using namespace OpenShock::Config; + +WiFiCredentials::WiFiCredentials() { + ToDefault(); +} + +WiFiCredentials::WiFiCredentials(std::uint8_t id, const std::string& ssid, const std::uint8_t (&bssid)[6], const std::string& password) { + this->id = id; + this->ssid = ssid; + this->password = password; + std::copy(std::begin(bssid), std::end(bssid), this->bssid); +} + +void WiFiCredentials::ToDefault() { + id = 0; + ssid = ""; + std::fill(std::begin(bssid), std::end(bssid), 0); + password = ""; +} + +bool WiFiCredentials::FromFlatbuffers(const Serialization::Configuration::WiFiCredentials* config) { + if (config == nullptr) { + ESP_LOGE(TAG, "config is null"); + return false; + } + + id = config->id(); + Internal::Utils::FromFbsStr(ssid, config->ssid(), ""); + Internal::Utils::FromFbsStr(password, config->password(), ""); + + auto fbsBssid = config->bssid(); + if (fbsBssid != nullptr) { + auto fbsBssidArr = fbsBssid->array(); + if (fbsBssidArr != nullptr) { + std::copy(fbsBssidArr->begin(), fbsBssidArr->end(), bssid); + } else { + std::fill(std::begin(bssid), std::end(bssid), 0); + } + } else { + std::fill(std::begin(bssid), std::end(bssid), 0); + } + + return true; +} + +flatbuffers::Offset WiFiCredentials::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder) const { + Serialization::Configuration::BSSID fbsBssid(bssid); + + return Serialization::Configuration::CreateWiFiCredentials(builder, id, builder.CreateString(ssid), &fbsBssid, builder.CreateString(password)); +} + +bool WiFiCredentials::FromJSON(const cJSON* json) { + if (json == nullptr) { + ESP_LOGE(TAG, "json is null"); + return false; + } + + if (!cJSON_IsObject(json)) { + ESP_LOGE(TAG, "json is not an object"); + return false; + } + + const cJSON* idJson = cJSON_GetObjectItemCaseSensitive(json, "id"); + if (idJson == nullptr) { + ESP_LOGE(TAG, "id is null"); + return false; + } + + if (!cJSON_IsNumber(idJson)) { + ESP_LOGE(TAG, "id is not a number"); + return false; + } + + if (idJson->valueint < 0 || idJson->valueint > UINT8_MAX) { + ESP_LOGE(TAG, "id is out of range"); + return false; + } + + id = idJson->valueint; + + const cJSON* ssidJson = cJSON_GetObjectItemCaseSensitive(json, "ssid"); + if (ssidJson == nullptr) { + ESP_LOGE(TAG, "ssid is null"); + return false; + } + + if (!cJSON_IsString(ssidJson)) { + ESP_LOGE(TAG, "ssid is not a string"); + return false; + } + + ssid = ssidJson->valuestring; + + const cJSON* bssidJson = cJSON_GetObjectItemCaseSensitive(json, "bssid"); + if (bssidJson == nullptr) { + ESP_LOGE(TAG, "bssid is null"); + return false; + } + + if (!cJSON_IsString(bssidJson)) { + ESP_LOGE(TAG, "bssid is not a string"); + return false; + } + + std::size_t bssidLen = strlen(bssidJson->valuestring); + + if (bssidLen != 17) { + ESP_LOGE(TAG, "bssid is not a valid MAC address"); + return false; + } + + if (!HexUtils::TryParseHexMac(bssidJson->valuestring, bssidLen, bssid, 6)) { + ESP_LOGE(TAG, "bssid has a invalid format"); + return false; + } + + return true; +} + +cJSON* WiFiCredentials::ToJSON() const { + cJSON* root = cJSON_CreateObject(); + + cJSON_AddNumberToObject(root, "id", id); + cJSON_AddStringToObject(root, "ssid", ssid.c_str()); + + char bssidStr[17]; + HexUtils::ToHexMac<6>(bssid, bssidStr); + cJSON_AddStringToObject(root, "bssid", bssidStr); + + return root; +} diff --git a/src/event_handlers/WiFiScan.cpp b/src/event_handlers/WiFiScan.cpp index 60529fe2..3ce78b80 100644 --- a/src/event_handlers/WiFiScan.cpp +++ b/src/event_handlers/WiFiScan.cpp @@ -1,7 +1,7 @@ #include "event_handlers/WiFiScan.h" #include "CaptivePortal.h" -#include "Config.h" +#include "config/Config.h" #include "serialization/WSLocal.h" #include "wifi/WiFiManager.h" #include "wifi/WiFiNetwork.h" diff --git a/src/main.cpp b/src/main.cpp index bed426f6..cc209f32 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ #include "CaptivePortal.h" #include "CommandHandler.h" -#include "Config.h" +#include "config/Config.h" #include "Constants.h" #include "EStopManager.h" #include "event_handlers/Init.h" diff --git a/src/wifi/WiFiManager.cpp b/src/wifi/WiFiManager.cpp index af72d62d..66a00783 100644 --- a/src/wifi/WiFiManager.cpp +++ b/src/wifi/WiFiManager.cpp @@ -1,7 +1,7 @@ #include "wifi/WiFiManager.h" #include "CaptivePortal.h" -#include "Config.h" +#include "config/Config.h" #include "FormatHelpers.h" #include "Logging.h" #include "serialization/WSLocal.h"