17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 06:19:19 +02:00

hardware protocol v2: configuration, wiring and debug panel

Adds the WITH_HARDWARE_PROTOCOL_V2 build option and the eu07.ini entries
which configure it: uart2 declares a serial link and may be repeated for
several controllers, the remaining uart2* entries tune frame size,
heartbeat, rate limits and logging. UART v1 is untouched and both may be
active at the same time on different ports.

The driver mode starts the manager when at least one link is configured
and pumps it from the input polling loop, and the debug panel gains a
Hardware Protocol v2 section reporting link and session condition,
packet rates, round trip time, protocol and transport error counters and
whatever diagnostics the device itself reports, with switches for
protocol and frame logging.
This commit is contained in:
2026-09-05 20:35:04 +00:00
parent 4e3afdd2a6
commit 91be90548f
11 changed files with 269 additions and 9 deletions

View File

@@ -71,6 +71,7 @@ option(WITH_OPENGL_MODERN "Compile with OpenGL modern renderer" ON)
option(WITH_OPENGL_LEGACY "Compile with OpenGL legacy renderer" ON)
option(WITH_PYTHON "Python 3.14 embedded scripting" ON)
option(WITH_UART "Compile with libserialport" ON)
option(WITH_HARDWARE_PROTOCOL_V2 "Compile with MaSzyna Hardware Protocol v2 (UART v2)" ON)
option(WITH_LUA "Compile with lua scripting support" ON)
option(WITH_OPENVR "Compile with OpenVR" OFF)
option(WITH_ZMQ "Compile with cppzmq" OFF)
@@ -267,6 +268,24 @@ if (WITH_UART)
set(SOURCES ${SOURCES} "utilities/uart.cpp")
endif()
if (WITH_HARDWARE_PROTOCOL_V2)
set(DEFINITIONS ${DEFINITIONS} "WITH_HARDWARE_PROTOCOL_V2")
set(SOURCES ${SOURCES}
"hardware/hardware_manager.cpp"
"hardware/diagnostics/hardware_diagnostics.cpp"
"hardware/protocol/protocol_codec.cpp"
"hardware/protocol/protocol_defs.cpp"
"hardware/protocol/protocol_session.cpp"
"hardware/registry/command_registry.cpp"
"hardware/registry/hardware_symbol_registry.cpp"
"hardware/registry/state_registry.cpp"
"hardware/subscriptions/subscription_manager.cpp")
if (WITH_UART)
# the serial transport is the only one which needs libserialport
set(SOURCES ${SOURCES} "hardware/transport/serial_transport.cpp")
endif()
endif()
if (WITH_ZMQ)
set(DEFINITIONS ${DEFINITIONS} "WITH_ZMQ")
set(SOURCES ${SOURCES} "input/zmq_input.cpp")

View File

@@ -55,6 +55,13 @@ void driver_mode::drivermode_input::poll()
uart->poll();
}
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
if (hardware != nullptr)
{
// exchanges frames already gathered by the hardware worker thread, never touches the port itself
hardware->update();
}
#endif
#ifdef WITH_ZMQ
if (zmq != nullptr)
{
@@ -86,6 +93,12 @@ bool driver_mode::drivermode_input::init()
uart->init();
}
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
if (true == Global.hardware_conf.enable)
{
hardware = std::make_unique<hardware::hardware_manager>(Global.hardware_conf);
}
#endif
#ifdef WITH_ZMQ
if (!Global.zmq_address.empty())
{

View File

@@ -20,6 +20,9 @@ http://mozilla.org/MPL/2.0/.
#ifdef WITH_UART
#include "utilities/uart.h"
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
#include "hardware/hardware_manager.h"
#endif
#ifdef WITH_ZMQ
#include "input/zmq_input.h"
#endif
@@ -76,6 +79,9 @@ private:
#ifdef WITH_UART
std::unique_ptr<uart_input> uart;
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
std::unique_ptr<hardware::hardware_manager> hardware;
#endif
#ifdef WITH_ZMQ
std::unique_ptr<zmq_input> zmq;
#endif

View File

@@ -38,6 +38,10 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/uart.h"
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
#include "hardware/hardware_manager.h"
#endif
void
drivingaid_panel::update() {
@@ -544,6 +548,9 @@ void debug_panel::update()
#ifdef WITH_UART
m_uartlines.clear();
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
m_hardwarelines.clear();
#endif
update_section_vehicle( m_vehiclelines );
update_section_engine( m_enginelines );
@@ -557,6 +564,9 @@ void debug_panel::update()
#ifdef WITH_UART
update_section_uart(m_uartlines);
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
update_section_hardware(m_hardwarelines);
#endif
}
void
@@ -620,6 +630,18 @@ debug_panel::render() {
ImGui::Combo("Baud", &UartStatus.selected_baud_index, uart_baudrates_list, uart_baudrates_list_num);
ImGui::Checkbox("Enabled", &UartStatus.enabled);
}
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
if( true == render_section( "Hardware Protocol v2", m_hardwarelines ) ) {
bool logmessages = hardware::debug_flags.log_messages;
if( ImGui::Checkbox( "Log protocol messages", &logmessages ) ) {
hardware::debug_flags.log_messages = logmessages;
}
bool logframes = hardware::debug_flags.log_frames;
if( ImGui::Checkbox( "Log every frame", &logframes ) ) {
hardware::debug_flags.log_frames = logframes;
}
}
#endif
// toggles
ImGui::Separator();
@@ -1311,6 +1333,97 @@ debug_panel::update_section_uart( std::vector<text_line> &Output ) {
}
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
void
debug_panel::update_section_hardware( std::vector<text_line> &Output ) {
auto const *manager = hardware::hardware_manager::instance();
if( ( manager == nullptr ) || ( false == manager->active() ) ) {
Output.emplace_back( "(no links configured, add an \"uart2 <port> <baud>\" entry to eu07.ini)", Global.UITextColor );
return;
}
for( auto const &report : manager->reports() ) {
auto const &protocol = report.protocol;
std::string textline =
( report.device_name.empty() ? std::string( "(waiting for device)" ) : report.device_name )
+ ( report.device_role.empty() ? "" : " [" + report.device_role + "]" )
+ ( report.device_id.empty() ? "" : " id: " + report.device_id )
+ ( report.firmware_version.empty() ? "" : " fw: " + report.firmware_version );
textline +=
"\n" + report.transport_kind + " " + report.endpoint
+ " " + hardware::to_string( report.state )
+ " / " + hardware::to_string( report.session );
if( report.session_id != 0 ) {
textline +=
"\nprotocol v" + std::to_string( report.protocol_major ) + "." + std::to_string( report.protocol_minor )
+ " session " + std::to_string( report.session_id )
+ " frame " + std::to_string( report.frame_size ) + " b"
+ " handles " + std::to_string( report.handles )
+ " subscriptions " + std::to_string( report.subscriptions );
}
textline +=
"\nrx " + to_string( protocol.rx_rate, 0 ) + " pkt/s"
+ " tx " + to_string( protocol.tx_rate, 0 ) + " pkt/s"
+ " rtt " + to_string( protocol.round_trip_time_ms, 1 ) + " ms"
+ " last frame " + to_string( protocol.last_rx_age, 2 ) + " s ago";
textline +=
"\nframes rx " + std::to_string( protocol.rx_frames ) + " tx " + std::to_string( protocol.tx_frames )
+ " bytes rx " + std::to_string( report.transport.rx_bytes ) + " tx " + std::to_string( report.transport.tx_bytes );
textline +=
"\nprotocol errors: crc " + std::to_string( protocol.crc_errors )
+ " frame " + std::to_string( protocol.frame_errors )
+ " length " + std::to_string( protocol.length_errors )
+ " version " + std::to_string( protocol.version_errors )
+ " gaps " + std::to_string( protocol.sequence_gaps );
textline +=
"\nunknown: message " + std::to_string( protocol.unknown_messages )
+ " symbol " + std::to_string( protocol.unknown_symbols )
+ " handle " + std::to_string( protocol.unknown_handles )
+ " duplicates " + std::to_string( protocol.duplicate_commands )
+ " rate limited " + std::to_string( protocol.rate_limited );
textline +=
"\nack " + std::to_string( protocol.acks )
+ " nack " + std::to_string( protocol.nacks )
+ " state updates " + std::to_string( protocol.state_updates )
+ " commands " + std::to_string( protocol.commands_executed )
+ " controls " + std::to_string( protocol.controls_applied );
textline +=
"\nsession uptime " + to_string( protocol.connection_uptime, 0 ) + " s"
+ " transport errors " + std::to_string( report.transport.transport_errors )
+ " reconnects " + std::to_string( report.transport.reconnects );
if( false == report.transport.last_error.empty() ) {
textline += "\nlast transport error: " + report.transport.last_error;
}
if( true == report.device.available ) {
textline +=
"\ndevice: uptime " + std::to_string( report.device.uptime_ms / 1000 ) + " s"
+ ", supply " + to_string( report.device.supply_voltage, 2 ) + " V"
+ ", temperature " + to_string( report.device.temperature, 1 ) + " deg C"
+ ", overflows rx " + std::to_string( report.device.rx_overflows ) + " tx " + std::to_string( report.device.tx_overflows )
+ ", watchdog resets " + std::to_string( report.device.watchdog_resets );
if( false == report.device.firmware_build.empty() ) {
textline += ", build " + report.device.firmware_build;
}
}
Output.emplace_back( textline, Global.UITextColor );
}
}
#endif
void
debug_panel::update_section_scenario( std::vector<text_line> &Output ) {

View File

@@ -91,6 +91,9 @@ private:
void update_section_renderer( std::vector<text_line> &Output );
#ifdef WITH_UART
void update_section_uart( std::vector<text_line> &Output );
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
void update_section_hardware( std::vector<text_line> &Output );
#endif
// section update helpers
std::string update_vehicle_coupler( int const Side );
@@ -116,7 +119,8 @@ private:
m_eventqueuelines,
m_powergridlines,
m_rendererlines,
m_uartlines;
m_uartlines,
m_hardwarelines;
double last_time = std::numeric_limits<double>::quiet_NaN();

View File

@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include <atomic>
#include <string>
#include <vector>
@@ -53,4 +54,13 @@ struct config
bool debug_frames = false;
};
// logging switches which can be flipped at run time from the simulator debug panel
struct debug_switches
{
std::atomic<bool> log_messages { false };
std::atomic<bool> log_frames { false };
};
extern debug_switches debug_flags;
} // namespace hardware

View File

@@ -30,6 +30,8 @@ constexpr std::size_t read_chunk_size = 4096;
} // anonymous namespace
debug_switches debug_flags;
hardware_manager *hardware_manager::s_instance = nullptr;
hardware_link::hardware_link( std::unique_ptr<hardware_transport> Transport, config const &Config ) : m_transport( std::move( Transport ) ), m_config( Config ), m_session( *this, Config )
@@ -224,6 +226,9 @@ hardware_manager::hardware_manager( config const &Config ) : m_config( Config )
}
#endif
debug_flags.log_messages = m_config.debug;
debug_flags.log_frames = m_config.debug_frames;
m_reports.resize( m_links.size() );
if( false == m_links.empty() )
@@ -259,18 +264,28 @@ void hardware_manager::worker()
}
}
void hardware_manager::update( double const Deltatime )
void hardware_manager::update()
{
if( true == m_links.empty() )
{
return;
}
// the input polling code doesn't carry a time step, so the sessions get their own
auto const now = std::chrono::steady_clock::now();
double deltatime = 0.0;
if( true == m_updatestarted )
{
deltatime = std::clamp( std::chrono::duration<double>( now - m_lastupdate ).count(), 0.0, 0.5 );
}
m_updatestarted = true;
m_lastupdate = now;
state_registry::capture( m_snapshot );
for( std::size_t index = 0; index < m_links.size(); ++index )
{
m_links[ index ]->update( Deltatime, m_snapshot );
m_links[ index ]->update( deltatime, m_snapshot );
m_reports[ index ] = m_links[ index ]->report();
}
}

View File

@@ -82,8 +82,8 @@ public:
explicit hardware_manager( config const &Config );
~hardware_manager();
// called once per simulation frame from the input polling code
void update( double const Deltatime );
// called once per simulation frame from the input polling code; the time step is measured here
void update();
std::vector<link_report> const &reports() const { return m_reports; }
bool active() const { return false == m_links.empty(); }
@@ -102,6 +102,8 @@ private:
std::vector<std::unique_ptr<hardware_link>> m_links;
std::vector<link_report> m_reports;
state_snapshot m_snapshot;
std::chrono::steady_clock::time_point m_lastupdate;
bool m_updatestarted = false;
std::thread m_thread;
std::atomic<bool> m_quit { false };
};

View File

@@ -182,7 +182,7 @@ void protocol_session::send( message_type const Type, std::uint8_t const Flags,
++m_diagnostics.tx_frames;
m_txrate.add();
if( true == m_config.debug_frames )
if( true == debug_flags.log_frames )
{
WriteLog( "hardware: tx " + std::string( to_string( Type ) ) + ", " + std::to_string( Payload.size() ) + " bytes" );
}
@@ -206,7 +206,7 @@ void protocol_session::send_nack( std::uint32_t const Transaction, message_type
send( message_type::nack, packetflag_response | packetflag_error, writer );
++m_diagnostics.nacks;
if( true == m_config.debug )
if( true == debug_flags.log_messages )
{
WriteLog( "hardware: nack " + std::string( to_string( Type ) ) + ", " + std::string( to_string( Error ) ) );
}
@@ -318,7 +318,7 @@ void protocol_session::handle_packet( decoded_packet const &Packet )
m_lastreceivedsequence = Packet.header.sequence;
m_sequenceseen = true;
if( true == m_config.debug_frames )
if( true == debug_flags.log_frames )
{
WriteLog( "hardware: rx " + std::string( to_string( Packet.header.type ) ) + ", " + std::to_string( Packet.payload.size() ) + " bytes" );
}
@@ -366,7 +366,7 @@ void protocol_session::handle_packet( decoded_packet const &Packet )
case message_type::device_ready:
m_state = session_state::ready;
if( true == m_config.debug )
if( true == debug_flags.log_messages )
{
WriteLog( "hardware: device '" + m_devicename + "' is ready" );
}

View File

@@ -1040,6 +1040,77 @@ bool global_settings::ConfigParseHardware(cParser& Parser, const std::string& to
}
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
// hardware protocol v2 (uart v2). the legacy uart entries above keep working unchanged, both
// protocols may be used at the same time as long as they don't share a port
if (token == "uart2")
{
Parser.getTokens(2, false);
hardware::serial_link_config link;
Parser >> link.port
>> link.baud;
if (false == link.port.empty())
{
hardware_conf.serial_links.emplace_back(link);
hardware_conf.enable = true;
}
return true;
}
if (token == "uart2framesize")
{
int framesize = static_cast<int>(hardware_conf.frame_size);
ParseOneClamped(Parser, framesize, static_cast<int>(hardware::protocol_frame_size_min), static_cast<int>(hardware::protocol_frame_size_max));
hardware_conf.frame_size = static_cast<std::size_t>(framesize);
return true;
}
if (token == "uart2heartbeat")
{
ParseOneClamped(Parser, hardware_conf.heartbeat_interval_ms, 50, 10000);
return true;
}
if (token == "uart2timeout")
{
ParseOneClamped(Parser, hardware_conf.heartbeat_timeout_count, 2, 100);
return true;
}
if (token == "uart2ratelimit")
{
Parser.getTokens(3);
Parser >> hardware_conf.max_frames_per_second
>> hardware_conf.max_payload_bytes_per_second
>> hardware_conf.max_commands_per_second;
return true;
}
if (token == "uart2pollinterval")
{
ParseOneClamped(Parser, hardware_conf.poll_interval_ms, 1, 100);
return true;
}
if (token == "uart2reconnect")
{
ParseOneClamped(Parser, hardware_conf.reconnect_interval, 0.1f, 60.0f);
return true;
}
if (token == "uart2debug")
{
ParseOne(Parser, hardware_conf.debug, 1);
return true;
}
if (token == "uart2debugframes")
{
ParseOne(Parser, hardware_conf.debug_frames, 1);
return true;
}
#endif
#ifdef WITH_ZMQ
if (token == "zmq.address")
{

View File

@@ -20,6 +20,9 @@ http://mozilla.org/MPL/2.0/.
#ifdef WITH_UART
#include "utilities/uart.h"
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
#include "hardware/hardware_config.h"
#endif
#ifdef WITH_ZMQ
#include "input/zmq_input.h"
#endif
@@ -228,6 +231,10 @@ struct global_settings {
{"dynamicbrake", &uart_conf.dynamicenable},
};
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
// hardware protocol v2; independent of the legacy uart link above, both may run at the same time
hardware::config hardware_conf;
#endif
#ifdef WITH_ZMQ
std::string zmq_address;
#endif