diff --git a/Firmware/src/Messages/Composer.cpp b/Firmware/src/Messages/Composer.cpp new file mode 100644 index 0000000..87d2428 --- /dev/null +++ b/Firmware/src/Messages/Composer.cpp @@ -0,0 +1,15 @@ +#include "Composer.h" + +bool message::composer::sendMessage(protocol::Message type, + etl::string_view content) { + if (content.length() > protocol::MAX_PAYLOAD_LEN) { + return false; + } + uint8_t length = + protocol::HEADER_LEN + static_cast(content.length()); + // Serial.write("HX"); + // Serial.write(length); + // Serial.write(protocol::to_underlying(type)); + // Serial.write(content.data()); + return true; +}; diff --git a/Firmware/src/Messages/Composer.h b/Firmware/src/Messages/Composer.h new file mode 100644 index 0000000..469d527 --- /dev/null +++ b/Firmware/src/Messages/Composer.h @@ -0,0 +1,9 @@ +#pragma once +#include "Protocol.h" +#include + +namespace message { +namespace composer { +bool sendMessage(protocol::Message type, etl::string_view content); +} // namespace composer +} // namespace message diff --git a/Firmware/src/Messages/Parser.cpp b/Firmware/src/Messages/Parser.cpp new file mode 100644 index 0000000..d619148 --- /dev/null +++ b/Firmware/src/Messages/Parser.cpp @@ -0,0 +1,242 @@ +#include "Parser.h" + +message::parser::StateVisitor::StateVisitor(etl::byte_stream_reader &sreader) + : stream{sreader} {} + +auto message::parser::StateVisitor::operator()(state::ModeSelection) -> State { + if (!stream.available()) { + return state::Invalid{}; + } + + auto mode = stream.read(); + if (!mode.has_value()) { + return state::Invalid{}; + } + + switch (mode.value()) { + case static_cast(protocol::Mode::Message): + return state::Message{}; + case static_cast(protocol::Mode::Settings): + return state::Settings{}; + case static_cast(protocol::Mode::LightControl): + return state::LightControl{}; + case static_cast(protocol::Mode::Command): + return state::Command{}; + default: + return state::Invalid{}; + } +} + +auto message::parser::StateVisitor::operator()(state::Message) -> State { + if (!stream.available()) { + return state::Invalid{}; + } + + auto mode = stream.read(); + if (!mode.has_value()) { + return state::Invalid{}; + } + + switch (mode.value()) { + case static_cast(protocol::Message::LightData): + return state::MessageLightData{}; + case static_cast(protocol::Message::Info): + return state::MessageInfo{}; + case static_cast(protocol::Message::Warning): + return state::MessageWarning{}; + case static_cast(protocol::Message::Error): + return state::MessageError{}; + case static_cast(protocol::Message::Success): + return state::MessageSuccess{}; + default: + return state::Invalid{}; + } +} +auto message::parser::StateVisitor::operator()(state::MessageLightData) + -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::MessageInfo) -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::MessageWarning) -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::MessageError) -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::MessageSuccess) -> State { + return state::Complete{}; +} + +auto message::parser::StateVisitor::operator()(state::Settings) -> State { + if (!stream.available()) { + return state::Invalid{}; + } + + auto mode = stream.read(); + if (!mode.has_value()) { + return state::Invalid{}; + } + + switch (mode.value()) { + case static_cast(protocol::Settings::SetBaud): + return state::SettingsSetBaud{}; + case static_cast(protocol::Settings::SetWifiPassword): + return state::SettingsWifiPassword{}; + case static_cast(protocol::Settings::SetWifiSSID): + return state::SettingsWifiSSID{}; + default: + return state::Invalid{}; + } +} +auto message::parser::StateVisitor::operator()(state::SettingsSetBaud) + -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::SettingsWifiPassword) + -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::SettingsWifiSSID) + -> State { + return state::Complete{}; +} + +auto message::parser::StateVisitor::operator()(state::LightControl) -> State { + if (!stream.available()) { + return state::Invalid{}; + } + + auto mode = stream.read(); + if (!mode.has_value()) { + return state::Invalid{}; + } + + switch (mode.value()) { + case static_cast(protocol::LightControl::On): + return state::LightControlOn{}; + case static_cast(protocol::LightControl::Off): + return state::LightControlOff{}; + case static_cast(protocol::LightControl::Toggle): + return state::LightControlToggle{}; + case static_cast(protocol::LightControl::Increase): + return state::LightControlIncrease{}; + case static_cast(protocol::LightControl::Decrease): + return state::LightControlDecrease{}; + default: + return state::Invalid{}; + } +} +auto message::parser::StateVisitor::operator()(state::LightControlOn) -> State { + if (stream.available_bytes() > 0) { + return state::Invalid{}; + } + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::LightControlOff) + -> State { + if (stream.available_bytes() > 0) { + return state::Invalid{}; + } + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::LightControlToggle) + -> State { + if (stream.available_bytes() > 1) { + return state::Invalid{}; + } + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::LightControlIncrease) + -> State { + if (stream.available_bytes() > 1) { + return state::Invalid{}; + } + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::LightControlDecrease) + -> State { + if (stream.available_bytes() > 1) { + return state::Invalid{}; + } + return state::Complete{}; +} + +auto message::parser::StateVisitor::operator()(state::Command) -> State { + if (!stream.available()) { + return state::Invalid{}; + } + + auto mode = stream.read(); + if (!mode.has_value()) { + return state::Invalid{}; + } + + switch (mode.value()) { + case static_cast(protocol::Command::RequestLightData): + return state::CommandRequestLightData{}; + case static_cast(protocol::Command::EnterConsoleFlashing): + return state::CommandEnterConsoleFlashing{}; + case static_cast(protocol::Command::ExitConsoleFlashing): + return state::CommandExitConsoleFlashing{}; + case static_cast(protocol::Command::PairBluetooth): + return state::CommandPairBluetooth{}; + case static_cast(protocol::Command::Help): + return state::CommandHelp{}; + case static_cast(protocol::Command::Version): + return state::CommandVersion{}; + default: + return state::Invalid{}; + } +} +auto message::parser::StateVisitor::operator()(state::CommandRequestLightData) + -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()( + state::CommandEnterConsoleFlashing) -> State { + if (stream.available_bytes() > 0) { + return state::Invalid{}; + } + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()( + state::CommandExitConsoleFlashing) -> State { + if (stream.available_bytes() > 0) { + return state::Invalid{}; + } + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::CommandPairBluetooth) + -> State { + if (stream.available_bytes() > 0) { + return state::Invalid{}; + } + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::CommandHelp) -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::CommandVersion) -> State { + return state::Complete{}; +} + +auto message::parser::StateVisitor::operator()(state::Complete) -> State { + return state::Complete{}; +} +auto message::parser::StateVisitor::operator()(state::Invalid) -> State { + return state::Invalid{}; +} + +auto message::parser::parse(etl::byte_stream_reader &reader) -> void { + StateVisitor visitor{reader}; + State state{state::ModeSelection{}}; + while (!etl::holds_alternative(state) && + !etl::holds_alternative(state)) { + state = etl::visit(visitor, state); + } + if (etl::holds_alternative(state)) { + // TODO: error + } +} diff --git a/Firmware/src/Messages/Parser.h b/Firmware/src/Messages/Parser.h new file mode 100644 index 0000000..9ecaf66 --- /dev/null +++ b/Firmware/src/Messages/Parser.h @@ -0,0 +1,88 @@ +#pragma once +#include "Protocol.h" +#include +#include + +namespace message { +namespace parser { +namespace state { +struct Invalid {}; +struct ModeSelection {}; +struct Message {}; +struct MessageLightData {}; +struct MessageInfo {}; +struct MessageWarning {}; +struct MessageError {}; +struct MessageSuccess {}; +struct Settings {}; +struct SettingsSetBaud {}; +struct SettingsWifiPassword {}; +struct SettingsWifiSSID {}; +struct LightControl {}; +struct LightControlOn {}; +struct LightControlOff {}; +struct LightControlToggle {}; +struct LightControlIncrease {}; +struct LightControlDecrease {}; +struct Command {}; +struct CommandRequestLightData {}; +struct CommandEnterConsoleFlashing {}; +struct CommandExitConsoleFlashing {}; +struct CommandPairBluetooth {}; +struct CommandHelp {}; +struct CommandVersion {}; +struct Complete {}; +} // namespace state + +using State = etl::variant< + state::Invalid, state::ModeSelection, state::Message, + state::MessageLightData, state::MessageInfo, state::MessageWarning, + state::MessageError, state::MessageSuccess, state::Settings, + state::SettingsSetBaud, state::SettingsWifiPassword, + state::SettingsWifiSSID, state::LightControl, state::LightControlOn, + state::LightControlOff, state::LightControlToggle, + state::LightControlIncrease, state::LightControlDecrease, state::Command, + state::CommandRequestLightData, state::CommandEnterConsoleFlashing, + state::CommandExitConsoleFlashing, state::CommandPairBluetooth, + state::CommandHelp, state::CommandVersion, state::Complete>; + +void parse(etl::byte_stream_reader &reader); + +class StateVisitor { +private: + etl::byte_stream_reader stream; + +public: + StateVisitor(etl::byte_stream_reader &); + State operator()(state::ModeSelection); + State operator()(state::Message); + State operator()(state::MessageLightData); + State operator()(state::MessageInfo); + State operator()(state::MessageWarning); + State operator()(state::MessageError); + State operator()(state::MessageSuccess); + + State operator()(state::Settings); + State operator()(state::SettingsSetBaud); + State operator()(state::SettingsWifiPassword); + State operator()(state::SettingsWifiSSID); + + State operator()(state::LightControl); + State operator()(state::LightControlOn); + State operator()(state::LightControlOff); + State operator()(state::LightControlToggle); + State operator()(state::LightControlIncrease); + State operator()(state::LightControlDecrease); + State operator()(state::Command); + State operator()(state::CommandRequestLightData); + State operator()(state::CommandEnterConsoleFlashing); + State operator()(state::CommandExitConsoleFlashing); + State operator()(state::CommandPairBluetooth); + State operator()(state::CommandHelp); + State operator()(state::CommandVersion); + + State operator()(state::Complete); + State operator()(state::Invalid); +}; +} // namespace parser +} // namespace message diff --git a/Firmware/src/Messages/Protocol.h b/Firmware/src/Messages/Protocol.h new file mode 100644 index 0000000..7afcf2b --- /dev/null +++ b/Firmware/src/Messages/Protocol.h @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace message { +namespace protocol { +static const uint8_t HEADER_LEN = 5; +static const uint8_t MAX_TOTAL_LEN = 255; +static const uint8_t MAX_PAYLOAD_LEN = MAX_TOTAL_LEN - HEADER_LEN; + +enum class Mode : uint8_t { + Message = 0x0, + Settings = 0x1, + LightControl = 0x2, + Command = 0x3 +}; + +enum class Message : uint8_t { + LightData = 0x0, + Info = 0x1, + Warning = 0x2, + Error = 0x3, + Success = 0x5, +}; + +enum class Settings : uint8_t { + SetBaud = 0x0, + SetWifiPassword = 0x1, + SetWifiSSID = 0x2, +}; + +enum class LightControl : uint8_t { + On = 0x0, + Off = 0x1, + Toggle = 0x2, + Increase = 0x3, + Decrease = 0x4, +}; + +enum class Command : uint8_t { + RequestLightData = 0x0, + EnterConsoleFlashing = 0x1, + ExitConsoleFlashing = 0x2, + PairBluetooth = 0x3, + Help = 0x4, + Version = 0x5, +}; + +using Submode = etl::variant; +} // namespace protocol +} // namespace message