mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 20:19:20 +02:00
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.
1297 lines
37 KiB
C++
1297 lines
37 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/protocol/protocol_session.h"
|
|
|
|
#include "hardware/registry/command_registry.h"
|
|
#include "hardware/registry/hardware_symbol_registry.h"
|
|
#include "simulation/simulation.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/Logs.h"
|
|
#include "vehicle/Train.h"
|
|
|
|
namespace hardware
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
// how many transaction results are remembered for duplicate detection
|
|
constexpr std::size_t transaction_cache_size = 128;
|
|
// upper bound on session handles, one per resolved symbol
|
|
constexpr std::size_t handles_max = 1024;
|
|
// round trip time is measured this often
|
|
constexpr double ping_interval = 5.0;
|
|
|
|
std::uint32_t generate_session_id()
|
|
{
|
|
static std::mt19937 generator( static_cast<std::uint32_t>( std::chrono::steady_clock::now().time_since_epoch().count() ) );
|
|
std::uint32_t const value = generator();
|
|
// zero is reserved for "no session"
|
|
return ( value != 0 ? value : 1 );
|
|
}
|
|
|
|
void write_state_value( payload_writer &Writer, state_value const &Value )
|
|
{
|
|
switch( Value.type )
|
|
{
|
|
case value_type::boolean:
|
|
case value_type::uint8:
|
|
case value_type::int8:
|
|
case value_type::enumeration:
|
|
Writer.write_uint8( static_cast<std::uint8_t>( Value.numeric ) );
|
|
break;
|
|
case value_type::uint16:
|
|
case value_type::int16:
|
|
Writer.write_uint16( static_cast<std::uint16_t>( Value.numeric ) );
|
|
break;
|
|
case value_type::uint32:
|
|
case value_type::int32:
|
|
case value_type::bitfield:
|
|
Writer.write_uint32( static_cast<std::uint32_t>( Value.numeric ) );
|
|
break;
|
|
case value_type::uint64:
|
|
case value_type::int64:
|
|
Writer.write_uint64( static_cast<std::uint64_t>( Value.numeric ) );
|
|
break;
|
|
case value_type::float64:
|
|
Writer.write_float64( Value.numeric );
|
|
break;
|
|
case value_type::utf8_string:
|
|
Writer.write_string( Value.text );
|
|
break;
|
|
default:
|
|
Writer.write_float32( static_cast<float>( Value.numeric ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::size_t state_value_size( state_value const &Value )
|
|
{
|
|
if( Value.type == value_type::utf8_string )
|
|
{
|
|
return 1 + std::min( Value.text.size(), protocol_string_max );
|
|
}
|
|
auto const size = value_type_size( Value.type );
|
|
return ( size > 0 ? size : 4 );
|
|
}
|
|
|
|
int glfw_action( command_action const Action )
|
|
{
|
|
switch( Action )
|
|
{
|
|
case command_action::release: return GLFW_RELEASE;
|
|
case command_action::repeat: return GLFW_REPEAT;
|
|
default: return GLFW_PRESS;
|
|
}
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
protocol_session::protocol_session( session_output &Output, config const &Config ) : m_output( Output ), m_config( Config )
|
|
{
|
|
m_framesize = std::clamp<std::size_t>( m_config.frame_size, protocol_frame_size_min, protocol_frame_size_max );
|
|
reset();
|
|
}
|
|
|
|
void protocol_session::reset()
|
|
{
|
|
m_state = session_state::waiting_for_hello;
|
|
m_sessionid = 0;
|
|
m_sequence = 0;
|
|
m_lastreceivedsequence = 0;
|
|
m_sequenceseen = false;
|
|
m_outgoingtransaction = 0;
|
|
m_framesize = std::clamp<std::size_t>( m_config.frame_size, protocol_frame_size_min, protocol_frame_size_max );
|
|
m_capabilities = 0;
|
|
m_peermajor = 0;
|
|
m_peerminor = 0;
|
|
|
|
m_devicename.clear();
|
|
m_devicerole.clear();
|
|
m_deviceid.clear();
|
|
m_firmwareversion.clear();
|
|
m_hardwareversion.clear();
|
|
|
|
m_handles.clear();
|
|
m_handlelookup.clear();
|
|
m_subscriptions.clear();
|
|
m_controlsequence.clear();
|
|
m_transactions.clear();
|
|
m_transactionorder.clear();
|
|
|
|
m_uptime = 0.0;
|
|
m_lastreceived = 0.0;
|
|
m_heartbeattimer = 0.0;
|
|
m_pingtimer = 0.0;
|
|
m_pingpending = false;
|
|
m_ratewindow = 0.0;
|
|
m_framebudget = m_config.max_frames_per_second;
|
|
m_payloadbudget = m_config.max_payload_bytes_per_second;
|
|
m_commandbudget = m_config.max_commands_per_second;
|
|
|
|
m_contextvalid = false;
|
|
m_context = 0;
|
|
|
|
m_diagnostics = protocol_diagnostics {};
|
|
m_devicediagnostics = device_diagnostics {};
|
|
m_functions.clear();
|
|
m_diagnostic = diagnostic_report {};
|
|
m_prompt = diagnostic_prompt {};
|
|
m_log.clear();
|
|
}
|
|
|
|
link_state protocol_session::link_condition() const
|
|
{
|
|
if( m_state == session_state::waiting_for_hello )
|
|
{
|
|
return link_state::connecting;
|
|
}
|
|
if( m_diagnostics.missed_heartbeats <= 2 )
|
|
{
|
|
return link_state::healthy;
|
|
}
|
|
if( m_diagnostics.missed_heartbeats <= 4 )
|
|
{
|
|
return link_state::degraded;
|
|
}
|
|
return link_state::error;
|
|
}
|
|
|
|
void protocol_session::add_frame_errors( frame_decoder::counters const &Delta )
|
|
{
|
|
m_diagnostics.crc_errors += Delta.crc_errors;
|
|
m_diagnostics.frame_errors += Delta.frame_errors;
|
|
m_diagnostics.length_errors += Delta.length_errors;
|
|
}
|
|
|
|
void protocol_session::send( message_type const Type, std::uint8_t const Flags, payload_writer const &Payload )
|
|
{
|
|
packet_header header;
|
|
header.flags = Flags;
|
|
header.type = Type;
|
|
header.session_id = m_sessionid;
|
|
header.sequence = m_sequence++;
|
|
header.timestamp_ms = static_cast<std::uint32_t>( m_uptime * 1000.0 );
|
|
header.payload_length = static_cast<std::uint16_t>( Payload.size() );
|
|
|
|
m_output.send_packet( header, Payload.buffer() );
|
|
|
|
++m_diagnostics.tx_frames;
|
|
m_txrate.add();
|
|
|
|
if( true == debug_flags.log_frames )
|
|
{
|
|
WriteLog( "hardware: tx " + std::string( to_string( Type ) ) + ", " + std::to_string( Payload.size() ) + " bytes" );
|
|
}
|
|
}
|
|
|
|
void protocol_session::send_ack( std::uint32_t const Transaction, message_type const Type )
|
|
{
|
|
payload_writer writer;
|
|
writer.write_uint32( Transaction );
|
|
writer.write_uint16( static_cast<std::uint16_t>( Type ) );
|
|
send( message_type::ack, packetflag_response, writer );
|
|
++m_diagnostics.acks;
|
|
}
|
|
|
|
void protocol_session::send_nack( std::uint32_t const Transaction, message_type const Type, error_code const Error )
|
|
{
|
|
payload_writer writer;
|
|
writer.write_uint32( Transaction );
|
|
writer.write_uint16( static_cast<std::uint16_t>( Type ) );
|
|
writer.write_uint16( static_cast<std::uint16_t>( Error ) );
|
|
send( message_type::nack, packetflag_response | packetflag_error, writer );
|
|
++m_diagnostics.nacks;
|
|
|
|
if( true == debug_flags.log_messages )
|
|
{
|
|
WriteLog( "hardware: nack " + std::string( to_string( Type ) ) + ", " + std::string( to_string( Error ) ) );
|
|
}
|
|
}
|
|
|
|
std::size_t protocol_session::symbol_of_handle( std::uint16_t const Handle ) const
|
|
{
|
|
if( ( Handle == 0 ) || ( Handle > m_handles.size() ) )
|
|
{
|
|
return symbol_registry::npos;
|
|
}
|
|
return m_handles[ Handle - 1 ];
|
|
}
|
|
|
|
std::uint16_t protocol_session::handle_of_symbol( std::size_t const SymbolIndex )
|
|
{
|
|
auto const lookup = m_handlelookup.find( SymbolIndex );
|
|
if( lookup != m_handlelookup.end() )
|
|
{
|
|
return lookup->second;
|
|
}
|
|
if( m_handles.size() >= handles_max )
|
|
{
|
|
return 0;
|
|
}
|
|
m_handles.emplace_back( SymbolIndex );
|
|
auto const handle = static_cast<std::uint16_t>( m_handles.size() );
|
|
m_handlelookup.emplace( SymbolIndex, handle );
|
|
return handle;
|
|
}
|
|
|
|
bool protocol_session::consume_frame_budget( std::size_t const PayloadSize )
|
|
{
|
|
if( ( m_framebudget <= 0 ) || ( m_payloadbudget < static_cast<int>( PayloadSize ) ) )
|
|
{
|
|
return false;
|
|
}
|
|
--m_framebudget;
|
|
m_payloadbudget -= static_cast<int>( PayloadSize );
|
|
return true;
|
|
}
|
|
|
|
bool protocol_session::consume_command_budget()
|
|
{
|
|
if( m_commandbudget <= 0 )
|
|
{
|
|
return false;
|
|
}
|
|
--m_commandbudget;
|
|
return true;
|
|
}
|
|
|
|
bool protocol_session::remember_transaction( std::uint32_t const Transaction, transaction_result const &Result )
|
|
{
|
|
if( Transaction == 0 )
|
|
{
|
|
// transaction id zero means the device doesn't care about duplicate protection
|
|
return false;
|
|
}
|
|
m_transactions[ Transaction ] = Result;
|
|
m_transactionorder.emplace_back( Transaction );
|
|
while( m_transactionorder.size() > transaction_cache_size )
|
|
{
|
|
m_transactions.erase( m_transactionorder.front() );
|
|
m_transactionorder.pop_front();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protocol_session::transaction_result const *protocol_session::recall_transaction( std::uint32_t const Transaction ) const
|
|
{
|
|
if( Transaction == 0 )
|
|
{
|
|
return nullptr;
|
|
}
|
|
auto const lookup = m_transactions.find( Transaction );
|
|
return ( lookup != m_transactions.end() ? &lookup->second : nullptr );
|
|
}
|
|
|
|
void protocol_session::handle_packet( decoded_packet const &Packet )
|
|
{
|
|
++m_diagnostics.rx_frames;
|
|
m_rxrate.add();
|
|
m_lastreceived = m_uptime;
|
|
|
|
if( false == consume_frame_budget( Packet.payload.size() ) )
|
|
{
|
|
++m_diagnostics.rate_limited;
|
|
return;
|
|
}
|
|
|
|
if( Packet.header.protocol_major != protocol_version_major )
|
|
{
|
|
++m_diagnostics.version_errors;
|
|
if( Packet.header.type == message_type::hello )
|
|
{
|
|
send_nack( 0, message_type::hello, error_code::unsupported_version );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if( true == m_sequenceseen )
|
|
{
|
|
if( Packet.header.sequence != m_lastreceivedsequence + 1 )
|
|
{
|
|
++m_diagnostics.sequence_gaps;
|
|
}
|
|
}
|
|
m_lastreceivedsequence = Packet.header.sequence;
|
|
m_sequenceseen = true;
|
|
|
|
if( true == debug_flags.log_frames )
|
|
{
|
|
WriteLog( "hardware: rx " + std::string( to_string( Packet.header.type ) ) + ", " + std::to_string( Packet.payload.size() ) + " bytes" );
|
|
}
|
|
|
|
if( Packet.header.type == message_type::hello )
|
|
{
|
|
handle_hello( Packet );
|
|
return;
|
|
}
|
|
|
|
if( Packet.header.type == message_type::ping )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const token = reader.read_uint32();
|
|
payload_writer writer;
|
|
writer.write_uint32( token );
|
|
send( message_type::pong, packetflag_response, writer );
|
|
return;
|
|
}
|
|
|
|
// everything past this point belongs to an established session
|
|
if( ( m_state == session_state::waiting_for_hello ) || ( Packet.header.session_id != m_sessionid ) )
|
|
{
|
|
send_nack( 0, Packet.header.type, error_code::invalid_session );
|
|
return;
|
|
}
|
|
|
|
switch( Packet.header.type )
|
|
{
|
|
case message_type::heartbeat:
|
|
handle_heartbeat( Packet );
|
|
break;
|
|
|
|
case message_type::pong:
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const token = reader.read_uint32();
|
|
if( ( true == m_pingpending ) && ( token == m_pingtoken ) )
|
|
{
|
|
m_diagnostics.round_trip_time_ms = ( m_uptime - m_pingsent ) * 1000.0;
|
|
m_pingpending = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case message_type::device_ready:
|
|
m_state = session_state::ready;
|
|
if( true == debug_flags.log_messages )
|
|
{
|
|
WriteLog( "hardware: device '" + m_devicename + "' is ready" );
|
|
}
|
|
break;
|
|
|
|
case message_type::device_diagnostics:
|
|
handle_device_diagnostics( Packet );
|
|
break;
|
|
|
|
case message_type::device_log:
|
|
handle_device_log( Packet );
|
|
break;
|
|
|
|
case message_type::diagnostic_functions:
|
|
handle_diagnostic_functions( Packet );
|
|
break;
|
|
|
|
case message_type::diagnostic_status:
|
|
handle_diagnostic_status( Packet );
|
|
break;
|
|
|
|
case message_type::diagnostic_prompt:
|
|
handle_diagnostic_prompt( Packet );
|
|
break;
|
|
|
|
case message_type::ack:
|
|
handle_device_answer( Packet, true );
|
|
break;
|
|
|
|
case message_type::nack:
|
|
handle_device_answer( Packet, false );
|
|
break;
|
|
|
|
case message_type::resolve_symbols:
|
|
handle_resolve_symbols( Packet );
|
|
break;
|
|
|
|
case message_type::query_symbol:
|
|
handle_query_symbol( Packet );
|
|
break;
|
|
|
|
case message_type::subscribe:
|
|
handle_subscribe( Packet );
|
|
break;
|
|
|
|
case message_type::unsubscribe:
|
|
handle_unsubscribe( Packet );
|
|
break;
|
|
|
|
case message_type::command_event:
|
|
handle_command_event( Packet );
|
|
break;
|
|
|
|
case message_type::control_set:
|
|
handle_control_set( Packet );
|
|
break;
|
|
|
|
case message_type::control_claim:
|
|
case message_type::control_release:
|
|
case message_type::property_read:
|
|
case message_type::property_write:
|
|
// reserved for a later revision
|
|
send_nack( 0, Packet.header.type, error_code::unsupported_operation );
|
|
break;
|
|
|
|
default:
|
|
++m_diagnostics.unknown_messages;
|
|
if( ( Packet.header.flags & packetflag_ack_required ) != 0 )
|
|
{
|
|
send_nack( 0, Packet.header.type, error_code::unknown_message );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void protocol_session::handle_hello( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
|
|
auto const major = reader.read_uint8();
|
|
auto const minor = reader.read_uint8();
|
|
auto const framesize = reader.read_uint16();
|
|
auto const capabilities = reader.read_uint32();
|
|
auto const name = reader.read_string();
|
|
auto const role = reader.read_string();
|
|
|
|
std::string deviceid;
|
|
std::string firmware;
|
|
std::string hardware;
|
|
// the remaining fields are optional, a minimal controller simply doesn't send them
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { deviceid = reader.read_string(); }
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { firmware = reader.read_string(); }
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { hardware = reader.read_string(); }
|
|
|
|
if( false == reader.ok() )
|
|
{
|
|
++m_diagnostics.length_errors;
|
|
send_nack( 0, message_type::hello, error_code::invalid_length );
|
|
return;
|
|
}
|
|
|
|
if( major != protocol_version_major )
|
|
{
|
|
++m_diagnostics.version_errors;
|
|
send_nack( 0, message_type::hello, error_code::unsupported_version );
|
|
return;
|
|
}
|
|
|
|
if( true == name.empty() )
|
|
{
|
|
send_nack( 0, message_type::hello, error_code::invalid_field );
|
|
return;
|
|
}
|
|
|
|
// a fresh hello always starts a new session, invalidating whatever the previous one held
|
|
m_handles.clear();
|
|
m_handlelookup.clear();
|
|
m_subscriptions.clear();
|
|
m_controlsequence.clear();
|
|
m_transactions.clear();
|
|
m_transactionorder.clear();
|
|
|
|
m_peermajor = major;
|
|
m_peerminor = minor;
|
|
m_capabilities = capabilities;
|
|
m_devicename = name;
|
|
m_devicerole = role;
|
|
m_deviceid = deviceid;
|
|
m_firmwareversion = firmware;
|
|
m_hardwareversion = hardware;
|
|
|
|
auto const requested = ( framesize > 0 ? static_cast<std::size_t>( framesize ) : protocol_frame_size_default );
|
|
m_framesize = std::clamp<std::size_t>( std::min( requested, m_config.frame_size ), protocol_frame_size_min, protocol_frame_size_max );
|
|
|
|
m_sessionid = generate_session_id();
|
|
m_state = session_state::established;
|
|
m_heartbeattimer = 0.0;
|
|
m_pingtimer = 0.0;
|
|
m_pingpending = false;
|
|
m_contextvalid = false;
|
|
|
|
payload_writer writer;
|
|
writer.write_uint8( protocol_version_major );
|
|
writer.write_uint8( protocol_version_minor );
|
|
writer.write_uint32( m_sessionid );
|
|
writer.write_uint16( static_cast<std::uint16_t>( m_framesize ) );
|
|
writer.write_uint16( static_cast<std::uint16_t>( m_config.heartbeat_interval_ms ) );
|
|
writer.write_string( Global.asVersion );
|
|
send( message_type::welcome, packetflag_response, writer );
|
|
|
|
WriteLog( "hardware: session " + std::to_string( m_sessionid ) + " established with '" + m_devicename + "' (" + m_devicerole + ")" );
|
|
}
|
|
|
|
void protocol_session::handle_heartbeat( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
// the fields are informational; a device is free to send an empty heartbeat
|
|
reader.read_uint32();
|
|
reader.read_uint32();
|
|
reader.read_uint16();
|
|
}
|
|
|
|
void protocol_session::handle_device_diagnostics( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
|
|
device_diagnostics diagnostics;
|
|
diagnostics.available = true;
|
|
diagnostics.uptime_ms = reader.read_uint32();
|
|
// every field past the uptime is optional
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { diagnostics.supply_voltage = reader.read_float32(); }
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { diagnostics.temperature = reader.read_float32(); }
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { diagnostics.rx_overflows = reader.read_uint32(); }
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { diagnostics.tx_overflows = reader.read_uint32(); }
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { diagnostics.watchdog_resets = reader.read_uint32(); }
|
|
if( ( true == reader.ok() ) && ( false == reader.exhausted() ) ) { diagnostics.firmware_build = reader.read_string(); }
|
|
|
|
if( false == reader.ok() )
|
|
{
|
|
++m_diagnostics.length_errors;
|
|
return;
|
|
}
|
|
|
|
m_devicediagnostics = diagnostics;
|
|
}
|
|
|
|
void protocol_session::handle_resolve_symbols( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const transaction = reader.read_uint32();
|
|
auto const count = reader.read_uint8();
|
|
|
|
if( ( false == reader.ok() ) || ( count > protocol_symbols_per_message_max ) )
|
|
{
|
|
send_nack( transaction, message_type::resolve_symbols, error_code::invalid_field );
|
|
return;
|
|
}
|
|
|
|
auto const &symbols = symbol_registry::instance().entries();
|
|
|
|
payload_writer writer;
|
|
writer.write_uint32( transaction );
|
|
writer.write_uint8( count );
|
|
|
|
for( std::size_t index = 0; index < count; ++index )
|
|
{
|
|
auto const name = reader.read_string( protocol_symbol_name_max );
|
|
if( false == reader.ok() )
|
|
{
|
|
send_nack( transaction, message_type::resolve_symbols, error_code::invalid_length );
|
|
return;
|
|
}
|
|
|
|
auto const symbolindex = symbol_registry::instance().find( name );
|
|
if( symbolindex == symbol_registry::npos )
|
|
{
|
|
++m_diagnostics.unknown_symbols;
|
|
// handle zero tells the device the symbol isn't available in this simulator build
|
|
writer.write_uint16( 0 );
|
|
writer.write_uint8( 0 );
|
|
writer.write_uint8( static_cast<std::uint8_t>( value_type::none ) );
|
|
writer.write_uint8( 0 );
|
|
continue;
|
|
}
|
|
|
|
auto const &symbol = symbols[ symbolindex ];
|
|
writer.write_uint16( handle_of_symbol( symbolindex ) );
|
|
writer.write_uint8( static_cast<std::uint8_t>( symbol.kind ) );
|
|
writer.write_uint8( static_cast<std::uint8_t>( symbol.type ) );
|
|
writer.write_uint8( symbol.access );
|
|
}
|
|
|
|
send( message_type::symbols_resolved, packetflag_response, writer );
|
|
}
|
|
|
|
void protocol_session::handle_query_symbol( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const transaction = reader.read_uint32();
|
|
auto const handle = reader.read_uint16();
|
|
|
|
if( false == reader.ok() )
|
|
{
|
|
send_nack( transaction, message_type::query_symbol, error_code::invalid_length );
|
|
return;
|
|
}
|
|
|
|
auto const symbolindex = symbol_of_handle( handle );
|
|
if( symbolindex == symbol_registry::npos )
|
|
{
|
|
++m_diagnostics.unknown_handles;
|
|
send_nack( transaction, message_type::query_symbol, error_code::unknown_handle );
|
|
return;
|
|
}
|
|
|
|
auto const &symbol = symbol_registry::instance().entries()[ symbolindex ];
|
|
|
|
payload_writer writer;
|
|
writer.write_uint32( transaction );
|
|
writer.write_uint16( handle );
|
|
writer.write_uint8( static_cast<std::uint8_t>( symbol.kind ) );
|
|
writer.write_uint8( static_cast<std::uint8_t>( symbol.type ) );
|
|
writer.write_uint8( symbol.access );
|
|
writer.write_uint8( static_cast<std::uint8_t>( ( symbol.minimum ? 0x01 : 0x00 ) | ( symbol.maximum ? 0x02 : 0x00 ) ) );
|
|
writer.write_float32( static_cast<float>( symbol.minimum.value_or( 0.0 ) ) );
|
|
writer.write_float32( static_cast<float>( symbol.maximum.value_or( 0.0 ) ) );
|
|
writer.write_string( symbol.name );
|
|
writer.write_string( symbol.unit );
|
|
send( message_type::symbol_info, packetflag_response, writer );
|
|
}
|
|
|
|
void protocol_session::handle_subscribe( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const transaction = reader.read_uint32();
|
|
auto const count = reader.read_uint8();
|
|
|
|
if( ( false == reader.ok() ) || ( count > protocol_symbols_per_message_max ) )
|
|
{
|
|
send_nack( transaction, message_type::subscribe, error_code::invalid_field );
|
|
return;
|
|
}
|
|
|
|
auto const &symbols = symbol_registry::instance().entries();
|
|
|
|
payload_writer writer;
|
|
writer.write_uint32( transaction );
|
|
writer.write_uint8( count );
|
|
|
|
for( std::size_t index = 0; index < count; ++index )
|
|
{
|
|
auto const handle = reader.read_uint16();
|
|
auto const mode = static_cast<subscription_mode>( reader.read_uint8() );
|
|
auto const period = reader.read_uint16();
|
|
auto const deadband = reader.read_float32();
|
|
|
|
if( false == reader.ok() )
|
|
{
|
|
send_nack( transaction, message_type::subscribe, error_code::invalid_length );
|
|
return;
|
|
}
|
|
|
|
auto result = error_code::none;
|
|
auto const symbolindex = symbol_of_handle( handle );
|
|
if( symbolindex == symbol_registry::npos )
|
|
{
|
|
++m_diagnostics.unknown_handles;
|
|
result = error_code::unknown_handle;
|
|
}
|
|
else if( symbols[ symbolindex ].kind != symbol_kind::state )
|
|
{
|
|
result = error_code::access_denied;
|
|
}
|
|
else
|
|
{
|
|
result = m_subscriptions.subscribe( handle, symbolindex, symbols[ symbolindex ].source_index, mode, period, deadband );
|
|
}
|
|
|
|
writer.write_uint16( handle );
|
|
writer.write_uint16( static_cast<std::uint16_t>( result ) );
|
|
}
|
|
|
|
send( message_type::subscribe_result, packetflag_response, writer );
|
|
|
|
// the device initialises its gauges and indicators from the initial snapshot
|
|
std::vector<state_item> items;
|
|
m_subscriptions.collect( 0.0, m_snapshot, true, items );
|
|
send_state_update( items, true );
|
|
}
|
|
|
|
void protocol_session::handle_unsubscribe( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const transaction = reader.read_uint32();
|
|
auto const count = reader.read_uint8();
|
|
|
|
if( ( false == reader.ok() ) || ( count > protocol_symbols_per_message_max ) )
|
|
{
|
|
send_nack( transaction, message_type::unsubscribe, error_code::invalid_field );
|
|
return;
|
|
}
|
|
|
|
for( std::size_t index = 0; index < count; ++index )
|
|
{
|
|
auto const handle = reader.read_uint16();
|
|
if( false == reader.ok() )
|
|
{
|
|
send_nack( transaction, message_type::unsubscribe, error_code::invalid_length );
|
|
return;
|
|
}
|
|
m_subscriptions.unsubscribe( handle );
|
|
}
|
|
|
|
send_ack( transaction, message_type::unsubscribe );
|
|
}
|
|
|
|
void protocol_session::handle_command_event( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const transaction = reader.read_uint32();
|
|
auto const handle = reader.read_uint16();
|
|
auto const action = reader.read_uint8();
|
|
auto const parameter1 = reader.read_float32();
|
|
auto const parameter2 = reader.read_float32();
|
|
auto const entity = reader.read_uint32();
|
|
|
|
bool const acknowledge = ( ( Packet.header.flags & packetflag_ack_required ) != 0 );
|
|
|
|
if( false == reader.ok() )
|
|
{
|
|
++m_diagnostics.length_errors;
|
|
if( true == acknowledge )
|
|
{
|
|
send_nack( transaction, message_type::command_event, error_code::invalid_length );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// a retransmission of an already executed command must not execute it a second time
|
|
if( auto const *previous = recall_transaction( transaction ) )
|
|
{
|
|
++m_diagnostics.duplicate_commands;
|
|
if( true == acknowledge )
|
|
{
|
|
if( true == previous->accepted )
|
|
{
|
|
send_ack( transaction, message_type::command_event );
|
|
}
|
|
else
|
|
{
|
|
send_nack( transaction, message_type::command_event, previous->error );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto result = error_code::none;
|
|
|
|
if( m_state != session_state::ready )
|
|
{
|
|
result = error_code::not_ready;
|
|
}
|
|
else if( false == consume_command_budget() )
|
|
{
|
|
++m_diagnostics.rate_limited;
|
|
result = error_code::rate_limited;
|
|
}
|
|
else if( action > static_cast<std::uint8_t>( command_action::repeat ) )
|
|
{
|
|
result = error_code::invalid_field;
|
|
}
|
|
else if( ( false == std::isfinite( parameter1 ) ) || ( false == std::isfinite( parameter2 ) ) )
|
|
{
|
|
result = error_code::invalid_field;
|
|
}
|
|
else
|
|
{
|
|
auto const symbolindex = symbol_of_handle( handle );
|
|
if( symbolindex == symbol_registry::npos )
|
|
{
|
|
++m_diagnostics.unknown_handles;
|
|
result = error_code::unknown_handle;
|
|
}
|
|
else if( symbol_registry::instance().entries()[ symbolindex ].kind != symbol_kind::command )
|
|
{
|
|
result = error_code::access_denied;
|
|
}
|
|
else
|
|
{
|
|
result = execute_command( symbolindex, static_cast<command_action>( action ), parameter1, parameter2, static_cast<std::uint16_t>( entity ) );
|
|
}
|
|
}
|
|
|
|
transaction_result const outcome { result == error_code::none, result };
|
|
remember_transaction( transaction, outcome );
|
|
|
|
if( true == acknowledge )
|
|
{
|
|
if( true == outcome.accepted )
|
|
{
|
|
send_ack( transaction, message_type::command_event );
|
|
}
|
|
else
|
|
{
|
|
send_nack( transaction, message_type::command_event, result );
|
|
}
|
|
}
|
|
}
|
|
|
|
void protocol_session::handle_control_set( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const handle = reader.read_uint16();
|
|
auto const sequence = reader.read_uint32();
|
|
auto const value = reader.read_float32();
|
|
auto const entity = reader.read_uint32();
|
|
|
|
if( false == reader.ok() )
|
|
{
|
|
++m_diagnostics.length_errors;
|
|
return;
|
|
}
|
|
|
|
if( m_state != session_state::ready )
|
|
{
|
|
send_nack( 0, message_type::control_set, error_code::not_ready );
|
|
return;
|
|
}
|
|
|
|
// updates are idempotent, so a late one is simply dropped
|
|
auto const previous = m_controlsequence.find( handle );
|
|
if( previous != m_controlsequence.end() )
|
|
{
|
|
if( static_cast<std::int32_t>( sequence - previous->second ) <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
m_controlsequence[ handle ] = sequence;
|
|
|
|
auto const symbolindex = symbol_of_handle( handle );
|
|
if( symbolindex == symbol_registry::npos )
|
|
{
|
|
++m_diagnostics.unknown_handles;
|
|
send_nack( 0, message_type::control_set, error_code::unknown_handle );
|
|
return;
|
|
}
|
|
|
|
auto const &symbol = symbol_registry::instance().entries()[ symbolindex ];
|
|
if( symbol.kind != symbol_kind::command )
|
|
{
|
|
send_nack( 0, message_type::control_set, error_code::access_denied );
|
|
return;
|
|
}
|
|
|
|
auto const &descriptor = command_registry::instance().entries()[ symbol.source_index ];
|
|
if( false == descriptor.continuous )
|
|
{
|
|
send_nack( 0, message_type::control_set, error_code::wrong_type );
|
|
return;
|
|
}
|
|
|
|
if( false == std::isfinite( value ) )
|
|
{
|
|
send_nack( 0, message_type::control_set, error_code::invalid_field );
|
|
return;
|
|
}
|
|
|
|
// values outside the declared range are rejected rather than silently clipped
|
|
if( ( descriptor.minimum && ( value < *descriptor.minimum ) ) || ( descriptor.maximum && ( value > *descriptor.maximum ) ) )
|
|
{
|
|
send_nack( 0, message_type::control_set, error_code::out_of_range );
|
|
return;
|
|
}
|
|
|
|
if( false == consume_command_budget() )
|
|
{
|
|
++m_diagnostics.rate_limited;
|
|
return;
|
|
}
|
|
|
|
auto const result = execute_command( symbolindex, command_action::press, value, 0.0, static_cast<std::uint16_t>( entity ) );
|
|
if( result == error_code::none )
|
|
{
|
|
++m_diagnostics.controls_applied;
|
|
}
|
|
else
|
|
{
|
|
send_nack( 0, message_type::control_set, result );
|
|
}
|
|
}
|
|
|
|
error_code protocol_session::execute_command( std::size_t const SymbolIndex, command_action const Action, double const Parameter1, double const Parameter2, std::uint16_t const Recipient )
|
|
{
|
|
auto const &symbol = symbol_registry::instance().entries()[ SymbolIndex ];
|
|
auto const &descriptor = command_registry::instance().entries()[ symbol.source_index ];
|
|
|
|
if( descriptor.command == user_command::none )
|
|
{
|
|
return error_code::unsupported_operation;
|
|
}
|
|
|
|
double parameter = Parameter1;
|
|
|
|
if( descriptor.transform == command_transform::mastercontroller_percent )
|
|
{
|
|
auto *train = simulation::Train;
|
|
if( ( train == nullptr ) || ( train->Occupied() == nullptr ) )
|
|
{
|
|
return error_code::not_ready;
|
|
}
|
|
auto const percentage = std::clamp( Parameter1, 0.0, 1.0 );
|
|
auto const positions = train->Occupied()->MainCtrlPosNo;
|
|
parameter = ( percentage > 0.01 ? 1.0 + ( positions - 1 ) * percentage : 0.0 );
|
|
train->Occupied()->eimic_analog = percentage;
|
|
}
|
|
|
|
m_relay.post( descriptor.command, parameter, Parameter2, glfw_action( Action ), Recipient );
|
|
++m_diagnostics.commands_executed;
|
|
|
|
return error_code::none;
|
|
}
|
|
|
|
void protocol_session::handle_device_log( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const severity = static_cast<log_severity>( reader.read_uint8() );
|
|
auto const uptime = reader.read_uint32();
|
|
auto const text = reader.read_string();
|
|
|
|
if( ( false == reader.ok() ) || ( true == text.empty() ) )
|
|
{
|
|
++m_diagnostics.length_errors;
|
|
return;
|
|
}
|
|
|
|
device_log_entry entry;
|
|
entry.severity = ( severity <= log_severity::error ? severity : log_severity::info );
|
|
entry.device_uptime_ms = uptime;
|
|
entry.text = text;
|
|
m_log.emplace_back( std::move( entry ) );
|
|
while( m_log.size() > protocol_log_history )
|
|
{
|
|
m_log.pop_front();
|
|
}
|
|
|
|
// debug chatter stays in the panel, anything the device considers worth reporting
|
|
// also goes to the simulator log
|
|
if( ( severity != log_severity::debug ) || ( true == debug_flags.log_messages ) )
|
|
{
|
|
auto const line = "hardware: [" + m_devicename + "] " + std::string( to_string( severity ) ) + ": " + text;
|
|
if( severity == log_severity::error )
|
|
{
|
|
ErrorLog( line );
|
|
}
|
|
else
|
|
{
|
|
WriteLog( line );
|
|
}
|
|
}
|
|
}
|
|
|
|
void protocol_session::handle_diagnostic_functions( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const count = reader.read_uint8();
|
|
if( ( false == reader.ok() ) || ( count > protocol_diagnostic_functions_max ) )
|
|
{
|
|
send_nack( 0, message_type::diagnostic_functions, error_code::invalid_field );
|
|
return;
|
|
}
|
|
|
|
std::vector<diagnostic_function> functions;
|
|
functions.reserve( count );
|
|
for( std::size_t index = 0; index < count; ++index )
|
|
{
|
|
diagnostic_function function;
|
|
function.id = reader.read_uint8();
|
|
function.name = reader.read_string();
|
|
if( false == reader.ok() )
|
|
{
|
|
send_nack( 0, message_type::diagnostic_functions, error_code::invalid_length );
|
|
return;
|
|
}
|
|
if( false == function.name.empty() )
|
|
{
|
|
functions.emplace_back( std::move( function ) );
|
|
}
|
|
}
|
|
|
|
m_functions = std::move( functions );
|
|
if( true == debug_flags.log_messages )
|
|
{
|
|
WriteLog( "hardware: device '" + m_devicename + "' offers " + std::to_string( m_functions.size() ) + " diagnostic function(s)" );
|
|
}
|
|
}
|
|
|
|
void protocol_session::handle_diagnostic_status( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
auto const function = reader.read_uint8();
|
|
auto const state = static_cast<diagnostic_state>( reader.read_uint8() );
|
|
auto const progress = reader.read_uint8();
|
|
auto const message = reader.read_string();
|
|
|
|
if( ( false == reader.ok() ) || ( state > diagnostic_state::failed ) )
|
|
{
|
|
++m_diagnostics.length_errors;
|
|
return;
|
|
}
|
|
|
|
m_diagnostic.function = function;
|
|
m_diagnostic.state = state;
|
|
m_diagnostic.progress = ( progress <= 100 ? progress : 100 );
|
|
m_diagnostic.message = message;
|
|
|
|
if( state != diagnostic_state::running )
|
|
{
|
|
// a routine which ended cannot be waiting for an answer any more
|
|
m_prompt = diagnostic_prompt {};
|
|
WriteLog( "hardware: device '" + m_devicename + "' diagnostic " + std::to_string( function ) + " " + std::string( to_string( state ) ) + ( message.empty() ? "" : ": " + message ) );
|
|
}
|
|
}
|
|
|
|
void protocol_session::handle_diagnostic_prompt( decoded_packet const &Packet )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
diagnostic_prompt prompt;
|
|
prompt.id = reader.read_uint32();
|
|
prompt.text = reader.read_string();
|
|
auto const options = reader.read_uint8();
|
|
|
|
if( ( false == reader.ok() ) || ( options > protocol_prompt_options_max ) )
|
|
{
|
|
send_nack( 0, message_type::diagnostic_prompt, error_code::invalid_field );
|
|
return;
|
|
}
|
|
|
|
for( std::size_t index = 0; index < options; ++index )
|
|
{
|
|
auto label = reader.read_string();
|
|
if( false == reader.ok() )
|
|
{
|
|
send_nack( 0, message_type::diagnostic_prompt, error_code::invalid_length );
|
|
return;
|
|
}
|
|
prompt.options.emplace_back( std::move( label ) );
|
|
}
|
|
|
|
if( true == prompt.options.empty() )
|
|
{
|
|
prompt.options.emplace_back( "OK" );
|
|
}
|
|
prompt.active = true;
|
|
m_prompt = std::move( prompt );
|
|
}
|
|
|
|
void protocol_session::handle_device_answer( decoded_packet const &Packet, bool const Positive )
|
|
{
|
|
payload_reader reader( Packet.payload );
|
|
reader.read_uint32();
|
|
auto const message = static_cast<message_type>( reader.read_uint16() );
|
|
auto const error = ( Positive ? error_code::none : static_cast<error_code>( reader.read_uint16() ) );
|
|
|
|
if( false == reader.ok() )
|
|
{
|
|
++m_diagnostics.length_errors;
|
|
return;
|
|
}
|
|
|
|
if( ( message == message_type::diagnostic_run ) && ( false == Positive ) )
|
|
{
|
|
m_diagnostic.state = diagnostic_state::failed;
|
|
m_diagnostic.message = to_string( error );
|
|
WriteLog( "hardware: device '" + m_devicename + "' refused to start the diagnostic: " + std::string( to_string( error ) ) );
|
|
}
|
|
}
|
|
|
|
bool protocol_session::start_diagnostic( std::uint8_t const Function )
|
|
{
|
|
if( m_state != session_state::ready )
|
|
{
|
|
return false;
|
|
}
|
|
auto const function = std::find_if( m_functions.begin(), m_functions.end(), [ Function ]( diagnostic_function const &Entry ) { return Entry.id == Function; } );
|
|
if( function == m_functions.end() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
payload_writer writer;
|
|
writer.write_uint32( ++m_outgoingtransaction );
|
|
writer.write_uint8( Function );
|
|
send( message_type::diagnostic_run, packetflag_ack_required, writer );
|
|
|
|
m_prompt = diagnostic_prompt {};
|
|
m_diagnostic.function = Function;
|
|
m_diagnostic.state = diagnostic_state::running;
|
|
m_diagnostic.progress = 0;
|
|
m_diagnostic.message = "requested";
|
|
WriteLog( "hardware: device '" + m_devicename + "' asked to run diagnostic '" + function->name + "'" );
|
|
return true;
|
|
}
|
|
|
|
void protocol_session::cancel_diagnostic()
|
|
{
|
|
if( m_state != session_state::ready )
|
|
{
|
|
return;
|
|
}
|
|
payload_writer writer;
|
|
writer.write_uint8( m_diagnostic.function );
|
|
send( message_type::diagnostic_cancel, packetflag_none, writer );
|
|
m_prompt = diagnostic_prompt {};
|
|
}
|
|
|
|
void protocol_session::answer_prompt( std::uint8_t const Option )
|
|
{
|
|
if( false == m_prompt.active )
|
|
{
|
|
return;
|
|
}
|
|
payload_writer writer;
|
|
writer.write_uint32( m_prompt.id );
|
|
writer.write_uint8( Option );
|
|
send( message_type::diagnostic_prompt_result, packetflag_response, writer );
|
|
m_prompt = diagnostic_prompt {};
|
|
}
|
|
|
|
void protocol_session::send_state_update( std::vector<state_item> const &Items, bool const Snapshot )
|
|
{
|
|
if( true == Items.empty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::size_t const budget = ( m_framesize > protocol_header_size + protocol_crc_size ? m_framesize - protocol_header_size - protocol_crc_size : 64 );
|
|
std::uint8_t const flags = ( Snapshot ? packetflag_snapshot : packetflag_none );
|
|
|
|
payload_writer writer;
|
|
writer.write_uint16( 0 );
|
|
std::uint16_t count = 0;
|
|
|
|
for( auto const &item : Items )
|
|
{
|
|
std::size_t const itemsize = 2 + 1 + 1 + state_value_size( item.value );
|
|
if( ( count > 0 ) && ( writer.size() + itemsize > budget ) )
|
|
{
|
|
writer.patch_uint16( 0, count );
|
|
send( message_type::state_update, flags, writer );
|
|
++m_diagnostics.state_updates;
|
|
writer.clear();
|
|
writer.write_uint16( 0 );
|
|
count = 0;
|
|
}
|
|
|
|
writer.write_uint16( item.handle );
|
|
writer.write_uint8( static_cast<std::uint8_t>( item.value.type ) );
|
|
writer.write_uint8( static_cast<std::uint8_t>( item.value.quality ) );
|
|
write_state_value( writer, item.value );
|
|
++count;
|
|
}
|
|
|
|
if( count > 0 )
|
|
{
|
|
writer.patch_uint16( 0, count );
|
|
send( message_type::state_update, flags, writer );
|
|
++m_diagnostics.state_updates;
|
|
}
|
|
}
|
|
|
|
void protocol_session::send_symbol_context_changed( std::uint32_t const Context )
|
|
{
|
|
payload_writer writer;
|
|
writer.write_uint32( Context );
|
|
send( message_type::symbol_context_changed, packetflag_none, writer );
|
|
}
|
|
|
|
void protocol_session::update( double const Deltatime, state_snapshot const &Snapshot )
|
|
{
|
|
m_snapshot = Snapshot;
|
|
m_uptime += Deltatime;
|
|
|
|
m_rxrate.update( Deltatime );
|
|
m_txrate.update( Deltatime );
|
|
m_diagnostics.rx_rate = m_rxrate.rate();
|
|
m_diagnostics.tx_rate = m_txrate.rate();
|
|
m_diagnostics.connection_uptime = m_uptime;
|
|
m_diagnostics.last_rx_age = m_uptime - m_lastreceived;
|
|
|
|
m_ratewindow += Deltatime;
|
|
if( m_ratewindow >= 1.0 )
|
|
{
|
|
m_ratewindow = 0.0;
|
|
m_framebudget = m_config.max_frames_per_second;
|
|
m_payloadbudget = m_config.max_payload_bytes_per_second;
|
|
m_commandbudget = m_config.max_commands_per_second;
|
|
}
|
|
|
|
if( m_state == session_state::waiting_for_hello )
|
|
{
|
|
return;
|
|
}
|
|
|
|
double const heartbeatinterval = std::max( 0.05, m_config.heartbeat_interval_ms * 0.001 );
|
|
m_diagnostics.missed_heartbeats = static_cast<int>( m_diagnostics.last_rx_age / heartbeatinterval );
|
|
|
|
if( m_diagnostics.missed_heartbeats >= m_config.heartbeat_timeout_count )
|
|
{
|
|
WriteLog( "hardware: device '" + m_devicename + "' stopped responding, session closed" );
|
|
reset();
|
|
return;
|
|
}
|
|
|
|
// the metadata of some symbols depends on the controlled vehicle
|
|
if( ( true == Snapshot.available ) && ( ( false == m_contextvalid ) || ( Snapshot.context_id != m_context ) ) )
|
|
{
|
|
bool const notify = m_contextvalid;
|
|
m_context = Snapshot.context_id;
|
|
m_contextvalid = true;
|
|
if( true == notify )
|
|
{
|
|
send_symbol_context_changed( m_context );
|
|
}
|
|
}
|
|
|
|
m_heartbeattimer += Deltatime;
|
|
if( m_heartbeattimer >= heartbeatinterval )
|
|
{
|
|
m_heartbeattimer = 0.0;
|
|
payload_writer writer;
|
|
writer.write_uint32( static_cast<std::uint32_t>( m_uptime * 1000.0 ) );
|
|
writer.write_uint32( m_lastreceivedsequence );
|
|
writer.write_uint16( static_cast<std::uint16_t>( m_state ) );
|
|
send( message_type::heartbeat, packetflag_none, writer );
|
|
}
|
|
|
|
m_pingtimer += Deltatime;
|
|
if( ( false == m_pingpending ) && ( m_pingtimer >= ping_interval ) )
|
|
{
|
|
m_pingtimer = 0.0;
|
|
m_pingpending = true;
|
|
m_pingsent = m_uptime;
|
|
++m_pingtoken;
|
|
payload_writer writer;
|
|
writer.write_uint32( m_pingtoken );
|
|
send( message_type::ping, packetflag_none, writer );
|
|
}
|
|
else if( ( true == m_pingpending ) && ( m_uptime - m_pingsent > ping_interval ) )
|
|
{
|
|
// the answer never arrived, try again later
|
|
m_pingpending = false;
|
|
m_pingtimer = 0.0;
|
|
}
|
|
|
|
std::vector<state_item> items;
|
|
m_subscriptions.collect( Deltatime, Snapshot, false, items );
|
|
send_state_update( items, false );
|
|
}
|
|
|
|
void protocol_session::fill_report( link_report &Report ) const
|
|
{
|
|
Report.device_name = m_devicename;
|
|
Report.device_role = m_devicerole;
|
|
Report.device_id = m_deviceid;
|
|
Report.firmware_version = m_firmwareversion;
|
|
Report.session = m_state;
|
|
Report.session_id = m_sessionid;
|
|
Report.protocol_major = m_peermajor;
|
|
Report.protocol_minor = m_peerminor;
|
|
Report.handles = m_handles.size();
|
|
Report.subscriptions = m_subscriptions.size();
|
|
Report.frame_size = m_framesize;
|
|
Report.protocol = m_diagnostics;
|
|
Report.device = m_devicediagnostics;
|
|
Report.functions = m_functions;
|
|
Report.diagnostic = m_diagnostic;
|
|
Report.prompt = m_prompt;
|
|
Report.log.assign( m_log.begin(), m_log.end() );
|
|
}
|
|
|
|
} // namespace hardware
|