mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-01 21:39:16 +01:00
hardware protocol v2: frame layer and shared symbol registry
Adds the transport-independent core of the MaSzyna Hardware Protocol v2: COBS framing with a CRC32C protected 24 byte header, bounds checked payload readers and writers, and a resynchronising stream decoder. The shared TrainState registry describes simulator state in engineering units with per-value quality, the command registry generates the command namespace from Commands_descriptions, and the symbol registry merges both into the public, name-addressed hardware api.
This commit is contained in:
96
hardware/registry/command_registry.cpp
Normal file
96
hardware/registry/command_registry.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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/registry/command_registry.h"
|
||||
|
||||
namespace hardware
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct command_metadata
|
||||
{
|
||||
char const *name;
|
||||
bool continuous;
|
||||
std::optional<double> minimum;
|
||||
std::optional<double> maximum;
|
||||
};
|
||||
|
||||
// hardware metadata for the commands which carry an analogue value; everything else is an event command
|
||||
command_metadata const continuous_commands[] = {
|
||||
{ "mastercontrollerset", true, 0.0, std::nullopt },
|
||||
{ "secondcontrollerset", true, 0.0, std::nullopt },
|
||||
{ "jointcontrollerset", true, 0.0, std::nullopt },
|
||||
{ "trainbrakeset", true, 0.0, 1.0 },
|
||||
{ "independentbrakeset", true, 0.0, 1.0 },
|
||||
{ "dynamicbrakecontrollerset", true, 0.0, 1.0 },
|
||||
{ "radiovolumeset", true, 0.0, 1.0 },
|
||||
{ "radiochannelset", true, 1.0, 15.0 }
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
command_registry const &command_registry::instance()
|
||||
{
|
||||
static command_registry const registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
command_descriptor const *command_registry::find( std::string const &Name ) const
|
||||
{
|
||||
auto const lookup = m_index.find( Name );
|
||||
return ( lookup != m_index.end() ? &m_entries[ lookup->second ] : nullptr );
|
||||
}
|
||||
|
||||
command_registry::command_registry()
|
||||
{
|
||||
std::size_t commandid = 0;
|
||||
for( auto const &description : simulation::Commands_descriptions )
|
||||
{
|
||||
command_descriptor descriptor;
|
||||
descriptor.name = description.name;
|
||||
descriptor.command = static_cast<user_command>( commandid );
|
||||
m_index.emplace( descriptor.name, m_entries.size() );
|
||||
m_entries.emplace_back( std::move( descriptor ) );
|
||||
++commandid;
|
||||
}
|
||||
|
||||
for( auto const &metadata : continuous_commands )
|
||||
{
|
||||
auto const lookup = m_index.find( metadata.name );
|
||||
if( lookup == m_index.end() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto &descriptor = m_entries[ lookup->second ];
|
||||
descriptor.continuous = metadata.continuous;
|
||||
descriptor.minimum = metadata.minimum;
|
||||
descriptor.maximum = metadata.maximum;
|
||||
}
|
||||
|
||||
// protocol level alias: master controller driven with a 0...1 fraction of its range, the way
|
||||
// uart v1 does it in percentage mode. it maps onto the regular mastercontrollerset command
|
||||
auto const mastercontroller = m_index.find( "mastercontrollerset" );
|
||||
if( mastercontroller != m_index.end() )
|
||||
{
|
||||
command_descriptor descriptor;
|
||||
descriptor.name = "mastercontrollersetpercent";
|
||||
descriptor.command = m_entries[ mastercontroller->second ].command;
|
||||
descriptor.continuous = true;
|
||||
descriptor.minimum = 0.0;
|
||||
descriptor.maximum = 1.0;
|
||||
descriptor.transform = command_transform::mastercontroller_percent;
|
||||
m_index.emplace( descriptor.name, m_entries.size() );
|
||||
m_entries.emplace_back( std::move( descriptor ) );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hardware
|
||||
67
hardware/registry/command_registry.h
Normal file
67
hardware/registry/command_registry.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "hardware/protocol/protocol_defs.h"
|
||||
#include "input/command.h"
|
||||
|
||||
// command namespace of the hardware api
|
||||
// the list is generated from simulation::Commands_descriptions, with hardware metadata added on top,
|
||||
// so that external devices never depend on the numeric values of the user_command enumeration
|
||||
|
||||
namespace hardware
|
||||
{
|
||||
|
||||
// optional conversion applied to the value received from the device before the command is posted
|
||||
enum class command_transform : std::uint8_t
|
||||
{
|
||||
none = 0,
|
||||
// 0...1 fraction of the master controller range of the occupied vehicle
|
||||
mastercontroller_percent = 1
|
||||
};
|
||||
|
||||
struct command_descriptor
|
||||
{
|
||||
// registry name without namespace prefix, i.e. "trainbrakeset" rather than "command.trainbrakeset"
|
||||
std::string name;
|
||||
user_command command = user_command::none;
|
||||
// true for controls which may be driven with CONTROL_SET, false for event-only commands
|
||||
bool continuous = false;
|
||||
value_type parameter_type = value_type::float32;
|
||||
std::optional<double> minimum;
|
||||
std::optional<double> maximum;
|
||||
command_transform transform = command_transform::none;
|
||||
};
|
||||
|
||||
class command_registry
|
||||
{
|
||||
|
||||
public:
|
||||
// methods
|
||||
static command_registry const &instance();
|
||||
|
||||
std::vector<command_descriptor> const &entries() const { return m_entries; }
|
||||
command_descriptor const *find( std::string const &Name ) const;
|
||||
|
||||
private:
|
||||
// methods
|
||||
command_registry();
|
||||
|
||||
// members
|
||||
std::vector<command_descriptor> m_entries;
|
||||
std::unordered_map<std::string, std::size_t> m_index;
|
||||
};
|
||||
|
||||
} // namespace hardware
|
||||
69
hardware/registry/hardware_symbol_registry.cpp
Normal file
69
hardware/registry/hardware_symbol_registry.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
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/registry/hardware_symbol_registry.h"
|
||||
|
||||
#include "hardware/registry/command_registry.h"
|
||||
#include "hardware/registry/state_registry.h"
|
||||
|
||||
namespace hardware
|
||||
{
|
||||
|
||||
symbol_registry const &symbol_registry::instance()
|
||||
{
|
||||
static symbol_registry const registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
std::size_t symbol_registry::find( std::string const &Name ) const
|
||||
{
|
||||
auto const lookup = m_index.find( Name );
|
||||
return ( lookup != m_index.end() ? lookup->second : npos );
|
||||
}
|
||||
|
||||
symbol_registry::symbol_registry()
|
||||
{
|
||||
auto const &states = state_registry::instance().entries();
|
||||
m_entries.reserve( states.size() + command_registry::instance().entries().size() );
|
||||
|
||||
for( std::size_t index = 0; index < states.size(); ++index )
|
||||
{
|
||||
auto const &state = states[ index ];
|
||||
symbol_descriptor descriptor;
|
||||
descriptor.name = "state." + state.name;
|
||||
descriptor.kind = symbol_kind::state;
|
||||
descriptor.type = state.type;
|
||||
descriptor.access = access_read;
|
||||
descriptor.unit = state.unit;
|
||||
descriptor.minimum = state.minimum;
|
||||
descriptor.maximum = state.maximum;
|
||||
descriptor.source_index = index;
|
||||
m_index.emplace( descriptor.name, m_entries.size() );
|
||||
m_entries.emplace_back( std::move( descriptor ) );
|
||||
}
|
||||
|
||||
auto const &commands = command_registry::instance().entries();
|
||||
for( std::size_t index = 0; index < commands.size(); ++index )
|
||||
{
|
||||
auto const &command = commands[ index ];
|
||||
symbol_descriptor descriptor;
|
||||
descriptor.name = "command." + command.name;
|
||||
descriptor.kind = symbol_kind::command;
|
||||
descriptor.type = command.parameter_type;
|
||||
descriptor.access = access_write;
|
||||
descriptor.minimum = command.minimum;
|
||||
descriptor.maximum = command.maximum;
|
||||
descriptor.source_index = index;
|
||||
m_index.emplace( descriptor.name, m_entries.size() );
|
||||
m_entries.emplace_back( std::move( descriptor ) );
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hardware
|
||||
64
hardware/registry/hardware_symbol_registry.h
Normal file
64
hardware/registry/hardware_symbol_registry.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 <limits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "hardware/protocol/protocol_defs.h"
|
||||
|
||||
// public symbol table of the hardware api
|
||||
// merges the state and command registries into one namespace-prefixed list; devices refer to symbols
|
||||
// by these names and receive short, session scoped handles in return
|
||||
|
||||
namespace hardware
|
||||
{
|
||||
|
||||
struct symbol_descriptor
|
||||
{
|
||||
// full public name, i.e. "state.velocity" or "command.trainbrakeset"
|
||||
std::string name;
|
||||
symbol_kind kind = symbol_kind::state;
|
||||
value_type type = value_type::float32;
|
||||
std::uint8_t access = access_read;
|
||||
std::string unit;
|
||||
std::optional<double> minimum;
|
||||
std::optional<double> maximum;
|
||||
// index into the registry the symbol originates from
|
||||
std::size_t source_index = 0;
|
||||
};
|
||||
|
||||
class symbol_registry
|
||||
{
|
||||
|
||||
public:
|
||||
// types
|
||||
static constexpr std::size_t npos = std::numeric_limits<std::size_t>::max();
|
||||
|
||||
// methods
|
||||
static symbol_registry const &instance();
|
||||
|
||||
std::vector<symbol_descriptor> const &entries() const { return m_entries; }
|
||||
// returns index of the symbol, or npos if the name isn't known
|
||||
std::size_t find( std::string const &Name ) const;
|
||||
|
||||
private:
|
||||
// methods
|
||||
symbol_registry();
|
||||
|
||||
// members
|
||||
std::vector<symbol_descriptor> m_entries;
|
||||
std::unordered_map<std::string, std::size_t> m_index;
|
||||
};
|
||||
|
||||
} // namespace hardware
|
||||
170
hardware/registry/state_registry.cpp
Normal file
170
hardware/registry/state_registry.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
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/registry/state_registry.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
namespace hardware
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
state_value make_value( state_snapshot const &Snapshot, value_type const Type, double const Value, bool const Applicable = true )
|
||||
{
|
||||
state_value result;
|
||||
result.type = Type;
|
||||
result.numeric = Value;
|
||||
result.quality = (
|
||||
false == Snapshot.available ? value_quality::unavailable :
|
||||
false == Applicable ? value_quality::not_applicable :
|
||||
value_quality::valid );
|
||||
if( result.quality != value_quality::valid )
|
||||
{
|
||||
result.numeric = 0.0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// values which don't depend on the vehicle are always valid
|
||||
state_value make_global_value( value_type const Type, double const Value )
|
||||
{
|
||||
state_value result;
|
||||
result.type = Type;
|
||||
result.numeric = Value;
|
||||
result.quality = value_quality::valid;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
state_registry const &state_registry::instance()
|
||||
{
|
||||
static state_registry const registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
state_descriptor const *state_registry::find( std::string const &Name ) const
|
||||
{
|
||||
auto const lookup = m_index.find( Name );
|
||||
return ( lookup != m_index.end() ? &m_entries[ lookup->second ] : nullptr );
|
||||
}
|
||||
|
||||
void state_registry::add( std::string Name, value_type const Type, std::string Unit, std::optional<double> const Minimum, std::optional<double> const Maximum, std::function<state_value( state_snapshot const & )> Getter )
|
||||
{
|
||||
m_index.emplace( Name, m_entries.size() );
|
||||
state_descriptor descriptor;
|
||||
descriptor.name = std::move( Name );
|
||||
descriptor.type = Type;
|
||||
descriptor.unit = std::move( Unit );
|
||||
descriptor.minimum = Minimum;
|
||||
descriptor.maximum = Maximum;
|
||||
descriptor.getter = std::move( Getter );
|
||||
m_entries.emplace_back( std::move( descriptor ) );
|
||||
}
|
||||
|
||||
state_registry::state_registry()
|
||||
{
|
||||
// indicators and switch states
|
||||
add( "shp", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.shp != 0 ); } );
|
||||
add( "alerter", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.alerter != 0 ); } );
|
||||
add( "alerter_sound", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.alerter_sound != 0 ); } );
|
||||
add( "radio_stop", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.radio_stop != 0 ); } );
|
||||
add( "motor_resistors", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.motor_resistors != 0 ); } );
|
||||
add( "line_breaker", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.line_breaker != 0 ); } );
|
||||
add( "ground_relay", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.ground_relay != 0 ); } );
|
||||
add( "motor_overload", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.motor_overload != 0 ); } );
|
||||
add( "motor_overload_threshold", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.motor_overload_threshold != 0 ); } );
|
||||
add( "motor_connectors", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.motor_connectors != 0 ); } );
|
||||
add( "wheelslip", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.wheelslip != 0 ); } );
|
||||
add( "converter_overload", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.converter_overload != 0 ); } );
|
||||
add( "converter_off", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.converter_off != 0 ); } );
|
||||
add( "compressor_overload", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.compressor_overload != 0 ); } );
|
||||
add( "ventilator_overload", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.ventilator_overload != 0 ); } );
|
||||
add( "train_heating", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.train_heating != 0 ); } );
|
||||
add( "coupled_hv_voltage_relays", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.coupled_hv_voltage_relays != 0 ); } );
|
||||
add( "recorder_braking", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.recorder_braking != 0 ); } );
|
||||
add( "recorder_power", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.recorder_power != 0 ); } );
|
||||
add( "springbrake_active", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.springbrake_active != 0 ); } );
|
||||
add( "epbrake_enabled", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.epbrake_enabled != 0 ); } );
|
||||
add( "emergencybrake", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.emergencybrake != 0 ); } );
|
||||
add( "lockpipe", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.lockpipe != 0 ); } );
|
||||
add( "battery", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.battery != 0 ); } );
|
||||
add( "radiomessageindicator", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.radiomessageindicator ); } );
|
||||
add( "dir_forward", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.dir_forward != 0 ); } );
|
||||
add( "dir_backward", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.dir_backward != 0 ); } );
|
||||
add( "doorleftallowed", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.doorleftallowed != 0 ); } );
|
||||
add( "doorleftopened", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.doorleftopened != 0 ); } );
|
||||
add( "doorrightallowed", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.doorrightallowed != 0 ); } );
|
||||
add( "doorrightopened", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.doorrightopened != 0 ); } );
|
||||
add( "doorstepallowed", value_type::boolean, "", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::boolean, s.train.doorstepallowed != 0 ); } );
|
||||
|
||||
// enumerations and counters
|
||||
add( "cab", value_type::uint8, "", 0.0, 2.0, []( state_snapshot const &s ) { return make_value( s, value_type::uint8, s.train.cab ); } );
|
||||
add( "cab_indicator", value_type::uint8, "", 0.0, 1.0, []( state_snapshot const &s ) { return make_value( s, value_type::uint8, s.cab_indicator ); } );
|
||||
add( "radio_channel", value_type::uint8, "", 0.0, 15.0, []( state_snapshot const &s ) { return make_value( s, value_type::uint8, s.train.radio_channel ); } );
|
||||
|
||||
// measurements, in engineering units
|
||||
add( "velocity", value_type::float32, "km/h", 0.0, 400.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, Global.iPause ? 0.0 : s.train.velocity ); } );
|
||||
add( "reservoir_pressure", value_type::float32, "bar", 0.0, 16.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.reservoir_pressure ); } );
|
||||
add( "pipe_pressure", value_type::float32, "bar", 0.0, 16.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.pipe_pressure ); } );
|
||||
add( "brake_pressure", value_type::float32, "bar", 0.0, 16.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.brake_pressure ); } );
|
||||
add( "pantograph_pressure", value_type::float32, "bar", 0.0, 16.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.pantograph_pressure, s.pantographs ); } );
|
||||
add( "hv_voltage", value_type::float32, "V", 0.0, 40000.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.hv_voltage, s.electric ); } );
|
||||
add( "hv_current_1", value_type::float32, "A", -5000.0, 5000.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.hv_current[ 0 ], s.electric ); } );
|
||||
add( "hv_current_2", value_type::float32, "A", -5000.0, 5000.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.hv_current[ 1 ], s.electric ); } );
|
||||
add( "hv_current_3", value_type::float32, "A", -5000.0, 5000.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.hv_current[ 2 ], s.electric ); } );
|
||||
add( "lv_voltage", value_type::float32, "V", 0.0, 200.0, []( state_snapshot const &s ) { return make_value( s, value_type::float32, s.train.lv_voltage ); } );
|
||||
add( "distance", value_type::float64, "m", {}, {}, []( state_snapshot const &s ) { return make_value( s, value_type::float64, s.train.distance ); } );
|
||||
|
||||
// simulation clock, available regardless of the occupied vehicle
|
||||
add( "time_month_of_era", value_type::uint32, "", {}, {}, []( state_snapshot const &s ) { return make_global_value( value_type::uint32, s.time_month_of_era ); } );
|
||||
add( "time_minute_of_month", value_type::uint32, "", {}, {}, []( state_snapshot const &s ) { return make_global_value( value_type::uint32, s.time_minute_of_month ); } );
|
||||
add( "time_millisecond_of_day", value_type::uint32, "", {}, {}, []( state_snapshot const &s ) { return make_global_value( value_type::uint32, s.time_millisecond_of_day ); } );
|
||||
}
|
||||
|
||||
void state_registry::capture( state_snapshot &Snapshot )
|
||||
{
|
||||
SYSTEMTIME const time = simulation::Time.data();
|
||||
Snapshot.time_month_of_era = ( time.wYear - 1 ) * 12 + time.wMonth - 1;
|
||||
Snapshot.time_minute_of_month = ( time.wDay - 1 ) * 1440 + time.wHour * 60 + time.wMinute;
|
||||
Snapshot.time_millisecond_of_day = time.wSecond * 1000 + time.wMilliseconds;
|
||||
|
||||
auto const *train = simulation::Train;
|
||||
if( train == nullptr )
|
||||
{
|
||||
Snapshot.available = false;
|
||||
Snapshot.electric = false;
|
||||
Snapshot.pantographs = false;
|
||||
Snapshot.context_id = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
Snapshot.available = true;
|
||||
Snapshot.train = train->get_state();
|
||||
|
||||
if( Snapshot.train.cab > 0 )
|
||||
{
|
||||
// NOTE: moving from a cab to the engine room doesn't change the cab indicator
|
||||
Snapshot.cab_indicator = Snapshot.train.cab - 1;
|
||||
}
|
||||
|
||||
auto const *controlled = train->Controlled();
|
||||
auto const enginetype = ( controlled != nullptr ? controlled->EngineType : TEngineType::None );
|
||||
Snapshot.electric = ( ( enginetype == TEngineType::ElectricSeriesMotor ) || ( enginetype == TEngineType::ElectricInductionMotor ) );
|
||||
Snapshot.pantographs = Snapshot.electric;
|
||||
|
||||
Snapshot.context_id = static_cast<std::uint32_t>( std::hash<std::string>()( train->name() ) );
|
||||
}
|
||||
|
||||
} // namespace hardware
|
||||
89
hardware/registry/state_registry.h
Normal file
89
hardware/registry/state_registry.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "hardware/protocol/protocol_defs.h"
|
||||
#include "vehicle/Train.h"
|
||||
|
||||
// shared TrainState registry
|
||||
// one description of the simulator state exposed to the outside world, meant to be used by every
|
||||
// external interface (hardware protocol v2, zmq, python) instead of each of them keeping its own list
|
||||
|
||||
namespace hardware
|
||||
{
|
||||
|
||||
// values captured once per simulation frame, so that all consumers see a consistent set
|
||||
struct state_snapshot
|
||||
{
|
||||
// false when there's no occupied vehicle; state symbols then report UNAVAILABLE
|
||||
bool available = false;
|
||||
// high voltage circuit is meaningful for this vehicle
|
||||
bool electric = false;
|
||||
// vehicle is equipped with pantographs
|
||||
bool pantographs = false;
|
||||
TTrain::state_t train {};
|
||||
// last active cab, 0: front, 1: rear. moving to the engine room doesn't change it
|
||||
std::uint8_t cab_indicator = 0;
|
||||
// changes whenever the controlled vehicle changes, used to notify devices about metadata changes
|
||||
std::uint32_t context_id = 0;
|
||||
double time_month_of_era = 0.0;
|
||||
double time_minute_of_month = 0.0;
|
||||
double time_millisecond_of_day = 0.0;
|
||||
};
|
||||
|
||||
struct state_value
|
||||
{
|
||||
value_type type = value_type::float32;
|
||||
value_quality quality = value_quality::unavailable;
|
||||
double numeric = 0.0;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct state_descriptor
|
||||
{
|
||||
// registry name without namespace prefix, i.e. "velocity" rather than "state.velocity"
|
||||
std::string name;
|
||||
value_type type = value_type::float32;
|
||||
std::string unit;
|
||||
std::optional<double> minimum;
|
||||
std::optional<double> maximum;
|
||||
std::function<state_value( state_snapshot const & )> getter;
|
||||
};
|
||||
|
||||
class state_registry
|
||||
{
|
||||
|
||||
public:
|
||||
// methods
|
||||
static state_registry const &instance();
|
||||
|
||||
std::vector<state_descriptor> const &entries() const { return m_entries; }
|
||||
state_descriptor const *find( std::string const &Name ) const;
|
||||
|
||||
// fills the snapshot from the currently occupied train; simulation thread only
|
||||
static void capture( state_snapshot &Snapshot );
|
||||
|
||||
private:
|
||||
// methods
|
||||
state_registry();
|
||||
void add( std::string Name, value_type const Type, std::string Unit, std::optional<double> const Minimum, std::optional<double> const Maximum, std::function<state_value( state_snapshot const & )> Getter );
|
||||
|
||||
// members
|
||||
std::vector<state_descriptor> m_entries;
|
||||
std::unordered_map<std::string, std::size_t> m_index;
|
||||
};
|
||||
|
||||
} // namespace hardware
|
||||
Reference in New Issue
Block a user