diff --git a/src/logging/LogQueue.cpp b/src/logging/LogQueue.cpp new file mode 100644 index 000000000..301e9ac42 --- /dev/null +++ b/src/logging/LogQueue.cpp @@ -0,0 +1,82 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Jabberrock & SlimeVR contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include "LogQueue.h" + +namespace SlimeVR::Logging +{ + +bool LogQueue::empty() const +{ + return m_Count == 0; +} + +const char* LogQueue::front() const +{ + if (empty()) + { + return nullptr; + } + + return m_MessageQueue[m_StartIndex].data(); +} + +void LogQueue::push(const char* message) +{ + if (m_Count >= m_MessageQueue.max_size()) + { + // Drop the earliest message to make space + pop(); + } + + setMessageAt(m_Count, message); + ++m_Count; +} + +void LogQueue::pop() +{ + if (m_Count > 0) + { + m_StartIndex = (m_StartIndex + 1) % m_MessageQueue.max_size(); + --m_Count; + } +} + +void LogQueue::setMessageAt(int offset, const char* message) +{ + size_t index = (m_StartIndex + offset) % m_MessageQueue.max_size(); + std::array& entry = m_MessageQueue[index]; + + std::strncpy(entry.data(), message, entry.max_size()); + entry[entry.max_size() - 1] = '\0'; // NULL terminate string in case message overflows because strncpy does not do that +} + +// Global instance of the log queue +LogQueue& LogQueue::instance() +{ + return s_Instance; +} + +LogQueue LogQueue::s_Instance{}; + +} // namespace SlimeVR::Logging diff --git a/src/logging/LogQueue.h b/src/logging/LogQueue.h new file mode 100644 index 000000000..9b1dd0b7e --- /dev/null +++ b/src/logging/LogQueue.h @@ -0,0 +1,69 @@ +/* + SlimeVR Code is placed under the MIT license + Copyright (c) 2024 Jabberrock & SlimeVR contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef LOGGING_LOGQUEUE_H +#define LOGGING_LOGQUEUE_H + +#include +#include +#include +#include + +namespace SlimeVR::Logging { + +class LogQueue { +public: + // Whether there are any messages in the queue. + bool empty() const; + + // First message in the queue. + const char* front() const; + + // Adds a message to the end of the queue. + // - Messages that are too long will be truncated. + // - If the queue is full, the earliest message will be silently dropped + void push(const char* message); + + // Removes a message from the front of the queue. + void pop(); + + // Global instance of the log queue + static LogQueue& instance(); + +private: + // Sets the message at a particular offset + void setMessageAt(int offset, const char* message); + + static constexpr size_t MaxMessages = 30; + static constexpr size_t MaxMessageLength = std::numeric_limits::max(); + + size_t m_StartIndex = 0; + size_t m_Count = 0; + std::array, MaxMessages> m_MessageQueue; + + static LogQueue s_Instance; +}; + +} // namespace SlimeVR::Logging + +#endif /* LOGGING_LOGQUEUE_H */ diff --git a/src/logging/Logger.cpp b/src/logging/Logger.cpp index be8d68b9b..c5444d1a0 100644 --- a/src/logging/Logger.cpp +++ b/src/logging/Logger.cpp @@ -1,4 +1,5 @@ #include "Logger.h" +#include "LogQueue.h" namespace SlimeVR { namespace Logging { @@ -65,6 +66,9 @@ void Logger::log(Level level, const char* format, va_list args) { } Serial.printf("[%-5s] [%s] %s\n", levelToString(level), buf, buffer); + + // REVIEW: Do we want to print the log level and tags too? + LogQueue::instance().push(buffer); } } // namespace Logging } // namespace SlimeVR diff --git a/src/network/connection.cpp b/src/network/connection.cpp index 36dec41bc..a5bf1be98 100644 --- a/src/network/connection.cpp +++ b/src/network/connection.cpp @@ -25,6 +25,7 @@ #include "GlobalVars.h" #include "logging/Logger.h" +#include "logging/LogQueue.h" #include "packets.h" #define TIMEOUT 3000UL @@ -189,7 +190,12 @@ bool Connection::sendPacketNumber() { } bool Connection::sendShortString(const char* str) { - uint8_t size = strlen(str); + size_t size = strlen(str); + + constexpr size_t maxLength = std::numeric_limits::max(); + if (size > maxLength) { + size = maxLength; + } MUST_TRANSFER_BOOL(sendByte(size)); MUST_TRANSFER_BOOL(sendBytes((const uint8_t*)str, size)); @@ -395,6 +401,31 @@ void Connection::sendAcknowledgeConfigChange(uint8_t sensorId, uint16_t configTy MUST(endPacket()); } +// PACKET_LOG 26 +void Connection::sendLogMessage(const char* message) { + MUST(m_Connected); + + MUST(beginPacket()); + + MUST(sendPacketType(PACKET_LOG)); + MUST(sendPacketNumber()); + MUST(sendShortString(message)); + + MUST(endPacket()); +} + +void Connection::sendPendingLogMessage() { + MUST(m_Connected); + MUST(m_ServerFeatures.has(ServerFeatures::PROTOCOL_LOG_SUPPORT)); + + Logging::LogQueue& logQueue = Logging::LogQueue::instance(); + if (!logQueue.empty()) { + const char* message = logQueue.front(); + sendLogMessage(message); + logQueue.pop(); + } +} + void Connection::sendTrackerDiscovery() { MUST(!m_Connected); @@ -634,6 +665,7 @@ void Connection::update() { auto& sensors = sensorManager.getSensors(); updateSensorState(sensors); + sendPendingLogMessage(); maybeRequestFeatureFlags(); if (!m_Connected) { @@ -731,6 +763,9 @@ void Connection::update() { m_Logger.debug("Server supports packet bundling"); } #endif + if (m_ServerFeatures.has(ServerFeatures::PROTOCOL_LOG_SUPPORT)) { + m_Logger.debug("Server supports network logging"); + } } break; diff --git a/src/network/connection.h b/src/network/connection.h index 1b4240024..25805c4b7 100644 --- a/src/network/connection.h +++ b/src/network/connection.h @@ -158,6 +158,10 @@ class Connection { void sendAcknowledgeConfigChange(uint8_t sensorId, uint16_t configType); + // PACKET_LOG 26 + void sendLogMessage(const char* message); + void sendPendingLogMessage(); + bool m_Connected = false; SlimeVR::Logging::Logger m_Logger = SlimeVR::Logging::Logger("UDPConnection"); diff --git a/src/network/featureflags.h b/src/network/featureflags.h index bc917ba2e..8b4a2e198 100644 --- a/src/network/featureflags.h +++ b/src/network/featureflags.h @@ -38,6 +38,14 @@ struct ServerFeatures { // Server can parse bundle packets: `PACKET_BUNDLE` = 100 (0x64). PROTOCOL_BUNDLE_SUPPORT, + // Server can parse bundle packets with compact headers and packed IMU rotation/acceleration frames: + // - `PACKET_BUNDLE_COMPACT` = 101 (0x65), + // - `PACKET_ROTATION_AND_ACCELERATION` = 23 (0x17). + PROTOCOL_BUNDLE_COMPACT_SUPPORT, + + // Server can receive log messages: `PACKET_LOG` = 102 (0x66). + PROTOCOL_LOG_SUPPORT, + // Add new flags here BITS_TOTAL, diff --git a/src/network/packets.h b/src/network/packets.h index f6c3d9b53..a0e9d419a 100644 --- a/src/network/packets.h +++ b/src/network/packets.h @@ -51,6 +51,7 @@ // packet #define PACKET_ACKNOWLEDGE_CONFIG_CHANGE 24 #define PACKET_SET_CONFIG_FLAG 25 +#define PACKET_LOG 26 #define PACKET_BUNDLE 100