17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 16:49:21 +02:00
Files
maszyna/hardware/hardware_manager.cpp
Hirek193 eed44dc858 hardware protocol v2.1: device log, diagnostic routines, manual links
Three additions, all driven from the simulator debug panel.

DEVICE_LOG lets a controller send text lines; they reach the simulator log
and a per connection view in the panel which keeps the last 64 of them.

Diagnostic routines: a controller lists the procedures it offers, the
operator picks one from a drop down and starts it, and the controller
reports progress and the result. While a routine runs the controller can
ask the simulator to show a message box with its own text and buttons and
gets back which one was pressed, which is what a valve or gauge
calibration needs. The message box appears whether the debug panel is
open or not.

Links can now be disconnected and reconnected by hand: disconnecting
closes the serial port so the controller can be reprogrammed, and the
session is rebuilt from scratch on reconnect. Ports which appear while
the simulation runs can be picked from a refreshed list, given a baud
rate and added as a new controller, so the manager now starts even with
no uart2 entries in eu07.ini.
2026-09-06 11:29:26 +00:00

450 lines
12 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "hardware/hardware_manager.h"
#include "utilities/Logs.h"
#ifdef WITH_UART
#include "hardware/transport/serial_transport.h"
#endif
namespace hardware
{
namespace
{
// received frames waiting to be picked up by the simulation thread
constexpr std::size_t rx_queue_limit = 256;
// frames waiting to be pushed out by the worker thread
constexpr std::size_t tx_queue_limit = 512;
constexpr std::size_t read_chunk_size = 4096;
// how often the list of serial ports is refreshed for the debug panel
constexpr float port_scan_interval = 2.0f;
// the worker has nothing to do when no link is configured
constexpr int idle_poll_interval_ms = 250;
} // 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 )
{
m_readbuffer.resize( read_chunk_size );
m_decoder.set_frame_size_limit( m_config.frame_size );
m_lastconnectattempt = std::chrono::steady_clock::now() - std::chrono::seconds( 10 );
m_endpoint = m_transport->endpoint();
m_report.transport_kind = m_transport->kind();
m_report.endpoint = m_endpoint;
m_transportstatus.endpoint = m_endpoint;
}
hardware_link::~hardware_link()
{
m_transport->disconnect();
}
void hardware_link::set_enabled( bool const Enabled )
{
m_enabled = Enabled;
m_report.enabled = Enabled;
}
void hardware_link::send_packet( packet_header const &Header, std::vector<std::uint8_t> const &Payload )
{
std::vector<std::uint8_t> frame;
encode_frame( Header, Payload.data(), Payload.size(), frame );
std::lock_guard<std::mutex> lock( m_mutex );
if( m_txqueue.size() >= tx_queue_limit )
{
// the device isn't keeping up; the oldest update is the least interesting one
m_txqueue.pop_front();
}
m_txqueue.emplace_back( std::move( frame ) );
}
void hardware_link::service()
{
if( false == m_enabled )
{
// the link was switched off from the debug panel; release the port and stay quiet
if( true == m_transport->connected() )
{
m_transport->disconnect();
m_writebuffer.clear();
m_decoder.reset();
std::lock_guard<std::mutex> lock( m_mutex );
m_transportstatus.connected = false;
m_transportstatus.last_error.clear();
m_transportdropped = true;
m_rxqueue.clear();
m_txqueue.clear();
}
return;
}
if( false == m_transport->connected() )
{
auto const now = std::chrono::steady_clock::now();
if( std::chrono::duration<float>( now - m_lastconnectattempt ).count() < m_config.reconnect_interval )
{
return;
}
m_lastconnectattempt = now;
if( true == m_transport->connect() )
{
m_decoder.reset();
std::lock_guard<std::mutex> lock( m_mutex );
m_rxqueue.clear();
m_txqueue.clear();
m_transportstatus.connected = true;
m_transportstatus.last_error.clear();
++m_transportstatus.reconnects;
}
else
{
std::lock_guard<std::mutex> lock( m_mutex );
if( true == m_transportstatus.last_error.empty() )
{
++m_transportstatus.transport_errors;
}
m_transportstatus.connected = false;
m_transportstatus.last_error = m_transport->last_error();
}
return;
}
bool failed = false;
// inbound
std::vector<decoded_packet> packets;
int const received = m_transport->read( m_readbuffer.data(), m_readbuffer.size() );
if( received < 0 )
{
failed = true;
}
else if( received > 0 )
{
m_decoder.feed( m_readbuffer.data(), static_cast<std::size_t>( received ), packets );
}
// outbound; partially written frames are kept until the port accepts the rest
if( false == failed )
{
if( true == m_writebuffer.empty() )
{
std::lock_guard<std::mutex> lock( m_mutex );
while( ( false == m_txqueue.empty() ) && ( m_writebuffer.size() < read_chunk_size ) )
{
auto const &frame = m_txqueue.front();
m_writebuffer.insert( m_writebuffer.end(), frame.begin(), frame.end() );
m_txqueue.pop_front();
}
}
if( false == m_writebuffer.empty() )
{
int const written = m_transport->write( m_writebuffer.data(), m_writebuffer.size() );
if( written < 0 )
{
failed = true;
}
else if( written > 0 )
{
m_writebuffer.erase( m_writebuffer.begin(), m_writebuffer.begin() + written );
std::lock_guard<std::mutex> lock( m_mutex );
m_transportstatus.tx_bytes += written;
}
}
}
if( true == failed )
{
m_transport->disconnect();
m_writebuffer.clear();
m_decoder.reset();
std::lock_guard<std::mutex> lock( m_mutex );
m_transportstatus.connected = false;
++m_transportstatus.transport_errors;
m_transportstatus.last_error = m_transport->last_error();
m_transportdropped = true;
m_rxqueue.clear();
m_txqueue.clear();
return;
}
std::lock_guard<std::mutex> lock( m_mutex );
if( received > 0 )
{
m_transportstatus.rx_bytes += received;
}
m_framecounters = m_decoder.stats();
for( auto &packet : packets )
{
if( m_rxqueue.size() >= rx_queue_limit )
{
m_rxqueue.pop_front();
}
m_rxqueue.emplace_back( std::move( packet ) );
}
}
void hardware_link::update( double const Deltatime, state_snapshot const &Snapshot )
{
std::deque<decoded_packet> packets;
transport_diagnostics status;
frame_decoder::counters counters;
bool dropped = false;
{
std::lock_guard<std::mutex> lock( m_mutex );
packets.swap( m_rxqueue );
status = m_transportstatus;
counters = m_framecounters;
dropped = m_transportdropped;
m_transportdropped = false;
}
if( true == dropped )
{
m_session.reset();
}
frame_decoder::counters delta;
delta.frames = counters.frames - m_lastframeerrors.frames;
delta.crc_errors = counters.crc_errors - m_lastframeerrors.crc_errors;
delta.frame_errors = counters.frame_errors - m_lastframeerrors.frame_errors;
delta.length_errors = counters.length_errors - m_lastframeerrors.length_errors;
m_lastframeerrors = counters;
m_session.add_frame_errors( delta );
// the session answers a subscription with an immediate snapshot, so it needs the current state first
m_session.set_snapshot( Snapshot );
for( auto const &packet : packets )
{
m_session.handle_packet( packet );
}
m_session.update( Deltatime, Snapshot );
m_report.transport = status;
m_report.enabled = m_enabled;
m_report.state = (
false == m_enabled ? link_state::disconnected :
false == status.connected ? link_state::disconnected :
m_session.link_condition() );
m_session.fill_report( m_report );
}
hardware_manager::hardware_manager( config const &Config ) : m_config( Config )
{
debug_flags.log_messages = m_config.debug;
debug_flags.log_frames = m_config.debug_frames;
m_lastportscan = std::chrono::steady_clock::now() - std::chrono::seconds( 10 );
#ifdef WITH_UART
for( auto const &link : m_config.serial_links )
{
m_links.emplace_back( std::make_shared<hardware_link>( std::make_unique<serial_transport>( link.port, link.baud ), m_config ) );
WriteLog( "hardware: protocol v2 link on " + link.port + " @ " + std::to_string( link.baud ) );
}
#endif
m_reports.resize( m_links.size() );
// the worker also runs with no links at all, so that ports can be scanned and controllers
// added from the debug panel while the simulation is running
m_thread = std::thread( &hardware_manager::worker, this );
s_instance = this;
}
hardware_manager::~hardware_manager()
{
s_instance = nullptr;
m_quit = true;
if( true == m_thread.joinable() )
{
m_thread.join();
}
std::lock_guard<std::mutex> lock( m_linksmutex );
m_links.clear();
}
void hardware_manager::worker()
{
while( false == m_quit )
{
std::vector<std::shared_ptr<hardware_link>> links;
{
std::lock_guard<std::mutex> lock( m_linksmutex );
links = m_links;
}
for( auto &link : links )
{
link->service();
}
#ifdef WITH_UART
auto const now = std::chrono::steady_clock::now();
if( std::chrono::duration<float>( now - m_lastportscan ).count() > port_scan_interval )
{
m_lastportscan = now;
auto ports = list_serial_ports();
std::lock_guard<std::mutex> lock( m_portsmutex );
m_ports = std::move( ports );
}
#endif
auto const interval = std::chrono::milliseconds( links.empty() ? idle_poll_interval_ms : std::max( 1, m_config.poll_interval_ms ) );
std::this_thread::sleep_for( interval );
}
}
std::shared_ptr<hardware_link> hardware_manager::link_at( std::size_t const Index ) const
{
std::lock_guard<std::mutex> lock( m_linksmutex );
return ( Index < m_links.size() ? m_links[ Index ] : nullptr );
}
std::vector<std::string> hardware_manager::available_ports() const
{
std::lock_guard<std::mutex> lock( m_portsmutex );
return m_ports;
}
bool hardware_manager::add_serial_link( std::string const &Port, int const Baud )
{
#ifdef WITH_UART
if( true == Port.empty() )
{
return false;
}
std::lock_guard<std::mutex> lock( m_linksmutex );
for( auto const &link : m_links )
{
if( link->endpoint().compare( 0, Port.size() + 1, Port + " " ) == 0 )
{
// the port already has a link, enabled or not
return false;
}
}
m_links.emplace_back( std::make_shared<hardware_link>( std::make_unique<serial_transport>( Port, Baud ), m_config ) );
WriteLog( "hardware: protocol v2 link added on " + Port + " @ " + std::to_string( Baud ) );
return true;
#else
( void )Port;
( void )Baud;
return false;
#endif
}
bool hardware_manager::remove_link( std::size_t const Index )
{
std::lock_guard<std::mutex> lock( m_linksmutex );
if( Index >= m_links.size() )
{
return false;
}
// the worker may still hold a reference for the rest of its pass; the port is released
// when that reference goes away
m_links[ Index ]->set_enabled( false );
WriteLog( "hardware: protocol v2 link on " + m_links[ Index ]->endpoint() + " removed" );
m_links.erase( m_links.begin() + Index );
return true;
}
void hardware_manager::set_link_enabled( std::size_t const Index, bool const Enabled )
{
auto const link = link_at( Index );
if( link == nullptr )
{
return;
}
if( link->enabled() == Enabled )
{
return;
}
link->set_enabled( Enabled );
WriteLog( "hardware: protocol v2 link on " + link->endpoint() + ( Enabled ? " connected" : " disconnected" ) );
}
bool hardware_manager::start_diagnostic( std::size_t const Index, std::uint8_t const Function )
{
auto const link = link_at( Index );
return ( link != nullptr ? link->session().start_diagnostic( Function ) : false );
}
void hardware_manager::cancel_diagnostic( std::size_t const Index )
{
auto const link = link_at( Index );
if( link != nullptr )
{
link->session().cancel_diagnostic();
}
}
void hardware_manager::answer_prompt( std::size_t const Index, std::uint8_t const Option )
{
auto const link = link_at( Index );
if( link != nullptr )
{
link->session().answer_prompt( Option );
}
}
void hardware_manager::update()
{
std::vector<std::shared_ptr<hardware_link>> links;
{
std::lock_guard<std::mutex> lock( m_linksmutex );
links = m_links;
}
if( true == links.empty() )
{
m_reports.clear();
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 );
m_reports.resize( links.size() );
for( std::size_t index = 0; index < links.size(); ++index )
{
links[ index ]->update( deltatime, m_snapshot );
m_reports[ index ] = links[ index ]->report();
m_reports[ index ].index = index;
}
}
} // namespace hardware