From 3b196fc90de13a668f3e860f94a1e40d05ae4ff5 Mon Sep 17 00:00:00 2001 From: Proddy Date: Thu, 5 Jan 2023 15:05:30 +0100 Subject: [PATCH] updated uuid libs --- lib/uuid-common/src/compare_flash_string.cpp | 71 - lib/uuid-common/src/get_uptime_ms.cpp | 2 +- lib/uuid-common/src/uuid/common.h | 48 +- lib/uuid-console/src/commands.cpp | 182 +-- lib/uuid-console/src/commands_iterable.cpp | 103 ++ lib/uuid-console/src/shell.cpp | 101 +- lib/uuid-console/src/shell_log.cpp | 111 +- lib/uuid-console/src/shell_loop_all.cpp | 14 +- lib/uuid-console/src/shell_print.cpp | 75 +- lib/uuid-console/src/shell_stream.cpp | 25 +- lib/uuid-console/src/stream_console.cpp | 63 - lib/uuid-console/src/uuid/console.h | 1409 ++++++++++------- lib/uuid-log/src/format_level_lowercase.cpp | 25 +- lib/uuid-log/src/format_level_uppercase.cpp | 25 +- lib/uuid-log/src/format_timestamp_ms.cpp | 3 +- .../src/handler.cpp} | 24 +- lib/uuid-log/src/levels_lowercase.cpp | 22 +- lib/uuid-log/src/levels_uppercase.cpp | 22 +- lib/uuid-log/src/log.cpp | 199 +-- lib/uuid-log/src/parse_level_lowercase.cpp | 2 +- lib/uuid-log/src/parse_level_uppercase.cpp | 2 +- lib/uuid-log/src/print_handler.cpp | 106 ++ lib/uuid-log/src/uuid/log.h | 354 +++-- lib/uuid-syslog/src/syslog.cpp | 258 +-- lib/uuid-syslog/src/uuid/syslog.h | 100 +- lib/uuid-telnet/src/telnet.cpp | 54 +- lib/uuid-telnet/src/uuid/telnet.h | 24 +- 27 files changed, 2013 insertions(+), 1411 deletions(-) delete mode 100644 lib/uuid-common/src/compare_flash_string.cpp create mode 100644 lib/uuid-console/src/commands_iterable.cpp delete mode 100644 lib/uuid-console/src/stream_console.cpp rename lib/{uuid-common/src/read_flash_string.cpp => uuid-log/src/handler.cpp} (59%) create mode 100644 lib/uuid-log/src/print_handler.cpp diff --git a/lib/uuid-common/src/compare_flash_string.cpp b/lib/uuid-common/src/compare_flash_string.cpp deleted file mode 100644 index 64700ad67..000000000 --- a/lib/uuid-common/src/compare_flash_string.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * EMS-ESP - https://github.com/emsesp/EMS-ESP - * Copyright 2020 Paul Derbyshire - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -// #ifdef ESP8266 -// #include -// #else -// #include -// #endif - -#include - -namespace uuid { - -// On ESP8266, pgm_read_byte() already takes care of 4-byte alignment, and -// memcpy_P(s, p, 4) makes 4 calls to pgm_read_byte() anyway, so don't bother -// optimizing for 4-byte alignment here. - -// class __FlashStringHelper; - -int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b) { - const char * aa = reinterpret_cast(a); - const char * bb = reinterpret_cast(b); - - while (true) { - uint8_t ca = pgm_read_byte(aa); - uint8_t cb = pgm_read_byte(bb); - if (ca != cb) - return (int)ca - (int)cb; - if (ca == 0) - return 0; - aa++; - bb++; - } -} - -int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b, size_t n) { - const char * aa = reinterpret_cast(a); - const char * bb = reinterpret_cast(b); - - while (n > 0) { - uint8_t ca = pgm_read_byte(aa); - uint8_t cb = pgm_read_byte(bb); - if (ca != cb) - return (int)ca - (int)cb; - if (ca == 0) - return 0; - aa++; - bb++; - n--; - } - return 0; -} - -} // namespace uuid diff --git a/lib/uuid-common/src/get_uptime_ms.cpp b/lib/uuid-common/src/get_uptime_ms.cpp index 7bc572ce2..48e5fe3c9 100644 --- a/lib/uuid-common/src/get_uptime_ms.cpp +++ b/lib/uuid-common/src/get_uptime_ms.cpp @@ -22,7 +22,7 @@ namespace uuid { -// added by proddy, modified +// added by proddy for EMS-ESP static uint64_t now_millis = 0; // returns system uptime in seconds diff --git a/lib/uuid-common/src/uuid/common.h b/lib/uuid-common/src/uuid/common.h index 578c3198a..6184ef971 100644 --- a/lib/uuid-common/src/uuid/common.h +++ b/lib/uuid-common/src/uuid/common.h @@ -26,6 +26,20 @@ #include #include +#ifndef UUID_COMMON_STD_MUTEX_AVAILABLE +#if __has_include() && (!defined(_GLIBCXX_MUTEX) || defined(_GLIBCXX_HAS_GTHREADS)) +#define UUID_COMMON_STD_MUTEX_AVAILABLE 1 +#else +#define UUID_COMMON_STD_MUTEX_AVAILABLE 0 +#endif +#endif + +#if defined(ARDUINO_ARCH_ESP32) || UUID_COMMON_STD_MUTEX_AVAILABLE +#define UUID_COMMON_THREAD_SAFE 1 +#else +#define UUID_COMMON_THREAD_SAFE 0 +#endif + /** * Common utilities. * @@ -35,31 +49,15 @@ namespace uuid { /** - * String compare two flash strings + * Thread-safe status of the library. * - * The flash string must be stored with appropriate alignment for - * reading it on the platform. - * - * @param[in] a Pointer to string stored in flash. - * @param[in] b Pointer to string stored in flash. - * @param[in] n optional max length - * @return 0 for match, otherwise diff - * @since 1.0.0 + * @since 1.1.2 */ -int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b); -int compare_flash_string(const __FlashStringHelper * a, const __FlashStringHelper * b, size_t n); - -/** - * Read a string from flash and convert it to a std::string. - * - * The flash string must be stored with appropriate alignment for - * reading it on the platform. - * - * @param[in] flash_str Pointer to string stored in flash. - * @return A string copy of the flash string. - * @since 1.0.0 - */ -std::string read_flash_string(const __FlashStringHelper * flash_str); +#if UUID_COMMON_THREAD_SAFE +static constexpr bool thread_safe = true; +#else +static constexpr bool thread_safe = false; +#endif /** * Append to a std::string by printing a Printable object. @@ -104,8 +102,8 @@ void loop(); */ uint64_t get_uptime_ms(); -uint32_t get_uptime(); // added by proddy -uint32_t get_uptime_sec(); // added by proddy +uint32_t get_uptime(); // added by proddy for EMS-ESP +uint32_t get_uptime_sec(); // added by proddy for EMS-ESP void set_uptime(); diff --git a/lib/uuid-console/src/commands.cpp b/lib/uuid-console/src/commands.cpp index 4a8c47563..a76eb09d5 100644 --- a/lib/uuid-console/src/commands.cpp +++ b/lib/uuid-console/src/commands.cpp @@ -1,6 +1,6 @@ /* * uuid-console - Microcontroller console shell - * Copyright 2019 Simon Arlott + * Copyright 2019,2022 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -12,7 +12,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a std::copy of the GNU General Public License + * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ @@ -25,6 +25,7 @@ #include #include #include +#include #include #ifndef __cpp_lib_make_unique @@ -43,23 +44,39 @@ namespace uuid { namespace console { void Commands::add_command(const string_vector & name, command_function function) { - add_command(0, 0, name, string_vector{}, function, nullptr); + add_command(0, 0, 0, name, string_vector{}, function, nullptr); } void Commands::add_command(const string_vector & name, const string_vector & arguments, command_function function) { - add_command(0, 0, name, arguments, function, nullptr); + add_command(0, 0, 0, name, arguments, function, nullptr); } void Commands::add_command(const string_vector & name, const string_vector & arguments, command_function function, argument_completion_function arg_function) { - add_command(0, 0, name, arguments, function, arg_function); + add_command(0, 0, 0, name, arguments, function, arg_function); +} + +void Commands::add_command(unsigned int context, const string_vector & name, command_function function) { + add_command(context, 0, 0, name, string_vector{}, function, nullptr); +} + +void Commands::add_command(unsigned int context, const string_vector & name, const string_vector & arguments, command_function function) { + add_command(context, 0, 0, name, arguments, function, nullptr); +} + +void Commands::add_command(unsigned int context, + const string_vector & name, + const string_vector & arguments, + command_function function, + argument_completion_function arg_function) { + add_command(context, 0, 0, name, arguments, function, arg_function); } void Commands::add_command(unsigned int context, unsigned int flags, const string_vector & name, command_function function) { - add_command(context, flags, name, string_vector{}, function, nullptr); + add_command(context, flags, 0, name, string_vector{}, function, nullptr); } void Commands::add_command(unsigned int context, unsigned int flags, const string_vector & name, const string_vector & arguments, command_function function) { - add_command(context, flags, name, arguments, function, nullptr); + add_command(context, flags, 0, name, arguments, function, nullptr); } void Commands::add_command(unsigned int context, @@ -68,34 +85,30 @@ void Commands::add_command(unsigned int context, const string_vector & arguments, command_function function, argument_completion_function arg_function) { - commands_.emplace(std::piecewise_construct, std::forward_as_tuple(context), std::forward_as_tuple(flags, name, arguments, function, arg_function)); + add_command(context, flags, 0, name, arguments, function, arg_function); } - -// added by proddy -// note we should really iterate and free up the lambda code and any flashstrings -void Commands::remove_all_commands() { - commands_.clear(); +void Commands::add_command(unsigned int context, unsigned int flags, unsigned int not_flags, const string_vector & name, command_function function) { + add_command(context, flags, not_flags, name, string_vector{}, function, nullptr); } -// added by proddy -// note we should really iterate and free up the lambda code and any flashstrings -void Commands::remove_context_commands(unsigned int context) { - commands_.erase(context); +void Commands::add_command(unsigned int context, + unsigned int flags, + unsigned int not_flags, + const string_vector & name, + const string_vector & arguments, + command_function function) { + add_command(context, flags, not_flags, name, arguments, function, nullptr); +} - /* - auto commands = commands_.equal_range(context); - for (auto command_it = commands.first; command_it != commands.second; command_it++) { - shell.printf("Got: "); - for (auto flash_name : command_it->second.name_) { - shell.printf("%s ", read_flash_string(flash_name).c_str()); - } - shell.println(); - } - - size_t nun = commands_.erase(context); - shell.printfln("Erased %d commands", nun); - */ +void Commands::add_command(unsigned int context, + unsigned int flags, + unsigned int not_flags, + const string_vector & name, + const string_vector & arguments, + command_function function, + argument_completion_function arg_function) { + commands_.emplace(std::piecewise_construct, std::forward_as_tuple(context), std::forward_as_tuple(flags, not_flags, name, arguments, function, arg_function)); } Commands::Execution Commands::execute_command(Shell & shell, CommandLine && command_line) { @@ -106,7 +119,7 @@ Commands::Execution Commands::execute_command(Shell & shell, CommandLine && comm result.error = nullptr; if (commands.exact.empty()) { - result.error = F("Command not found"); + result.error = "Command not found"; } else if (commands.exact.count(longest->first) == 1) { auto & command = longest->second; std::vector arguments; @@ -117,16 +130,16 @@ Commands::Execution Commands::execute_command(Shell & shell, CommandLine && comm command_line.reset(); if (commands.partial.upper_bound(longest->first) != commands.partial.end() && !arguments.empty()) { - result.error = F("Command not found"); + result.error = "Command not found"; } else if (arguments.size() < command->minimum_arguments()) { - result.error = F("Not enough arguments for command"); + result.error = "Not enough arguments for command"; } else if (arguments.size() > command->maximum_arguments()) { - result.error = F("Too many arguments for command"); + result.error = "Too many arguments for command"; } else { command->function_(shell, arguments); } } else { - result.error = F("Fatal error (multiple commands found)"); + result.error = "Fatal error (multiple commands found)"; } return result; @@ -145,7 +158,7 @@ bool Commands::find_longest_common_prefix(const std::multimapsecond->name_.begin(), length))) { + if (*std::next(first.begin(), length) != *std::next(command_it->second->name_.begin(), length)) { all_match = false; break; } @@ -158,7 +171,7 @@ bool Commands::find_longest_common_prefix(const std::multimap(first) + length) - != pgm_read_byte(reinterpret_cast(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) { + + if (pgm_read_byte(first + length) != pgm_read_byte((*std::next(command_it->second->name_.begin(), component_prefix)) + length)) { all_match = false; break; } + + // if (pgm_read_byte(reinterpret_cast(first) + length) + // != pgm_read_byte(reinterpret_cast(*std::next(command_it->second->name_.begin(), component_prefix)) + length)) { + // all_match = false; + // break; + // } } if (all_match) { @@ -223,12 +242,16 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine auto match = commands.partial.begin(); size_t count; + bool multiple_matches; if (match != commands.partial.end()) { count = commands.partial.count(match->first); + multiple_matches = + count > 1 || commands.partial.size() > count || (!commands.exact.empty() && commands.exact.rbegin()->first >= command_line.total_size()); } else if (!commands.exact.empty()) { // Use prev() because both iterators must be forwards - match = std::prev(commands.exact.end()); - count = commands.exact.count(match->first); + match = std::prev(commands.exact.end()); + count = commands.exact.count(match->first); + multiple_matches = false; } else { return result; } @@ -237,20 +260,13 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine std::vector temp_command_name; std::multimap::iterator temp_command_it; - if (commands.partial.size() > 1 && (commands.exact.empty() || command_line.total_size() > commands.exact.begin()->second->name_.size())) { - // There are multiple partial matching commands, find the longest common prefix + if (multiple_matches && (commands.exact.empty() || command_line.total_size() > commands.exact.begin()->second->name_.size())) { + // There are multiple matching commands, find the longest common prefix bool whole_components = find_longest_common_prefix(commands.partial, temp_command_name); - if (count == 1 && whole_components && temp_command_name.size() == match->first) { - // If the longest common prefix is the same as the single shortest matching command - // then there's no need for a temporary command, but add a trailing space because - // there are longer commands that matched. - temp_command_name.clear(); - result.replacement.trailing_space = true; - } - + // Construct a temporary command with the longest common prefix to use as the replacement if (!temp_command_name.empty() && command_line.total_size() <= temp_command_name.size()) { - temp_command = std::make_unique(0, string_vector{}, string_vector{}, nullptr, nullptr); + temp_command = std::make_unique(0, 0, string_vector{}, string_vector{}, nullptr, nullptr); count = 1; match = commands.partial.end(); result.replacement.trailing_space = whole_components; @@ -261,12 +277,12 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine } } - if (count == 1 && !temp_command) { + if (count == 1 && !multiple_matches) { // Construct a replacement string for a single matching command auto & matching_command = match->second; for (auto & name : matching_command->name_) { - result.replacement->push_back(std::move((name))); + result.replacement->push_back(name); } if (command_line.total_size() > result.replacement->size() @@ -290,7 +306,8 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine } } - auto potential_arguments = matching_command->arg_function_ ? matching_command->arg_function_(shell, arguments) : std::vector{}; + auto potential_arguments = + matching_command->arg_function_ ? matching_command->arg_function_(shell, arguments, last_argument) : std::vector{}; // Remove arguments that can't match if (!command_line.trailing_space) { @@ -340,7 +357,7 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine remaining_help.escape_initial_parameters(); for (auto it = std::next(matching_command->arguments_.cbegin(), current_args_count); it != matching_command->arguments_.cend(); it++) { - remaining_help->push_back(std::move((*it))); + remaining_help->push_back(std::move(*it)); } } @@ -366,13 +383,13 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine // Add a space because there are more arguments for this command result.replacement.trailing_space = true; } - } else if (count > 1 || temp_command) { + } else if (count != 0) { // Provide help for all of the potential commands - for (auto command_it = commands.partial.begin(); command_it != commands.partial.end(); command_it++) { + for (auto command_it = commands.all.begin(); command_it != commands.all.end(); command_it++) { CommandLine help; auto line_it = command_line->cbegin(); - auto flash_name_it = command_it->second->name_.cbegin(); + auto flash_name_it = (*command_it)->name_.cbegin(); if (temp_command) { // Skip parts of the command name/line when the longest common prefix was used @@ -381,14 +398,19 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine skip--; } + // Exact match that is shorter than the replacement + if (flash_name_it + skip > (*command_it)->name_.cend()) { + continue; + } + flash_name_it += skip; while (line_it != command_line->cend()) { line_it++; } } - for (; flash_name_it != command_it->second->name_.cend(); flash_name_it++) { - std::string name = (*flash_name_it); + for (; flash_name_it != (*command_it)->name_.cend(); flash_name_it++) { + std::string name = *flash_name_it; // Skip parts of the command name that match the command line if (line_it != command_line->cend()) { @@ -404,22 +426,22 @@ Commands::Completion Commands::complete_command(Shell & shell, const CommandLine help.escape_initial_parameters(); - for (auto argument : command_it->second->arguments_) { + for (auto argument : (*command_it)->arguments_) { // Skip parts of the command arguments that exist in the command line if (line_it != command_line->cend()) { line_it++; continue; } - help->push_back(std::move((argument))); + help->push_back(argument); } result.help.push_back(std::move(help)); } } - if (count > 1 && !commands.exact.empty()) { - // Try to add a space to exact matches + if (multiple_matches && !commands.exact.empty() && result.replacement.total_size() == 0) { + // Try to add a space to exact matches if there's no other partial match replacement auto longest = commands.exact.crbegin(); if (commands.exact.count(longest->first) == 1) { @@ -449,7 +471,7 @@ Commands::Match Commands::find_command(Shell & shell, const CommandLine & comman bool match = true; bool exact = true; - if (!shell.has_flags(command.flags_)) { + if (!shell.has_flags(command.flags_, command.not_flags_)) { continue; } @@ -457,7 +479,7 @@ Commands::Match Commands::find_command(Shell & shell, const CommandLine & comman auto line_it = command_line->cbegin(); for (; name_it != command.name_.cend() && line_it != command_line->cend(); name_it++, line_it++) { - std::string name = (*name_it); + std::string name = *name_it; size_t found = name.rfind(*line_it, 0); if (found == std::string::npos) { @@ -491,41 +513,21 @@ Commands::Match Commands::find_command(Shell & shell, const CommandLine & comman } else { commands.partial.emplace(command.name_.size(), &command); } + commands.all.push_back(&command); } } return commands; } -void Commands::for_each_available_command(Shell & shell, apply_function f) const { - auto commands = commands_.equal_range(shell.context()); - - for (auto command_it = commands.first; command_it != commands.second; command_it++) { - if (shell.has_flags(command_it->second.flags_)) { - std::vector name; - std::vector arguments; - - name.reserve(command_it->second.name_.size()); - for (auto flash_name : command_it->second.name_) { - name.push_back(std::move((flash_name))); - } - - arguments.reserve(command_it->second.arguments_.size()); - for (auto flash_argument : command_it->second.arguments_) { - arguments.push_back(std::move((flash_argument))); - } - - f(name, arguments); - } - } -} - Commands::Command::Command(unsigned int flags, + unsigned int not_flags, const string_vector name, const string_vector arguments, command_function function, argument_completion_function arg_function) : flags_(flags) + , not_flags_(not_flags) , name_(name) , arguments_(arguments) , function_(function) diff --git a/lib/uuid-console/src/commands_iterable.cpp b/lib/uuid-console/src/commands_iterable.cpp new file mode 100644 index 000000000..01f94c1ee --- /dev/null +++ b/lib/uuid-console/src/commands_iterable.cpp @@ -0,0 +1,103 @@ +/* + * uuid-console - Microcontroller console shell + * Copyright 2022 Simon Arlott + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include + +namespace uuid { + +namespace console { + +Commands::AvailableCommands Commands::available_commands(const Shell & shell) const { + auto range = commands_.equal_range(shell.context()); + return AvailableCommands(shell, range.first, range.second); +} + +Commands::AvailableCommand::AvailableCommand(const Command & command) + : command_(command) { + name_.reserve(command_.name_.size()); + for (auto name : command_.name_) { + name_.push_back(name); + } + + arguments_.reserve(command_.arguments_.size()); + for (auto argument : command_.arguments_) { + arguments_.push_back(argument); + } +} + +Commands::AvailableCommands::AvailableCommands(const Shell & shell, const command_iterator & begin, const command_iterator & end) + : shell_(shell) + , begin_(begin) + , end_(end) { + // Skip over unavailable commands at the beginning + while (begin_ != end_ && !shell_.has_flags(begin_->second.flags_, begin_->second.not_flags_)) { + ++begin_; + } +} + +Commands::AvailableCommands::const_iterator Commands::AvailableCommands::cbegin() const { + return AvailableCommands::const_iterator(shell_, begin_, begin_, end_); +} + +Commands::AvailableCommands::const_iterator Commands::AvailableCommands::cend() const { + return AvailableCommands::const_iterator(shell_, begin_, end_, end_); +} + +Commands::AvailableCommands::const_iterator::const_iterator(const Shell & shell, + const command_iterator & begin, + const command_iterator & command, + const command_iterator & end) + : shell_(std::move(shell)) + , begin_(begin) + , command_(command) + , end_(end) { + update(); +} + +void Commands::AvailableCommands::const_iterator::update() { + if (command_ != end_) { + available_command_ = std::make_shared(command_->second); + } else { + available_command_.reset(); + } +} + +Commands::AvailableCommands::const_iterator & Commands::AvailableCommands::const_iterator::operator++() { + do { + ++command_; + } while (command_ != end_ && !shell_.has_flags(command_->second.flags_, command_->second.not_flags_)); + + update(); + return *this; +} + +Commands::AvailableCommands::const_iterator & Commands::AvailableCommands::const_iterator::operator--() { + do { + --command_; + } while (command_ != begin_ && !shell_.has_flags(command_->second.flags_, command_->second.not_flags_)); + + update(); + return *this; +} + +} // namespace console + +} // namespace uuid \ No newline at end of file diff --git a/lib/uuid-console/src/shell.cpp b/lib/uuid-console/src/shell.cpp index 489ea69f3..fe22d2a32 100644 --- a/lib/uuid-console/src/shell.cpp +++ b/lib/uuid-console/src/shell.cpp @@ -45,21 +45,23 @@ namespace uuid { namespace console { -// cppcheck-suppress passedByValue -Shell::Shell(std::shared_ptr commands, unsigned int context, unsigned int flags) - : commands_(std::move(commands)) +Shell::Shell(Stream & stream, std::shared_ptr commands, unsigned int context, unsigned int flags) + : stream_(stream) + , commands_(std::move(commands)) , flags_(flags) { enter_context(context); } -Shell::~Shell() { - uuid::log::Logger::unregister_handler(this); +void Shell::started() { +} + +bool Shell::running() const { + return !stopped_; } void Shell::start() { #ifdef EMSESP_DEBUG - uuid::log::Logger::register_handler(this, uuid::log::Level::DEBUG); // added by proddy - //uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // added by proddy + uuid::log::Logger::register_handler(this, uuid::log::Level::DEBUG); // added for EMS-ESP #else uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); #endif @@ -71,18 +73,11 @@ void Shell::start() { } display_banner(); display_prompt(); - shells_.insert(shared_from_this()); + registered_shells().insert(shared_from_this()); idle_time_ = uuid::get_uptime_ms(); started(); }; -void Shell::started() { -} - -bool Shell::running() const { - return !stopped_; -} - void Shell::stop() { if (mode_ == Mode::BLOCKING) { auto * blocking_data = reinterpret_cast(mode_data_.get()); @@ -108,6 +103,10 @@ bool Shell::exit_context() { } } +Commands::AvailableCommands Shell::available_commands() const { + return commands_->available_commands(*this); +} + void Shell::loop_one() { if (!running()) { return; @@ -144,7 +143,7 @@ void Shell::set_command_str(const char * str) { } void Shell::loop_normal() { - const int input = read_one_char(); + const int input = stream_.read(); if (input < 0) { check_idle_timeout(); @@ -158,8 +157,10 @@ void Shell::loop_normal() { // Interrupt (^C) line_buffer_.clear(); println(); - cursor_ = 0; - line_no_ = 0; + cursor_ = 0; + line_no_ = 0; + // prompt_displayed_ = false; + // display_prompt(); break; case '\x04': @@ -304,18 +305,20 @@ void Shell::loop_normal() { } // common for all, display the complete line + // added for EMS-ESP erase_current_line(); prompt_displayed_ = false; display_prompt(); + if (cursor_) { - printf(F("\033[%dD"), cursor_); + printf("\033[%dD", cursor_); } previous_ = c; // This is a hack to let TelnetStream know that command // execution is complete and that output can be flushed. - available_char(); + stream_.available(); idle_time_ = uuid::get_uptime_ms(); } @@ -326,7 +329,7 @@ Shell::PasswordData::PasswordData(const char * password_prompt, password_functio } void Shell::loop_password() { - const int input = read_one_char(); + const int input = stream_.read(); if (input < 0) { check_idle_timeout(); @@ -393,7 +396,7 @@ void Shell::loop_password() { // This is a hack to let TelnetStream know that command // execution is complete and that output can be flushed. - available_char(); + stream_.available(); idle_time_ = uuid::get_uptime_ms(); } @@ -414,9 +417,10 @@ void Shell::loop_delay() { function_copy(*this); - // if (running()) { - // display_prompt(); - // } + // TODO comment this like in v3.5? display_prompt + if (running()) { + display_prompt(); + } idle_time_ = uuid::get_uptime_ms(); } @@ -444,9 +448,10 @@ void Shell::loop_blocking() { stop(); } - // if (running()) { - // display_prompt(); - // } + // TODO comment this like in v3.5? display_prompt + if (running()) { + display_prompt(); + } idle_time_ = uuid::get_uptime_ms(); } @@ -513,14 +518,17 @@ void Shell::maximum_command_line_length(size_t length) { } void Shell::process_command() { + // added for EMS-ESP if (line_buffer_.empty()) { println(); return; } + uint8_t no = line_no_ ? line_no_ : MAX_LINES; while (--no) { line_old_[no] = line_old_[no - 1]; } + line_no_ = 0; line_old_[0] = line_buffer_; while (!line_buffer_.empty()) { @@ -542,18 +550,21 @@ void Shell::process_command() { if (!command_line->empty()) { if (commands_) { auto execution = commands_->execute_command(*this, std::move(command_line)); + if (execution.error != nullptr) { println(execution.error); } } else { - println(F("No commands configured")); + println("No commands configured"); } } - ::yield(); } - // if (running()) { - // display_prompt(); - // } + + // TODO comment this like in v3.5? display_prompt + if (running()) { + display_prompt(); + } + ::yield(); } void Shell::process_completion() { @@ -561,9 +572,11 @@ void Shell::process_completion() { if (!command_line->empty() && commands_) { auto completion = commands_->complete_command(*this, command_line); + bool redisplay = false; if (!completion.help.empty()) { println(); + redisplay = true; for (auto & help : completion.help) { std::string help_line = help.to_string(maximum_command_line_length_); @@ -573,8 +586,18 @@ void Shell::process_completion() { } if (!completion.replacement->empty()) { + if (!redisplay) { + erase_current_line(); + prompt_displayed_ = false; + redisplay = true; + } + line_buffer_ = completion.replacement.to_string(maximum_command_line_length_); } + + if (redisplay) { + display_prompt(); + } } ::yield(); @@ -599,9 +622,15 @@ void Shell::process_password(bool completed) { void Shell::invoke_command(const std::string & line) { erase_current_line(); - prompt_displayed_ = false; - line_buffer_ = line; - display_prompt(); + if (!line_buffer_.empty()) { + println(); + prompt_displayed_ = false; + } + if (!prompt_displayed_) { + display_prompt(); + } + line_buffer_ = line; + print(line_buffer_); process_command(); } diff --git a/lib/uuid-console/src/shell_log.cpp b/lib/uuid-console/src/shell_log.cpp index 3349270c0..decf47e67 100644 --- a/lib/uuid-console/src/shell_log.cpp +++ b/lib/uuid-console/src/shell_log.cpp @@ -22,6 +22,9 @@ #include #include +#if UUID_CONSOLE_THREAD_SAFE +#include +#endif #include #include @@ -30,8 +33,13 @@ namespace uuid { namespace console { -static const char __pstr__logger_name[] = "shell"; -const uuid::log::Logger Shell::logger_{(__pstr__logger_name), uuid::log::Facility::LPR}; +static const char __pstr__logger_name[] = "shell"; + +const uuid::log::Logger & Shell::logger() { + static const uuid::log::Logger logger_instance{reinterpret_cast(__pstr__logger_name), uuid::log::Facility::LPR}; + + return logger_instance; +} Shell::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr && content) : id_(id) @@ -39,6 +47,9 @@ Shell::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr message) { +#if UUID_CONSOLE_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif if (log_messages_.size() >= maximum_log_messages_) { log_messages_.pop_front(); } @@ -55,10 +66,18 @@ void Shell::log_level(uuid::log::Level level) { } size_t Shell::maximum_log_messages() const { +#if UUID_CONSOLE_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + return maximum_log_messages_; } void Shell::maximum_log_messages(size_t count) { +#if UUID_CONSOLE_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + maximum_log_messages_ = std::max((size_t)1, count); while (log_messages_.size() > maximum_log_messages_) { log_messages_.pop_front(); @@ -66,39 +85,71 @@ void Shell::maximum_log_messages(size_t count) { } void Shell::output_logs() { - if (!log_messages_.empty()) { - if (mode_ != Mode::DELAY) { - erase_current_line(); - prompt_displayed_ = false; - } +#if UUID_CONSOLE_THREAD_SAFE + std::unique_lock lock{mutex_}; +#endif - while (!log_messages_.empty()) { - auto message = std::move(log_messages_.front()); - log_messages_.pop_front(); + if (log_messages_.empty()) + return; - print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3)); - printf(F(" %c %lu: [%S] "), uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name); + size_t count = std::max((size_t)1, MAX_LOG_MESSAGES); + auto message = log_messages_.front(); - if ((message.content_->level == uuid::log::Level::ERR) || (message.content_->level == uuid::log::Level::WARNING)) { - print(COLOR_RED); - println(message.content_->text); - print(COLOR_RESET); - } else if (message.content_->level == uuid::log::Level::INFO) { - print(COLOR_YELLOW); - println(message.content_->text); - print(COLOR_RESET); - } else if (message.content_->level == uuid::log::Level::DEBUG) { - print(COLOR_CYAN); - println(message.content_->text); - print(COLOR_RESET); - } else { - println(message.content_->text); - } + log_messages_.pop_front(); +#if UUID_CONSOLE_THREAD_SAFE + lock.unlock(); +#endif - ::yield(); - } - display_prompt(); + if (mode_ != Mode::DELAY) { + erase_current_line(); + prompt_displayed_ = false; } + + while (1) { + print(uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3)); + printf(" %c %lu: [%s] ", uuid::log::format_level_char(message.content_->level), message.id_, message.content_->name); + + if ((message.content_->level == uuid::log::Level::ERR) || (message.content_->level == uuid::log::Level::WARNING)) { + print(COLOR_RED); + println(message.content_->text); + print(COLOR_RESET); + } else if (message.content_->level == uuid::log::Level::INFO) { + print(COLOR_YELLOW); + println(message.content_->text); + print(COLOR_RESET); + } else if (message.content_->level == uuid::log::Level::DEBUG) { + print(COLOR_CYAN); + println(message.content_->text); + print(COLOR_RESET); + } else { + println(message.content_->text); + } + + ::yield(); + + count--; + if (count == 0) { + break; + } + +#if UUID_CONSOLE_THREAD_SAFE + lock.lock(); +#endif + if (log_messages_.empty()) { +#if UUID_CONSOLE_THREAD_SAFE + lock.unlock(); +#endif + break; + } + + message = log_messages_.front(); + log_messages_.pop_front(); +#if UUID_CONSOLE_THREAD_SAFE + lock.unlock(); +#endif + } + + display_prompt(); } } // namespace console diff --git a/lib/uuid-console/src/shell_loop_all.cpp b/lib/uuid-console/src/shell_loop_all.cpp index 72da9b777..8fb1d90dc 100644 --- a/lib/uuid-console/src/shell_loop_all.cpp +++ b/lib/uuid-console/src/shell_loop_all.cpp @@ -1,6 +1,6 @@ /* * uuid-console - Microcontroller console shell - * Copyright 2019 Simon Arlott + * Copyright 2019,2021 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,15 +25,21 @@ namespace uuid { namespace console { -std::set> Shell::shells_; +std::set> & Shell::registered_shells() { + static std::set> shells; + + return shells; +} void Shell::loop_all() { - for (auto shell = shells_.begin(); shell != shells_.end();) { + auto & shells = registered_shells(); + + for (auto shell = shells.begin(); shell != shells.end();) { shell->get()->loop_one(); // This avoids copying the shared_ptr every time loop_one() is called if (!shell->get()->running()) { - shell = shells_.erase(shell); + shell = shells.erase(shell); } else { shell++; } diff --git a/lib/uuid-console/src/shell_print.cpp b/lib/uuid-console/src/shell_print.cpp index b1d2c84c4..d45eb585b 100644 --- a/lib/uuid-console/src/shell_print.cpp +++ b/lib/uuid-console/src/shell_print.cpp @@ -51,16 +51,6 @@ size_t Shell::printf(const char * format, ...) { return len; } -size_t Shell::printf(const __FlashStringHelper * format, ...) { - va_list ap; - - va_start(ap, format); - size_t len = vprintf(format, ap); - va_end(ap); - - return len; -} - size_t Shell::printfln(const char * format, ...) { va_list ap; @@ -72,17 +62,6 @@ size_t Shell::printfln(const char * format, ...) { return len; } -size_t Shell::printfln(const __FlashStringHelper * format, ...) { - va_list ap; - - va_start(ap, format); - size_t len = vprintf(format, ap); - va_end(ap); - - len += println(); - return len; -} - size_t Shell::vprintf(const char * format, va_list ap) { size_t print_len = 0; va_list copy_ap; @@ -101,64 +80,26 @@ size_t Shell::vprintf(const char * format, va_list ap) { return print_len; } -size_t Shell::vprintf(const __FlashStringHelper * format, va_list ap) { - size_t print_len = 0; - va_list copy_ap; - - va_copy(copy_ap, ap); - - int format_len = ::vsnprintf_P(nullptr, 0, reinterpret_cast(format), ap); - if (format_len > 0) { - std::string text(static_cast(format_len), '\0'); - - ::vsnprintf_P(&text[0], text.capacity() + 1, reinterpret_cast(format), copy_ap); - print_len = print(text); - } - - va_end(copy_ap); - return print_len; -} - -// modified by proddy void Shell::print_all_available_commands() { - /* - commands_->for_each_available_command(*this, [this](std::vector & name, std::vector & arguments) { - CommandLine command_line{name, arguments}; + // TODO add back sorting of commands? - command_line.escape_initial_parameters(name.size()); - name.clear(); - arguments.clear(); + for (auto & available_command : available_commands()) { + CommandLine command_line{available_command.name(), available_command.arguments()}; + + command_line.escape_initial_parameters(available_command.name().size()); println(command_line.to_string(maximum_command_line_length())); - }); - */ - - // changed by proddy - sort the help commands - std::list sorted_cmds; - - commands_->for_each_available_command(*this, [&](std::vector & name, std::vector & arguments) { - CommandLine command_line{name, arguments}; - command_line.escape_initial_parameters(name.size()); - name.clear(); - arguments.clear(); - sorted_cmds.push_back(command_line.to_string(maximum_command_line_length())); - }); - - sorted_cmds.sort(); - for (auto & cl : sorted_cmds) { - // print(" "); - println(cl); } } void Shell::erase_current_line() { - print(F("\033[0G\033[K")); + print("\033[0G\033[K"); } void Shell::erase_characters(size_t count) { print(std::string(count, '\x08')); - print(F("\033[K")); + print("\033[K"); } } // namespace console -} // namespace uuid +} // namespace uuid \ No newline at end of file diff --git a/lib/uuid-console/src/shell_stream.cpp b/lib/uuid-console/src/shell_stream.cpp index 7aea12050..b09eecef2 100644 --- a/lib/uuid-console/src/shell_stream.cpp +++ b/lib/uuid-console/src/shell_stream.cpp @@ -1,6 +1,6 @@ /* * uuid-console - Microcontroller console shell - * Copyright 2019 Simon Arlott + * Copyright 2019,2022 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,12 +28,12 @@ int Shell::available() { if (mode_ == Mode::BLOCKING) { auto * blocking_data = reinterpret_cast(mode_data_.get()); - if (!available_char()) { + if (!stream_.available()) { return 0; } if (blocking_data->consume_line_feed_) { - const int input = peek_one_char(); + const int input = stream_.peek(); if (input >= 0) { const unsigned char c = input; @@ -42,7 +42,7 @@ int Shell::available() { if (previous_ == '\x0D' && c == '\x0A') { // Consume the first LF following a CR - read_one_char(); + stream_.read(); previous_ = c; return available(); } @@ -62,7 +62,7 @@ int Shell::available() { int Shell::read() { if (mode_ == Mode::BLOCKING) { auto * blocking_data = reinterpret_cast(mode_data_.get()); - const int input = read_one_char(); + const int input = stream_.read(); if (input >= 0) { const unsigned char c = input; @@ -90,7 +90,7 @@ int Shell::read() { int Shell::peek() { if (mode_ == Mode::BLOCKING) { auto * blocking_data = reinterpret_cast(mode_data_.get()); - const int input = peek_one_char(); + const int input = stream_.peek(); if (blocking_data->consume_line_feed_) { if (input >= 0) { @@ -100,7 +100,7 @@ int Shell::peek() { if (previous_ == '\x0D' && c == '\x0A') { // Consume the first LF following a CR - read_one_char(); + stream_.read(); previous_ = c; return peek(); } @@ -113,6 +113,15 @@ int Shell::peek() { } } +size_t Shell::write(uint8_t data) { + return stream_.write(data); +} + +size_t Shell::write(const uint8_t * buffer, size_t size) { + return stream_.write(buffer, size); +} + + void Shell::flush() { // This is a pure virtual function in Arduino's Stream class, which // makes no sense because that class is for input and this is an @@ -122,4 +131,4 @@ void Shell::flush() { } // namespace console -} // namespace uuid +} // namespace uuid \ No newline at end of file diff --git a/lib/uuid-console/src/stream_console.cpp b/lib/uuid-console/src/stream_console.cpp deleted file mode 100644 index caacdc46e..000000000 --- a/lib/uuid-console/src/stream_console.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * uuid-console - Microcontroller console shell - * Copyright 2019 Simon Arlott - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include - -#include -#include - -namespace uuid { - -namespace console { - -StreamConsole::StreamConsole(Stream & stream) - : Shell() - , stream_(stream) { -} - -// cppcheck-suppress passedByValue -StreamConsole::StreamConsole(std::shared_ptr commands, Stream & stream, unsigned int context, unsigned int flags) - : Shell(std::move(commands), context, flags) - , stream_(stream) { -} - -size_t StreamConsole::write(uint8_t data) { - return stream_.write(data); -} - -size_t StreamConsole::write(const uint8_t * buffer, size_t size) { - return stream_.write(buffer, size); -} - -bool StreamConsole::available_char() { - return stream_.available() > 0; -} - -int StreamConsole::read_one_char() { - return stream_.read(); -} - -int StreamConsole::peek_one_char() { - return stream_.peek(); -} - -} // namespace console - -} // namespace uuid diff --git a/lib/uuid-console/src/uuid/console.h b/lib/uuid-console/src/uuid/console.h index fc15563d7..99fb1ed00 100644 --- a/lib/uuid-console/src/uuid/console.h +++ b/lib/uuid-console/src/uuid/console.h @@ -1,6 +1,6 @@ /* * uuid-console - Microcontroller console shell - * Copyright 2019 Simon Arlott + * Copyright 2019,2021-2022 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,24 @@ #include #include +#ifndef UUID_LOG_THREAD_SAFE +#define UUID_LOG_THREAD_SAFE 0 +#endif + +#ifndef UUID_COMMON_STD_MUTEX_AVAILABLE +#define UUID_COMMON_STD_MUTEX_AVAILABLE 0 +#endif + +#if UUID_COMMON_STD_MUTEX_AVAILABLE +#define UUID_CONSOLE_THREAD_SAFE 1 +#else +#define UUID_CONSOLE_THREAD_SAFE 0 +#endif + +#if UUID_CONSOLE_THREAD_SAFE +#include +#endif + namespace uuid { /** @@ -46,24 +65,754 @@ namespace uuid { */ namespace console { +/** + * Thread-safe status of the library. + * + * @since 1.0.0 + */ +#if UUID_COMMON_THREAD_SAFE && UUID_LOG_THREAD_SAFE && UUID_CONSOLE_THREAD_SAFE +static constexpr bool thread_safe = true; +#else +static constexpr bool thread_safe = false; +#endif + class Commands; +class CommandLine; +class Shell; + +/** + * Container of commands for use by a Shell. + * + * These should normally be stored in a std::shared_ptr and reused. + * + * @since 0.1.0 + */ +class Commands { + private: + class Command; + + public: + struct Completion; + + /** + * Result of a command execution operation. + * + * @since 0.1.0 + */ + struct Execution { + const char * error; /*!< Error message if the command could not be executed. @since 0.1.0 */ + }; + + /** + * Function to handle a command. + * + * @param[in] shell Shell instance that is executing the command. + * @param[in] arguments Command line arguments. + * @since 0.1.0 + */ + using command_function = std::function & arguments)>; + /** + * Function to obtain completions for a command line. + * + * This is a vector instead of set so that a custom sort order can + * be used. It should normally be sorted lexicographically so that + * the list of options is not confusing. + * + * @param[in] shell Shell instance that has a command line matching + * this command. + * @param[in] current_arguments Command line arguments prior to (but + * excluding) the argument being + * completed. + * @param[in] next_argument Next argument (the one being completed). + * @return Possible values for the next argument on the command + * line. + * @since 2.0.0 + */ + using argument_completion_function = + std::function(Shell & shell, const std::vector & current_arguments, const std::string & next_argument)>; + + /** + * Available command for execution on a Shell. + * + * @since 0.9.0 + */ + class AvailableCommand { + public: + /** + * Construct a new command that is available for execution on a Shell. + * + * @since 0.9.0 + */ + AvailableCommand(const Command & command); + + /** + * Get the name of the command. + * + * @return Name of the command as a std::vector of strings. + * @since 0.9.0 + */ + inline const std::vector & name() const { + return name_; + } + + /** + * Get the help text of the command's arguments. + * + * @return Help text for arguments that the command accepts as a std::vector of strings. + * @since 0.9.0 + */ + inline const std::vector & arguments() const { + return arguments_; + } + + /** + * Get the function to be used when the command is executed. + * + * @return Function that can be used to execute the command. + * @since 0.9.0 + */ + inline const command_function & function() const { + return command_.function_; + }; + + /** + * Get the function to be used to perform argument completions for the command. + * + * @return Function that can be used to perform argument completions for the command. + * @since 0.9.0 + */ + inline const argument_completion_function & arg_function() const { + return command_.arg_function_; + }; + + /** + * Get the shell flags that must be set for this command to be available. + * + * @return Shell flags. + * @since 0.9.0 + */ + inline int flags() const { + return command_.flags_; + } + + /** + * Get the shell flags that must not be set for this command to be available. + * + * @return Shell flags. + * @since 0.9.0 + */ + inline int not_flags() const { + return command_.not_flags_; + } + + private: + const Commands::Command & command_; /*!< Command that is available. @since 0.9.0 */ + std::vector name_; /*!< Name of the command. @since 0.9.0 */ + std::vector arguments_; /*!< Help text for arguments that the command accepts. @since 0.9.0 */ + }; + + /** + * Available commands in the shell's context with the current flags. + * + * Iterators are invalidated if the commands, context or flags for + * the shell change. + * + * @since 0.9.0 + */ + class AvailableCommands { + public: + using command_iterator = std::multimap::const_iterator; /*!< Type of the underlying command iterator. @since 0.9.0 */ + + /** + * Iterator of available commands for execution on a Shell. + * + * @since 0.9.0 + */ + class const_iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; /*!< Category of this iterator (bidirectional). @since 0.9.0 */ + using difference_type = std::size_t; /*!< Type used by std::distance(). @since 0.9.0 */ + using value_type = AvailableCommand; /*!< Type of this iterator's values. @since 0.9.0 */ + using pointer = const value_type *; /*!< Pointer type of this iterator. @since 0.9.0 */ + using reference = const value_type &; /*!< Reference type of this iterator. @since 0.9.0 */ + + /** + * Create an iterator describing an available command in a + * shell with the current flags. + * + * Iterators are invalidated if the commands, context or flags for + * the shell change. + * + * @param[in] shell Shell that is accessing commands. + * @param[in] begin Beginning of command iterators. + * @param[in] command Initial command iterator. + * @param[in] end End of command iterators. + * @since 0.9.0 + */ + const_iterator(const Shell & shell, const command_iterator & begin, const command_iterator & command, const command_iterator & end); + + /** + * Dereference the current available command. + * + * @returns A pointer to the current available command, + * that remains valid only while the iterator at + * this position exists. + * @since 0.9.0 + */ + inline reference operator*() const { + return *available_command_; + } + /** + * Access the current available command. + * + * @returns A reference to the current available command, + * that remains valid only while the iterator at + * this position exists. + * @since 0.9.0 + */ + inline pointer operator->() const { + return available_command_.get(); + } + + /** + * Pre-increment the current iterator to the next + * available command. + * + * @returns The iterator to the next available command. + * @since 0.9.0 + */ + const_iterator & operator++(); + /** + * Post-increment the current iterator to the next + * available command. + * + * @returns The current iterator. + * @since 0.9.0 + */ + inline const_iterator operator++(int) { + auto tmp = *this; + ++(*this); + return tmp; + } + + /** + * Pre-decrement the current iterator to the previous + * available command. + * + * @returns The iterator to the previous available command. + * @since 0.9.0 + */ + const_iterator & operator--(); + /** + * Post-decrement the current iterator to the previous + * available command. + * + * @returns The current iterator. + * @since 0.9.0 + */ + inline const_iterator operator--(int) { + auto tmp = *this; + --(*this); + return tmp; + } + + /** + * Compare an available commands iterator to another available + * commands iterator for equality. + * + * @param[in] lhs Left-hand side available commands iterator. + * @param[in] rhs Right-hand side available commands iterator. + * @return True if the available commands iterators are equal, otherwise false. + * @since 0.9.0 + */ + friend inline bool operator==(const const_iterator & lhs, const const_iterator & rhs) { + return lhs.command_ == rhs.command_; + } + /** + * Compare an available commands iterator to another available + * commands iterator for inequality. + * + * @param[in] lhs Left-hand side available commands iterator. + * @param[in] rhs Right-hand side available commands iterator. + * @return True if the available commands iterators are not equal, otherwise false. + * @since 0.9.0 + */ + friend inline bool operator!=(const const_iterator & lhs, const const_iterator & rhs) { + return lhs.command_ != rhs.command_; + } + + private: + /** + * Update the available command from the current command iterator. + * + * @since 0.9.0 + */ + void update(); + + const Shell & shell_; /*!< Shell that is accessing commands. @since 0.9.0 */ + const command_iterator begin_; /*!< Beginning of command iterators. @since 0.9.0 */ + command_iterator command_; /*!< Current command iterator. @since 0.9.0 */ + const command_iterator end_; /*!< End of command iterators. @since 0.9.0 */ + std::shared_ptr available_command_; /*!< Current available command. @since 0.9.0 */ + }; + + /** + * Create an iterable object describing available commands in a + * shell with the current flags. + * + * Iterators are invalidated if the commands, context or flags for + * the shell change. + * + * @param[in] shell Shell that is accessing commands. + * @param[in] begin Beginning of command iterators. + * @param[in] end End of command iterators. + * @since 0.9.0 + */ + AvailableCommands(const Shell & shell, const command_iterator & begin, const command_iterator & end); + + /** + * Returns an iterator to the first command. + * + * @since 0.9.0 + */ + inline const_iterator begin() const { + return cbegin(); + } + /** + * Returns an iterator to the first command. + * + * @since 0.9.0 + */ + const_iterator cbegin() const; + /** + * Returns an iterator to the element following the last command. + * + * @since 0.9.0 + */ + inline const_iterator end() const { + return cend(); + } + /** + * Returns an iterator to the element following the last command. + * + * @since 0.9.0 + */ + const_iterator cend() const; + + private: + const Shell & shell_; /*!< Shell that is accessing commands. @since 0.9.0 */ + command_iterator begin_; /*!< Beginning of command iterators. @since 0.9.0 */ + command_iterator end_; /*!< End of command iterators. @since 0.9.0 */ + }; + + /** + * Construct a new container of commands for use by a Shell. + * + * This should normally be stored in a std::shared_ptr and reused. + * + * @since 0.1.0 + */ + Commands() = default; + ~Commands() = default; + + /** + * Add a command with no arguments to the list of commands in this + * container. + * + * The shell context for the command will default to 0 and not + * require any flags for it to be available. + * + * @param[in] name Name of the command as a std::vector of strings. + * @param[in] function Function to be used when the command is + * executed. + * @since 0.2.0 + */ + void add_command(const string_vector & name, command_function function); + /** + * Add a command with arguments to the list of commands in this + * container. + * + * The shell context for the command will default to 0 and not + * require any flags for it to be available. + * + * @param[in] name Name of the command as a std::vector of strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @since 0.2.0 + */ + void add_command(const string_vector & name, const string_vector & arguments, command_function function); + /** + * Add a command with arguments and automatic argument completion + * to the list of commands in this container. + * + * The shell context for the command will default to 0 and not + * require any flags for it to be available. + * + * @param[in] name Name of the command as a std::vector of strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @param[in] arg_function Function to be used to perform argument + * completions for this command. + * @since 0.2.0 + */ + void add_command(const string_vector & name, const string_vector & arguments, command_function function, argument_completion_function arg_function); + + /** + * Add a command with no arguments to the list of commands in this + * container. + * + * The command will not require any flags for it to be available. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] function Function to be used when the command is + * executed. + * @since 0.8.0 + */ + void add_command(unsigned int context, const string_vector & name, command_function function); + /** + * Add a command with arguments to the list of commands in this + * container. + * + * The command will not require any flags for it to be available. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @since 0.8.0 + */ + void add_command(unsigned int context, const string_vector & name, const string_vector & arguments, command_function function); + /** + * Add a command with arguments and automatic argument completion + * to the list of commands in this container. + * + * The command will not require any flags for it to be available. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @param[in] arg_function Function to be used to perform argument + * completions for this command. + * @since 0.8.0 + */ + void add_command(unsigned int context, + const string_vector & name, + const string_vector & arguments, + command_function function, + argument_completion_function arg_function); + /** + * Add a command with no arguments to the list of commands in this + * container. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] flags Shell flags that must be set for this command + * to be available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] function Function to be used when the command is + * executed. + * @since 0.1.0 + */ + void add_command(unsigned int context, unsigned int flags, const string_vector & name, command_function function); + /** + * Add a command with arguments to the list of commands in this + * container. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] flags Shell flags that must be set for this command + * to be available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @since 0.1.0 + */ + void add_command(unsigned int context, unsigned int flags, const string_vector & name, const string_vector & arguments, command_function function); + /** + * Add a command with arguments and automatic argument completion + * to the list of commands in this container. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] flags Shell flags that must be set for this command + * to be available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @param[in] arg_function Function to be used to perform argument + * completions for this command. + * @since 0.1.0 + */ + void add_command(unsigned int context, + unsigned int flags, + const string_vector & name, + const string_vector & arguments, + command_function function, + argument_completion_function arg_function); + /** + * Add a command with no arguments to the list of commands in this + * container. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] flags Shell flags that must be set for this command + * to be available. + * @param[in] not_flags Shell flags that must not be set for this command + * to be available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] function Function to be used when the command is + * executed. + * @since 0.8.0 + */ + void add_command(unsigned int context, unsigned int flags, unsigned int not_flags, const string_vector & name, command_function function); + /** + * Add a command with arguments to the list of commands in this + * container. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] flags Shell flags that must be set for this command + * to be available. + * @param[in] not_flags Shell flags that must not be set for this command + * to be available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @since 0.8.0 + */ + void add_command(unsigned int context, + unsigned int flags, + unsigned int not_flags, + const string_vector & name, + const string_vector & arguments, + command_function function); + /** + * Add a command with arguments and automatic argument completion + * to the list of commands in this container. + * + * @param[in] context Shell context in which this command is + * available. + * @param[in] flags Shell flags that must be set for this command + * to be available. + * @param[in] not_flags Shell flags that must not be set for this command + * to be available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @param[in] arg_function Function to be used to perform argument + * completions for this command. + * @since 0.8.0 + */ + void add_command(unsigned int context, + unsigned int flags, + unsigned int not_flags, + const string_vector & name, + const string_vector & arguments, + command_function function, + argument_completion_function arg_function); + + /** + * Execute a command for a Shell if it exists in the current + * context and with the current flags. + * + * @param[in] shell Shell that is executing the command. + * @param[in] command_line Command line parameters. + * @return An object describing the result of the command execution + * operation. + * @since 0.1.0 + */ + Execution execute_command(Shell & shell, CommandLine && command_line); + + /** + * Complete a partial command for a Shell if it exists in the + * current context and with the current flags. + * + * @param[in] shell Shell that is completing the command. + * @param[in] command_line Command line parameters. + * @return An object describing the result of the command + * completion operation. + * @since 0.1.0 + */ + Completion complete_command(Shell & shell, const CommandLine & command_line); + + /** + * Get the available commands in the current context and with the + * current flags. + * + * This iterable object and its iterators are invalidated if the + * commands, context or flags for the shell change. + * + * @param[in] shell Shell that is accessing commands. + * @return An iterable object describing the available commands. + * @since 0.9.0 + */ + AvailableCommands available_commands(const Shell & shell) const; + + private: + /** + * Command for execution on a Shell. + * @since 0.1.0 + */ + class Command { + public: + /** + * Create a command for execution on a Shell. + * + * @param[in] flags Shell flags that must be set for this command + * to be available. + * @param[in] not_flags Shell flags that must not be set for this + * command to be available. + * @param[in] name Name of the command as a std::vector of + * strings. + * @param[in] arguments Help text for arguments that the command + * accepts as a std::vector of strings + * (use "<" to indicate a required argument). + * @param[in] function Function to be used when the command is + * executed. + * @param[in] arg_function Function to be used to perform argument + * completions for this command. + * @since 0.8.0 + */ + Command(unsigned int flags, + unsigned int not_flags, + const string_vector name, + const string_vector arguments, + command_function function, + argument_completion_function arg_function); + ~Command(); + + /** + * Determine the minimum number of arguments for this command + * based on the help text for the arguments that begin with the + * "<" character. + * + * @return The minimum number of arguments for this command. + * @since 0.1.0 + */ + size_t minimum_arguments() const; + /** + * Determine the maximum number of arguments for this command + * based on the length of help text for the arguments. + * + * @return The maximum number of arguments for this command. + * @since 0.1.0 + */ + inline size_t maximum_arguments() const { + return arguments_.size(); + } + + unsigned int flags_; /*!< Shell flags that must be set for this command to be available. @since 0.1.0 */ + unsigned int not_flags_; /*!< Shell flags that must not be set for this command to be available. @since 0.8.0 */ + const string_vector name_; /*!< Name of the command as a std::vector of strings. @since 0.1.0 */ + const string_vector arguments_; /*!< Help text for arguments that the command accepts as a std::vector of strings. @since 0.1.0 */ + command_function function_; /*!< Function to be used when the command is executed. @since 0.1.0 */ + argument_completion_function arg_function_; /*!< Function to be used to perform argument completions for this command. @since 0.1.0 */ + + private: + Command(const Command &) = delete; + Command & operator=(const Command &) = delete; + }; + + /** + * Result of a command find operation. + * + * Each matching command is returned in a map grouped by size. + * @since 0.1.0 + */ + struct Match { + std::multimap exact; /*!< Commands that match the command line exactly, grouped by the size of the command names. @since 0.1.0 */ + std::multimap partial; /*!< Commands that the command line partially matches, grouped by the size of the command names. @since 0.1.0 */ + std::vector all; /*!< Commands that match the command line, in defined order. @since 0.7.6 */ + }; + + /** + * Find commands by matching them against the command line. + * + * @param[in] shell Shell that is accessing commands. + * @param[in] command_line Command line parameters. + * @return An object describing the result of the command find + * operation. + * @since 0.1.0 + */ + Match find_command(Shell & shell, const CommandLine & command_line); + + /** + * Find the longest common prefix from a shortest match of commands. + * + * @param[in] commands All commands that matched (at least 2). + * @param[out] longest_name The longest common prefix as a list of + * strings. + * @return True if the longest common prefix is made up of whole + * components, false if the last part is constructed from + * a partial component. + * @since 0.1.0 + */ + static bool find_longest_common_prefix(const std::multimap & commands, std::vector & longest_name); + + /** + * Find the longest common prefix from a list of potential arguments. + * + * @param[in] arguments All potential arguments (at least 2). + * @return The longest common prefix, which could be empty. + * @since 0.1.0 + */ + static std::string find_longest_common_prefix(const std::vector & arguments); + + std::multimap commands_; /*!< Commands stored in this container, separated by context. @since 0.1.0 */ +}; /** * Base class for a command shell. * * Must be constructed within a std::shared_ptr. * - * Requires a derived class to provide input/output. Derived classes - * should use virtual inheritance to allow the behaviour to be further - * extended. - * * @since 0.1.0 */ class Shell : public std::enable_shared_from_this, public uuid::log::Handler, public ::Stream { public: - static constexpr size_t MAX_COMMAND_LINE_LENGTH = 80; /*!< Maximum length of a command line. @since 0.1.0 */ - static constexpr size_t MAX_LOG_MESSAGES = 20; /*!< Maximum number of log messages to buffer before they are output. @since 0.1.0 */ - static constexpr uint8_t MAX_LINES = 5; /*!< Maximum lines in buffer */ + static constexpr size_t MAX_COMMAND_LINE_LENGTH = 80; /*!< Maximum length of a command line. @since 0.1.0 */ + static constexpr size_t MAX_LOG_MESSAGES = 20; /*!< Maximum number of log messages to buffer before they are output. @since 0.1.0 */ + + // added for EMS-ESP + static constexpr uint8_t MAX_LINES = 5; /*!< Maximum lines in buffer */ /** * Function to handle the response to a password entry prompt. @@ -96,7 +845,25 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand */ using blocking_function = std::function; - ~Shell() override; + /** + * Create a new Shell operating on a Stream with the given commands, + * default context and initial flags. + * + * The default context is put on the stack and cannot be removed. + * + * The Stream must remain valid until the Shell has been destroyed. + * Monitor the Shell using a std::weak_ptr and destroy the Stream + * only after it has expired. + * + * @param[in] stream Stream used for the input/output of this shell. + * @param[in] commands Commands available for execution in this shell. + * @param[in] context Default context for the shell. + * @param[in] flags Initial flags for the shell. + * @since 3.0.0 + */ + Shell(Stream & stream, std::shared_ptr commands, unsigned int context = 0, unsigned int flags = 0); + + ~Shell() = default; /** * Loop through all registered shell objects. @@ -118,7 +885,6 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * The started() function will be called after startup is complete. * * Do not call this function more than once. - * Do not call this function from a static initializer. * * @since 0.1.0 */ @@ -159,9 +925,7 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * @return Logger instance. * @since 0.1.0 */ - static inline const uuid::log::Logger & logger() { - return logger_; - } + static const uuid::log::Logger & logger(); /** * Add a new log message. * @@ -172,7 +936,7 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * @param[in] message New log message, shared by all handlers. * @since 0.1.0 */ - virtual void operator<<(std::shared_ptr message) override; + virtual void operator<<(std::shared_ptr message); /** * Get the current log level. * @@ -293,6 +1057,22 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand inline void add_flags(unsigned int flags) { flags_ |= flags; } + /** + * Check if the current flags include all of the specified flags + * and none of the specified not_flags. + * + * Flags are not affected by execution context. The current flags + * affect which commands are available (for access control). + * + * @param[in] flags Flag bits to check for presence of. + * @param[in] not_flags Flag bits to check for absence of. + * @return True if the current flags includes all of the specified + * flags and none of the specified not_flags, otherwise false. + * @since 0.8.0 + */ + inline bool has_flags(unsigned int flags, unsigned int not_flags = 0) const { + return has_all_flags(flags) && !has_any_flags(not_flags); + } /** * Check if the current flags include all of the specified flags. * @@ -302,11 +1082,25 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * @param[in] flags Flag bits to check for. * @return True if the current flags includes all of the specified * flags, otherwise false. - * @since 0.1.0 + * @since 0.8.0 */ - inline bool has_flags(unsigned int flags) const { + inline bool has_all_flags(unsigned int flags) const { return (flags_ & flags) == flags; } + /** + * Check if the current flags include any of the specified flags. + * + * Flags are not affected by execution context. The current flags + * affect which commands are available (for access control). + * + * @param[in] flags Flag bits to check for. + * @return True if the current flags includes any of the specified + * flags, otherwise false. + * @since 0.8.0 + */ + inline bool has_any_flags(unsigned int flags) const { + return (flags_ & flags) != 0; + } /** * Remove one or more flags from the current flags. * @@ -422,18 +1216,18 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * * @param[in] data Data to be output. * @return The number of bytes that were output. - * @since 0.1.0 + * @since 3.0.0 */ - size_t write(uint8_t data) override = 0; + size_t write(uint8_t data) final override; /** * Write an array of bytes to the output stream. * * @param[in] buffer Buffer to be output. * @param[in] size Length of the buffer. * @return The number of bytes that were output. - * @since 0.1.0 + * @since 3.0.0 */ - size_t write(const uint8_t * buffer, size_t size) override = 0; + size_t write(const uint8_t * buffer, size_t size) final override; /** * Does nothing. * @@ -442,9 +1236,9 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * output function. Later versions move it to Print as an empty * virtual function so this is here for backward compatibility. * - * @since 0.2.0 + * @since 3.0.0 */ - void flush() override; + void flush() final override; using ::Print::print; /*!< Include standard Arduino print() functions. */ /** @@ -473,15 +1267,7 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * @since 0.1.0 */ size_t printf(const char * format, ...) /* __attribute__((format(printf, 2, 3))) */; - /** - * Output a message. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @return The number of bytes that were output. - * @since 0.1.0 - */ - size_t printf(const __FlashStringHelper * format, ...) /* __attribute__((format(printf, 2, 3))) */; + /** * Output a message followed by CRLF end of line characters. * @@ -491,16 +1277,18 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * @since 0.1.0 */ size_t printfln(const char * format, ...) /* __attribute__((format (printf, 2, 3))) */; - /** - * Output a message followed by CRLF end of line characters. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @return The number of bytes that were output, including CRLF. - * @since 0.1.0 - */ - size_t printfln(const __FlashStringHelper * format, ...) /* __attribute__((format(printf, 2, 3))) */; + /** + * Get the available commands in the current context and with the + * current flags. + * + * The iterable object and its iterators are invalidated if the + * commands, context or flags for the shell change. + * + * @return An iterable object describing the available commands. + * @since 0.9.0 + */ + Commands::AvailableCommands available_commands() const; /** * Output a list of all available commands with their arguments. * @@ -508,45 +1296,7 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand */ void print_all_available_commands(); - /** - * Invoke a command on the shell. - * - * This will output a prompt with the provided command line and - * then try to execute it. - * - * Intended for use from end_of_transmission() to execute an "exit" - * or "logout" command. - * - * @param[in] line The command line to be executed. - * @since 0.1.0 - */ - void invoke_command(const std::string & line); - protected: - /** - * Default constructor used by intermediate derived classes for - * multiple inheritance. - * - * This does not initialise the shell completely so the outer - * derived class must call the public constructor or there will be - * no commands. Does not put any default context on the stack. - * - * @since 0.1.0 - */ - Shell() = default; - /** - * Create a new Shell with the given commands, default context and - * initial flags. - * - * The default context is put on the stack and cannot be removed. - * - * @param[in] commands Commands available for execution in this shell. - * @param[in] context Default context for the shell. - * @param[in] flags Initial flags for the shell. - * @since 0.1.0 - */ - Shell(std::shared_ptr commands, unsigned int context = 0, unsigned int flags = 0); - /** * Output ANSI escape sequence to erase the current line. * @@ -638,6 +1388,22 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand */ virtual void stopped(); + // change for EMS-ESP to make public so can be used in test.cpp + public: + /** + * Invoke a command on the shell. + * + * This will output a prompt with the provided command line and + * then try to execute it. + * + * Intended for use from end_of_transmission() to execute an "exit" + * or "logout" command. + * + * @param[in] line The command line to be executed. + * @since 0.1.0 + */ + void invoke_command(const std::string & line); + private: /** * Current mode of the shell. @@ -731,7 +1497,7 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand explicit BlockingData(blocking_function && blocking_function); ~BlockingData() override = default; - blocking_function blocking_function_; /*!< Function execute on every loop_one(). @since 0.2.0 */ + blocking_function blocking_function_; /*!< Function executed on every loop_one(). @since 0.2.0 */ bool consume_line_feed_ = true; /*!< Stream input should try to consume the first line feed following a carriage return. @since 0.2.0 */ bool stop_ = false; /*!< There is a stop pending for the shell. @since 0.2.0 */ }; @@ -757,13 +1523,21 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand QueuedLogMessage(unsigned long id, std::shared_ptr && content); ~QueuedLogMessage() = default; - const unsigned long id_; /*!< Sequential identifier for this log message. @since 0.1.0 */ - const std::shared_ptr content_; /*!< Log message content. @since 0.1.0 */ + unsigned long id_; /*!< Sequential identifier for this log message. @since 0.1.0 */ + std::shared_ptr content_; /*!< Log message content. @since 0.1.0 */ }; Shell(const Shell &) = delete; Shell & operator=(const Shell &) = delete; + /** + * Get registered running shells to be executed. + * + * @return Registered running shells to be executed. + * @since 0.7.4 + */ + static std::set> & registered_shells(); + /** * Perform one execution step in Mode::NORMAL mode. * @@ -797,29 +1571,6 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand */ void loop_blocking(); - /** - * Check for at least one character of available input. - * - * @return True if a character is available, otherwise false. - * @since 0.2.0 - */ - virtual bool available_char() = 0; - /** - * Read one character from the available input. - * - * @return An unsigned char if input is available, otherwise -1. - * @since 0.1.0 - */ - virtual int read_one_char() = 0; - /** - * Read one character from the available input without advancing to - * the next one. - * - * @return An unsigned char if input is available, otherwise -1. - * @since 0.2.0 - */ - virtual int peek_one_char() = 0; - /** * Output a prompt on the shell. * @@ -884,33 +1635,27 @@ class Shell : public std::enable_shared_from_this, public uuid::log::Hand * @since 0.1.0 */ size_t vprintf(const char * format, va_list ap); - /** - * Output a message. - * - * @param[in] format Format string (flash string). - * @param[in] ap Variable arguments pointer for format string. - * @return The number of bytes that were output. - * @since 0.1.0 - */ - size_t vprintf(const __FlashStringHelper * format, va_list ap); - void set_command_str(const char * str); - static const uuid::log::Logger logger_; /*!< uuid::log::Logger instance for shells. @since 0.1.0 */ - static std::set> shells_; /*!< Registered running shells to be executed. @since 0.1.0 */ + // added for EMS-ESP + void set_command_str(const char * str); + std::string line_old_[MAX_LINES]; /*!< old Command line buffer.*/ + uint8_t line_no_ = 0; + uint8_t cursor_ = 0; /*!< cursor position from end of line */ + uint8_t esc_ = 0; /*!< esc sequence running */ - std::shared_ptr commands_; /*!< Commands available for execution in this shell. @since 0.1.0 */ - std::deque context_; /*!< Context stack for this shell. Should never be empty. @since 0.1.0 */ - unsigned int flags_ = 0; /*!< Current flags for this shell. Affects which commands are available. @since 0.1.0 */ - unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 0.1.0 */ - std::list log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */ + Stream & stream_; /*!< Stream used for the input/output of this shell. @since 3.0.0 */ + std::shared_ptr commands_; /*!< Commands available for execution in this shell. @since 0.1.0 */ + std::deque context_; /*!< Context stack for this shell. Affects which commands are available. Should never be empty. @since 0.1.0 */ + unsigned int flags_ = 0; /*!< Current flags for this shell. Affects which commands are available. @since 0.1.0 */ +#if UUID_CONSOLE_THREAD_SAFE + mutable std::mutex mutex_; /*!< Mutex for queued log messages. @since 1.0.0 */ +#endif + unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 0.1.0 */ + std::list log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */ size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */ - std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */ - std::string line_old_[MAX_LINES]; /*!< old Command line buffer.*/ - uint8_t line_no_ = 0; + std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */ size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */ unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */ - uint8_t cursor_ = 0; /*!< cursor position from end of line */ - uint8_t esc_ = 0; /*!< esc sequence running */ Mode mode_ = Mode::NORMAL; /*!< Current execution mode. @since 0.1.0 */ std::unique_ptr mode_data_ = nullptr; /*!< Data associated with the current execution mode. @since 0.1.0 */ bool stopped_ = false; /*!< Indicates that the shell has been stopped. @since 0.1.0 */ @@ -1101,419 +1846,19 @@ class CommandLine { }; /** - * Container of commands for use by a Shell. + * Result of a command completion operation. * - * These should normally be stored in a std::shared_ptr and reused. + * Each space-delimited parameter is a separate string. * * @since 0.1.0 */ -class Commands { - public: - /** - * Result of a command completion operation. - * - * Each space-delimited parameter is a separate string. - * - * @since 0.1.0 - */ - struct Completion { - std::list help; /*!< Suggestions for matching commands. @since 0.1.0 */ - CommandLine replacement; /*!< Replacement matching full or partial command line. @since 0.1.0 */ - }; - - /** - * Result of a command execution operation. - * - * @since 0.1.0 - */ - struct Execution { - const __FlashStringHelper * error; /*!< Error message if the command could not be executed. @since 0.1.0 */ - }; - - /** - * Function to handle a command. - * - * @param[in] shell Shell instance that is executing the command. - * @param[in] arguments Command line arguments. - * @since 0.1.0 - */ - using command_function = std::function & arguments)>; - /** - * Function to obtain completions for a command line. - * - * This is a vector instead of set so that a custom sort order can - * be used. It should normally be sorted lexicographically so that - * the list of options is not confusing. - * - * @param[in] shell Shell instance that has a command line matching - * this command. - * @param[in] arguments Command line arguments prior to (but - * excluding) the argument being completed. - * @return Possible values for the next argument on the command - * line. - * @since 0.1.0 - */ - using argument_completion_function = std::function(Shell & shell, const std::vector & arguments)>; - - /** - * Function to apply an operation to a command. - * - * @param[in] name Name of the command as a std::vector of strings. - * @param[in] arguments Help text for arguments that the command - * accepts as a std::vector of strings. - * @since 0.4.0 - */ - using apply_function = std::function & name, std::vector & arguments)>; - - /** - * Construct a new container of commands for use by a Shell. - * - * This should normally be stored in a std::shared_ptr and reused. - * - * @since 0.1.0 - */ - Commands() = default; - ~Commands() = default; - - /** - * Add a command with no arguments to the list of commands in this - * container. - * - * The shell context for the command will default to 0 and not - * require any flags for it to be available. - * - * @param[in] name Name of the command as a std::vector of flash - * strings. - * @param[in] function Function to be used when the command is - * executed. - * @since 0.2.0 - */ - void add_command(const string_vector & name, command_function function); - /** - * Add a command with arguments to the list of commands in this - * container. - * - * The shell context for the command will default to 0 and not - * require any flags for it to be available. - * - * @param[in] name Name of the command as a std::vector of flash - * strings. - * @param[in] arguments Help text for arguments that the command - * accepts as a std::vector of flash strings - * (use "<" to indicate a required argument). - * @param[in] function Function to be used when the command is - * executed. - * @since 0.2.0 - */ - void add_command(const string_vector & name, const string_vector & arguments, command_function function); - /** - * Add a command with arguments and automatic argument completion - * to the list of commands in this container. - * - * The shell context for the command will default to 0 and not - * require any flags for it to be available. - * - * @param[in] name Name of the command as a std::vector of flash - * strings. - * @param[in] arguments Help text for arguments that the command - * accepts as a std::vector of flash strings - * (use "<" to indicate a required argument). - * @param[in] function Function to be used when the command is - * executed. - * @param[in] arg_function Function to be used to perform argument - * completions for this command. - * @since 0.2.0 - */ - void add_command(const string_vector & name, const string_vector & arguments, command_function function, argument_completion_function arg_function); - /** - * Add a command with no arguments to the list of commands in this - * container. - * - * @param[in] context Shell context in which this command is - * available. - * @param[in] flags Shell flags that must be set for this command - * to be available. - * @param[in] name Name of the command as a std::vector of flash - * strings. - * @param[in] function Function to be used when the command is - * executed. - * @since 0.1.0 - */ - void add_command(unsigned int context, unsigned int flags, const string_vector & name, command_function function); - /** - * Add a command with arguments to the list of commands in this - * container. - * - * @param[in] context Shell context in which this command is - * available. - * @param[in] flags Shell flags that must be set for this command - * to be available. - * @param[in] name Name of the command as a std::vector of flash - * strings. - * @param[in] arguments Help text for arguments that the command - * accepts as a std::vector of flash strings - * (use "<" to indicate a required argument). - * @param[in] function Function to be used when the command is - * executed. - * @since 0.1.0 - */ - void add_command(unsigned int context, unsigned int flags, const string_vector & name, const string_vector & arguments, command_function function); - /** - * Add a command with arguments and automatic argument completion - * to the list of commands in this container. - * - * @param[in] context Shell context in which this command is - * available. - * @param[in] flags Shell flags that must be set for this command - * to be available. - * @param[in] name Name of the command as a std::vector of flash - * strings. - * @param[in] arguments Help text for arguments that the command - * accepts as a std::vector of flash strings - * (use "<" to indicate a required argument). - * @param[in] function Function to be used when the command is - * executed. - * @param[in] arg_function Function to be used to perform argument - * completions for this command. - * @since 0.1.0 - */ - void add_command(unsigned int context, - unsigned int flags, - const string_vector & name, - const string_vector & arguments, - command_function function, - argument_completion_function arg_function); - - /** - * Execute a command for a Shell if it exists in the current - * context and with the current flags. - * - * @param[in] shell Shell that is executing the command. - * @param[in] command_line Command line parameters. - * @return An object describing the result of the command execution - * operation. - * @since 0.1.0 - */ - Execution execute_command(Shell & shell, CommandLine && command_line); - - /** - * Complete a partial command for a Shell if it exists in the - * current context and with the current flags. - * - * @param[in] shell Shell that is completing the command. - * @param[in] command_line Command line parameters. - * @return An object describing the result of the command - * completion operation. - * @since 0.1.0 - */ - Completion complete_command(Shell & shell, const CommandLine & command_line); - - /** - * Applies the given function object f to all commands in the - * current context and with the current flags. - * - * @param[in] shell Shell that is accessing commands. - * @param[in] f Function to apply to each command. - * @since 0.4.0 - */ - void for_each_available_command(Shell & shell, apply_function f) const; - - void remove_context_commands(unsigned int context); // added by proddy - void remove_all_commands(); // added by proddy - - private: - /** - * Command for execution on a Shell. - * @since 0.1.0 - */ - class Command { - public: - /** - * Create a command for execution on a Shell. - * - * @param[in] flags Shell flags that must be set for this command - * to be available. - * @param[in] name Name of the command as a std::vector of flash - * strings. - * @param[in] arguments Help text for arguments that the command - * accepts as a std::vector of flash strings - * (use "<" to indicate a required argument). - * @param[in] function Function to be used when the command is - * executed. - * @param[in] arg_function Function to be used to perform argument - * completions for this command. - * @since 0.1.0 - */ - Command(unsigned int flags, const string_vector name, const string_vector arguments, command_function function, argument_completion_function arg_function); - ~Command(); - - /** - * Determine the minimum number of arguments for this command - * based on the help text for the arguments that begin with the - * "<" character. - * - * @return The minimum number of arguments for this command. - * @since 0.1.0 - */ - size_t minimum_arguments() const; - /** - * Determine the maximum number of arguments for this command - * based on the length of help text for the arguments. - * - * @return The maximum number of arguments for this command. - * @since 0.1.0 - */ - inline size_t maximum_arguments() const { - return arguments_.size(); - } - - unsigned int flags_; /*!< Shell flags that must be set for this command to be available. @since 0.1.0 */ - const string_vector name_; /*!< Name of the command as a std::vector of flash strings. @since 0.1.0 */ - const string_vector arguments_; /*!< Help text for arguments that the command accepts as a std::vector of flash strings. @since 0.1.0 */ - command_function function_; /*!< Function to be used when the command is executed. @since 0.1.0 */ - argument_completion_function arg_function_; /*!< Function to be used to perform argument completions for this command. @since 0.1.0 */ - - private: - Command(const Command &) = delete; - Command & operator=(const Command &) = delete; - }; - - /** - * Result of a command find operation. - * - * Each matching command is returned in a map grouped by size. - * @since 0.1.0 - */ - struct Match { - std::multimap exact; /*!< Commands that match the command line exactly, grouped by the size of the command names. @since 0.1.0 */ - std::multimap partial; /*!< Commands that the command line partially matches, grouped by the size of the command names. @since 0.1.0 */ - }; - - /** - * Find commands by matching them against the command line. - * - * @param[in] shell Shell that is accessing commands. - * @param[in] command_line Command line parmeters. - * @return An object describing the result of the command find - * operation. - * @since 0.1.0 - */ - Match find_command(Shell & shell, const CommandLine & command_line); - - /** - * Find the longest common prefix from a shortest match of commands. - * - * @param[in] commands All commands that matched (at least 2). - * @param[out] longest_name The longest common prefix as a list of - * strings. - * @return True if the longest common prefix is made up of whole - * components, false if the last part is constructed from - * a partial component. - * @since 0.1.0 - */ - static bool find_longest_common_prefix(const std::multimap & commands, std::vector & longest_name); - - /** - * Find the longest common prefix from a list of potential arguments. - * - * @param[in] arguments All potential arguments (at least 2). - * @return The longest common prefix, which could be empty. - * @since 0.1.0 - */ - static std::string find_longest_common_prefix(const std::vector & arguments); - - std::multimap commands_; /*!< Commands stored in this container, separated by context. @since 0.1.0 */ -}; - -/** - * A command shell console using a Stream for input/output. - * - * Must be constructed within a std::shared_ptr. - * - * Derived classes must call the Shell constructor explicitly. - * - * @since 0.1.0 - */ -class StreamConsole : virtual public Shell { - public: - /** - * Create a new StreamConsole shell with the given commands, - * default context and initial flags. - * - * The default context is put on the stack and cannot be removed. - * - * @param[in] commands Commands available for execution in this shell. - * @param[in] stream Stream used for the input/output of this shell. - * @param[in] context Default context for the shell. - * @param[in] flags Initial flags for the shell. - * @since 0.1.0 - */ - StreamConsole(std::shared_ptr commands, Stream & stream, unsigned int context = 0, unsigned int flags = 0); - ~StreamConsole() override = default; - - /** - * Write one byte to the output stream. - * - * @param[in] data Data to be output. - * @return The number of bytes that were output. - * @since 0.1.0 - */ - size_t write(uint8_t data) override; - /** - * Write an array of bytes to the output stream. - * - * @param[in] buffer Buffer to be output. - * @param[in] size Length of the buffer. - * @return The number of bytes that were output. - * @since 0.1.0 - */ - size_t write(const uint8_t * buffer, size_t size) override; - - protected: - /** - * Constructor used by intermediate derived classes for multiple - * inheritance. - * - * This does not initialise the shell completely so the outer - * derived class must call the public constructor or there will be - * no commands. Does not put any default context on the stack. - * - * @since 0.1.0 - */ - explicit StreamConsole(Stream & stream); - - private: - StreamConsole(const StreamConsole &) = delete; - StreamConsole & operator=(const StreamConsole &) = delete; - - /** - * Check for at least one character of available input. - * - * @return True if a character is available, otherwise false. - * @since 0.2.0 - */ - bool available_char() override; - /** - * Read one character from the available input. - * - * @return An unsigned char if input is available, otherwise -1. - * @since 0.1.0 - */ - int read_one_char() override; - /** - * Read one character from the available input without advancing to - * the next one. - * - * @return An unsigned char if input is available, otherwise -1. - * @since 0.2.0 - */ - int peek_one_char() override; - - Stream & stream_; /*!< Stream used for the input/output of this shell. @since 0.1.0 */ +struct Commands::Completion { + std::list help; /*!< Suggestions for matching commands. @since 0.1.0 */ + CommandLine replacement; /*!< Replacement matching full or partial command line. @since 0.1.0 */ }; } // namespace console } // namespace uuid -#endif +#endif \ No newline at end of file diff --git a/lib/uuid-log/src/format_level_lowercase.cpp b/lib/uuid-log/src/format_level_lowercase.cpp index 159b24072..010a4d1c9 100644 --- a/lib/uuid-log/src/format_level_lowercase.cpp +++ b/lib/uuid-log/src/format_level_lowercase.cpp @@ -38,20 +38,19 @@ static constexpr const char * pstr_level_lowercase_debug = "debug"; static constexpr const char * pstr_level_lowercase_trace = "trace"; static constexpr const char * pstr_level_lowercase_all = "all"; -static const __FlashStringHelper * log_level_lowercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(uint32_t)))) -PROGMEM = {reinterpret_cast(pstr_level_lowercase_off), - reinterpret_cast(pstr_level_lowercase_emerg), - reinterpret_cast(pstr_level_lowercase_crit), - reinterpret_cast(pstr_level_lowercase_alert), - reinterpret_cast(pstr_level_lowercase_err), - reinterpret_cast(pstr_level_lowercase_warning), - reinterpret_cast(pstr_level_lowercase_notice), - reinterpret_cast(pstr_level_lowercase_info), - reinterpret_cast(pstr_level_lowercase_debug), - reinterpret_cast(pstr_level_lowercase_trace), - reinterpret_cast(pstr_level_lowercase_all)}; +static const char * log_level_lowercase[(int)Level::ALL - (int)Level::OFF + 1] = {pstr_level_lowercase_off, + pstr_level_lowercase_emerg, + pstr_level_lowercase_crit, + pstr_level_lowercase_alert, + pstr_level_lowercase_err, + pstr_level_lowercase_warning, + pstr_level_lowercase_notice, + pstr_level_lowercase_info, + pstr_level_lowercase_debug, + pstr_level_lowercase_trace, + pstr_level_lowercase_all}; -const __FlashStringHelper * format_level_lowercase(Level level) { +const char * format_level_lowercase(Level level) { return log_level_lowercase[(int)level + 1]; } diff --git a/lib/uuid-log/src/format_level_uppercase.cpp b/lib/uuid-log/src/format_level_uppercase.cpp index 259ea3dc3..c3f864193 100644 --- a/lib/uuid-log/src/format_level_uppercase.cpp +++ b/lib/uuid-log/src/format_level_uppercase.cpp @@ -38,20 +38,19 @@ static constexpr const char * pstr_level_uppercase_debug = "DEBUG"; static constexpr const char * pstr_level_uppercase_trace = "TRACE"; static constexpr const char * pstr_level_uppercase_all = "ALL"; -static const __FlashStringHelper * log_level_uppercase[(int)Level::ALL - (int)Level::OFF + 1] __attribute__((__aligned__(sizeof(uint32_t)))) -PROGMEM = {reinterpret_cast(pstr_level_uppercase_off), - reinterpret_cast(pstr_level_uppercase_emerg), - reinterpret_cast(pstr_level_uppercase_crit), - reinterpret_cast(pstr_level_uppercase_alert), - reinterpret_cast(pstr_level_uppercase_err), - reinterpret_cast(pstr_level_uppercase_warning), - reinterpret_cast(pstr_level_uppercase_notice), - reinterpret_cast(pstr_level_uppercase_info), - reinterpret_cast(pstr_level_uppercase_debug), - reinterpret_cast(pstr_level_uppercase_trace), - reinterpret_cast(pstr_level_uppercase_all)}; +static const char * log_level_uppercase[(int)Level::ALL - (int)Level::OFF + 1] = {pstr_level_uppercase_off, + pstr_level_uppercase_emerg, + pstr_level_uppercase_crit, + pstr_level_uppercase_alert, + pstr_level_uppercase_err, + pstr_level_uppercase_warning, + pstr_level_uppercase_notice, + pstr_level_uppercase_info, + pstr_level_uppercase_debug, + pstr_level_uppercase_trace, + pstr_level_uppercase_all}; -const __FlashStringHelper * format_level_uppercase(Level level) { +const char * format_level_uppercase(Level level) { return log_level_uppercase[(int)level + 1]; } diff --git a/lib/uuid-log/src/format_timestamp_ms.cpp b/lib/uuid-log/src/format_timestamp_ms.cpp index 3169980ca..1441062b0 100644 --- a/lib/uuid-log/src/format_timestamp_ms.cpp +++ b/lib/uuid-log/src/format_timestamp_ms.cpp @@ -20,6 +20,7 @@ #include +#include #include #include @@ -45,7 +46,7 @@ std::string format_timestamp_ms(uint64_t timestamp_ms, unsigned int days_width) milliseconds = timestamp_ms; - static std::vector text(10 + 1 /* days */ + 2 + 1 /* hours */ + 2 + 1 /* minutes */ + 2 + 1 /* seconds */ + 3 /* milliseconds */ + 1); + std::array text; snprintf(text.data(), text.size(), ("%0*lu+%02u:%02u:%02u.%03u"), std::min(days_width, 10U), days, hours, minutes, seconds, milliseconds); diff --git a/lib/uuid-common/src/read_flash_string.cpp b/lib/uuid-log/src/handler.cpp similarity index 59% rename from lib/uuid-common/src/read_flash_string.cpp rename to lib/uuid-log/src/handler.cpp index 94fccb6b1..ee434bf3b 100644 --- a/lib/uuid-common/src/read_flash_string.cpp +++ b/lib/uuid-log/src/handler.cpp @@ -1,6 +1,6 @@ /* - * uuid-common - Microcontroller common utilities - * Copyright 2019 Simon Arlott + * uuid-log - Microcontroller logging framework + * Copyright 2021 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,24 +16,16 @@ * along with this program. If not, see . */ -#include - -#include - -#include +#include namespace uuid { -std::string read_flash_string(const __FlashStringHelper * flash_str) { - if (flash_str == nullptr) { - return std::string(""); // prevent crash - } +namespace log { - std::string str(::strlen_P(reinterpret_cast(flash_str)), '\0'); - - ::strncpy_P(&str[0], reinterpret_cast(flash_str), str.capacity() + 1); - - return str; +Handler::~Handler() { + Logger::unregister_handler(this); } +} // namespace log + } // namespace uuid diff --git a/lib/uuid-log/src/levels_lowercase.cpp b/lib/uuid-log/src/levels_lowercase.cpp index 30b3a4c29..4555c1e3d 100644 --- a/lib/uuid-log/src/levels_lowercase.cpp +++ b/lib/uuid-log/src/levels_lowercase.cpp @@ -27,17 +27,17 @@ namespace uuid { namespace log { std::vector levels_lowercase() { - return {uuid::read_flash_string(format_level_lowercase(Level::OFF)), - uuid::read_flash_string(format_level_lowercase(Level::EMERG)), - uuid::read_flash_string(format_level_lowercase(Level::ALERT)), - uuid::read_flash_string(format_level_lowercase(Level::CRIT)), - uuid::read_flash_string(format_level_lowercase(Level::ERR)), - uuid::read_flash_string(format_level_lowercase(Level::WARNING)), - uuid::read_flash_string(format_level_lowercase(Level::NOTICE)), - uuid::read_flash_string(format_level_lowercase(Level::INFO)), - uuid::read_flash_string(format_level_lowercase(Level::DEBUG)), - uuid::read_flash_string(format_level_lowercase(Level::TRACE)), - uuid::read_flash_string(format_level_lowercase(Level::ALL))}; + return {format_level_lowercase(Level::OFF), + format_level_lowercase(Level::EMERG), + format_level_lowercase(Level::ALERT), + format_level_lowercase(Level::CRIT), + format_level_lowercase(Level::ERR), + format_level_lowercase(Level::WARNING), + format_level_lowercase(Level::NOTICE), + format_level_lowercase(Level::INFO), + format_level_lowercase(Level::DEBUG), + format_level_lowercase(Level::TRACE), + format_level_lowercase(Level::ALL)}; } } // namespace log diff --git a/lib/uuid-log/src/levels_uppercase.cpp b/lib/uuid-log/src/levels_uppercase.cpp index b7d546603..9f4812654 100644 --- a/lib/uuid-log/src/levels_uppercase.cpp +++ b/lib/uuid-log/src/levels_uppercase.cpp @@ -27,17 +27,17 @@ namespace uuid { namespace log { std::vector levels_uppercase() { - return {uuid::read_flash_string(format_level_uppercase(Level::OFF)), - uuid::read_flash_string(format_level_uppercase(Level::EMERG)), - uuid::read_flash_string(format_level_uppercase(Level::ALERT)), - uuid::read_flash_string(format_level_uppercase(Level::CRIT)), - uuid::read_flash_string(format_level_uppercase(Level::ERR)), - uuid::read_flash_string(format_level_uppercase(Level::WARNING)), - uuid::read_flash_string(format_level_uppercase(Level::NOTICE)), - uuid::read_flash_string(format_level_uppercase(Level::INFO)), - uuid::read_flash_string(format_level_uppercase(Level::DEBUG)), - uuid::read_flash_string(format_level_uppercase(Level::TRACE)), - uuid::read_flash_string(format_level_uppercase(Level::ALL))}; + return {format_level_uppercase(Level::OFF), + format_level_uppercase(Level::EMERG), + format_level_uppercase(Level::ALERT), + format_level_uppercase(Level::CRIT), + format_level_uppercase(Level::ERR), + format_level_uppercase(Level::WARNING), + format_level_uppercase(Level::NOTICE), + format_level_uppercase(Level::INFO), + format_level_uppercase(Level::DEBUG), + format_level_uppercase(Level::TRACE), + format_level_uppercase(Level::ALL)}; } } // namespace log diff --git a/lib/uuid-log/src/log.cpp b/lib/uuid-log/src/log.cpp index 3b0fd926a..b10a4ba6a 100644 --- a/lib/uuid-log/src/log.cpp +++ b/lib/uuid-log/src/log.cpp @@ -1,6 +1,6 @@ /* * uuid-log - Microcontroller logging framework - * Copyright 2019 Simon Arlott + * Copyright 2019,2021-2022 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,11 +20,15 @@ #include +#include #include #include #include #include #include +#if UUID_LOG_THREAD_SAFE +#include +#endif #include #include #include @@ -33,8 +37,21 @@ namespace uuid { namespace log { -std::map Logger::handlers_; -Level Logger::level_ = Level::OFF; +std::atomic Logger::global_level_{Level::OFF}; +#if UUID_LOG_THREAD_SAFE +std::mutex Logger::mutex_; +#endif + +//! @cond false +static Level constrain_level(Level level) { + if (level < Level::EMERG) { + level = Level::EMERG; + } else if (level > Level::TRACE) { + level = Level::TRACE; + } + return level; +} +//! @endcond Message::Message(uint64_t uptime_ms, Level level, Facility facility, const char * name, const std::string && text) : uptime_ms(uptime_ms) @@ -50,20 +67,46 @@ Logger::Logger(const char * name, Facility facility) }; +std::shared_ptr> & Logger::registered_handlers() { + static std::shared_ptr> handlers = std::make_shared>(); + + return handlers; +} + void Logger::register_handler(Handler * handler, Level level) { - handlers_[handler] = level; +#if UUID_LOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + auto & handlers = registered_handlers(); + + handler->handlers_ = handlers; + (*handlers)[handler] = level; refresh_log_level(); }; void Logger::unregister_handler(Handler * handler) { - handlers_.erase(handler); - refresh_log_level(); + auto handlers = handler->handlers_.lock(); + + if (handlers) { +#if UUID_LOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + + if (handlers->erase(handler)) { + refresh_log_level(); + } + } }; Level Logger::get_log_level(const Handler * handler) { - const auto level = handlers_.find(const_cast(handler)); +#if UUID_LOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + auto & handlers = registered_handlers(); - if (level != handlers_.end()) { + const auto level = handlers->find(const_cast(handler)); + + if (level != handlers->end()) { return level->second; } @@ -80,16 +123,6 @@ void Logger::emerg(const char * format, ...) const { } }; -void Logger::emerg(const __FlashStringHelper * format, ...) const { - if (enabled(Level::EMERG)) { - va_list ap; - - va_start(ap, format); - vlog(Level::EMERG, format, ap); - va_end(ap); - } -}; - void Logger::crit(const char * format, ...) const { if (enabled(Level::CRIT)) { va_list ap; @@ -100,16 +133,6 @@ void Logger::crit(const char * format, ...) const { } }; -void Logger::crit(const __FlashStringHelper * format, ...) const { - if (enabled(Level::CRIT)) { - va_list ap; - - va_start(ap, format); - vlog(Level::CRIT, format, ap); - va_end(ap); - } -}; - void Logger::alert(const char * format, ...) const { if (enabled(Level::ALERT)) { va_list ap; @@ -120,15 +143,6 @@ void Logger::alert(const char * format, ...) const { } }; -void Logger::alert(const __FlashStringHelper * format, ...) const { - if (enabled(Level::ALERT)) { - va_list ap; - - va_start(ap, format); - vlog(Level::ALERT, format, ap); - va_end(ap); - } -}; void Logger::err(const char * format, ...) const { if (enabled(Level::ERR)) { va_list ap; @@ -139,16 +153,6 @@ void Logger::err(const char * format, ...) const { } }; -void Logger::err(const __FlashStringHelper * format, ...) const { - if (enabled(Level::ERR)) { - va_list ap; - - va_start(ap, format); - vlog(Level::ERR, format, ap); - va_end(ap); - } -}; - void Logger::warning(const char * format, ...) const { if (enabled(Level::WARNING)) { va_list ap; @@ -159,16 +163,6 @@ void Logger::warning(const char * format, ...) const { } }; -void Logger::warning(const __FlashStringHelper * format, ...) const { - if (enabled(Level::WARNING)) { - va_list ap; - - va_start(ap, format); - vlog(Level::WARNING, format, ap); - va_end(ap); - } -}; - void Logger::notice(const char * format, ...) const { if (enabled(Level::NOTICE)) { va_list ap; @@ -179,16 +173,6 @@ void Logger::notice(const char * format, ...) const { } }; -void Logger::notice(const __FlashStringHelper * format, ...) const { - if (enabled(Level::NOTICE)) { - va_list ap; - - va_start(ap, format); - vlog(Level::NOTICE, format, ap); - va_end(ap); - } -}; - void Logger::info(const char * format, ...) const { if (enabled(Level::INFO)) { va_list ap; @@ -199,16 +183,6 @@ void Logger::info(const char * format, ...) const { } }; -void Logger::info(const __FlashStringHelper * format, ...) const { - if (enabled(Level::INFO)) { - va_list ap; - - va_start(ap, format); - vlog(Level::INFO, format, ap); - va_end(ap); - } -}; - void Logger::debug(const char * format, ...) const { if (enabled(Level::DEBUG)) { va_list ap; @@ -219,16 +193,6 @@ void Logger::debug(const char * format, ...) const { } }; -void Logger::debug(const __FlashStringHelper * format, ...) const { - if (enabled(Level::DEBUG)) { - va_list ap; - - va_start(ap, format); - vlog(Level::DEBUG, format, ap); - va_end(ap); - } -}; - void Logger::trace(const char * format, ...) const { if (enabled(Level::TRACE)) { va_list ap; @@ -239,38 +203,20 @@ void Logger::trace(const char * format, ...) const { } }; -void Logger::trace(const __FlashStringHelper * format, ...) const { - if (enabled(Level::TRACE)) { - va_list ap; - - va_start(ap, format); - vlog(Level::TRACE, format, ap); - va_end(ap); - } -}; - -void Logger::log(Level level, Facility facility, const char * format, ...) const { - if (level < Level::EMERG) { - level = Level::EMERG; - } else if (level > Level::TRACE) { - level = Level::TRACE; - } +void Logger::log(Level level, const char * format, ...) const { + level = constrain_level(level); if (enabled(level)) { va_list ap; va_start(ap, format); - vlog(level, facility, format, ap); + vlog(level, format, ap); va_end(ap); } }; -void Logger::log(Level level, Facility facility, const __FlashStringHelper * format, ...) const { - if (level < Level::EMERG) { - level = Level::EMERG; - } else if (level > Level::TRACE) { - level = Level::TRACE; - } +void Logger::log(Level level, Facility facility, const char * format, ...) const { + level = constrain_level(level); if (enabled(level)) { va_list ap; @@ -295,41 +241,34 @@ void Logger::vlog(Level level, Facility facility, const char * format, va_list a dispatch(level, facility, text); } -void Logger::vlog(Level level, const __FlashStringHelper * format, va_list ap) const { - vlog(level, facility_, format, ap); -} - -void Logger::vlog(Level level, Facility facility, const __FlashStringHelper * format, va_list ap) const { - std::vector text(MAX_LOG_LENGTH + 1); - - if (vsnprintf_P(text.data(), text.size(), reinterpret_cast(format), ap) <= 0) { - return; - } - - dispatch(level, facility, text); -} - void Logger::dispatch(Level level, Facility facility, std::vector & text) const { std::shared_ptr message = std::make_shared(get_uptime_ms(), level, facility, name_, text.data()); text.resize(0); - for (auto & handler : handlers_) { +#if UUID_LOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + + for (auto & handler : *registered_handlers()) { if (level <= handler.second) { *handler.first << message; } } } +/* Mutex already locked by caller. */ void Logger::refresh_log_level() { - level_ = Level::OFF; + Level level = Level::OFF; - for (auto & handler : handlers_) { - if (level_ < handler.second) { - level_ = handler.second; + for (auto & handler : *registered_handlers()) { + if (level < handler.second) { + level = handler.second; } } + + global_level_ = level; } } // namespace log -} // namespace uuid +} // namespace uuid \ No newline at end of file diff --git a/lib/uuid-log/src/parse_level_lowercase.cpp b/lib/uuid-log/src/parse_level_lowercase.cpp index 691233046..a6670451f 100644 --- a/lib/uuid-log/src/parse_level_lowercase.cpp +++ b/lib/uuid-log/src/parse_level_lowercase.cpp @@ -28,7 +28,7 @@ namespace log { bool parse_level_lowercase(const std::string & name, Level & level) { for (auto value : levels()) { - if (!strcmp_P(name.c_str(), reinterpret_cast(format_level_lowercase(value)))) { + if (!strcmp(name.c_str(), format_level_lowercase(value))) { level = value; return true; } diff --git a/lib/uuid-log/src/parse_level_uppercase.cpp b/lib/uuid-log/src/parse_level_uppercase.cpp index 0dddcf332..5337fac0a 100644 --- a/lib/uuid-log/src/parse_level_uppercase.cpp +++ b/lib/uuid-log/src/parse_level_uppercase.cpp @@ -28,7 +28,7 @@ namespace log { bool parse_level_uppercase(const std::string & name, Level & level) { for (auto value : levels()) { - if (!strcmp_P(name.c_str(), reinterpret_cast(format_level_uppercase(value)))) { + if (!strcmp(name.c_str(), format_level_uppercase(value))) { level = value; return true; } diff --git a/lib/uuid-log/src/print_handler.cpp b/lib/uuid-log/src/print_handler.cpp new file mode 100644 index 000000000..6ba52cc6a --- /dev/null +++ b/lib/uuid-log/src/print_handler.cpp @@ -0,0 +1,106 @@ +/* + * uuid-log - Microcontroller logging framework + * Copyright 2022 Simon Arlott + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +#include +#if UUID_LOG_THREAD_SAFE +#include +#endif + +namespace uuid { + +namespace log { + +PrintHandler::PrintHandler(::Print & print) + : print_(print) { +} + +size_t PrintHandler::maximum_log_messages() const { +#if UUID_LOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + + return maximum_log_messages_; +} + +void PrintHandler::maximum_log_messages(size_t count) { +#if UUID_LOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + + maximum_log_messages_ = std::max((size_t)1, count); + + while (log_messages_.size() > maximum_log_messages_) { + log_messages_.pop_front(); + } +} + +void PrintHandler::loop(size_t count) { +#if UUID_LOG_THREAD_SAFE + std::unique_lock lock{mutex_}; +#endif + + count = std::max((size_t)1, count); + + while (!log_messages_.empty()) { + auto message = log_messages_.front(); + + log_messages_.pop_front(); +#if UUID_LOG_THREAD_SAFE + lock.unlock(); +#endif + + print_.print(uuid::log::format_timestamp_ms(message->uptime_ms, 3).c_str()); + print_.print(' '); + print_.print(uuid::log::format_level_char(message->level)); + print_.print(" ["); + print_.print(message->name); + print_.print("] "); + print_.println(message->text.c_str()); + + count--; + if (count == 0) { + break; + } + + ::yield(); + +#if UUID_LOG_THREAD_SAFE + lock.lock(); +#endif + } +} + +void PrintHandler::operator<<(std::shared_ptr message) { +#if UUID_LOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + + if (log_messages_.size() >= maximum_log_messages_) { + log_messages_.pop_front(); + } + + log_messages_.emplace_back(std::move(message)); +} + +} // namespace log + +} // namespace uuid \ No newline at end of file diff --git a/lib/uuid-log/src/uuid/log.h b/lib/uuid-log/src/uuid/log.h index 615bc7577..32496a05b 100644 --- a/lib/uuid-log/src/uuid/log.h +++ b/lib/uuid-log/src/uuid/log.h @@ -1,6 +1,6 @@ /* * uuid-log - Microcontroller logging framework - * Copyright 2019 Simon Arlott + * Copyright 2019,2021-2022 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,6 +21,8 @@ #include +#include +#include #include #include #include @@ -31,6 +33,24 @@ #include +#ifndef UUID_COMMON_THREAD_SAFE +#define UUID_COMMON_THREAD_SAFE 0 +#endif + +#ifndef UUID_COMMON_STD_MUTEX_AVAILABLE +#define UUID_COMMON_STD_MUTEX_AVAILABLE 0 +#endif + +#if UUID_COMMON_STD_MUTEX_AVAILABLE +#define UUID_LOG_THREAD_SAFE 1 +#else +#define UUID_LOG_THREAD_SAFE 0 +#endif + +#if UUID_LOG_THREAD_SAFE +#include +#endif + namespace uuid { /** @@ -70,6 +90,17 @@ namespace uuid { namespace log { +/** + * Thread-safe status of the library. + * + * @since 2.3.0 + */ +#if UUID_COMMON_THREAD_SAFE && UUID_LOG_THREAD_SAFE +static constexpr bool thread_safe = true; +#else +static constexpr bool thread_safe = false; +#endif + /** * Severity level of log messages. * @@ -158,10 +189,10 @@ char format_level_char(Level level); * Format a log level as an uppercase string. * * @param[in] level Log level. - * @return Uppercase name of the log level (flash string). + * @return Uppercase name of the log level * @since 1.0.0 */ -const __FlashStringHelper * format_level_uppercase(Level level); +const char * format_level_uppercase(Level level); /** * Get all log levels as uppercase strings. @@ -187,10 +218,10 @@ bool parse_level_uppercase(const std::string & name, Level & level); * Format a log level as a lowercase string. * * @param[in] level Log level. - * @return Lowercase name of the log level (flash string). + * @return Lowercase name of the log level * @since 1.0.0 */ -const __FlashStringHelper * format_level_lowercase(Level level); +const char * format_level_lowercase(Level level); /** * Get all log levels as lowercase strings. @@ -227,7 +258,7 @@ struct Message { * @param[in] uptime_ms System uptime, see uuid::get_uptime_ms(). * @param[in] level Severity level of the message. * @param[in] facility Facility type of the process logging the message. - * @param[in] name Logger name (flash string). + * @param[in] name Logger name * @param[in] text Log message text. * @since 1.0.0 */ @@ -257,7 +288,7 @@ struct Message { const Facility facility; /** - * Name of the logger used (flash string). + * Name of the logger used * * @since 1.0.0 */ @@ -274,14 +305,24 @@ struct Message { const std::string text; }; +class Logger; + /** * Logger handler used to process log messages. * * @since 1.0.0 */ class Handler { + /** + * Logger needs to be able to access the private reference to + * the registered log handlers. + * + * @since 2.1.2 + */ + friend Logger; + public: - virtual ~Handler() = default; + virtual ~Handler(); /** * Add a new log message. @@ -290,9 +331,20 @@ class Handler { * processed immediately so that log messages have minimal impact * at the time of use. * + * Handlers must avoid holding a lock on a mutex used for adding + * messages while processing those messages. Release the lock while + * performing the processing. + * * Queues should have a maximum size and discard the oldest message * when full. * + * It is not safe for the handler to directly or indirectly do any + * of the following while this function is being called: + * - Log a message. + * - Read the log level of any handler. + * - Modify the log level of any handler. + * - Unregister any handler. + * * @param[in] message New log message, shared by all handlers. * @since 1.0.0 */ @@ -300,6 +352,17 @@ class Handler { protected: Handler() = default; + + private: + /** + * Reference to registered log handlers. + * + * Used in the destructor to safely unregister the handler even if + * the underlying map has already been destroyed. + * + * @since 2.1.2 + */ + std::weak_ptr> handlers_; }; /** @@ -309,7 +372,7 @@ class Handler { */ class Logger { public: -/** + /** * This is the maximum length of any log message. * * Determines the size of the buffer used for format string @@ -317,16 +380,12 @@ class Logger { * * @since 1.0.0 */ -#if defined(EMSESP_STANDALONE) - static constexpr size_t MAX_LOG_LENGTH = 500; -#else static constexpr size_t MAX_LOG_LENGTH = 255; -#endif /** * Create a new logger with the given name and logging facility. * - * @param[in] name Logger name (flash string). + * @param[in] name Logger name * @param[in] facility Default logging facility for messages. * * @since 1.0.0 @@ -339,8 +398,6 @@ class Logger { * * Call again to change the log level. * - * Do not call this function from a static initializer. - * * @param[in] handler Handler object that will handle log * messages. * @param[in] level Minimum log level that the handler is @@ -354,8 +411,6 @@ class Logger { * * It is safe to call this with a handler that is not registered. * - * Do not call this function from a static initializer. - * * @param[in] handler Handler object that will no longer handle * log messages. * @since 1.0.0 @@ -367,8 +422,6 @@ class Logger { * * It is safe to call this with a handler that is not registered. * - * Do not call this function from a static initializer. - * * @param[in] handler Handler object that may handle log * messages. * @return The current log level of the specified handler. @@ -377,17 +430,71 @@ class Logger { static Level get_log_level(const Handler * handler); /** - * Determine if the current log level is enabled by any registered - * handlers. + * Get the current global log level. * - * @return The current minimum global log level across all - * handlers. - * @since 1.0.0 + * @return The minimum log level across all handlers. + * @since 3.0.0 */ - static inline bool enabled(Level level) { - return level <= level_; + static Level global_level() { + return global_level_; + }; + + /** + * Determine if the specified log level is enabled by the effective + * log level. + * + * @param[in] level Log level to check. + * @return If the specified log level is enabled on this logger. + * @since 3.0.0 + */ + inline bool enabled(Level level) const { + return level <= global_level_ && level <= local_level_; } + /** + * Get the default logging facility for new messages of this logger. + * + * @return The default logging facility for messages. + * @since 2.3.0 + */ + inline Facility facility() const { + return facility_; + } + + /** + * Get the log level. + * + * The effective log level will be depend on handlers. + * + * @return The log level of this logger. + * @since 3.0.0 + */ + inline Level level() const { + return local_level_; + } + + /** + * Set the log level. + * + * The effective log level will be depend on handlers. + * + * @param[in] level Log level for this logger. + * @since 3.0.0 + */ + inline void level(Level level) { + local_level_ = level; + } + + /** + * Get the effective log level. + * + * @return The effective log level for this logger. + * @since 3.0.0 + */ + Level effective_level() const { + return std::min(global_level(), local_level_); + }; + /** * Log a message at level Level::EMERG. * @@ -396,14 +503,6 @@ class Logger { * @since 1.0.0 */ void emerg(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::EMERG. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void emerg(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::ALERT. @@ -413,14 +512,6 @@ class Logger { * @since 1.0.0 */ void alert(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::ALERT. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void alert(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::CRIT. @@ -430,14 +521,6 @@ class Logger { * @since 1.0.0 */ void crit(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::CRIT. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void crit(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::ERR. @@ -447,14 +530,6 @@ class Logger { * @since 1.0.0 */ void err(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::ERR. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void err(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::WARNING. @@ -464,14 +539,6 @@ class Logger { * @since 1.0.0 */ void warning(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::WARNING. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void warning(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::NOTICE. @@ -481,14 +548,6 @@ class Logger { * @since 1.0.0 */ void notice(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::NOTICE. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void notice(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::INFO. @@ -498,13 +557,6 @@ class Logger { * @since 1.0.0 */ void info(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::INFO. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - */ - void info(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::DEBUG. @@ -514,14 +566,6 @@ class Logger { * @since 1.0.0 */ void debug(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; - /** - * Log a message at level Level::DEBUG. - * - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void debug(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; /** * Log a message at level Level::TRACE. @@ -531,14 +575,16 @@ class Logger { * @since 1.0.0 */ void trace(const char * format, ...) const /* __attribute__((format (printf, 2, 3))) */; + /** - * Log a message at level Level::TRACE. + * Log a message with default facility. * - * @param[in] format Format string (flash string). + * @param[in] level Severity level of the message. + * @param[in] format Format string. * @param[in] ... Format string arguments. - * @since 1.0.0 + * @since 3.0.0 */ - void trace(const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 2, 3))) */; + void log(Level level, const char * format, ...) const /* __attribute__((format (printf, 3, 4))) */; /** * Log a message with a custom facility. @@ -549,17 +595,7 @@ class Logger { * @param[in] ... Format string arguments. * @since 1.0.0 */ - void log(Level level, Facility facility, const char * format, ...) const /* __attribute__((format (printf, 3, 4))) */; - /** - * Log a message with a custom facility. - * - * @param[in] level Severity level of the message. - * @param[in] facility Facility type of the process logging the message. - * @param[in] format Format string (flash string). - * @param[in] ... Format string arguments. - * @since 1.0.0 - */ - void log(Level level, Facility facility, const __FlashStringHelper * format, ...) const /* __attribute__((format (printf, 4, 5))) */; + void log(Level level, Facility facility, const char * format, ...) const /* __attribute__((format (printf, 4, 5))) */; private: /** @@ -568,6 +604,13 @@ class Logger { * @since 1.0.0 */ static void refresh_log_level(); + /** + * Get registered log handlers. + * + * @return The registered log handlers. + * @since 2.1.2 + */ + static std::shared_ptr> & registered_handlers(); /** * Log a message at the specified level. @@ -578,15 +621,6 @@ class Logger { * @since 1.0.0 */ void vlog(Level level, const char * format, va_list ap) const; - /** - * Log a message at the specified level. - * - * @param[in] level Severity level of the message. - * @param[in] format Format string (flash string). - * @param[in] ap Variable arguments pointer for format string. - * @since 1.0.0 - */ - void vlog(Level level, const __FlashStringHelper * format, va_list ap) const; /** * Log a message at the specified level and facility. @@ -598,16 +632,6 @@ class Logger { * @since 1.0.0 */ void vlog(Level level, Facility facility, const char * format, va_list ap) const; - /** - * Log a message at the specified level and facility. - * - * @param[in] level Severity level of the message. - * @param[in] facility Facility type of the process logging the message. - * @param[in] format Format string (flash string). - * @param[in] ap Variable arguments pointer for format string. - * @since 1.0.0 - */ - void vlog(Level level, Facility facility, const __FlashStringHelper * format, va_list ap) const; /** * Dispatch a log message to all handlers that are registered to @@ -623,15 +647,87 @@ class Logger { */ void dispatch(Level level, Facility facility, std::vector & text) const; - static std::map handlers_; /*!< Registered log handlers. @since 1.0.0 */ - static Level level_; /*!< Minimum global log level across all handlers. @since 1.0.0 */ + static std::atomic global_level_; /*!< Minimum global log level across all handlers. @since 3.0.0 */ +#if UUID_LOG_THREAD_SAFE + static std::mutex mutex_; /*!< Mutex for handlers. @since 2.3.0 */ +#endif - const char * name_; /*!< Logger name (flash string). @since 1.0.0 */ - const Facility facility_; /*!< Default logging facility for messages. @since 1.0.0 */ + const char * name_; /*!< Logger name */ + const Facility facility_; /*!< Default logging facility for messages. @since 1.0.0 */ + Level local_level_{Level::ALL}; /*!< Logger level. @since 3.0.0 */ +}; + +/** + * Basic log handler for writing messages to any object supporting the + * Print interface. + * + * Outputs all queued messages by default, which may result in the + * application blocking until writes complete if the Print destination + * buffer is full. + * + * @since 2.2.0 + */ +class PrintHandler : public uuid::log::Handler { + public: + static constexpr size_t MAX_LOG_MESSAGES = 50; /*!< Maximum number of log messages to buffer before they are output. @since 2.2.0 */ + + /** + * Create a new Print log handler. + * + * @param[in] print Destination for output of log messages. + * @since 2.2.0 + */ + PrintHandler(Print & print); + ~PrintHandler() = default; + + /** + * Get the maximum number of queued log messages. + * + * @return The maximum number of queued log messages. + * @since 2.2.0 + */ + size_t maximum_log_messages() const; + /** + * Set the maximum number of queued log messages. + * + * Defaults to PrintHandler::MAX_LOG_MESSAGES. + * + * @since 2.2.0 + */ + void maximum_log_messages(size_t count); + + /** + * Dispatch queued log messages. + * + * @param[in] count Maximum number of messages to output. + * @since 2.2.0 + */ + void loop(size_t count = SIZE_MAX); + + /** + * Add a new log message. + * + * This will be put in a queue for output at the next loop() + * process. The queue has a maximum size of + * get_maximum_log_messages() and will discard the oldest message + * first. + * + * @param[in] message New log message, shared by all handlers. + * @since 2.2.0 + */ + virtual void operator<<(std::shared_ptr message); + + private: + Print & print_; /*!< Destination for output of log messages. @since 2.2.0 */ +#if UUID_LOG_THREAD_SAFE + mutable std::mutex mutex_; /*!< Mutex for configuration, state and queued log messages. @since 2.3.0 */ +#endif + size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum number of log messages to buffer before they are output. @since 2.2.0 */ + std::list> log_messages_; /*!< Queued log messages, in the order they were received. @since 2.2.0 */ }; } // namespace log } // namespace uuid -#endif +#endif \ No newline at end of file diff --git a/lib/uuid-syslog/src/syslog.cpp b/lib/uuid-syslog/src/syslog.cpp index 327b0c7d7..5b12cb2f3 100644 --- a/lib/uuid-syslog/src/syslog.cpp +++ b/lib/uuid-syslog/src/syslog.cpp @@ -18,7 +18,6 @@ #include "uuid/syslog.h" - #include "../../../src/emsesp.h" #ifndef UUID_SYSLOG_HAVE_GETTIMEOFDAY @@ -32,12 +31,6 @@ #define UUID_SYSLOG_HAVE_GETTIMEOFDAY 0 #endif -#ifndef UUID_SYSLOG_HAVE_IPADDRESS_TYPE -#if defined(ARDUINO_ARCH_ESP8266) -#define UUID_SYSLOG_HAVE_IPADDRESS_TYPE 1 -#endif -#endif - #ifndef UUID_SYSLOG_HAVE_IPADDRESS_TYPE #define UUID_SYSLOG_HAVE_IPADDRESS_TYPE 0 #endif @@ -77,11 +70,26 @@ #include #include #include +#if UUID_SYSLOG_THREAD_SAFE +#include +#endif #include #include #include +#ifndef UUID_SYSLOG_UDP_BASE_MESSAGE_DELAY +#define UUID_SYSLOG_UDP_BASE_MESSAGE_DELAY 100 +#endif + +#ifndef UUID_SYSLOG_UDP_IPV4_ARP_MESSAGE_DELAY +#define UUID_SYSLOG_UDP_IPV4_ARP_MESSAGE_DELAY 10 +#endif + +#ifndef UUID_SYSLOG_UDP_IPV6_NDP_MESSAGE_DELAY +#define UUID_SYSLOG_UDP_IPV6_NDP_MESSAGE_DELAY 10 +#endif + static const char __pstr__logger_name[] = "syslog"; namespace uuid { @@ -91,10 +99,6 @@ namespace syslog { uuid::log::Logger SyslogService::logger_{__pstr__logger_name, uuid::log::Facility::SYSLOG}; bool SyslogService::QueuedLogMessage::time_good_ = false; -SyslogService::~SyslogService() { - uuid::log::Logger::unregister_handler(this); -} - void SyslogService::start() { uuid::log::Logger::register_handler(this, uuid::log::Level::ALL); } @@ -104,6 +108,9 @@ uuid::log::Level SyslogService::log_level() const { } void SyslogService::remove_queued_messages(uuid::log::Level level) { +#if UUID_SYSLOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif unsigned long offset = 0; for (auto it = log_messages_.begin(); it != log_messages_.end();) { @@ -130,17 +137,23 @@ void SyslogService::log_level(uuid::log::Level level) { level_set = true; if (level_changed) { - logger_.info(F("Log level set to %S"), uuid::log::format_level_uppercase(level)); + logger_.info("Log level set to %S", uuid::log::format_level_uppercase(level)); } uuid::log::Logger::register_handler(this, level); } size_t SyslogService::maximum_log_messages() const { +#if UUID_SYSLOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif return maximum_log_messages_; } void SyslogService::maximum_log_messages(size_t count) { +#if UUID_SYSLOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif maximum_log_messages_ = std::max((size_t)1, count); while (log_messages_.size() > maximum_log_messages_) { @@ -148,8 +161,16 @@ void SyslogService::maximum_log_messages(size_t count) { } } +size_t SyslogService::current_log_messages() const { +#if UUID_SYSLOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + + return log_messages_.size(); +} + std::pair SyslogService::destination() const { - return std::make_pair(ip_, port_); + return std::make_pair(host_, port_); } void SyslogService::destination(IPAddress host, uint16_t port) { @@ -159,28 +180,7 @@ void SyslogService::destination(IPAddress host, uint16_t port) { if ((uint32_t)ip_ == (uint32_t)0) { started_ = false; remove_queued_messages(log_level()); - host_.clear(); - } -} - -void SyslogService::destination(const char * host, uint16_t port) { - if (host == nullptr || host[0] == '\0') { - started_ = false; - remove_queued_messages(log_level()); - ip_ = (IPAddress)(uint32_t)0; - host_.clear(); - return; - } - host_ = host; - port_ = port; - if (ip_.fromString(host)) { - host_.clear(); - if ((uint32_t)ip_ == (uint32_t)0) { - started_ = false; - remove_queued_messages(log_level()); - } - } else { - ip_ = (IPAddress)(uint32_t)0; + // host_.clear(); } } @@ -207,7 +207,8 @@ void SyslogService::mark_interval(unsigned long interval) { SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr && content) : id_(id) , content_(std::move(content)) { - // Added by proddy - check for Ethernet too. This assumes the network has already started. + // Added by proddy for EMS-ESP + // check for Ethernet too. This assumes the network has already started. if (time_good_ || emsesp::EMSESP::system_.network_connected()) { #if UUID_SYSLOG_HAVE_GETTIMEOFDAY if (gettimeofday(&time_, nullptr) != 0) { @@ -229,9 +230,9 @@ SyslogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ } } -void SyslogService::operator<<(std::shared_ptr message) { +/* Mutex already locked by caller. */ +void SyslogService::add_message(std::shared_ptr & message) { if (log_messages_.size() >= maximum_log_messages_) { - log_messages_overflow_ = true; log_messages_.pop_front(); log_message_fails_++; } @@ -239,89 +240,140 @@ void SyslogService::operator<<(std::shared_ptr message) { log_messages_.emplace_back(log_message_id_++, std::move(message)); } +void SyslogService::operator<<(std::shared_ptr message) { +#if UUID_SYSLOG_THREAD_SAFE + std::lock_guard lock{mutex_}; +#endif + + add_message(message); +} + void SyslogService::loop() { - while (!log_messages_.empty() && can_transmit()) { +#if UUID_SYSLOG_THREAD_SAFE + std::unique_lock lock{mutex_}; +#endif + size_t count = std::max((size_t)1, MAX_LOG_MESSAGES); + + while (!log_messages_.empty()) { +#if UUID_SYSLOG_THREAD_SAFE + lock.unlock(); +#endif + + if (!can_transmit()) + return; + +#if UUID_SYSLOG_THREAD_SAFE + lock.lock(); + + if (log_messages_.empty()) + break; +#endif + auto message = log_messages_.front(); - started_ = true; - log_messages_overflow_ = false; - auto ok = transmit(message); + started_ = true; + +#if UUID_SYSLOG_THREAD_SAFE + lock.unlock(); +#endif + + auto ok = transmit(message); if (ok) { - // The transmit() may have called yield() allowing - // other messages to have been added to the queue. - if (!log_messages_overflow_) { +#if UUID_SYSLOG_THREAD_SAFE + lock.lock(); +#endif + + last_message_ = last_transmit_; + if (!log_messages_.empty() && log_messages_.front().content_ == message.content_) { log_messages_.pop_front(); } - last_message_ = uuid::get_uptime_ms(); + +#if UUID_SYSLOG_THREAD_SAFE + lock.unlock(); +#endif } ::yield(); +#if UUID_SYSLOG_THREAD_SAFE + lock.lock(); +#endif + if (!ok) { break; } + + count--; + if (count == 0) { + break; + } } if (started_ && mark_interval_ != 0 && log_messages_.empty()) { if (uuid::get_uptime_ms() - last_message_ >= mark_interval_) { // This is generated manually because the log level may not // be high enough to receive INFO messages. - operator<<(std::make_shared(uuid::get_uptime_ms(), - uuid::log::Level::INFO, - uuid::log::Facility::SYSLOG, - (__pstr__logger_name), - (F("-- MARK --")))); + auto message = std::make_shared(uuid::get_uptime_ms(), + uuid::log::Level::INFO, + uuid::log::Facility::SYSLOG, + (__pstr__logger_name), + "-- MARK --"); + add_message(message); } } } bool SyslogService::can_transmit() { + // TODO add this like in v3.5? + /* if (!host_.empty() && (uint32_t)ip_ == 0) { WiFi.hostByName(host_.c_str(), ip_); } + */ + #if UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (ip_.isV4() && (uint32_t)ip_ == (uint32_t)0) { + if (host_.isV4() && (uint32_t)host_ == (uint32_t)0) { return false; } #else - if ((uint32_t)ip_ == (uint32_t)0) { + if ((uint32_t)host_ == (uint32_t)0) { return false; } #endif - if (!emsesp::EMSESP::system_.network_connected()) { - return false; // added by proddy. Check Ethernet + if (WiFi.status() != WL_CONNECTED) { + return false; } const uint64_t now = uuid::get_uptime_ms(); - uint64_t message_delay = 100; + uint64_t message_delay = UUID_SYSLOG_UDP_BASE_MESSAGE_DELAY; #if UUID_SYSLOG_ARP_CHECK #if UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (ip_.isV4()) + if (host_.isV4()) #endif { - message_delay = 10; + message_delay = UUID_SYSLOG_UDP_IPV4_ARP_MESSAGE_DELAY; } #endif #if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (ip_.isV6()) { - message_delay = 10; + if (host_.isV6()) { + message_delay = UUID_SYSLOG_UDP_IPV6_NDP_MESSAGE_DELAY; } #endif - if (now < last_transmit_ || now - last_transmit_ < message_delay) { + if (started_ && (now < last_transmit_ || now - last_transmit_ < message_delay)) { return false; } #if UUID_SYSLOG_ARP_CHECK #if UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (ip_.isV4()) + if (host_.isV4()) #endif { ip4_addr_t ipaddr; - ip4_addr_set_u32(&ipaddr, (uint32_t)ip_); + ip4_addr_set_u32(&ipaddr, (uint32_t)host_); if (!ip4_addr_isloopback(&ipaddr) && !ip4_addr_ismulticast(&ipaddr) && !ip4_addr_isbroadcast(&ipaddr, netif_default)) { struct eth_addr * eth_ret = nullptr; @@ -348,10 +400,10 @@ bool SyslogService::can_transmit() { #endif #if UUID_SYSLOG_NDP_CHECK && UUID_SYSLOG_HAVE_IPADDRESS_TYPE - if (ip_.isV6()) { + if (host_.isV6()) { ip6_addr_t ip6addr; - IP6_ADDR(&ip6addr, ip_.raw6()[0], ip_.raw6()[1], ip_.raw6()[2], ip_.raw6()[3]); + IP6_ADDR(&ip6addr, host_.raw6()[0], host_.raw6()[1], host_.raw6()[2], host_.raw6()[3]); ip6_addr_assign_zone(&ip6addr, IP6_UNICAST, netif_default); if (!ip6_addr_isloopback(&ip6addr) && !ip6_addr_ismulticast(&ip6addr)) { @@ -361,26 +413,17 @@ bool SyslogService::can_transmit() { for (size_t i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { if (ip6_addr_isvalid(netif_ip6_addr_state(netif_default, i))) { - if (ip6_addr_isglobal(&ip6addr)) { - if (ip6_addr_isglobal(netif_ip6_addr(netif_default, i))) { - have_address = true; - break; - } - } else if (ip6_addr_issitelocal(&ip6addr)) { - if (ip6_addr_issitelocal(netif_ip6_addr(netif_default, i))) { - have_address = true; - break; - } - } else if (ip6_addr_isuniquelocal(&ip6addr)) { - if (ip6_addr_isuniquelocal(netif_ip6_addr(netif_default, i))) { - have_address = true; - break; - } - } else if (ip6_addr_islinklocal(&ip6addr)) { + if (ip6_addr_islinklocal(&ip6addr)) { if (ip6_addr_islinklocal(netif_ip6_addr(netif_default, i))) { have_address = true; break; } + } else if (ip6_addr_isglobal(&ip6addr) || ip6_addr_isuniquelocal(&ip6addr) || ip6_addr_issitelocal(&ip6addr)) { + if (ip6_addr_isglobal(netif_ip6_addr(netif_default, i)) || ip6_addr_isuniquelocal(netif_ip6_addr(netif_default, i)) + || ip6_addr_issitelocal(netif_ip6_addr(netif_default, i))) { + have_address = true; + break; + } } else { have_address = true; break; @@ -408,8 +451,11 @@ bool SyslogService::can_transmit() { bool SyslogService::transmit(const QueuedLogMessage & message) { struct tm tm; - int8_t tzh = 0; - int8_t tzm = 0; + + // Changes for EMS-ESP by MichaelDvP + // TODO add this like in v3.5? + // int8_t tzh = 0; + // int8_t tzm = 0; tm.tm_year = 0; if (message.time_.tv_sec != (time_t)-1) { @@ -418,8 +464,10 @@ bool SyslogService::transmit(const QueuedLogMessage & message) { localtime_r(&message.time_.tv_sec, &tm); int16_t diff = 60 * (tm.tm_hour - utc.tm_hour) + tm.tm_min - utc.tm_min; diff = diff > 720 ? diff - 1440 : diff < -720 ? diff + 1440 : diff; - tzh = diff / 60; - tzm = diff < 0 ? (0 - diff) % 60 : diff % 60; + + // From previous EMS-ESP. Need to check if still needed. + // tzh = diff / 60; + // tzm = diff < 0 ? (0 - diff) % 60 : diff % 60; } if (udp_.beginPacket(ip_, port_) != 1) { @@ -427,26 +475,54 @@ bool SyslogService::transmit(const QueuedLogMessage & message) { return false; } - udp_.printf_P(PSTR("<%u>1 "), ((unsigned int)message.content_->facility * 8) + std::min(7U, (unsigned int)message.content_->level)); + /* + * The level is constrained to 0-7 by design in RFC 5424 because higher + * severity values would be a different severity in another facility. The + * TRACE level and all other invalid values (because of the conversion to + * unsigned) are converted to DEBUG. + * + * RFC 5424 requires that the facility MUST be 0-23. Values here are allowed + * to go up to 31 because there is no obvious candidate to convert them to + * and because the multiplication by a factor of 256 means that (with the + * cast back to uint8_t) higher values just overflow into other facilities + * (which is easier than doing it by modulo 24). The enum doesn't allow + * values that are out of range of the RFC. + * + * The maximum possible priority value does not exceed the requirement that + * the PRI part MUST be 3-5 characters. + */ + udp_.printf("<%u>1 ", (uint8_t)(message.content_->facility * 8U) + std::min(7U, (unsigned int)message.content_->level)); + if (tm.tm_year != 0) { - udp_.printf_P(PSTR("%04u-%02u-%02uT%02u:%02u:%02u.%06u%+02d:%02d"), + udp_.printf_P("%04u-%02u-%02uT%02u:%02u:%02u.%06luZ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, - (uint32_t)message.time_.tv_usec, - tzh, - tzm); + (unsigned long)message.time_.tv_usec); + + // udp_.printf("%04u-%02u-%02uT%02u:%02u:%02u.%06u%+02d:%02d", + // tm.tm_year + 1900, + // tm.tm_mon + 1, + // tm.tm_mday, + // tm.tm_hour, + // tm.tm_min, + // tm.tm_sec, + // (unsigned long)message.time_.tv_usec, + // tzh, + // tzm); } else { udp_.print('-'); } + // Changes for EMS-ESP by MichaelDvP + udp_.printf_P(PSTR(" %s %s - - - "), hostname_.c_str(), (message.content_->name)); char id_c_str[15]; - snprintf_P(id_c_str, sizeof(id_c_str), PSTR(" %lu: "), message.id_); + snprintf(id_c_str, sizeof(id_c_str), " %lu: ", message.id_); std::string msgstr = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3) + ' ' + uuid::log::format_level_char(message.content_->level) + id_c_str + message.content_->text; for (uint16_t i = 0; i < msgstr.length(); i++) { diff --git a/lib/uuid-syslog/src/uuid/syslog.h b/lib/uuid-syslog/src/uuid/syslog.h index 065b77bd4..35a9e3f7b 100644 --- a/lib/uuid-syslog/src/uuid/syslog.h +++ b/lib/uuid-syslog/src/uuid/syslog.h @@ -1,6 +1,6 @@ /* - * uuid-syslog - Syslog service - * Copyright 2019 Simon Arlott + * uuid-syslog - Microcontroller syslog service + * Copyright 2019,2021-2022 Simon Arlott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,21 +20,34 @@ #define UUID_SYSLOG_H_ #include -#ifdef ARDUINO_ARCH_ESP8266 -#include -#else #include -#endif #include #include -#include #include #include #include #include +#ifndef UUID_LOG_THREAD_SAFE +#define UUID_LOG_THREAD_SAFE 0 +#endif + +#ifndef UUID_COMMON_STD_MUTEX_AVAILABLE +#define UUID_COMMON_STD_MUTEX_AVAILABLE 0 +#endif + +#if UUID_COMMON_STD_MUTEX_AVAILABLE +#define UUID_SYSLOG_THREAD_SAFE 1 +#else +#define UUID_SYSLOG_THREAD_SAFE 0 +#endif + +#if UUID_LOG_THREAD_SAFE +#include +#endif + namespace uuid { /** @@ -45,6 +58,17 @@ namespace uuid { */ namespace syslog { +/** + * Thread-safe status of the library. + * + * @since 2.2.0 + */ +#if UUID_COMMON_THREAD_SAFE && UUID_LOG_THREAD_SAFE && UUID_SYSLOG_THREAD_SAFE +static constexpr bool thread_safe = true; +#else +static constexpr bool thread_safe = false; +#endif + /** * Log handler for sending messages to a syslog server. * @@ -60,9 +84,8 @@ class SyslogService : public uuid::log::Handler { * * @since 1.0.0 */ - SyslogService() = default; - - ~SyslogService(); + SyslogService() = default; + ~SyslogService() = default; /** * Register the log handler with the logging framework. @@ -110,6 +133,14 @@ class SyslogService : public uuid::log::Handler { */ void maximum_log_messages(size_t count); + /** + * Get the current number of queued log messages. + * + * @return The current number of queued log messages. + * @since 2.1.0 + */ + size_t current_log_messages() const; + /** * Get the server to send messages to. * @@ -129,7 +160,6 @@ class SyslogService : public uuid::log::Handler { * @since 2.0.0 */ void destination(IPAddress host, uint16_t port = DEFAULT_PORT); - void destination(const char * host, uint16_t port = DEFAULT_PORT); /** * Get local hostname. @@ -185,7 +215,7 @@ class SyslogService : public uuid::log::Handler { virtual void operator<<(std::shared_ptr message); /** - * added MichaelDvP + * added for EMS-ESP * query status variables */ size_t queued() { @@ -227,14 +257,27 @@ class SyslogService : public uuid::log::Handler { QueuedLogMessage(unsigned long id, std::shared_ptr && content); ~QueuedLogMessage() = default; - unsigned long id_; /*!< Sequential identifier for this log message. @since 1.0.0 */ - struct timeval time_; /*!< Time message was received. @since 1.0.0 */ - const std::shared_ptr content_; /*!< Log message content. @since 1.0.0 */ + unsigned long id_; /*!< Sequential identifier for this log message. @since 1.0.0 */ + struct timeval time_; /*!< Time message was received. @since 1.0.0 */ + std::shared_ptr content_; /*!< Log message content. @since 1.0.0 */ private: static bool time_good_; /*!< System time appears to be valid. @since 1.0.0 */ }; + /** + * Add a new log message. + * + * This will be put in a queue for output at the next loop() + * process. The queue has a maximum size of + * get_maximum_log_messages() and will discard the oldest message + * first. + * + * @param[in] message New log message, shared by all handlers. + * @since 2.2.0 + */ + void add_message(std::shared_ptr & message); + /** * Remove messages that were queued before the log level was set. * @@ -264,20 +307,25 @@ class SyslogService : public uuid::log::Handler { static uuid::log::Logger logger_; /*!< uuid::log::Logger instance for syslog services. @since 1.0.0 */ - bool started_ = false; /*!< Flag to indicate that messages have started being transmitted. @since 1.0.0 */ - WiFiUDP udp_; /*!< UDP client. @since 1.0.0 */ - IPAddress ip_; /*!< Host-IP to send messages to. @since 1.0.0 */ - std::string host_; /*!< Host to send messages to. */ - uint16_t port_ = DEFAULT_PORT; /*!< Port to send messages to. @since 1.0.0 */ - uint64_t last_transmit_ = 0; /*!< Last transmit time. @since 1.0.0 */ - std::string hostname_{'-'}; /*!< Local hostname. @since 1.0.0 */ + bool started_ = false; /*!< Flag to indicate that messages have started being transmitted. @since 1.0.0 */ + bool level_set_ = false; /*!< Flag to indicate that the log level has been set at least once. @since 2.2.0 */ + WiFiUDP udp_; /*!< UDP client. @since 1.0.0 */ + IPAddress host_; /*!< Host to send messages to. @since 1.0.0 */ + uint16_t port_ = DEFAULT_PORT; /*!< Port to send messages to. @since 1.0.0 */ + uint64_t last_transmit_ = 0; /*!< Last transmit time. @since 1.0.0 */ + std::string hostname_{'-'}; /*!< Local hostname. @since 1.0.0 */ +#if UUID_SYSLOG_THREAD_SAFE + mutable std::mutex mutex_; /*!< Mutex for queued log messages. @since 2.2.0 */ +#endif size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum number of log messages to buffer before they are output. @since 1.0.0 */ unsigned long log_message_id_ = 0; /*!< The next identifier to use for queued log messages. @since 1.0.0 */ std::list log_messages_; /*!< Queued log messages, in the order they were received. @since 1.0.0 */ - std::atomic log_messages_overflow_{false}; /*!< Check if log messages have overflowed the buffer. @since 1.0.0 */ - uint64_t mark_interval_ = 0; /*!< Mark interval in milliseconds. @since 2.0.0 */ - uint64_t last_message_ = 0; /*!< Last message/mark time. @since 2.0.0 */ - unsigned long log_message_fails_ = 0; + uint64_t mark_interval_ = 0; /*!< Mark interval in milliseconds. @since 2.0.0 */ + uint64_t last_message_ = 0; /*!< Last message/mark time. @since 2.0.0 */ + + // added by MichaelDvP for EMS-ESP + IPAddress ip_; /*!< Host-IP to send messages to. @since 1.0.0 */ + unsigned long log_message_fails_ = 0; }; } // namespace syslog diff --git a/lib/uuid-telnet/src/telnet.cpp b/lib/uuid-telnet/src/telnet.cpp index e40bc51d9..3faeefd01 100644 --- a/lib/uuid-telnet/src/telnet.cpp +++ b/lib/uuid-telnet/src/telnet.cpp @@ -67,7 +67,7 @@ TelnetService::TelnetService(std::shared_ptr commands, TelnetService::TelnetService(uint16_t port, std::shared_ptr commands, unsigned int context, unsigned int flags) : TelnetService(port, [commands, context, flags](Stream & stream, const IPAddress & addr __attribute__((unused)), uint16_t port __attribute__((unused))) - -> std::shared_ptr { return std::make_shared(commands, stream, context, flags); }) { + -> std::shared_ptr { return std::make_shared(stream, commands, context, flags); }) { } TelnetService::TelnetService(shell_factory_function shell_factory) @@ -101,14 +101,13 @@ size_t TelnetService::maximum_connections() const { void TelnetService::maximum_connections(size_t count) { maximum_connections_ = std::max((size_t)1, count); - while (connections_.size() > maximum_connections_) { + if (connections_.size() > maximum_connections_) { + size_t stop = connections_.size() - maximum_connections_; + for (auto it = connections_.begin(); it != connections_.end();) { - if (it->active()) { - it->stop(); - it = connections_.erase(it); - break; - } else { - it = connections_.erase(it); + if (it->stop()) { + if (--stop == 0) + break; } } } @@ -143,21 +142,21 @@ void TelnetService::loop() { if (client) { if (connections_.size() >= maximum_connections_) { #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE - logger_.info(F("New connection from [%s]:%u rejected (connection limit reached)"), + logger_.info("New connection from [%s]:%u rejected (connection limit reached)", uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort()); #else - logger_.info(F("New connection rejected (connection limit reached)")); + logger_.info("New connection rejected (connection limit reached)"); #endif - client.println(F("Maximum connection limit reached")); + client.println("Maximum connection limit reached"); client.stop(); } else { #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE - logger_.info(F("New connection from [%s]:%u accepted"), uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort()); + logger_.info("New connection from [%s]:%u accepted", uuid::printable_to_string(client.remoteIP()).c_str(), client.remotePort()); #endif connections_.emplace_back(shell_factory_, std::move(client), initial_idle_timeout_, write_timeout_); #if !(UUID_TELNET_HAVE_WIFICLIENT_REMOTE) - logger_.info(F("New connection %p accepted"), &connections_.back()); + logger_.info("New connection %p accepted", &connections_.back()); #endif } } @@ -194,34 +193,37 @@ TelnetService::Connection::Connection(shell_factory_function & shell_factory, Wi shell->idle_timeout(idle_timeout); shell->start(); shell_ = shell; - } else { - shell_ = nullptr; } } -bool TelnetService::Connection::active() { - return shell_.use_count() > 1; -} - bool TelnetService::Connection::loop() { - if (active()) { + if (!shell_.expired()) { if (!client_.connected()) { - shell_->stop(); + auto shell = shell_.lock(); + + if (shell) { + shell->stop(); + } } return true; } else { #if UUID_TELNET_HAVE_WIFICLIENT_REMOTE - logger_.info(F("Connection from [%s]:%u closed"), uuid::printable_to_string(addr_).c_str(), port_); + logger_.info("Connection from [%s]:%u closed", uuid::printable_to_string(addr_).c_str(), port_); #else - logger_.info(F("Connection %p closed"), this); + logger_.info("Connection %p closed", this); #endif return false; } } -void TelnetService::Connection::stop() { - if (shell_) { - shell_->stop(); +bool TelnetService::Connection::stop() { + auto shell = shell_.lock(); + + if (shell) { + shell->stop(); + return true; + } else { + return false; } } diff --git a/lib/uuid-telnet/src/uuid/telnet.h b/lib/uuid-telnet/src/uuid/telnet.h index 46ece1d01..fb1245de9 100644 --- a/lib/uuid-telnet/src/uuid/telnet.h +++ b/lib/uuid-telnet/src/uuid/telnet.h @@ -392,36 +392,30 @@ class TelnetService { Connection(shell_factory_function & shell_factory, WiFiClient && client, unsigned long idle_timeout, unsigned long write_timeout); ~Connection() = default; - /** - * Check if the shell is still active. - * - * @return Active status of the shell. - * @since 0.1.0 - */ - bool active(); /** * Stop the shell if the client is not connected. * - * @return Active status of the shell. + * @return True if the shell had not already stopped. * @since 0.1.0 */ bool loop(); + /** * Stop the shell. * - * @since 0.1.0 + * @since 0.2.0 */ - void stop(); + bool stop(); private: Connection(const Connection &) = delete; Connection & operator=(const Connection &) = delete; - WiFiClient client_; /*!< Client connection. @since 0.1.0 */ - TelnetStream stream_; /*!< Telnet stream for the connection. @since 0.1.0 */ - std::shared_ptr shell_; /*!< Shell for connection. @since 0.1.0 */ - IPAddress addr_; /*!< Remote address of connection. @since 0.1.0 */ - uint16_t port_; /*!< Remote port of connection. @since 0.1.0 */ + WiFiClient client_; /*!< Client connection. @since 0.1.0 */ + TelnetStream stream_; /*!< Telnet stream for the connection. @since 0.1.0 */ + std::weak_ptr shell_; /*!< Shell for connection. @since 0.2.0 */ + IPAddress addr_; /*!< Remote address of connection. @since 0.1.0 */ + uint16_t port_; /*!< Remote port of connection. @since 0.1.0 */ }; TelnetService(const TelnetService &) = delete;