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

hardware protocol v2: sessions, subscriptions and the transport worker

Adds the logical protocol layer on top of the frame codec: session
handshake with negotiated frame size, session scoped symbol handles,
state subscriptions with periodic, on change and deadband filtering,
command events with duplicate protection and continuous controls with
stale update rejection, plus per connection rate limits and diagnostics.

A single worker thread owns all of the blocking transport work and
exchanges finished frames with the simulation thread through short locked
queues, so a stalled, flooding or unplugged controller cannot hold up the
simulation.
This commit is contained in:
2026-09-05 20:30:32 +00:00
parent 7cb6f789f5
commit 4e3afdd2a6
12 changed files with 2230 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
/*
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/diagnostics/hardware_diagnostics.h"
namespace hardware
{
void rate_counter::update( double const Deltatime )
{
m_window += Deltatime;
if( m_window < 1.0 )
{
return;
}
m_rate = static_cast<float>( m_accumulator / m_window );
m_accumulator = 0;
m_window = 0.0;
}
} // namespace hardware

View File

@@ -0,0 +1,114 @@
/*
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/.
*/
#pragma once
#include <cstdint>
#include <string>
#include "hardware/protocol/protocol_defs.h"
// diagnostics gathered per connection, presented in the simulator debug panel
// transport and protocol problems are counted separately, as they usually have different causes
namespace hardware
{
// sliding estimate of events per second
class rate_counter
{
public:
// methods
void add( std::uint64_t const Count = 1 ) { m_accumulator += Count; }
void update( double const Deltatime );
float rate() const { return m_rate; }
private:
// members
std::uint64_t m_accumulator = 0;
double m_window = 0.0;
float m_rate = 0.0f;
};
struct transport_diagnostics
{
bool connected = false;
std::string endpoint;
std::string last_error;
std::uint64_t rx_bytes = 0;
std::uint64_t tx_bytes = 0;
std::uint64_t transport_errors = 0;
std::uint64_t reconnects = 0;
};
struct protocol_diagnostics
{
std::uint64_t rx_frames = 0;
std::uint64_t tx_frames = 0;
std::uint64_t crc_errors = 0;
std::uint64_t frame_errors = 0;
std::uint64_t length_errors = 0;
std::uint64_t version_errors = 0;
std::uint64_t unknown_messages = 0;
std::uint64_t unknown_handles = 0;
std::uint64_t unknown_symbols = 0;
std::uint64_t duplicate_commands = 0;
std::uint64_t sequence_gaps = 0;
std::uint64_t acks = 0;
std::uint64_t nacks = 0;
std::uint64_t rate_limited = 0;
std::uint64_t state_updates = 0;
std::uint64_t commands_executed = 0;
std::uint64_t controls_applied = 0;
// seconds since the last valid frame arrived
double last_rx_age = 0.0;
double round_trip_time_ms = 0.0;
double connection_uptime = 0.0;
float rx_rate = 0.0f;
float tx_rate = 0.0f;
int missed_heartbeats = 0;
};
// optional information reported by the device itself
struct device_diagnostics
{
bool available = false;
std::uint32_t uptime_ms = 0;
float supply_voltage = 0.0f;
float temperature = 0.0f;
std::uint32_t rx_overflows = 0;
std::uint32_t tx_overflows = 0;
std::uint32_t watchdog_resets = 0;
std::string firmware_build;
};
// everything the debug panel needs about one connection, copied out once per frame
struct link_report
{
std::string transport_kind;
std::string endpoint;
std::string device_name;
std::string device_role;
std::string device_id;
std::string firmware_version;
link_state state = link_state::disconnected;
session_state session = session_state::disconnected;
std::uint32_t session_id = 0;
std::uint8_t protocol_major = 0;
std::uint8_t protocol_minor = 0;
std::size_t handles = 0;
std::size_t subscriptions = 0;
std::size_t frame_size = 0;
transport_diagnostics transport;
protocol_diagnostics protocol;
device_diagnostics device;
};
} // namespace hardware

View File

@@ -0,0 +1,56 @@
/*
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/.
*/
#pragma once
#include <string>
#include <vector>
#include "hardware/protocol/protocol_defs.h"
// configuration of the hardware protocol v2, filled from eu07.ini
// the header deliberately doesn't pull in any transport library, so that it can be included by Globals
namespace hardware
{
struct serial_link_config
{
std::string port;
int baud = 115200;
};
struct config
{
// v2 is active when at least one link is configured; uart v1 keeps working independently
bool enable = false;
std::vector<serial_link_config> serial_links;
std::size_t frame_size = protocol_frame_size_default;
int heartbeat_interval_ms = 500;
// link is dropped after this many missed heartbeats
int heartbeat_timeout_count = 5;
// per connection safety limits, see the rate limiting chapter of the specification
int max_frames_per_second = 500;
int max_payload_bytes_per_second = 262144;
int max_commands_per_second = 100;
// interval of the worker thread polling loop, in milliseconds
int poll_interval_ms = 1;
// delay between reconnection attempts, in seconds
float reconnect_interval = 1.0f;
// verbose protocol logging
bool debug = false;
// additionally log every frame; very noisy
bool debug_frames = false;
};
} // namespace hardware

View File

@@ -0,0 +1,278 @@
/*
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;
} // anonymous namespace
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_report.transport_kind = m_transport->kind();
m_report.endpoint = m_transport->endpoint();
m_transportstatus.endpoint = m_report.endpoint;
}
hardware_link::~hardware_link()
{
m_transport->disconnect();
}
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_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 );
for( auto const &packet : packets )
{
m_session.handle_packet( packet );
}
m_session.update( Deltatime, Snapshot );
m_report.transport = status;
m_report.state = (
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 )
{
#ifdef WITH_UART
for( auto const &link : m_config.serial_links )
{
m_links.emplace_back( std::make_unique<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() );
if( false == m_links.empty() )
{
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();
}
m_links.clear();
}
void hardware_manager::worker()
{
auto const interval = std::chrono::milliseconds( std::max( 1, m_config.poll_interval_ms ) );
while( false == m_quit )
{
for( auto &link : m_links )
{
link->service();
}
std::this_thread::sleep_for( interval );
}
}
void hardware_manager::update( double const Deltatime )
{
if( true == m_links.empty() )
{
return;
}
state_registry::capture( m_snapshot );
for( std::size_t index = 0; index < m_links.size(); ++index )
{
m_links[ index ]->update( Deltatime, m_snapshot );
m_reports[ index ] = m_links[ index ]->report();
}
}
} // namespace hardware

109
hardware/hardware_manager.h Normal file
View File

@@ -0,0 +1,109 @@
/*
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/.
*/
#pragma once
#include <atomic>
#include <deque>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include "hardware/diagnostics/hardware_diagnostics.h"
#include "hardware/hardware_config.h"
#include "hardware/protocol/protocol_session.h"
#include "hardware/transport/hardware_transport.h"
// owner of every hardware protocol v2 connection, regardless of the transport behind it
//
// threading: a single worker thread does all of the blocking work, that is opening ports, reading
// and writing bytes and turning them into frames. the simulation thread only exchanges ready made
// frames with it through small locked queues, and is the only one which ever touches simulation
// state. a device which stops responding, floods the link or disappears therefore cannot stall the
// simulation
namespace hardware
{
class hardware_link : public session_output
{
public:
// methods
hardware_link( std::unique_ptr<hardware_transport> Transport, config const &Config );
~hardware_link() override;
// worker thread: services the transport
void service();
// simulation thread: processes received frames and runs the session
void update( double const Deltatime, state_snapshot const &Snapshot );
// simulation thread: the report gathered during the last update
link_report const &report() const { return m_report; }
void send_packet( packet_header const &Header, std::vector<std::uint8_t> const &Payload ) override;
private:
// members
std::unique_ptr<hardware_transport> m_transport;
config m_config;
protocol_session m_session;
link_report m_report;
// worker thread only
frame_decoder m_decoder;
std::vector<std::uint8_t> m_readbuffer;
std::vector<std::uint8_t> m_writebuffer;
std::chrono::steady_clock::time_point m_lastconnectattempt;
// simulation thread only
frame_decoder::counters m_lastframeerrors;
// shared
std::mutex m_mutex;
std::deque<decoded_packet> m_rxqueue;
std::deque<std::vector<std::uint8_t>> m_txqueue;
transport_diagnostics m_transportstatus;
frame_decoder::counters m_framecounters;
bool m_transportdropped = false;
};
class hardware_manager
{
public:
// methods
explicit hardware_manager( config const &Config );
~hardware_manager();
// called once per simulation frame from the input polling code
void update( double const Deltatime );
std::vector<link_report> const &reports() const { return m_reports; }
bool active() const { return false == m_links.empty(); }
// the running instance, if any; used by the debug panel
static hardware_manager *instance() { return s_instance; }
private:
// methods
void worker();
// members
static hardware_manager *s_instance;
config m_config;
std::vector<std::unique_ptr<hardware_link>> m_links;
std::vector<link_report> m_reports;
state_snapshot m_snapshot;
std::thread m_thread;
std::atomic<bool> m_quit { false };
};
} // namespace hardware

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
/*
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/.
*/
#pragma once
#include <deque>
#include <string>
#include <unordered_map>
#include <vector>
#include "hardware/diagnostics/hardware_diagnostics.h"
#include "hardware/hardware_config.h"
#include "hardware/protocol/protocol_codec.h"
#include "hardware/registry/state_registry.h"
#include "hardware/subscriptions/subscription_manager.h"
#include "input/command.h"
// logical protocol layer of one connection
// runs on the simulation thread; the transport worker only hands over decoded frames, so nothing
// here ever blocks on hardware, and nothing outside here touches simulation state
namespace hardware
{
// sink for packets produced by the session
class session_output
{
public:
// methods
virtual ~session_output() = default;
virtual void send_packet( packet_header const &Header, std::vector<std::uint8_t> const &Payload ) = 0;
};
class protocol_session
{
public:
// methods
protocol_session( session_output &Output, config const &Config );
// drops the session, called when the transport connection is lost
void reset();
// consumes one decoded frame
void handle_packet( decoded_packet const &Packet );
// periodic work: heartbeats, subscription updates, timeouts
void update( double const Deltatime, state_snapshot const &Snapshot );
// copies the current condition into the debug report
void fill_report( link_report &Report ) const;
session_state state() const { return m_state; }
link_state link_condition() const;
protocol_diagnostics const &diagnostics() const { return m_diagnostics; }
// adds the frame level error counts gathered by the transport worker since the last call
void add_frame_errors( frame_decoder::counters const &Delta );
private:
// types
struct transaction_result
{
bool accepted = false;
error_code error = error_code::none;
};
// methods
void send( message_type const Type, std::uint8_t const Flags, payload_writer const &Payload );
void send_ack( std::uint32_t const Transaction, message_type const Type );
void send_nack( std::uint32_t const Transaction, message_type const Type, error_code const Error );
void handle_hello( decoded_packet const &Packet );
void handle_resolve_symbols( decoded_packet const &Packet );
void handle_query_symbol( decoded_packet const &Packet );
void handle_subscribe( decoded_packet const &Packet );
void handle_unsubscribe( decoded_packet const &Packet );
void handle_command_event( decoded_packet const &Packet );
void handle_control_set( decoded_packet const &Packet );
void handle_device_diagnostics( decoded_packet const &Packet );
void handle_heartbeat( decoded_packet const &Packet );
void send_state_update( std::vector<state_item> const &Items, bool const Snapshot );
void send_symbol_context_changed( std::uint32_t const Context );
// returns the registry index for a session handle, or symbol_registry::npos
std::size_t symbol_of_handle( std::uint16_t const Handle ) const;
std::uint16_t handle_of_symbol( std::size_t const SymbolIndex );
bool consume_frame_budget( std::size_t const PayloadSize );
bool consume_command_budget();
bool remember_transaction( std::uint32_t const Transaction, transaction_result const &Result );
transaction_result const *recall_transaction( std::uint32_t const Transaction ) const;
// executes a command, returns the error which should be reported to the device
error_code execute_command( std::size_t const SymbolIndex, command_action const Action, double const Parameter1, double const Parameter2, std::uint16_t const Recipient );
// members
session_output &m_output;
config m_config;
command_relay m_relay;
session_state m_state = session_state::waiting_for_hello;
std::uint32_t m_sessionid = 0;
std::uint32_t m_sequence = 0;
std::uint32_t m_lastreceivedsequence = 0;
bool m_sequenceseen = false;
std::size_t m_framesize = protocol_frame_size_default;
std::uint32_t m_capabilities = 0;
std::uint8_t m_peermajor = 0;
std::uint8_t m_peerminor = 0;
std::string m_devicename;
std::string m_devicerole;
std::string m_deviceid;
std::string m_firmwareversion;
std::string m_hardwareversion;
std::vector<std::size_t> m_handles;
std::unordered_map<std::size_t, std::uint16_t> m_handlelookup;
subscription_manager m_subscriptions;
// per control sequence counters, used to drop stale continuous updates
std::unordered_map<std::uint16_t, std::uint32_t> m_controlsequence;
std::unordered_map<std::uint32_t, transaction_result> m_transactions;
std::deque<std::uint32_t> m_transactionorder;
double m_uptime = 0.0;
double m_lastreceived = 0.0;
double m_heartbeattimer = 0.0;
double m_pingtimer = 0.0;
std::uint32_t m_pingtoken = 0;
double m_pingsent = 0.0;
bool m_pingpending = false;
double m_ratewindow = 0.0;
int m_framebudget = 0;
int m_payloadbudget = 0;
int m_commandbudget = 0;
std::uint32_t m_context = 0;
bool m_contextvalid = false;
// most recent simulator state, kept so that a subscription can be answered right away
state_snapshot m_snapshot;
protocol_diagnostics m_diagnostics;
device_diagnostics m_devicediagnostics;
rate_counter m_rxrate;
rate_counter m_txrate;
};
} // namespace hardware

View File

@@ -0,0 +1,110 @@
/*
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/subscriptions/subscription_manager.h"
namespace hardware
{
error_code subscription_manager::subscribe( std::uint16_t const Handle, std::size_t const SymbolIndex, std::size_t const StateIndex, subscription_mode const Mode, std::uint16_t const PeriodMilliseconds, float const Deadband )
{
if( ( Mode != subscription_mode::periodic ) && ( Mode != subscription_mode::on_change ) && ( Mode != subscription_mode::periodic_or_change ) )
{
return error_code::invalid_field;
}
if( ( false == std::isfinite( Deadband ) ) || ( Deadband < 0.0f ) )
{
return error_code::invalid_field;
}
auto const period = std::clamp<std::uint16_t>( ( PeriodMilliseconds > 0 ? PeriodMilliseconds : subscription_period_min_ms ), subscription_period_min_ms, subscription_period_max_ms ) * 0.001;
auto entry = std::find_if( m_entries.begin(), m_entries.end(), [ Handle ]( subscription_entry const &Entry ) { return Entry.handle == Handle; } );
if( entry == m_entries.end() )
{
if( m_entries.size() >= subscriptions_max )
{
return error_code::busy;
}
m_entries.emplace_back();
entry = std::prev( m_entries.end() );
}
entry->handle = Handle;
entry->symbol_index = SymbolIndex;
entry->state_index = StateIndex;
entry->mode = Mode;
entry->period = period;
entry->deadband = Deadband;
entry->timer = period;
entry->sampled = false;
return error_code::none;
}
bool subscription_manager::unsubscribe( std::uint16_t const Handle )
{
auto const entry = std::find_if( m_entries.begin(), m_entries.end(), [ Handle ]( subscription_entry const &Entry ) { return Entry.handle == Handle; } );
if( entry == m_entries.end() )
{
return false;
}
m_entries.erase( entry );
return true;
}
void subscription_manager::clear()
{
m_entries.clear();
}
void subscription_manager::collect( double const Deltatime, state_snapshot const &Snapshot, bool const Everything, std::vector<state_item> &Output )
{
auto const &states = state_registry::instance().entries();
for( auto &entry : m_entries )
{
if( entry.state_index >= states.size() )
{
continue;
}
entry.timer += Deltatime;
auto const value = states[ entry.state_index ].getter( Snapshot );
bool const periodic = ( ( entry.mode != subscription_mode::on_change ) && ( entry.timer >= entry.period ) );
bool changed = false;
if( entry.mode != subscription_mode::periodic )
{
changed = (
( false == entry.sampled )
|| ( value.quality != entry.last_quality )
|| ( std::abs( value.numeric - entry.last_numeric ) > entry.deadband ) );
}
if( ( false == Everything ) && ( false == periodic ) && ( false == changed ) )
{
continue;
}
entry.timer = 0.0;
entry.sampled = true;
entry.last_numeric = value.numeric;
entry.last_quality = value.quality;
state_item item;
item.handle = entry.handle;
item.value = value;
Output.emplace_back( std::move( item ) );
}
}
} // namespace hardware

View File

@@ -0,0 +1,71 @@
/*
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/.
*/
#pragma once
#include <cstdint>
#include <vector>
#include "hardware/protocol/protocol_defs.h"
#include "hardware/registry/state_registry.h"
// state subscriptions of a single session
// a device receives only the values it asked for, at the rate it asked for; unchanged values within
// the configured deadband generate no traffic at all
namespace hardware
{
// upper bound on subscriptions per session, keeps a misbehaving device from exhausting memory
constexpr std::size_t subscriptions_max = 128;
constexpr std::uint16_t subscription_period_min_ms = 10;
constexpr std::uint16_t subscription_period_max_ms = 60000;
struct subscription_entry
{
std::uint16_t handle = 0;
std::size_t symbol_index = 0;
std::size_t state_index = 0;
subscription_mode mode = subscription_mode::periodic;
double period = 0.05;
double deadband = 0.0;
double timer = 0.0;
bool sampled = false;
double last_numeric = 0.0;
value_quality last_quality = value_quality::unavailable;
};
struct state_item
{
std::uint16_t handle = 0;
state_value value;
};
class subscription_manager
{
public:
// methods
// registers or updates a subscription, returns the error to be reported back to the device
error_code subscribe( std::uint16_t const Handle, std::size_t const SymbolIndex, std::size_t const StateIndex, subscription_mode const Mode, std::uint16_t const PeriodMilliseconds, float const Deadband );
bool unsubscribe( std::uint16_t const Handle );
void clear();
std::size_t size() const { return m_entries.size(); }
std::vector<subscription_entry> const &entries() const { return m_entries; }
// advances the timers and collects the values which are due for transmission
void collect( double const Deltatime, state_snapshot const &Snapshot, bool const Everything, std::vector<state_item> &Output );
private:
// members
std::vector<subscription_entry> m_entries;
};
} // namespace hardware

View File

@@ -0,0 +1,47 @@
/*
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/.
*/
#pragma once
#include <cstdint>
#include <string>
// transport abstraction of the hardware protocol
// the upper layers only ever see a byte stream, which is what lets the same protocol run over
// serial, tcp or rs-485 without changing message semantics
// every method is called from the hardware worker thread only
namespace hardware
{
class hardware_transport
{
public:
// methods
virtual ~hardware_transport() = default;
virtual bool connected() const = 0;
// tries to establish the link, returns true on success
virtual bool connect() = 0;
virtual void disconnect() = 0;
// non-blocking read, returns the number of bytes placed in the buffer or -1 on error
virtual int read( std::uint8_t *Buffer, std::size_t const Size ) = 0;
// non-blocking write, returns the number of bytes accepted or -1 on error
virtual int write( std::uint8_t const *Data, std::size_t const Size ) = 0;
// human readable transport type, i.e. "Serial"
virtual std::string kind() const = 0;
// human readable endpoint, i.e. "COM4 @ 115200"
virtual std::string endpoint() const = 0;
// last error reported by the underlying device
virtual std::string last_error() const = 0;
};
} // namespace hardware

View File

@@ -0,0 +1,151 @@
/*
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/transport/serial_transport.h"
namespace hardware
{
serial_transport::serial_transport( std::string Port, int const Baud ) : m_portname( std::move( Port ) ), m_baud( Baud )
{
}
serial_transport::~serial_transport()
{
disconnect();
}
std::string serial_transport::endpoint() const
{
return m_portname + " @ " + std::to_string( m_baud );
}
bool serial_transport::connect()
{
disconnect();
sp_port *port = nullptr;
if( sp_get_port_by_name( m_portname.c_str(), &port ) != SP_OK )
{
m_lasterror = "port not found";
return false;
}
if( sp_open( port, static_cast<sp_mode>( SP_MODE_READ | SP_MODE_WRITE ) ) != SP_OK )
{
m_lasterror = "cannot open port";
sp_free_port( port );
return false;
}
sp_port_config *config = nullptr;
if( ( sp_new_config( &config ) != SP_OK )
|| ( sp_set_config_baudrate( config, m_baud ) != SP_OK )
|| ( sp_set_config_flowcontrol( config, SP_FLOWCONTROL_NONE ) != SP_OK )
|| ( sp_set_config_bits( config, 8 ) != SP_OK )
|| ( sp_set_config_stopbits( config, 1 ) != SP_OK )
|| ( sp_set_config_parity( config, SP_PARITY_NONE ) != SP_OK )
|| ( sp_set_config( port, config ) != SP_OK ) )
{
m_lasterror = "cannot configure port";
if( config != nullptr )
{
sp_free_config( config );
}
sp_close( port );
sp_free_port( port );
return false;
}
sp_free_config( config );
if( sp_flush( port, SP_BUF_BOTH ) != SP_OK )
{
m_lasterror = "cannot flush port";
sp_close( port );
sp_free_port( port );
return false;
}
m_port = port;
m_lasterror.clear();
return true;
}
void serial_transport::disconnect()
{
if( m_port == nullptr )
{
return;
}
sp_close( m_port );
sp_free_port( m_port );
m_port = nullptr;
}
int serial_transport::read( std::uint8_t *Buffer, std::size_t const Size )
{
if( m_port == nullptr )
{
return -1;
}
int const waiting = sp_input_waiting( m_port );
if( waiting < 0 )
{
m_lasterror = "read failed";
return -1;
}
if( waiting == 0 )
{
return 0;
}
int const result = sp_nonblocking_read( m_port, Buffer, std::min( Size, static_cast<std::size_t>( waiting ) ) );
if( result < 0 )
{
m_lasterror = "read failed";
return -1;
}
return result;
}
int serial_transport::write( std::uint8_t const *Data, std::size_t const Size )
{
if( m_port == nullptr )
{
return -1;
}
int const result = sp_nonblocking_write( m_port, Data, Size );
if( result < 0 )
{
m_lasterror = "write failed";
return -1;
}
return result;
}
std::vector<std::string> list_serial_ports()
{
std::vector<std::string> result;
sp_port **ports = nullptr;
if( sp_list_ports( &ports ) != SP_OK )
{
return result;
}
for( int index = 0; ports[ index ] != nullptr; ++index )
{
result.emplace_back( sp_get_port_name( ports[ index ] ) );
}
sp_free_port_list( ports );
return result;
}
} // namespace hardware

View File

@@ -0,0 +1,54 @@
/*
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/.
*/
#pragma once
#include <libserialport.h>
#include <string>
#include <vector>
#include "hardware/transport/hardware_transport.h"
// serial port transport, one connection per port, as described by the specification
// the port is opened and serviced exclusively from the hardware worker thread
namespace hardware
{
class serial_transport : public hardware_transport
{
public:
// methods
serial_transport( std::string Port, int const Baud );
~serial_transport() override;
bool connected() const override { return m_port != nullptr; }
bool connect() override;
void disconnect() override;
int read( std::uint8_t *Buffer, std::size_t const Size ) override;
int write( std::uint8_t const *Data, std::size_t const Size ) override;
std::string kind() const override { return "Serial"; }
std::string endpoint() const override;
std::string last_error() const override { return m_lasterror; }
private:
// members
std::string m_portname;
int m_baud = 115200;
sp_port *m_port = nullptr;
std::string m_lasterror;
};
// list of serial ports currently present in the system
std::vector<std::string> list_serial_ports();
} // namespace hardware