mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-06 05:48:35 +01:00
First implementation of graph vehicles
This commit is contained in:
@@ -43,6 +43,18 @@ std::vector<std::string> split_labels(std::string const &Text)
|
||||
|
||||
} // namespace
|
||||
|
||||
int cab_of(block_setup const &Setup)
|
||||
{
|
||||
auto const index{find_param(Setup.param_defs, "cab")};
|
||||
if (index < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// "any", "1", "2", "0" (the engine room)
|
||||
auto const &value{Setup.params[index].text};
|
||||
return value == "1" ? 1 : value == "2" ? 2 : value == "0" ? -1 : 0;
|
||||
}
|
||||
|
||||
// cab_switch
|
||||
|
||||
cab_switch::cab_switch(block_setup const &Setup)
|
||||
@@ -50,7 +62,10 @@ cab_switch::cab_switch(block_setup const &Setup)
|
||||
m_positioncount(std::max(1, static_cast<int>(std::lround(number(Setup, param::positions))))), m_min(number(Setup, param::min)), m_max(number(Setup, param::max)),
|
||||
m_spring(boolean(Setup, param::spring)), m_labels(split_labels(text(Setup, param::labels))), m_animation(static_cast<animation_type>(choice(Setup, param::animation))),
|
||||
m_step(number(Setup, param::step)), m_offset(number(Setup, param::offset)), m_smoothing(number(Setup, param::smoothing)), m_submodel(text(Setup, param::submodel)),
|
||||
m_keyincrease(text(Setup, param::key_inc)), m_keydecrease(text(Setup, param::key_dec)), m_command(text(Setup, param::command)), m_hardwarename(text(Setup, param::hw_name))
|
||||
m_soundincrease(text(Setup, param::sound_inc)), m_sounddecrease(text(Setup, param::sound_dec)),
|
||||
m_soundpositions(text(Setup, param::sound_positions)),
|
||||
m_keyincrease(text(Setup, param::key_inc)), m_keydecrease(text(Setup, param::key_dec)), m_command(text(Setup, param::command)), m_hardwarename(text(Setup, param::hw_name)),
|
||||
m_cab(cab_of(Setup))
|
||||
{
|
||||
// the default is given as a value, as the author sees it; the switch starts at the position closest to it
|
||||
auto const defaultvalue{number(Setup, param::default_value)};
|
||||
@@ -59,6 +74,7 @@ cab_switch::cab_switch(block_setup const &Setup)
|
||||
m_defaultposition = clamp_position(static_cast<int>(std::lround((defaultvalue - m_min) / (m_max - m_min) * (m_positioncount - 1))));
|
||||
}
|
||||
m_position = m_defaultposition;
|
||||
m_display = static_cast<float>(m_position);
|
||||
}
|
||||
|
||||
int cab_switch::clamp_position(int const Position) const
|
||||
@@ -84,6 +100,7 @@ void cab_switch::update(frame &Frame)
|
||||
{
|
||||
set(Frame, port::value, value_at(m_position));
|
||||
set(Frame, port::index, m_position);
|
||||
m_display = static_cast<float>(m_position);
|
||||
}
|
||||
|
||||
void cab_switch::increase()
|
||||
@@ -181,10 +198,14 @@ block_type cab_switch::describe()
|
||||
number_param("offset", 0.0, "deg/mm"),
|
||||
number_param("smoothing", 0.2, "s"),
|
||||
text_param("submodel"),
|
||||
text_param("sound_inc"),
|
||||
text_param("sound_dec"),
|
||||
text_param("sound_positions"),
|
||||
text_param("key_inc"),
|
||||
text_param("key_dec"),
|
||||
text_param("command"),
|
||||
text_param("hw_name"),
|
||||
cab_param(),
|
||||
};
|
||||
type.create = make_block<cab_switch>;
|
||||
return type;
|
||||
@@ -194,7 +215,8 @@ block_type cab_switch::describe()
|
||||
|
||||
cab_lamp::cab_lamp(block_setup const &Setup)
|
||||
: block(Setup), m_supplied(is_connected(Setup, port::supply)), m_blink(boolean(Setup, param::blink)), m_blinkperiod(std::max(0.01, number(Setup, param::blink_period))),
|
||||
m_minvoltage(number(Setup, param::min_voltage)), m_power(std::max(0.0, number(Setup, param::power))), m_submodel(text(Setup, param::submodel))
|
||||
m_minvoltage(number(Setup, param::min_voltage)), m_power(std::max(0.0, number(Setup, param::power))), m_submodel(text(Setup, param::submodel)),
|
||||
m_soundon(text(Setup, param::sound_on)), m_soundoff(text(Setup, param::sound_off)), m_cab(cab_of(Setup))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -233,6 +255,9 @@ block_type cab_lamp::describe()
|
||||
number_param("min_voltage", 70.0, "V"),
|
||||
number_param("power", 2.0, "W"),
|
||||
text_param("submodel"),
|
||||
text_param("sound_on"),
|
||||
text_param("sound_off"),
|
||||
cab_param(),
|
||||
};
|
||||
type.create = make_block<cab_lamp>;
|
||||
return type;
|
||||
@@ -243,7 +268,7 @@ block_type cab_lamp::describe()
|
||||
cab_gauge::cab_gauge(block_setup const &Setup, bool const Twoneedles)
|
||||
: block(Setup), m_twoneedles(Twoneedles), m_min(number(Setup, param::min)), m_max(number(Setup, param::max)), m_anglemin(number(Setup, param::angle_min)),
|
||||
m_anglemax(number(Setup, param::angle_max)), m_smoothing(number(Setup, param::smoothing)), m_submodel(text(Setup, param::submodel)),
|
||||
m_submodel2(Twoneedles ? text(Setup, param::submodel2) : std::string{})
|
||||
m_submodel2(Twoneedles ? text(Setup, param::submodel2) : std::string{}), m_cab(cab_of(Setup))
|
||||
{
|
||||
m_values[0] = m_values[1] = static_cast<float>(std::min(m_min, m_max));
|
||||
}
|
||||
@@ -292,6 +317,7 @@ block_type cab_gauge::describe(bool const Twoneedles)
|
||||
{
|
||||
type.params.push_back(text_param("submodel2"));
|
||||
}
|
||||
type.params.push_back(cab_param());
|
||||
if (Twoneedles)
|
||||
{
|
||||
type.create = [](block_setup const &Setup) -> std::unique_ptr<block> { return std::make_unique<cab_gauge>(Setup, true); };
|
||||
@@ -308,7 +334,7 @@ block_type cab_gauge::describe(bool const Twoneedles)
|
||||
vehicle_sound::vehicle_sound(block_setup const &Setup)
|
||||
: block(Setup), m_file(text(Setup, param::file)), m_basevolume(number(Setup, param::volume)), m_basepitch(number(Setup, param::pitch)), m_loop(boolean(Setup, param::loop)),
|
||||
m_placement(static_cast<placement_type>(choice(Setup, param::placement))), m_range(number(Setup, param::range)),
|
||||
m_offset{number(Setup, param::offset_x), number(Setup, param::offset_y), number(Setup, param::offset_z)}
|
||||
m_offset{number(Setup, param::offset_x), number(Setup, param::offset_y), number(Setup, param::offset_z)}, m_cab(cab_of(Setup))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -364,11 +390,35 @@ block_type vehicle_sound::describe()
|
||||
number_param("offset_x", 0.0, "m"),
|
||||
number_param("offset_y", 0.0, "m"),
|
||||
number_param("offset_z", 0.0, "m"),
|
||||
cab_param(),
|
||||
};
|
||||
type.create = make_block<vehicle_sound>;
|
||||
return type;
|
||||
}
|
||||
|
||||
// cab_export
|
||||
|
||||
cab_export::cab_export(block_setup const &Setup) : block(Setup), m_name(text(Setup, 0)), m_kind(static_cast<kind_type>(choice(Setup, 1))) {}
|
||||
|
||||
void cab_export::update(frame &Frame)
|
||||
{
|
||||
m_value = get(Frame, port::value);
|
||||
}
|
||||
|
||||
block_type cab_export::describe()
|
||||
{
|
||||
block_type type;
|
||||
type.id = "fb.export";
|
||||
type.category = "indicators";
|
||||
type.name = "Screen value";
|
||||
type.description = "Publishes the value for the python screens of the cab, as an entry of the train state with the given name.";
|
||||
type.origin = block_origin::cab;
|
||||
type.ports = {signal_in("value", value_type::real)};
|
||||
type.params = {text_param("name"), choice_param("kind", {"float", "int", "bool"}, "float")};
|
||||
type.create = make_block<cab_export>;
|
||||
return type;
|
||||
}
|
||||
|
||||
void register_cab_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(cab_switch::describe());
|
||||
@@ -376,6 +426,7 @@ void register_cab_blocks(catalog &Catalog)
|
||||
Catalog.add(cab_gauge::describe(false));
|
||||
Catalog.add(cab_gauge::describe(true));
|
||||
Catalog.add(vehicle_sound::describe());
|
||||
Catalog.add(cab_export::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
|
||||
@@ -48,10 +48,14 @@ public:
|
||||
offset,
|
||||
smoothing,
|
||||
submodel,
|
||||
sound_inc,
|
||||
sound_dec,
|
||||
sound_positions,
|
||||
key_inc,
|
||||
key_dec,
|
||||
command,
|
||||
hw_name
|
||||
hw_name,
|
||||
cab
|
||||
};
|
||||
};
|
||||
enum class behaviour_type
|
||||
@@ -136,6 +140,21 @@ public:
|
||||
{
|
||||
return m_submodel;
|
||||
}
|
||||
// sounds played by the animated submodel when the position increases or decreases, in the format of
|
||||
// soundinc: and sounddec: of the .mmd controls
|
||||
std::string const &sound_increase() const
|
||||
{
|
||||
return m_soundincrease;
|
||||
}
|
||||
std::string const &sound_decrease() const
|
||||
{
|
||||
return m_sounddecrease;
|
||||
}
|
||||
// sounds of single positions, as soundN: entries of the .mmd controls with N the number of the position
|
||||
std::string const &sound_positions() const
|
||||
{
|
||||
return m_soundpositions;
|
||||
}
|
||||
// bindings: keys, existing simulator command, name for the hardware desk
|
||||
std::string const &key_increase() const
|
||||
{
|
||||
@@ -153,6 +172,16 @@ public:
|
||||
{
|
||||
return m_hardwarename;
|
||||
}
|
||||
// cab whose model holds the submodel: 0 any, 1 or 2
|
||||
int cab() const
|
||||
{
|
||||
return m_cab;
|
||||
}
|
||||
// stable address of the value animating the submodel (the position), for TGauge::AssignFloat
|
||||
float *display_state()
|
||||
{
|
||||
return &m_display;
|
||||
}
|
||||
|
||||
static block_type describe();
|
||||
|
||||
@@ -171,11 +200,16 @@ private:
|
||||
double const m_offset;
|
||||
double const m_smoothing;
|
||||
std::string const m_submodel;
|
||||
std::string const m_soundincrease;
|
||||
std::string const m_sounddecrease;
|
||||
std::string const m_soundpositions;
|
||||
std::string const m_keyincrease;
|
||||
std::string const m_keydecrease;
|
||||
std::string const m_command;
|
||||
std::string const m_hardwarename;
|
||||
int const m_cab;
|
||||
int m_position{0};
|
||||
float m_display{0.f};
|
||||
};
|
||||
|
||||
// indicator lamp. when the graph simulates low voltage circuits and the supply input is connected, the lamp lights
|
||||
@@ -200,7 +234,10 @@ public:
|
||||
blink_period,
|
||||
min_voltage,
|
||||
power,
|
||||
submodel
|
||||
submodel,
|
||||
sound_on,
|
||||
sound_off,
|
||||
cab
|
||||
};
|
||||
};
|
||||
|
||||
@@ -229,6 +266,19 @@ public:
|
||||
{
|
||||
return m_submodel;
|
||||
}
|
||||
// sounds played when the lamp lights and goes out, as soundinc: and sounddec: of the .mmd lamps
|
||||
std::string const &sound_on() const
|
||||
{
|
||||
return m_soundon;
|
||||
}
|
||||
std::string const &sound_off() const
|
||||
{
|
||||
return m_soundoff;
|
||||
}
|
||||
int cab() const
|
||||
{
|
||||
return m_cab;
|
||||
}
|
||||
|
||||
static block_type describe();
|
||||
|
||||
@@ -239,9 +289,15 @@ private:
|
||||
double const m_minvoltage;
|
||||
double const m_power;
|
||||
std::string const m_submodel;
|
||||
std::string const m_soundon;
|
||||
std::string const m_soundoff;
|
||||
int const m_cab;
|
||||
bool m_lit{false};
|
||||
};
|
||||
|
||||
// cab given by the "cab" parameter of a cab block: 0 any, 1 or 2, -1 the engine room
|
||||
int cab_of(block_setup const &Setup);
|
||||
|
||||
// needle gauge with one or two needles: voltmeter, ammeter, speedometer, pressure gauge
|
||||
class cab_gauge final : public block
|
||||
{
|
||||
@@ -318,6 +374,10 @@ public:
|
||||
{
|
||||
return Needle == 0 ? m_submodel : m_submodel2;
|
||||
}
|
||||
int cab() const
|
||||
{
|
||||
return m_cab;
|
||||
}
|
||||
|
||||
static block_type describe(bool Twoneedles);
|
||||
|
||||
@@ -330,6 +390,7 @@ private:
|
||||
double const m_smoothing;
|
||||
std::string const m_submodel;
|
||||
std::string const m_submodel2;
|
||||
int const m_cab;
|
||||
float m_values[2]{0.f, 0.f};
|
||||
};
|
||||
|
||||
@@ -358,7 +419,8 @@ public:
|
||||
range,
|
||||
offset_x,
|
||||
offset_y,
|
||||
offset_z
|
||||
offset_z,
|
||||
cab
|
||||
};
|
||||
};
|
||||
// the same placements as sound_placement of the audio subsystem
|
||||
@@ -422,6 +484,11 @@ public:
|
||||
{
|
||||
return m_offset[2];
|
||||
}
|
||||
// for the sounds placed in the cab: the cab they belong to, 0 any
|
||||
int cab() const
|
||||
{
|
||||
return m_cab;
|
||||
}
|
||||
|
||||
static block_type describe();
|
||||
|
||||
@@ -433,9 +500,53 @@ private:
|
||||
placement_type const m_placement;
|
||||
double const m_range;
|
||||
double const m_offset[3];
|
||||
int const m_cab;
|
||||
bool m_playing{false};
|
||||
float m_volume{0.f};
|
||||
float m_pitch{1.f};
|
||||
};
|
||||
|
||||
// value published for the python screens of the cab (TTrain::GetTrainState), under the specified name
|
||||
class cab_export final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
value
|
||||
};
|
||||
};
|
||||
enum class kind_type
|
||||
{
|
||||
real,
|
||||
integer,
|
||||
boolean
|
||||
};
|
||||
|
||||
explicit cab_export(block_setup const &Setup);
|
||||
|
||||
void update(frame &Frame) override;
|
||||
|
||||
std::string const &name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
kind_type kind() const
|
||||
{
|
||||
return m_kind;
|
||||
}
|
||||
double value() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
static block_type describe();
|
||||
|
||||
private:
|
||||
std::string const m_name;
|
||||
kind_type const m_kind;
|
||||
double m_value{0.0};
|
||||
};
|
||||
|
||||
} // namespace vgraph
|
||||
|
||||
920
vehicle/graph/blocks/diesel.cpp
Normal file
920
vehicle/graph/blocks/diesel.cpp
Normal file
@@ -0,0 +1,920 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_body.h"
|
||||
#include "McZapkie/MOVER.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
// combustion engine drive: engine -> clutch and gearbox / hydraulic transmission / main generator.
|
||||
//
|
||||
// the mechanical connections keep the convention of the electric drives: torque forward, rotational speed back. the
|
||||
// engine is a torque source like the electric motors: it reads the speed of its shaft from the back channel and gives the
|
||||
// torque. the speed of the engine shaft is a state of the block connected to the engine (drive.gearbox, drive.hydro or
|
||||
// drive.generator), the same way the speed of the wheels is a state of the vehicle body behind drive.axle: that block
|
||||
// holds the inertia of the engine side (crankshaft, flywheel, generator rotor), integrates the engine speed with the engine
|
||||
// torque and the torque of its coupling (slipping clutch, torque converter, generator load), and returns it to the engine.
|
||||
// when the coupling is locked (clutch engaged without slip, lockup clutch of the converter) the engine follows the speed
|
||||
// of the wheels, as in TMoverParameters::dizel_Momentum, so the chain is rigid and stays stable at any step.
|
||||
// the engine shaft has to be connected to exactly one such block; its speed is in rad/s, positive, in the frame of the
|
||||
// engine. the direction of the vehicle is set after the coupling (dir inputs of the gearbox and the hydraulic transmission)
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
double constexpr two_pi{2.0 * M_PI};
|
||||
double constexpr rpm_to_rad{two_pi / 60.0};
|
||||
|
||||
TMoverParameters &vehicle(frame const &Frame)
|
||||
{
|
||||
return *Frame.body->mover;
|
||||
}
|
||||
|
||||
block_type make_type(std::string Id, std::string Name, std::string Description, block_origin Origin)
|
||||
{
|
||||
block_type type;
|
||||
type.id = std::move(Id);
|
||||
type.category = "drive";
|
||||
type.name = std::move(Name);
|
||||
type.description = std::move(Description);
|
||||
type.origin = Origin;
|
||||
return type;
|
||||
}
|
||||
|
||||
// direction of the drive in the frame of the vehicle: -1, 0 or 1 (as for the electric motors)
|
||||
int absolute_direction(frame const &Frame, double const Dir)
|
||||
{
|
||||
auto const dir{Dir > 0.5 ? 1 : Dir < -0.5 ? -1 : 0};
|
||||
auto const cab{Frame.body->cab};
|
||||
return dir * (cab != 0 ? cab : 1);
|
||||
}
|
||||
|
||||
double sign(double const Value)
|
||||
{
|
||||
return Value > 0.0 ? 1.0 : Value < 0.0 ? -1.0 : 0.0;
|
||||
}
|
||||
|
||||
// clamp which accepts the bounds in any order (safe_clamp of the Mover)
|
||||
double clamp_any(double const Value, double const Bound1, double const Bound2)
|
||||
{
|
||||
return std::clamp(Value, std::min(Bound1, Bound2), std::max(Bound1, Bound2));
|
||||
}
|
||||
|
||||
// linear interpolation in the rows of a table (first column the argument, sorted); the end rows outside of it
|
||||
double interpolate(std::vector<std::vector<double>> const &Rows, std::size_t const Column, double const Argument)
|
||||
{
|
||||
if (Rows.empty())
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
if (Argument <= Rows.front()[0])
|
||||
{
|
||||
return Rows.front()[Column];
|
||||
}
|
||||
for (std::size_t idx = 1; idx < Rows.size(); ++idx)
|
||||
{
|
||||
auto const &upper{Rows[idx]};
|
||||
if (Argument <= upper[0])
|
||||
{
|
||||
auto const &lower{Rows[idx - 1]};
|
||||
auto const span{upper[0] - lower[0]};
|
||||
return span > 0.0 ? lower[Column] + (upper[Column] - lower[Column]) * (Argument - lower[0]) / span : upper[Column];
|
||||
}
|
||||
}
|
||||
return Rows.back()[Column];
|
||||
}
|
||||
|
||||
// rows of a table parameter with at least the specified number of columns, sorted by the first one
|
||||
std::vector<std::vector<double>> sorted_rows(std::vector<std::vector<double>> const &Rows, std::size_t const Columns)
|
||||
{
|
||||
std::vector<std::vector<double>> result;
|
||||
for (auto const &row : Rows)
|
||||
{
|
||||
if (row.size() >= Columns)
|
||||
{
|
||||
result.push_back(row);
|
||||
}
|
||||
}
|
||||
std::stable_sort(std::begin(result), std::end(result), [](auto const &Left, auto const &Right) { return Left[0] < Right[0]; });
|
||||
return result;
|
||||
}
|
||||
|
||||
// combustion engine with a speed governor and a starter (adapted from TMoverParameters::dizel_Momentum, dizel_fillcheck
|
||||
// and dizel_Update)
|
||||
class drive_diesel final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
start,
|
||||
stop,
|
||||
fuel,
|
||||
demand,
|
||||
shaft,
|
||||
running,
|
||||
starting,
|
||||
rpm,
|
||||
torque,
|
||||
fill,
|
||||
power,
|
||||
fuel_rate
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
idle_rpm,
|
||||
max_rpm,
|
||||
governor,
|
||||
droop,
|
||||
fill_rate,
|
||||
curve,
|
||||
curve_type,
|
||||
m_max,
|
||||
rpm_m_max,
|
||||
m_nmax,
|
||||
friction,
|
||||
starter_torque,
|
||||
start_time,
|
||||
fuel_consumption,
|
||||
main_engine
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_diesel(block_setup const &Setup)
|
||||
: block(Setup), m_idle(std::max(1.0, number(Setup, param::idle_rpm))), m_max(std::max(m_idle + 1.0, number(Setup, param::max_rpm))),
|
||||
m_allspeed(choice(Setup, param::governor) == 0), m_band(std::max(1.0, std::max(0.001, number(Setup, param::droop)) * m_max)),
|
||||
m_fillrate(std::max(0.0, number(Setup, param::fill_rate))), m_curve(sorted_rows(table(Setup, param::curve), 2)), m_curvepower(choice(Setup, param::curve_type) == 1),
|
||||
m_mmax(number(Setup, param::m_max)), m_nmmax(number(Setup, param::rpm_m_max)), m_mnmax(number(Setup, param::m_nmax)), m_friction(std::max(0.0, number(Setup, param::friction))),
|
||||
m_startertorque(std::max(0.0, number(Setup, param::starter_torque))), m_starttime(std::max(0.0, number(Setup, param::start_time))),
|
||||
m_mainengine(boolean(Setup, param::main_engine))
|
||||
{
|
||||
if (m_curve.size() < 2)
|
||||
{
|
||||
m_curve.clear();
|
||||
}
|
||||
if (m_nmmax == m_max)
|
||||
{
|
||||
// as in LoadFIZ_DList: guards the division in the torque parabola
|
||||
m_nmmax = m_max - 1.0;
|
||||
}
|
||||
// fuel used per hour at the full power, as dizel_FuelConsumption of LoadFIZ_DList: g/kWh * kW / (850 g/l)
|
||||
auto const ratedpower{std::max(0.0, full_torque(m_max) - m_friction) * m_max * rpm_to_rad / 1000.0};
|
||||
m_fuelperhour = std::max(0.0, number(Setup, param::fuel_consumption)) * ratedpower / 850.0;
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const omega{back_out(Frame, port::shaft)};
|
||||
auto const rpm{omega / rpm_to_rad};
|
||||
auto const start{get_bool(Frame, port::start)};
|
||||
auto const stop{get_bool(Frame, port::stop)};
|
||||
auto const fuel{get_bool(Frame, port::fuel)};
|
||||
auto const startpressed{start && false == m_startprevious};
|
||||
m_startprevious = start;
|
||||
|
||||
// start procedure (dizel_Update): the starter cranks the engine for start_time, then the ignition; the starter
|
||||
// keeps helping until the engine reaches its idle speed (dizel_spinup)
|
||||
if (false == m_running && false == m_cranking && startpressed && fuel && false == stop)
|
||||
{
|
||||
m_cranking = true;
|
||||
m_cranktime = 0.0;
|
||||
}
|
||||
if (m_cranking)
|
||||
{
|
||||
if (stop || false == fuel)
|
||||
{
|
||||
m_cranking = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cranktime += Frame.dt;
|
||||
if (m_cranktime >= m_starttime)
|
||||
{
|
||||
m_cranking = false;
|
||||
m_running = true;
|
||||
m_spinup = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_running && (stop || false == fuel))
|
||||
{
|
||||
// knocked out without fuel, as dizel_Update does when the fuel pump stops
|
||||
m_running = false;
|
||||
}
|
||||
m_spinup = m_spinup && m_running && rpm < 0.95 * m_idle;
|
||||
if (m_running && false == m_spinup && rpm < 0.25 * m_idle)
|
||||
{
|
||||
// stalled, e.g. by engaging the clutch at standstill
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
// fuel injection set by the governor (dizel_fillcheck), reached at fill_rate (dizel_Update: fillspeed)
|
||||
auto target{0.0};
|
||||
if (m_running)
|
||||
{
|
||||
auto const demand{std::clamp(get(Frame, port::demand), 0.0, 1.0)};
|
||||
if (m_allspeed)
|
||||
{
|
||||
// all speed governor: the demand sets the speed between the idle and the maximum one
|
||||
auto const setspeed{m_idle + demand * (m_max - m_idle)};
|
||||
target = std::clamp((setspeed - rpm) / m_band, 0.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the demand is the injection; the governor keeps the idle speed and cuts the fuel at the maximum one
|
||||
target = std::max(demand, std::clamp((m_idle - rpm) / m_band, 0.0, 1.0));
|
||||
target = std::min(target, std::clamp((m_max - rpm) / m_band, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
m_fill += (target - m_fill) * std::min(1.0, m_fillrate * Frame.dt);
|
||||
|
||||
// torque (dizel_Momentum): the curve at full injection times the injection, less the motion resistance of
|
||||
// the engine (dizel_Mstand), which acts against the rotation and fades out at standstill
|
||||
m_combustion = rpm > 0.0 ? full_torque(rpm) * m_fill : 0.0;
|
||||
m_resistance = m_friction * std::clamp(rpm / (0.1 * m_idle), -1.0, 1.0);
|
||||
auto starter{0.0};
|
||||
if (m_cranking || m_spinup)
|
||||
{
|
||||
// starter, falling with the speed as in dizel_Momentum
|
||||
starter = m_startertorque * 0.3 / (0.3 + std::max(0.0, rpm / m_idle));
|
||||
}
|
||||
m_torque = m_combustion - m_resistance + starter;
|
||||
m_omega = omega;
|
||||
m_netpower = (m_combustion - m_resistance) * omega / 1000.0;
|
||||
m_fuelrate = m_running ? m_fuelperhour * std::max(0.0, rpm / m_max) * m_fill : 0.0;
|
||||
m_fuelused += m_fuelrate * Frame.dt / 3600.0;
|
||||
|
||||
set_forward(Frame, port::shaft, m_torque);
|
||||
set_bool(Frame, port::running, m_running);
|
||||
set_bool(Frame, port::starting, m_cranking || m_spinup);
|
||||
set(Frame, port::rpm, rpm);
|
||||
set(Frame, port::torque, m_torque);
|
||||
set(Frame, port::fill, m_fill);
|
||||
set(Frame, port::power, m_netpower);
|
||||
set(Frame, port::fuel_rate, m_fuelrate);
|
||||
}
|
||||
|
||||
// the fields of the vehicle body are written after all blocks were updated, so the engine speed replaces the
|
||||
// revolutions written by the traction motors of a diesel-electric vehicle (for the diesel vehicles enrot is the
|
||||
// engine speed: engine sounds, cab indicators)
|
||||
void late_update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
// for the diesel vehicles Mains is the running engine; with more engines (or line breakers) any of them
|
||||
body.Mains = Frame.body->mains.any(Frame.step, m_running);
|
||||
if (false == m_mainengine)
|
||||
{
|
||||
return;
|
||||
}
|
||||
body.enrot = m_omega / two_pi;
|
||||
body.dizel_fill = m_fill;
|
||||
body.Mm = m_combustion - m_resistance;
|
||||
body.dizel_Torque = m_torque;
|
||||
body.dizel_Power = m_netpower;
|
||||
// as in TractionForce: (dizel_Mstand + Mm) * enrot * 2pi / 1000
|
||||
body.EnginePower = m_combustion * m_omega / 1000.0;
|
||||
body.dizel_startup = m_cranking;
|
||||
body.dizel_spinup = m_spinup;
|
||||
body.dizel_FuelConsumptionActual = m_fuelrate;
|
||||
body.dizel_FuelConsumptedTotal = m_fuelused;
|
||||
// speeds the sounds and indicators scale with (EngineIdleRPM, EngineMaxRPM, EngineRPMRatio), rev/s
|
||||
body.dizel_nmin = m_idle / 60.0;
|
||||
body.dizel_nmax = m_max / 60.0;
|
||||
body.nmax = m_max / 60.0;
|
||||
}
|
||||
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "fuel_used";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_fuelused;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.diesel", "Diesel engine",
|
||||
"Combustion engine with a speed governor and a starter (dizel_Momentum). start begins the start procedure: the starter cranks the engine for "
|
||||
"start_time, then the fuel is injected. demand 0..1 sets the speed (all_speed governor) or the injection (min_max governor, idle and maximum "
|
||||
"speed kept). The torque is the curve at full injection (table rpm -> Nm or kW, or the Mover parabola m_max, rpm_m_max, m_nmax) times the "
|
||||
"injection, less the friction. The shaft has to go to one drive.gearbox, drive.hydro or drive.generator, which holds the engine speed.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {signal_in("start", value_type::boolean),
|
||||
signal_in("stop", value_type::boolean, 0.0, "", true),
|
||||
signal_in("fuel", value_type::boolean, 1.0, "", true),
|
||||
signal_in("demand", value_type::real),
|
||||
physical_out("shaft", domain::mechanical),
|
||||
signal_out("running", value_type::boolean),
|
||||
signal_out("starting", value_type::boolean),
|
||||
signal_out("rpm", value_type::real, "rpm"),
|
||||
signal_out("torque", value_type::real, "Nm"),
|
||||
signal_out("fill", value_type::real),
|
||||
signal_out("power", value_type::real, "kW"),
|
||||
signal_out("fuel_rate", value_type::real, "l/h")};
|
||||
type.params = {number_param("idle_rpm", 600.0, "rpm"),
|
||||
number_param("max_rpm", 1500.0, "rpm"),
|
||||
choice_param("governor", {"all_speed", "min_max"}, "all_speed"),
|
||||
number_param("droop", 0.05),
|
||||
number_param("fill_rate", 10.0, "1/s"),
|
||||
table_param("curve", {"rpm", "torque"}),
|
||||
choice_param("curve_type", {"torque", "power"}, "torque"),
|
||||
number_param("m_max", 3000.0, "Nm"),
|
||||
number_param("rpm_m_max", 1100.0, "rpm"),
|
||||
number_param("m_nmax", 2600.0, "Nm"),
|
||||
number_param("friction", 300.0, "Nm"),
|
||||
number_param("starter_torque", 600.0, "Nm"),
|
||||
number_param("start_time", 2.0, "s"),
|
||||
number_param("fuel_consumption", 220.0, "g/kWh"),
|
||||
boolean_param("main_engine", true)};
|
||||
type.late = true;
|
||||
type.create = make_block<drive_diesel>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
// torque at full injection, Nm
|
||||
double full_torque(double const Rpm) const
|
||||
{
|
||||
if (false == m_curve.empty())
|
||||
{
|
||||
auto const value{interpolate(m_curve, 1, Rpm)};
|
||||
// power curve in kW: torque = P / omega, the speed held at the first row to keep it finite
|
||||
return m_curvepower ? value * 1000.0 / (std::max(Rpm, std::max(1.0, m_curve.front()[0])) * rpm_to_rad) : value;
|
||||
}
|
||||
// parabola of dizel_Momentum: Mmax at nMmax, Mnmax at nmax
|
||||
auto const x{(Rpm - m_nmmax) / (m_nmmax - m_max)};
|
||||
return std::max(0.0, m_mmax - (m_mmax - m_mnmax) * x * x);
|
||||
}
|
||||
|
||||
double const m_idle; // rpm
|
||||
double const m_max; // rpm
|
||||
bool const m_allspeed;
|
||||
double const m_band; // rpm
|
||||
double const m_fillrate;
|
||||
std::vector<std::vector<double>> m_curve;
|
||||
bool const m_curvepower;
|
||||
double const m_mmax;
|
||||
double m_nmmax;
|
||||
double const m_mnmax;
|
||||
double const m_friction;
|
||||
double const m_startertorque;
|
||||
double const m_starttime;
|
||||
bool const m_mainengine;
|
||||
double m_fuelperhour{0.0}; // l/h at full power and maximum speed
|
||||
bool m_startprevious{false};
|
||||
bool m_cranking{false};
|
||||
bool m_running{false};
|
||||
bool m_spinup{false};
|
||||
double m_cranktime{0.0};
|
||||
double m_fill{0.0};
|
||||
double m_combustion{0.0};
|
||||
double m_resistance{0.0};
|
||||
double m_torque{0.0};
|
||||
double m_omega{0.0};
|
||||
double m_netpower{0.0};
|
||||
double m_fuelrate{0.0};
|
||||
double m_fuelused{0.0};
|
||||
};
|
||||
|
||||
// friction clutch and mechanical gearbox (adapted from TMoverParameters::dizel_Momentum without the converter,
|
||||
// dizel_EngageChange and dizel_AutoGearCheck). holds the speed of the engine: slipping, the clutch passes its friction
|
||||
// torque and the engine speed follows its own torque; synchronous, the engine follows the gearbox input and its whole
|
||||
// torque passes on
|
||||
class drive_gearbox final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
gear,
|
||||
clutch,
|
||||
dir,
|
||||
out,
|
||||
actual,
|
||||
engage,
|
||||
slip,
|
||||
shifting
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
gears,
|
||||
clutch_torque,
|
||||
engage_rate,
|
||||
release_rate,
|
||||
auto_clutch,
|
||||
inertia,
|
||||
eff
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_gearbox(block_setup const &Setup)
|
||||
: block(Setup), m_clutchtorque(std::max(0.0, number(Setup, param::clutch_torque))), m_engagerate(std::max(0.0, number(Setup, param::engage_rate))),
|
||||
m_releaserate(std::max(0.0, number(Setup, param::release_rate))), m_autoclutch(boolean(Setup, param::auto_clutch)),
|
||||
m_inertia(std::max(0.01, number(Setup, param::inertia))), m_efficiency(std::clamp(number(Setup, param::eff), 0.01, 1.0))
|
||||
{
|
||||
for (auto const &row : table(Setup, param::gears))
|
||||
{
|
||||
if (row.size() >= 2)
|
||||
{
|
||||
m_gears.emplace_back(static_cast<int>(std::lround(row[0])), row[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const moment{forward_in(Frame, port::in)};
|
||||
auto const requestedgear{static_cast<int>(std::lround(get(Frame, port::gear)))};
|
||||
auto const requesteddir{absolute_direction(Frame, get(Frame, port::dir))};
|
||||
|
||||
// a change of the gear or the direction waits for the released clutch; with auto_clutch the gearbox releases it
|
||||
m_shifting = (requestedgear != m_gear || requesteddir != m_dir);
|
||||
auto const demand{std::clamp(get(Frame, port::clutch), 0.0, 1.0)};
|
||||
m_engagestate = (m_shifting && m_autoclutch) ? 0.0 : demand;
|
||||
// dizel_EngageChange: the pressure approaches the set one, and jumps to it when close
|
||||
if (std::abs(m_engage - m_engagestate) < 0.11)
|
||||
{
|
||||
m_engage = m_engagestate;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_engage += (m_engage > m_engagestate ? m_releaserate : m_engagerate) * Frame.dt * (m_engagestate - m_engage);
|
||||
}
|
||||
if (m_shifting && m_engage < 0.1)
|
||||
{
|
||||
m_gear = requestedgear;
|
||||
m_dir = requesteddir;
|
||||
m_shifting = false;
|
||||
}
|
||||
|
||||
// ratio from the engine to the output shaft, in the frame of the vehicle; 0 in neutral
|
||||
auto ratio{0.0};
|
||||
for (auto const &gear : m_gears)
|
||||
{
|
||||
if (gear.first == m_gear && m_gear != 0)
|
||||
{
|
||||
ratio = gear.second * m_dir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto gearmoment{0.0};
|
||||
m_deltaomega = 0.0;
|
||||
m_slipping = false;
|
||||
if (ratio == 0.0)
|
||||
{
|
||||
// neutral: the input shaft turns with the engine, which runs free
|
||||
auto const newomega{m_omega + moment / m_inertia * Frame.dt};
|
||||
m_omega = (newomega < 0.0 && m_omega >= 0.0) ? 0.0 : newomega;
|
||||
m_previous = m_omega;
|
||||
}
|
||||
else
|
||||
{
|
||||
// dizel_Momentum, in rad/s and kg m2 (the .fiz AIM is the inertia times 2pi)
|
||||
auto const n{back_out(Frame, port::out) * ratio}; // speed of the gearbox input
|
||||
auto const torquec{m_clutchtorque * m_engage};
|
||||
if (std::abs(moment) > torquec || std::abs(m_previous - m_omega) > 0.1 * two_pi)
|
||||
{
|
||||
// slipping, because of the speed difference or the torque above the friction of the clutch
|
||||
m_slipping = true;
|
||||
m_deltaomega = m_omega - m_previous;
|
||||
gearmoment = torquec * sign(m_deltaomega);
|
||||
auto const eps{(moment - gearmoment) / m_inertia};
|
||||
auto newomega{m_omega + eps * Frame.dt};
|
||||
if ((newomega - n) * (m_omega - m_previous) < 0.0 && torquec > 0.1)
|
||||
{
|
||||
// the slip changed its sign: the clutch grips
|
||||
newomega = n;
|
||||
}
|
||||
if (newomega * m_omega <= 0.0 && eps * m_omega < 0.0)
|
||||
{
|
||||
// no passing through zero speed
|
||||
newomega = 0.0;
|
||||
}
|
||||
m_omega = newomega;
|
||||
}
|
||||
else
|
||||
{
|
||||
// synchronous: the engine follows the gearbox, within what the clutch can hold
|
||||
gearmoment = moment;
|
||||
m_omega = clamp_any(n, m_omega + (moment - torquec) / m_inertia * Frame.dt, m_omega + (moment + torquec) / m_inertia * Frame.dt);
|
||||
}
|
||||
m_previous = n;
|
||||
}
|
||||
|
||||
set_forward(Frame, port::out, gearmoment * ratio * m_efficiency);
|
||||
add_back(Frame, port::in, m_omega);
|
||||
set(Frame, port::actual, m_gear);
|
||||
set(Frame, port::engage, m_engage);
|
||||
set_bool(Frame, port::slip, m_slipping && m_engage > 0.1 && std::abs(m_deltaomega) > 0.2 * two_pi);
|
||||
set_bool(Frame, port::shifting, m_shifting);
|
||||
// clutch state for the sounds and the cab indicators (rsEngageSlippery, clutch gauges)
|
||||
body.dizel_engage = m_engage;
|
||||
body.dizel_engagestate = m_engagestate;
|
||||
body.dizel_engagedeltaomega = m_deltaomega / two_pi;
|
||||
body.dizel_automaticgearstatus = m_shifting ? sign(requestedgear - m_gear) : 0.0;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.gearbox", "Gearbox with clutch",
|
||||
"Friction clutch and mechanical gearbox (dizel_Momentum, dizel_EngageChange). gear selects the row of the gears table (gear number, ratio; "
|
||||
"0 or a missing number is neutral, reverse gears can have negative ratios), dir reverses. A change of the gear or the direction waits until "
|
||||
"the clutch is released; with auto_clutch the gearbox releases it itself. Holds the speed of the engine (inertia of the engine side).",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("in", domain::mechanical),
|
||||
signal_in("gear", value_type::integer, 1.0, "", true),
|
||||
signal_in("clutch", value_type::real, 1.0, "", true),
|
||||
signal_in("dir", value_type::real, 1.0, "", true),
|
||||
physical_out("out", domain::mechanical),
|
||||
signal_out("actual", value_type::integer),
|
||||
signal_out("engage", value_type::real),
|
||||
signal_out("slip", value_type::boolean),
|
||||
signal_out("shifting", value_type::boolean)};
|
||||
type.params = {table_param("gears", {"gear", "ratio"}),
|
||||
number_param("clutch_torque", 1500.0, "Nm"),
|
||||
number_param("engage_rate", 0.5, "1/s"),
|
||||
number_param("release_rate", 0.9, "1/s"),
|
||||
boolean_param("auto_clutch", true),
|
||||
number_param("inertia", 10.0, "kg m2"),
|
||||
number_param("eff", 0.95)};
|
||||
type.create = make_block<drive_gearbox>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_clutchtorque;
|
||||
double const m_engagerate;
|
||||
double const m_releaserate;
|
||||
bool const m_autoclutch;
|
||||
double const m_inertia;
|
||||
double const m_efficiency;
|
||||
std::vector<std::pair<int, double>> m_gears;
|
||||
int m_gear{0};
|
||||
int m_dir{0};
|
||||
bool m_shifting{false};
|
||||
bool m_slipping{false};
|
||||
double m_engage{0.0};
|
||||
double m_engagestate{0.0};
|
||||
double m_omega{0.0}; // engine speed, rad/s
|
||||
double m_previous{0.0}; // gearbox input speed in the previous step (dizel_n_old)
|
||||
double m_deltaomega{0.0};
|
||||
};
|
||||
|
||||
// hydraulic transmission: torque converter with filling, fluid coupling range and lockup clutch (adapted from the hydro_TC
|
||||
// part of TMoverParameters::dizel_Momentum). holds the speed of the engine like the gearbox
|
||||
class drive_hydro final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
power,
|
||||
running,
|
||||
dir,
|
||||
out,
|
||||
fill,
|
||||
lockup,
|
||||
ratio
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
tc_tmmax,
|
||||
tc_cp,
|
||||
tc_table,
|
||||
tc_tii,
|
||||
tc_tio,
|
||||
tc_too,
|
||||
fill_rate,
|
||||
drain_rate,
|
||||
lockup_torque,
|
||||
lockup_speed,
|
||||
unlock_speed,
|
||||
idle_rpm,
|
||||
ans_speed,
|
||||
inertia
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_hydro(block_setup const &Setup)
|
||||
: block(Setup), m_tmmax(number(Setup, param::tc_tmmax)), m_couplingpoint(std::clamp(number(Setup, param::tc_cp), 0.01, 0.99)),
|
||||
m_table(sorted_rows(table(Setup, param::tc_table), 2)), m_torqueinin(number(Setup, param::tc_tii)), m_torqueinout(number(Setup, param::tc_tio)),
|
||||
m_torqueoutout(number(Setup, param::tc_too)), m_fillrate(std::max(0.0, number(Setup, param::fill_rate))), m_drainrate(std::max(0.0, number(Setup, param::drain_rate))),
|
||||
m_lockuptorque(std::max(0.0, number(Setup, param::lockup_torque))), m_lockupspeed(number(Setup, param::lockup_speed)), m_unlockspeed(number(Setup, param::unlock_speed)),
|
||||
m_idle(std::max(1.0, number(Setup, param::idle_rpm)) * rpm_to_rad), m_ansspeed(number(Setup, param::ans_speed)),
|
||||
m_inertia(std::max(0.01, number(Setup, param::inertia)))
|
||||
{
|
||||
if (m_table.size() < 2)
|
||||
{
|
||||
m_table.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const moment{forward_in(Frame, port::in)};
|
||||
auto const dir{absolute_direction(Frame, get(Frame, port::dir))};
|
||||
auto const power{get_bool(Frame, port::power)};
|
||||
// the engine running (Mains of dizel_Momentum); a stopped engine drains the converter and releases the lockup clutch
|
||||
auto const running{get_bool(Frame, port::running)};
|
||||
auto const vel{std::abs(Frame.body->velocity) * 3.6};
|
||||
// speed of the output shaft in the frame of the engine; without a direction it turns with the engine
|
||||
auto const n{dir != 0 ? back_out(Frame, port::out) * dir : m_omega};
|
||||
|
||||
// filling and draining of the converter, engaging and releasing of the lockup clutch
|
||||
if (power && running && m_omega > 0.9 * m_idle)
|
||||
{
|
||||
m_fill += m_fillrate * Frame.dt;
|
||||
}
|
||||
if ((false == power && vel < m_ansspeed) || false == running || m_omega < 0.8 * m_idle)
|
||||
{
|
||||
m_fill -= m_drainrate * Frame.dt;
|
||||
}
|
||||
m_fill = std::clamp(m_fill, 0.0, 1.0);
|
||||
if (vel > m_lockupspeed && running && m_omega > 0.9 * m_idle && power)
|
||||
{
|
||||
m_lockup = true;
|
||||
m_lockuprate += m_fillrate * Frame.dt;
|
||||
}
|
||||
if (vel < (power ? m_lockupspeed : m_unlockspeed) || false == running || m_omega < 0.8 * m_idle)
|
||||
{
|
||||
m_lockup = false;
|
||||
m_lockuprate -= m_drainrate * Frame.dt;
|
||||
}
|
||||
m_lockuprate = std::clamp(m_lockuprate, 0.0, 1.0);
|
||||
|
||||
// torques of the converter; its constants are for the speeds in rev/s, as in the .fiz files
|
||||
auto const nin{m_omega / two_pi};
|
||||
auto const nout{m_previous / two_pi};
|
||||
auto const hydrotorque{nin * nin * m_torqueinin + (nin - nout) * m_torqueinout + nout * nout * m_torqueoutout};
|
||||
auto const speedratio{nout / std::max(0.01, nin)};
|
||||
double torquein;
|
||||
double torqueout;
|
||||
if (false == m_table.empty())
|
||||
{
|
||||
m_tmratio = interpolate(m_table, 1, speedratio);
|
||||
torqueout = hydrotorque * m_fill * m_tmratio;
|
||||
torquein = hydrotorque * m_fill * std::min(1.0, m_tmratio);
|
||||
}
|
||||
else if (speedratio < m_couplingpoint)
|
||||
{
|
||||
// converter range: the torque is multiplied
|
||||
auto const x{1.0 - speedratio / m_couplingpoint};
|
||||
m_tmratio = 1.0 + (m_tmmax - 1.0) * x * x;
|
||||
torquein = hydrotorque * m_fill;
|
||||
torqueout = hydrotorque * m_fill * m_tmratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
// fluid coupling range
|
||||
m_tmratio = (1.0 - speedratio) / (1.0 - m_couplingpoint);
|
||||
torquein = hydrotorque * m_fill * m_tmratio;
|
||||
torqueout = torquein;
|
||||
}
|
||||
auto const torquelock{m_lockuptorque * m_lockuprate};
|
||||
|
||||
// the main clutch of dizel_Momentum is taken as always engaged; the converter is drained to disconnect the engine
|
||||
double gearmoment;
|
||||
auto const capacity{torquelock + std::abs(torquein)};
|
||||
if (std::abs(moment) > capacity || std::abs(m_previous - m_omega) > 0.1 * two_pi)
|
||||
{
|
||||
auto const deltaomega{m_omega - m_previous};
|
||||
// NOTE: dizel_Momentum adds the lockup torque without the sign of the slip; here it acts against the slip too
|
||||
gearmoment = (torquelock + std::abs(torqueout)) * sign(deltaomega);
|
||||
auto const eps{(moment - capacity * sign(deltaomega)) / m_inertia};
|
||||
auto newomega{m_omega + eps * Frame.dt};
|
||||
if ((newomega - n) * deltaomega < 0.0 && torquelock > 0.1)
|
||||
{
|
||||
// the lockup clutch grips
|
||||
newomega = n;
|
||||
}
|
||||
if (newomega * m_omega <= 0.0 && eps * m_omega < 0.0)
|
||||
{
|
||||
newomega = 0.0;
|
||||
}
|
||||
m_omega = newomega;
|
||||
}
|
||||
else
|
||||
{
|
||||
gearmoment = moment;
|
||||
m_omega = clamp_any(n, m_omega + (moment - capacity) / m_inertia * Frame.dt, m_omega + (moment + capacity) / m_inertia * Frame.dt);
|
||||
}
|
||||
m_previous = n;
|
||||
|
||||
set_forward(Frame, port::out, gearmoment * dir);
|
||||
add_back(Frame, port::in, m_omega);
|
||||
set(Frame, port::fill, m_fill);
|
||||
set_bool(Frame, port::lockup, m_lockup);
|
||||
// the ratio of an empty converter means nothing (and grows without limit when the engine stops)
|
||||
set(Frame, port::ratio, m_fill > 0.0 ? m_tmratio : 0.0);
|
||||
body.hydro_TC_Fill = m_fill;
|
||||
body.hydro_TC_Lockup = m_lockup;
|
||||
body.hydro_TC_LockupRate = m_lockuprate;
|
||||
body.hydro_TC_TMRatio = m_tmratio;
|
||||
body.hydro_TC_TorqueIn = torquein;
|
||||
body.hydro_TC_TorqueOut = gearmoment;
|
||||
body.hydro_TC_nIn = nin;
|
||||
body.hydro_TC_nOut = nout;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.hydro", "Hydraulic transmission",
|
||||
"Torque converter with filling, fluid coupling range above the coupling point and a lockup clutch, as hydro_TC of the .fiz files (dizel_Momentum). "
|
||||
"power fills the converter (with the engine above 0.9 of idle_rpm); without it the converter drains below ans_speed. The lockup clutch engages "
|
||||
"above lockup_speed with power and releases below it (below unlock_speed without power). running (the engine's) false drains the converter and "
|
||||
"releases the lockup clutch. Holds the speed of the engine (inertia of the engine side).",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("in", domain::mechanical),
|
||||
signal_in("power", value_type::boolean),
|
||||
signal_in("running", value_type::boolean, 1.0, "", true),
|
||||
signal_in("dir", value_type::real, 1.0, "", true),
|
||||
physical_out("out", domain::mechanical),
|
||||
signal_out("fill", value_type::real),
|
||||
signal_out("lockup", value_type::boolean),
|
||||
signal_out("ratio", value_type::real)};
|
||||
type.params = {number_param("tc_tmmax", 2.0),
|
||||
number_param("tc_cp", 0.85),
|
||||
table_param("tc_table", {"speed_ratio", "torque_ratio"}),
|
||||
number_param("tc_tii", 4.5),
|
||||
number_param("tc_tio", 0.0),
|
||||
number_param("tc_too", 0.0),
|
||||
number_param("fill_rate", 1.0, "1/s"),
|
||||
number_param("drain_rate", 1.0, "1/s"),
|
||||
number_param("lockup_torque", 3000.0, "Nm"),
|
||||
number_param("lockup_speed", 40.0, "km/h"),
|
||||
number_param("unlock_speed", 30.0, "km/h"),
|
||||
number_param("idle_rpm", 600.0, "rpm"),
|
||||
number_param("ans_speed", 3.0, "km/h"),
|
||||
number_param("inertia", 10.0, "kg m2")};
|
||||
type.create = make_block<drive_hydro>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_tmmax;
|
||||
double const m_couplingpoint;
|
||||
std::vector<std::vector<double>> m_table;
|
||||
double const m_torqueinin;
|
||||
double const m_torqueinout;
|
||||
double const m_torqueoutout;
|
||||
double const m_fillrate;
|
||||
double const m_drainrate;
|
||||
double const m_lockuptorque;
|
||||
double const m_lockupspeed;
|
||||
double const m_unlockspeed;
|
||||
double const m_idle; // rad/s
|
||||
double const m_ansspeed;
|
||||
double const m_inertia;
|
||||
double m_fill{0.0};
|
||||
bool m_lockup{false};
|
||||
double m_lockuprate{0.0};
|
||||
double m_tmratio{1.0};
|
||||
double m_omega{0.0}; // engine speed, rad/s
|
||||
double m_previous{0.0}; // output speed in the previous step, rad/s
|
||||
};
|
||||
|
||||
// main generator of a diesel-electric vehicle: separately excited dc generator (adapted from the DieselElectric part of
|
||||
// TMoverParameters::TractionForce: the ellipse of the generator characteristic and the power available at the engine
|
||||
// speed). holds the speed of the engine
|
||||
class drive_generator final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
excitation,
|
||||
out,
|
||||
u,
|
||||
i,
|
||||
p
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
u_max,
|
||||
i_max,
|
||||
p_max,
|
||||
nominal_rpm,
|
||||
eff,
|
||||
field_time,
|
||||
inertia
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_generator(block_setup const &Setup)
|
||||
: block(Setup), m_umax(std::max(0.0, number(Setup, param::u_max))), m_imax(std::max(1.0, number(Setup, param::i_max))),
|
||||
m_pmax(std::max(0.0, number(Setup, param::p_max)) * 1000.0), m_nominal(std::max(1.0, number(Setup, param::nominal_rpm)) * rpm_to_rad),
|
||||
m_efficiency(std::clamp(number(Setup, param::eff), 0.01, 1.0)), m_fieldtime(std::max(0.0, number(Setup, param::field_time))),
|
||||
m_inertia(std::max(0.01, number(Setup, param::inertia)))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const moment{forward_in(Frame, port::in)};
|
||||
auto const current{back_out(Frame, port::out)}; // drawn by the load in the previous step
|
||||
auto const excitation{std::clamp(get(Frame, port::excitation), 0.0, 1.0)};
|
||||
auto const speedratio{std::clamp(m_omega / m_nominal, 0.0, 1.0)};
|
||||
// the voltage at no load grows with the excitation and the speed; under load it follows the ellipse of the
|
||||
// separately excited generator (zero at i_max) and the hyperbola of the power available at this speed
|
||||
auto const noload{m_umax * excitation * speedratio};
|
||||
auto const load{std::min(1.0, std::abs(current) / m_imax)};
|
||||
auto target{noload * std::sqrt(std::max(0.0, 1.0 - load * load))};
|
||||
if (std::abs(current) > 1.0)
|
||||
{
|
||||
target = std::min(target, m_pmax * speedratio / std::abs(current));
|
||||
}
|
||||
// the field of the generator follows with its time constant, which also keeps the loop with the motors stable
|
||||
m_voltage += (target - m_voltage) * (m_fieldtime > 0.0 ? std::min(1.0, Frame.dt / m_fieldtime) : 1.0);
|
||||
m_power = m_voltage * current;
|
||||
// load of the engine: the electric power and the losses
|
||||
auto const loadtorque{m_power / m_efficiency / std::max(m_omega, 1.0)};
|
||||
m_omega = std::max(0.0, m_omega + (moment - loadtorque) / m_inertia * Frame.dt);
|
||||
|
||||
set_forward(Frame, port::out, m_voltage);
|
||||
add_back(Frame, port::in, m_omega);
|
||||
set(Frame, port::u, m_voltage);
|
||||
set(Frame, port::i, current);
|
||||
set(Frame, port::p, m_power / 1000.0);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.generator", "Main generator",
|
||||
"Main generator of a diesel-electric vehicle. The voltage at no load is u_max times the excitation (0..1), proportional to the engine speed "
|
||||
"below nominal_rpm; under load it falls on an ellipse to zero at i_max, and the power is limited to p_max scaled with the engine speed "
|
||||
"(DieselElectric of TractionForce). The electric power loads the engine. Holds the speed of the engine (inertia of the engine and the rotor).",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("in", domain::mechanical), signal_in("excitation", value_type::real), physical_out("out", domain::high_voltage),
|
||||
signal_out("u", value_type::real, "V"), signal_out("i", value_type::real, "A"), signal_out("p", value_type::real, "kW")};
|
||||
type.params = {number_param("u_max", 750.0, "V"),
|
||||
number_param("i_max", 2400.0, "A"),
|
||||
number_param("p_max", 330.0, "kW"),
|
||||
number_param("nominal_rpm", 1500.0, "rpm"),
|
||||
number_param("eff", 0.93),
|
||||
number_param("field_time", 0.5, "s"),
|
||||
number_param("inertia", 20.0, "kg m2")};
|
||||
type.create = make_block<drive_generator>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_umax;
|
||||
double const m_imax;
|
||||
double const m_pmax; // W
|
||||
double const m_nominal; // rad/s
|
||||
double const m_efficiency;
|
||||
double const m_fieldtime;
|
||||
double const m_inertia;
|
||||
double m_voltage{0.0};
|
||||
double m_power{0.0};
|
||||
double m_omega{0.0}; // engine speed, rad/s
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_diesel_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(drive_diesel::describe());
|
||||
Catalog.add(drive_gearbox::describe());
|
||||
Catalog.add(drive_hydro::describe());
|
||||
Catalog.add(drive_generator::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
658
vehicle/graph/blocks/drive.cpp
Normal file
658
vehicle/graph/blocks/drive.cpp
Normal file
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_body.h"
|
||||
#include "McZapkie/MOVER.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
// drive chain: motor -> gear -> axle. the mechanical connections pass the torque forward and the rotational speed back.
|
||||
// both are in the frame of the vehicle: positive torque pushes the vehicle towards its front (cab 1), positive speed
|
||||
// is the rotation when moving that way. the dir input of the motors is the position of the reverser in the occupied cab,
|
||||
// so the motors turn it to the frame of the vehicle with the occupied cab
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
double constexpr two_pi{2.0 * M_PI};
|
||||
|
||||
TMoverParameters &vehicle(frame const &Frame)
|
||||
{
|
||||
return *Frame.body->mover;
|
||||
}
|
||||
|
||||
block_type make_type(std::string Id, std::string Name, std::string Description, block_origin Origin)
|
||||
{
|
||||
block_type type;
|
||||
type.id = std::move(Id);
|
||||
type.category = "drive";
|
||||
type.name = std::move(Name);
|
||||
type.description = std::move(Description);
|
||||
type.origin = Origin;
|
||||
return type;
|
||||
}
|
||||
|
||||
// direction of the drive in the frame of the vehicle: -1, 0 or 1
|
||||
int absolute_direction(frame const &Frame, double const Dir)
|
||||
{
|
||||
auto const dir{Dir > 0.5 ? 1 : Dir < -0.5 ? -1 : 0};
|
||||
auto const cab{Frame.body->cab};
|
||||
return dir * (cab != 0 ? cab : 1);
|
||||
}
|
||||
|
||||
// generic motor with a torque and power limit; a stand-in for the drives not extracted yet
|
||||
class drive_simple final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
wn,
|
||||
demand,
|
||||
dir,
|
||||
shaft,
|
||||
i,
|
||||
p
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
t_max,
|
||||
p_max,
|
||||
eff,
|
||||
min_voltage
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_simple(block_setup const &Setup)
|
||||
: block(Setup), m_supplied(is_connected(Setup, port::wn)), m_tmax(number(Setup, param::t_max)), m_pmax(number(Setup, param::p_max)),
|
||||
m_efficiency(std::clamp(number(Setup, param::eff), 0.01, 1.0)), m_minvoltage(number(Setup, param::min_voltage))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const voltage{m_supplied ? forward_in(Frame, port::wn) : 0.0};
|
||||
auto const powered{false == m_supplied || (voltage > 0.0 && voltage >= m_minvoltage)};
|
||||
auto const dir{absolute_direction(Frame, get(Frame, port::dir))};
|
||||
auto const demand{std::clamp(get(Frame, port::demand), -1.0, 1.0)};
|
||||
auto const speed{back_out(Frame, port::shaft)}; // rad/s
|
||||
auto torque{0.0};
|
||||
if (powered && dir != 0)
|
||||
{
|
||||
if (demand >= 0.0)
|
||||
{
|
||||
// traction, limited by the power above the base speed
|
||||
auto const limit{std::abs(speed) > 1e-3 ? std::min(m_tmax, m_pmax / std::abs(speed)) : m_tmax};
|
||||
torque = dir * demand * limit;
|
||||
}
|
||||
else
|
||||
{
|
||||
// electric brake, against the rotation
|
||||
auto const limit{std::abs(speed) > 1e-3 ? std::min(m_tmax, m_pmax / std::abs(speed)) : 0.0};
|
||||
torque = (speed >= 0.0 ? -1.0 : 1.0) * (-demand) * limit * std::min(1.0, std::abs(speed));
|
||||
}
|
||||
}
|
||||
auto const mechanical{torque * speed}; // W, positive when driving
|
||||
auto const electrical{mechanical >= 0.0 ? mechanical / m_efficiency : mechanical * m_efficiency};
|
||||
auto const current{m_supplied && voltage > 1.0 ? electrical / voltage : 0.0};
|
||||
if (m_supplied)
|
||||
{
|
||||
add_back(Frame, port::wn, current);
|
||||
}
|
||||
set_forward(Frame, port::shaft, torque);
|
||||
set(Frame, port::i, current);
|
||||
set(Frame, port::p, electrical);
|
||||
auto &body{vehicle(Frame)};
|
||||
body.Im = current;
|
||||
body.EngineVoltage = voltage * dir;
|
||||
body.enrot = speed / two_pi;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.simple", "Simple motor",
|
||||
"Motor with a torque limit (t_max) and a power limit (p_max): demand 0..1 drives, -1..0 brakes electrically. A stand-in for prototypes.",
|
||||
block_origin::native)};
|
||||
type.ports = {physical_in("wn", domain::high_voltage, true), signal_in("demand", value_type::real), signal_in("dir", value_type::real),
|
||||
physical_out("shaft", domain::mechanical), signal_out("i", value_type::real, "A"), signal_out("p", value_type::real, "W")};
|
||||
type.params = {number_param("t_max", 5000.0, "Nm"), number_param("p_max", 500000.0, "W"), number_param("eff", 0.9), number_param("min_voltage", 0.0, "V")};
|
||||
type.create = make_block<drive_simple>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_supplied;
|
||||
double const m_tmax;
|
||||
double const m_pmax;
|
||||
double const m_efficiency;
|
||||
double const m_minvoltage;
|
||||
};
|
||||
|
||||
class drive_gear final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
out
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_gear(block_setup const &Setup) : block(Setup), m_ratio(number(Setup, 0)), m_efficiency(number(Setup, 1)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
set_forward(Frame, port::out, forward_in(Frame, port::in) * m_ratio * m_efficiency);
|
||||
add_back(Frame, port::in, back_out(Frame, port::out) * m_ratio);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.gear", "Gear", "Transmission: torque times ratio and efficiency, speed of the input times ratio. ratio = wheel teeth / motor teeth.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("in", domain::mechanical), physical_out("out", domain::mechanical)};
|
||||
type.params = {number_param("ratio", 1.0), number_param("eff", 1.0)};
|
||||
type.create = make_block<drive_gear>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_ratio;
|
||||
double const m_efficiency;
|
||||
};
|
||||
|
||||
// wheelsets driven by one shaft: the torque becomes the tractive force of the vehicle
|
||||
class drive_axle final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
shaft,
|
||||
force,
|
||||
slip
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_axle(block_setup const &Setup) : block(Setup), m_axles(std::max(0.0, number(Setup, 0))) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const &body{vehicle(Frame)};
|
||||
auto const diameter{body.WheelDiameter > 0.0 ? body.WheelDiameter : 1.0};
|
||||
auto const force{forward_in(Frame, port::shaft) * 2.0 / diameter * m_axles};
|
||||
Frame.body->tractive_force += force;
|
||||
add_back(Frame, port::shaft, Frame.body->wheel_speed * two_pi);
|
||||
set(Frame, port::force, force);
|
||||
set_bool(Frame, port::slip, Frame.body->slipping);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.axle", "Driven axles",
|
||||
"Driven wheelsets: the torque of one shaft (of one motor, after the gear) times powered_axles gives the force at the wheel rim. All axle forces are "
|
||||
"summed into the tractive force of the vehicle.",
|
||||
block_origin::native)};
|
||||
type.ports = {physical_in("shaft", domain::mechanical), signal_out("force", value_type::real, "N"), signal_out("slip", value_type::boolean)};
|
||||
type.params = {number_param("powered_axles", 1.0)};
|
||||
type.create = make_block<drive_axle>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_axles;
|
||||
};
|
||||
|
||||
// series dc traction motors with resistor starting (TMoverParameters::Current, Momentum)
|
||||
class drive_dc_series final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
wn,
|
||||
position,
|
||||
shunt,
|
||||
dir,
|
||||
high,
|
||||
reset,
|
||||
shaft,
|
||||
i,
|
||||
u_motor,
|
||||
overload
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
rlist,
|
||||
mpt,
|
||||
volt,
|
||||
winding_res,
|
||||
circuit_res,
|
||||
imax_lo,
|
||||
imax_hi,
|
||||
imin_lo,
|
||||
imin_hi,
|
||||
trip_delay
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_dc_series(block_setup const &Setup)
|
||||
: block(Setup), m_windingres(number(Setup, param::winding_res)), m_circuitres(number(Setup, param::circuit_res)), m_imaxlo(number(Setup, param::imax_lo)),
|
||||
m_imaxhi(number(Setup, param::imax_hi)), m_tripdelay(number(Setup, param::trip_delay))
|
||||
{
|
||||
for (auto const &row : table(Setup, param::rlist))
|
||||
{
|
||||
// pos, r, bn, mn, auto
|
||||
scheme entry;
|
||||
entry.r = row[1];
|
||||
entry.bn = static_cast<int>(std::lround(row[2]));
|
||||
entry.mn = static_cast<int>(std::lround(row[3]));
|
||||
m_schemes.push_back(entry);
|
||||
}
|
||||
for (auto const &row : table(Setup, param::mpt))
|
||||
{
|
||||
// pos, mfi, mIsat, mfi0, fi, Isat, fi0
|
||||
TMotorParameters motor;
|
||||
motor.mfi = row[1];
|
||||
motor.mIsat = row[2];
|
||||
motor.mfi0 = row[3];
|
||||
motor.fi = row[4];
|
||||
motor.Isat = row[5];
|
||||
motor.fi0 = row[6];
|
||||
m_motors.push_back(motor);
|
||||
}
|
||||
if (m_schemes.empty())
|
||||
{
|
||||
m_schemes.emplace_back();
|
||||
}
|
||||
if (m_motors.empty())
|
||||
{
|
||||
m_motors.emplace_back();
|
||||
}
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const dir{absolute_direction(Frame, get(Frame, port::dir))};
|
||||
auto const voltage{forward_in(Frame, port::wn) * dir};
|
||||
auto const position{std::clamp<int>(static_cast<int>(std::lround(get(Frame, port::position))), 0, static_cast<int>(m_schemes.size()) - 1)};
|
||||
auto const shunt{std::clamp<int>(static_cast<int>(std::lround(get(Frame, port::shunt))), 0, static_cast<int>(m_motors.size()) - 1)};
|
||||
auto const &scheme{m_schemes[position]};
|
||||
auto const &motor{m_motors[shunt]};
|
||||
auto const revolutions{back_out(Frame, port::shaft) / two_pi}; // rev/s
|
||||
|
||||
if (get_bool(Frame, port::reset))
|
||||
{
|
||||
m_overload = false;
|
||||
}
|
||||
auto current{0.0};
|
||||
if (scheme.bn != 0 && std::abs(voltage) > 1.0 && false == m_overload)
|
||||
{
|
||||
auto const resistance{scheme.r + m_circuitres};
|
||||
current = series_motor_current(voltage, revolutions, scheme.mn, scheme.mn * m_windingres + resistance, motor);
|
||||
}
|
||||
// overload relay, as in TractionForce: trips when the current stays above the limit
|
||||
auto const imax{get_bool(Frame, port::high) ? m_imaxhi : m_imaxlo};
|
||||
if (imax > 0.0 && std::abs(current) > imax)
|
||||
{
|
||||
m_overtime += Frame.dt;
|
||||
if (m_overtime > m_tripdelay)
|
||||
{
|
||||
m_overload = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_overtime = 0.0;
|
||||
}
|
||||
auto const torque{series_motor_momentum(current, motor)};
|
||||
auto const linecurrent{current * scheme.bn};
|
||||
add_back(Frame, port::wn, std::abs(linecurrent));
|
||||
set_forward(Frame, port::shaft, torque);
|
||||
set(Frame, port::i, current);
|
||||
set(Frame, port::u_motor, scheme.mn > 0 ? (std::abs(voltage) - std::abs(current) * (scheme.r + m_circuitres)) / scheme.mn : 0.0);
|
||||
set_bool(Frame, port::overload, m_overload);
|
||||
body.Im = current;
|
||||
body.EngineVoltage = voltage;
|
||||
body.enrot = revolutions;
|
||||
body.MainCtrlActualPos = position;
|
||||
}
|
||||
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "overload";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_overload ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.dc_series", "Series DC motors",
|
||||
"Series wound DC traction motors with resistor starting, computed as for the .fiz vehicles. position selects the row of rlist (resistance, "
|
||||
"branches, motors in series), shunt the row of mpt (motor characteristics). The shaft carries the torque of one motor. high selects imax_hi "
|
||||
"for the overload relay; reset clears it.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("wn", domain::high_voltage),
|
||||
signal_in("position", value_type::integer),
|
||||
signal_in("shunt", value_type::integer, 0.0, "", true),
|
||||
signal_in("dir", value_type::real),
|
||||
signal_in("high", value_type::boolean, 0.0, "", true),
|
||||
signal_in("reset", value_type::boolean, 0.0, "", true),
|
||||
physical_out("shaft", domain::mechanical),
|
||||
signal_out("i", value_type::real, "A"),
|
||||
signal_out("u_motor", value_type::real, "V"),
|
||||
signal_out("overload", value_type::boolean)};
|
||||
type.params = {table_param("rlist", {"pos", "r", "bn", "mn", "auto"}),
|
||||
table_param("mpt", {"pos", "mfi", "misat", "mfi0", "fi", "isat", "fi0"}),
|
||||
number_param("volt", 1500.0, "V"),
|
||||
number_param("winding_res", 0.0, "ohm"),
|
||||
number_param("circuit_res", 0.0, "ohm"),
|
||||
number_param("imax_lo", 0.0, "A"),
|
||||
number_param("imax_hi", 0.0, "A"),
|
||||
number_param("imin_lo", 0.0, "A"),
|
||||
number_param("imin_hi", 0.0, "A"),
|
||||
number_param("trip_delay", 0.05, "s")};
|
||||
type.create = make_block<drive_dc_series>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
struct scheme
|
||||
{
|
||||
double r{0.0};
|
||||
int bn{0};
|
||||
int mn{0};
|
||||
};
|
||||
|
||||
double const m_windingres;
|
||||
double const m_circuitres;
|
||||
double const m_imaxlo;
|
||||
double const m_imaxhi;
|
||||
double const m_tripdelay;
|
||||
std::vector<scheme> m_schemes;
|
||||
std::vector<TMotorParameters> m_motors;
|
||||
double m_overtime{0.0};
|
||||
bool m_overload{false};
|
||||
};
|
||||
|
||||
// induction motors fed by inverters (the ElectricInductionMotor drive of TMoverParameters::TractionForce)
|
||||
class drive_async final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
wn,
|
||||
demand,
|
||||
dir,
|
||||
shaft,
|
||||
i,
|
||||
f,
|
||||
ed_active,
|
||||
ed
|
||||
};
|
||||
};
|
||||
// in the order of the eimc constants they're copied to
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
dfic,
|
||||
dfmax,
|
||||
p,
|
||||
cfu,
|
||||
cim,
|
||||
icif,
|
||||
uzmax,
|
||||
uzh,
|
||||
du,
|
||||
i0,
|
||||
fcfu,
|
||||
fcfuh,
|
||||
f0,
|
||||
a1,
|
||||
pmax,
|
||||
fh,
|
||||
ph,
|
||||
vh0,
|
||||
vh1,
|
||||
imax,
|
||||
abed,
|
||||
edep,
|
||||
flat,
|
||||
motors,
|
||||
ratio,
|
||||
eff,
|
||||
log_force
|
||||
};
|
||||
};
|
||||
|
||||
explicit drive_async(block_setup const &Setup)
|
||||
: block(Setup), m_flat(boolean(Setup, param::flat)), m_motors(static_cast<int>(std::lround(number(Setup, param::motors)))), m_ratio(number(Setup, param::ratio)),
|
||||
m_efficiency(number(Setup, param::eff)), m_logforce(boolean(Setup, param::log_force))
|
||||
{
|
||||
std::fill(std::begin(m_eimc), std::end(m_eimc), 0.0);
|
||||
std::fill(std::begin(m_eimv), std::end(m_eimv), 0.0);
|
||||
m_eimc[eimc_s_dfic] = number(Setup, param::dfic);
|
||||
m_eimc[eimc_s_dfmax] = number(Setup, param::dfmax);
|
||||
m_eimc[eimc_s_p] = number(Setup, param::p);
|
||||
m_eimc[eimc_s_cfu] = number(Setup, param::cfu);
|
||||
m_eimc[eimc_s_cim] = number(Setup, param::cim);
|
||||
m_eimc[eimc_s_icif] = number(Setup, param::icif);
|
||||
m_eimc[eimc_f_Uzmax] = number(Setup, param::uzmax);
|
||||
m_eimc[eimc_f_Uzh] = number(Setup, param::uzh);
|
||||
m_eimc[eimc_f_DU] = number(Setup, param::du);
|
||||
m_eimc[eimc_f_I0] = number(Setup, param::i0);
|
||||
m_eimc[eimc_f_cfu] = number(Setup, param::fcfu);
|
||||
// as in LoadFIZ_Engine: fcfuH defaults to fcfu
|
||||
m_eimc[eimc_f_cfuH] = number(Setup, param::fcfuh) > 0.0 ? number(Setup, param::fcfuh) : m_eimc[eimc_f_cfu];
|
||||
m_eimc[eimc_p_F0] = number(Setup, param::f0);
|
||||
m_eimc[eimc_p_a1] = number(Setup, param::a1);
|
||||
m_eimc[eimc_p_Pmax] = number(Setup, param::pmax);
|
||||
m_eimc[eimc_p_Fh] = number(Setup, param::fh);
|
||||
m_eimc[eimc_p_Ph] = number(Setup, param::ph);
|
||||
m_eimc[eimc_p_Vh0] = number(Setup, param::vh0);
|
||||
m_eimc[eimc_p_Vh1] = number(Setup, param::vh1);
|
||||
m_eimc[eimc_p_Imax] = number(Setup, param::imax);
|
||||
m_eimc[eimc_p_abed] = number(Setup, param::abed);
|
||||
m_eimc[eimc_p_eped] = number(Setup, param::edep);
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const voltage{forward_in(Frame, port::wn)};
|
||||
auto const dir{absolute_direction(Frame, get(Frame, port::dir))};
|
||||
auto const enrot{back_out(Frame, port::shaft) / two_pi};
|
||||
auto const demand{std::clamp(get(Frame, port::demand), -1.0, 1.0)};
|
||||
if (voltage < 1.0 || dir == 0)
|
||||
{
|
||||
// without supply the inverters stop, as for the vehicles without the main circuit (Mains)
|
||||
std::fill(std::begin(m_eimv), std::end(m_eimv), 0.0);
|
||||
m_eimvpr = 0.0;
|
||||
m_dynamicbrake = false;
|
||||
add_back(Frame, port::wn, 0.0);
|
||||
publish(Frame, body, 0.0, 0.0, 0.0, voltage * dir, enrot);
|
||||
return;
|
||||
}
|
||||
auto const vel{body.Vel};
|
||||
// set value of the force and the rate of its ramp, as in TractionForce
|
||||
double posratio;
|
||||
double rate;
|
||||
if (demand < -0.001)
|
||||
{
|
||||
m_dynamicbrake = true;
|
||||
auto const vhramp{std::clamp((vel - m_eimc[eimc_p_Vh0]) / std::max(0.001, m_eimc[eimc_p_Vh1] - m_eimc[eimc_p_Vh0]), 0.0, 1.0)};
|
||||
posratio = demand * vhramp;
|
||||
rate = 5.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dynamicbrake = false;
|
||||
posratio = std::max(demand, 0.0);
|
||||
if (m_flat && m_eimc[eimc_p_F0] * m_eimv[eimv_Fful] > 0)
|
||||
{
|
||||
posratio = std::min(posratio * m_eimc[eimc_p_F0] / m_eimv[eimv_Fful], 1.0);
|
||||
}
|
||||
rate = 4.0;
|
||||
}
|
||||
if (body.SlippingWheels)
|
||||
{
|
||||
// anti-slip: the set value drops quickly
|
||||
posratio = 0.0;
|
||||
rate = 10.0;
|
||||
}
|
||||
induction_motor_inputs input;
|
||||
input.demand = posratio;
|
||||
input.rate = rate;
|
||||
input.dynamic_brake = m_dynamicbrake;
|
||||
input.engine_voltage = voltage;
|
||||
input.enrot = enrot;
|
||||
input.vel = vel;
|
||||
input.v = body.V;
|
||||
input.dir_absolute = dir;
|
||||
input.dir_active = dir;
|
||||
input.powered_axles = m_motors;
|
||||
input.inverters_ratio = 1.0;
|
||||
input.ratio = m_ratio;
|
||||
input.efficiency = m_efficiency;
|
||||
input.wheel_diameter = body.WheelDiameter > 0.0 ? body.WheelDiameter : 1.0;
|
||||
input.log_force = m_logforce;
|
||||
input.max_voltage = body.EnginePowerSource.CollectorParameters.MaxV;
|
||||
input.dt = Frame.dt;
|
||||
induction_motor_outputs output;
|
||||
induction_motor_update(input, m_eimc, m_eimv, m_eimvpr, m_vadd, output);
|
||||
|
||||
add_back(Frame, port::wn, output.Itot);
|
||||
// share of the requested braking force the motors deliver, for the blending with the pneumatic brake
|
||||
auto edshare{0.0};
|
||||
if (m_dynamicbrake && m_eimc[eimc_p_Fh] > 0.0 && demand < -0.001)
|
||||
{
|
||||
edshare = std::clamp(std::abs(m_eimv[eimv_Fr]) / (m_eimc[eimc_p_Fh] * -demand), 0.0, 1.0);
|
||||
}
|
||||
publish(Frame, body, output.Mm, output.Im, edshare, voltage, enrot);
|
||||
}
|
||||
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
char const *state_name(std::size_t Index) const override
|
||||
{
|
||||
return Index == 0 ? "set_value" : "force_kn";
|
||||
}
|
||||
double state_value(std::size_t Index) const override
|
||||
{
|
||||
return Index == 0 ? m_eimvpr : m_eimv[eimv_Fr];
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("drive.async", "Induction motors with inverters",
|
||||
"Induction traction motors fed by inverters, computed as for the .fiz vehicles (Engine: ElectricInductionMotor). demand 0..1 drives, -1..0 brakes "
|
||||
"electrically (faded out between vh1 and vh0). The shaft carries the torque of one motor; ratio and eff have to match the gear, and motors the "
|
||||
"driven axles. ed is the share of the requested braking force the motors deliver, for the ed input of the distributor.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("wn", domain::high_voltage), signal_in("demand", value_type::real), signal_in("dir", value_type::real),
|
||||
physical_out("shaft", domain::mechanical), signal_out("i", value_type::real, "A"), signal_out("f", value_type::real, "Hz"),
|
||||
signal_out("ed_active", value_type::boolean), signal_out("ed", value_type::real)};
|
||||
type.params = {number_param("dfic", 0.0),
|
||||
number_param("dfmax", 0.0),
|
||||
number_param("p", 2.0),
|
||||
number_param("cfu", 0.0),
|
||||
number_param("cim", 0.0),
|
||||
number_param("icif", 0.0),
|
||||
number_param("uzmax", 0.0, "V"),
|
||||
number_param("uzh", 0.0, "V"),
|
||||
number_param("du", 0.0, "V"),
|
||||
number_param("i0", 0.0, "A"),
|
||||
number_param("fcfu", 0.0),
|
||||
number_param("fcfuh", 0.0),
|
||||
number_param("f0", 0.0, "kN"),
|
||||
number_param("a1", 0.0),
|
||||
number_param("pmax", 0.0, "kW"),
|
||||
number_param("fh", 0.0, "kN"),
|
||||
number_param("ph", 0.0, "kW"),
|
||||
number_param("vh0", 0.0, "km/h"),
|
||||
number_param("vh1", 0.0, "km/h"),
|
||||
number_param("imax", 0.0, "A"),
|
||||
number_param("abed", 1.0),
|
||||
number_param("edep", 1.5),
|
||||
boolean_param("flat", false),
|
||||
number_param("motors", 4.0),
|
||||
number_param("ratio", 1.0),
|
||||
number_param("eff", 1.0),
|
||||
boolean_param("log_force", false)};
|
||||
type.create = make_block<drive_async>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
void publish(frame &Frame, TMoverParameters &Body, double const Torque, double const Current, double const Edshare, double const Voltage, double const Revolutions)
|
||||
{
|
||||
set_forward(Frame, port::shaft, Torque);
|
||||
set(Frame, port::i, Current);
|
||||
set(Frame, port::f, std::abs(m_eimv[eimv_fp]));
|
||||
set_bool(Frame, port::ed_active, m_dynamicbrake);
|
||||
set(Frame, port::ed, Edshare);
|
||||
Body.Im = Current;
|
||||
Body.EngineVoltage = Voltage;
|
||||
Body.enrot = Revolutions;
|
||||
Body.DynamicBrakeFlag = m_dynamicbrake;
|
||||
Body.InverterFrequency = std::abs(m_eimv[eimv_fp]);
|
||||
std::copy(std::begin(m_eimv), std::end(m_eimv), Body.eimv);
|
||||
}
|
||||
|
||||
bool const m_flat;
|
||||
int const m_motors;
|
||||
double const m_ratio;
|
||||
double const m_efficiency;
|
||||
bool const m_logforce;
|
||||
double m_eimc[26];
|
||||
double m_eimv[21];
|
||||
double m_eimvpr{0.0};
|
||||
double m_vadd{0.0};
|
||||
bool m_dynamicbrake{false};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_drive_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(drive_simple::describe());
|
||||
Catalog.add(drive_dc_series::describe());
|
||||
Catalog.add(drive_async::describe());
|
||||
Catalog.add(drive_gear::describe());
|
||||
Catalog.add(drive_axle::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
658
vehicle/graph/blocks/electric.cpp
Normal file
658
vehicle/graph/blocks/electric.cpp
Normal file
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_body.h"
|
||||
#include "McZapkie/MOVER.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
// high voltage circuit and auxiliary supplies. voltage goes forward through the connections, current comes back; the
|
||||
// back channel lags one step, so a breaker sees the current its load drew in the previous step
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
TMoverParameters &vehicle(frame const &Frame)
|
||||
{
|
||||
return *Frame.body->mover;
|
||||
}
|
||||
|
||||
block_type make_type(std::string Id, std::string Name, std::string Description, block_origin Origin)
|
||||
{
|
||||
block_type type;
|
||||
type.id = std::move(Id);
|
||||
type.category = "electric";
|
||||
type.name = std::move(Name);
|
||||
type.description = std::move(Description);
|
||||
type.origin = Origin;
|
||||
return type;
|
||||
}
|
||||
|
||||
// current collector. in the simulator its contact with the catenary is the pantograph of the vehicle model
|
||||
class el_pantograph final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
raise,
|
||||
air,
|
||||
wn,
|
||||
up,
|
||||
height,
|
||||
u
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
index,
|
||||
raise_time,
|
||||
lower_time,
|
||||
min_pressure,
|
||||
preview_voltage
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_pantograph(block_setup const &Setup)
|
||||
: block(Setup), m_air(is_connected(Setup, port::air)), m_index(std::clamp(static_cast<int>(std::lround(number(Setup, param::index))), 0, 1)),
|
||||
m_raisetime(std::max(0.01, number(Setup, param::raise_time))), m_lowertime(std::max(0.01, number(Setup, param::lower_time))),
|
||||
m_minpressure(number(Setup, param::min_pressure)), m_previewvoltage(number(Setup, param::preview_voltage))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const pressure{m_air ? forward_in(Frame, port::air) : body.EnginePowerSource.CollectorParameters.MaxPress};
|
||||
auto const raise{get_bool(Frame, port::raise) && pressure >= m_minpressure};
|
||||
auto &pantograph{body.Pantographs[m_index]};
|
||||
pantograph.is_active = raise;
|
||||
// the model raises the pantograph at a speed depending on this pressure
|
||||
body.PantPress = pressure;
|
||||
m_height = std::clamp(m_height + (raise ? Frame.dt / m_raisetime : -Frame.dt / m_lowertime), 0.0, 1.0);
|
||||
double voltage;
|
||||
if (Frame.body->standalone)
|
||||
{
|
||||
voltage = m_height >= 1.0 ? m_previewvoltage : 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
voltage = std::abs(m_index == 0 ? body.PantFrontVolt : body.PantRearVolt);
|
||||
}
|
||||
pantograph.voltage = voltage;
|
||||
set_forward(Frame, port::wn, voltage);
|
||||
// current drawn by the vehicle through this pantograph, in the previous step
|
||||
Frame.body->current += back_out(Frame, port::wn);
|
||||
set_bool(Frame, port::up, m_height >= 1.0);
|
||||
set(Frame, port::height, m_height);
|
||||
set(Frame, port::u, voltage);
|
||||
}
|
||||
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "height";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.pantograph", "Pantograph",
|
||||
"Current collector: index 0 is the front pantograph of the vehicle model, 1 the rear one. Raising needs the air pressure (when connected). "
|
||||
"Without the simulator the catenary has the preview voltage.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {signal_in("raise", value_type::boolean), physical_in("air", domain::air, true), physical_out("wn", domain::high_voltage),
|
||||
signal_out("up", value_type::boolean), signal_out("height", value_type::real), signal_out("u", value_type::real, "V")};
|
||||
type.params = {number_param("index", 0.0), number_param("raise_time", 4.0, "s"), number_param("lower_time", 1.0, "s"), number_param("min_pressure", 3.5, "bar"),
|
||||
number_param("preview_voltage", 3000.0, "V")};
|
||||
type.create = make_block<el_pantograph>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_air;
|
||||
int const m_index;
|
||||
double const m_raisetime;
|
||||
double const m_lowertime;
|
||||
double const m_minpressure;
|
||||
double const m_previewvoltage;
|
||||
double m_height{0.0};
|
||||
};
|
||||
|
||||
// joins two supplies: the higher voltage wins, the current goes back to the source(s) supplying it
|
||||
class el_junction final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
a,
|
||||
b,
|
||||
out
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_junction(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const a{forward_in(Frame, port::a)};
|
||||
auto const b{forward_in(Frame, port::b)};
|
||||
set_forward(Frame, port::out, std::max(a, b));
|
||||
auto const current{back_out(Frame, port::out)};
|
||||
if (std::abs(a - b) < 1.0)
|
||||
{
|
||||
add_back(Frame, port::a, 0.5 * current);
|
||||
add_back(Frame, port::b, 0.5 * current);
|
||||
}
|
||||
else
|
||||
{
|
||||
add_back(Frame, a > b ? port::a : port::b, current);
|
||||
}
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.junction", "Junction", "Joins two high voltage supplies, e.g. two pantographs.", block_origin::primitive)};
|
||||
type.ports = {physical_in("a", domain::high_voltage), physical_in("b", domain::high_voltage, true), physical_out("out", domain::high_voltage)};
|
||||
type.create = make_block<el_junction>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
// line breaker (WS). its state is the Mains of the vehicle body, which the sounds and the AI watch; with more breakers
|
||||
// (multi-system vehicles) the Mains is closed when any of them is
|
||||
class el_hsb final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
close,
|
||||
open,
|
||||
hold,
|
||||
out,
|
||||
closed,
|
||||
i,
|
||||
tripped
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
trip_current,
|
||||
close_time,
|
||||
min_voltage,
|
||||
init_time
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_hsb(block_setup const &Setup)
|
||||
: block(Setup), m_tripcurrent(number(Setup, param::trip_current)), m_closetime(number(Setup, param::close_time)), m_minvoltage(number(Setup, param::min_voltage)),
|
||||
m_inittime(number(Setup, param::init_time)), m_initcountdown(m_inittime)
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
m_voltage = forward_in(Frame, port::in);
|
||||
m_current = back_out(Frame, port::out);
|
||||
// the current of the previous step passes to the supply
|
||||
add_back(Frame, port::in, m_current);
|
||||
auto const voltageok{m_voltage > 0.0 && m_voltage >= m_minvoltage};
|
||||
// the breaker can be closed after the voltage was present for the init time
|
||||
m_initcountdown = voltageok ? m_initcountdown - Frame.dt : m_inittime;
|
||||
if (m_closed)
|
||||
{
|
||||
auto const overload{m_tripcurrent > 0.0 && std::abs(m_current) > m_tripcurrent};
|
||||
if (overload || false == voltageok || get_bool(Frame, port::open) || false == get_bool(Frame, port::hold))
|
||||
{
|
||||
m_closed = false;
|
||||
m_tripped = m_tripped || overload;
|
||||
}
|
||||
}
|
||||
else if (get_bool(Frame, port::close) && voltageok && m_initcountdown <= 0.0 && get_bool(Frame, port::hold) && false == get_bool(Frame, port::open))
|
||||
{
|
||||
m_closing += Frame.dt;
|
||||
if (m_closing >= m_closetime)
|
||||
{
|
||||
m_closed = true;
|
||||
m_tripped = false;
|
||||
m_closing = 0.0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_closing = 0.0;
|
||||
}
|
||||
// the vehicle can have more breakers, e.g. one for each supply system; its Mains is closed when any of them is
|
||||
vehicle(Frame).Mains = Frame.body->mains.any(Frame.step, m_closed);
|
||||
}
|
||||
void commit(frame &Frame) override
|
||||
{
|
||||
set_forward(Frame, port::out, m_closed ? m_voltage : 0.0);
|
||||
set_bool(Frame, port::closed, m_closed);
|
||||
set(Frame, port::i, m_current);
|
||||
set_bool(Frame, port::tripped, m_tripped);
|
||||
}
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "closed";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_closed ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.hsb", "Line breaker",
|
||||
"Main (line) breaker. Closes after close is held for close_time, with enough voltage present for init_time; opens with open, without hold, "
|
||||
"below min_voltage or above trip_current (then tripped is set until the next closing).",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("in", domain::high_voltage),
|
||||
signal_in("close", value_type::boolean),
|
||||
signal_in("open", value_type::boolean, 0.0, "", true),
|
||||
signal_in("hold", value_type::boolean, 1.0, "", true),
|
||||
physical_out("out", domain::high_voltage),
|
||||
signal_out("closed", value_type::boolean),
|
||||
signal_out("i", value_type::real, "A"),
|
||||
signal_out("tripped", value_type::boolean)};
|
||||
type.params = {number_param("trip_current", 0.0, "A"), number_param("close_time", 0.2, "s"), number_param("min_voltage", 0.0, "V"), number_param("init_time", 0.0, "s")};
|
||||
type.deferred = true;
|
||||
type.create = make_block<el_hsb>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_tripcurrent;
|
||||
double const m_closetime;
|
||||
double const m_minvoltage;
|
||||
double const m_inittime;
|
||||
double m_initcountdown;
|
||||
double m_closing{0.0};
|
||||
double m_voltage{0.0};
|
||||
double m_current{0.0};
|
||||
bool m_closed{false};
|
||||
bool m_tripped{false};
|
||||
};
|
||||
|
||||
class el_contactor final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
on,
|
||||
out,
|
||||
closed
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_contactor(block_setup const &Setup) : block(Setup), m_delay(number(Setup, 0)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
m_voltage = forward_in(Frame, port::in);
|
||||
add_back(Frame, port::in, back_out(Frame, port::out));
|
||||
if (get_bool(Frame, port::on))
|
||||
{
|
||||
m_timer += Frame.dt;
|
||||
m_closed = m_timer >= m_delay;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_timer = 0.0;
|
||||
m_closed = false;
|
||||
}
|
||||
}
|
||||
void commit(frame &Frame) override
|
||||
{
|
||||
set_forward(Frame, port::out, m_closed ? m_voltage : 0.0);
|
||||
set_bool(Frame, port::closed, m_closed);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.contactor", "Contactor", "Connects the output to the input after on was held for the delay; disconnects at once.", block_origin::primitive)};
|
||||
type.ports = {physical_in("in", domain::high_voltage), signal_in("on", value_type::boolean), physical_out("out", domain::high_voltage),
|
||||
signal_out("closed", value_type::boolean)};
|
||||
type.params = {number_param("delay", 0.0, "s")};
|
||||
type.deferred = true;
|
||||
type.create = make_block<el_contactor>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_delay;
|
||||
double m_timer{0.0};
|
||||
double m_voltage{0.0};
|
||||
bool m_closed{false};
|
||||
};
|
||||
|
||||
// static converter or motor generator: auxiliary supply from the high voltage, and optionally the onboard low voltage
|
||||
// (charger). its state is the ConverterFlag of the body; with more converters the flag is set when any of them runs
|
||||
class el_converter final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
wn,
|
||||
on,
|
||||
aux,
|
||||
run,
|
||||
nn
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
power,
|
||||
start_time,
|
||||
min_voltage,
|
||||
out_voltage,
|
||||
nn_voltage,
|
||||
aux_kind
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_converter(block_setup const &Setup)
|
||||
: block(Setup), m_idlepower(number(Setup, param::power) * 0.05), m_starttime(number(Setup, param::start_time)), m_minvoltage(number(Setup, param::min_voltage)),
|
||||
m_outvoltage(number(Setup, param::out_voltage)), m_nnvoltage(std::max(0.0, number(Setup, param::nn_voltage)))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const voltage{forward_in(Frame, port::wn)};
|
||||
auto const on{get_bool(Frame, port::on)};
|
||||
if (on && voltage > 0.0 && voltage >= m_minvoltage)
|
||||
{
|
||||
m_timer += Frame.dt;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_timer = 0.0;
|
||||
}
|
||||
auto const run{on && voltage > 0.0 && voltage >= m_minvoltage && m_timer >= m_starttime};
|
||||
body.ConverterAllow = Frame.body->converter_allow.any(Frame.step, on);
|
||||
body.ConverterFlag = Frame.body->converter.any(Frame.step, run);
|
||||
set_forward(Frame, port::aux, run ? m_outvoltage : 0.0);
|
||||
set_forward(Frame, port::nn, run ? m_nnvoltage : 0.0);
|
||||
if (run)
|
||||
{
|
||||
// power taken by the auxiliaries and the low voltage loads in the previous step, and the losses
|
||||
auto const nnpower{m_nnvoltage * std::max(0.0, back_out(Frame, port::nn))};
|
||||
add_back(Frame, port::wn, (back_out(Frame, port::aux) + nnpower + m_idlepower) / voltage);
|
||||
}
|
||||
set_bool(Frame, port::run, run);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.converter", "Converter",
|
||||
"Converter of the auxiliary supply. Runs start_time after it was switched on with enough voltage; draws the power of its loads from the high "
|
||||
"voltage. nn is the onboard low voltage made by its charger (off when nn_voltage is 0). aux_kind only names the auxiliary circuit.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("wn", domain::high_voltage), signal_in("on", value_type::boolean), physical_out("aux", domain::aux_ac), signal_out("run", value_type::boolean),
|
||||
physical_out("nn", domain::low_voltage)};
|
||||
// aux_kind describes the auxiliary circuit for the editor (three phase, single phase or direct current); the simulator ignores it
|
||||
type.params = {number_param("power", 50000.0, "W"), number_param("start_time", 2.0, "s"), number_param("min_voltage", 0.0, "V"),
|
||||
number_param("out_voltage", 400.0, "V"), number_param("nn_voltage", 0.0, "V"), choice_param("aux_kind", {"ac3", "ac1", "dc"}, "ac3")};
|
||||
type.create = make_block<el_converter>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_idlepower;
|
||||
double const m_starttime;
|
||||
double const m_minvoltage;
|
||||
double const m_outvoltage;
|
||||
double const m_nnvoltage;
|
||||
double m_timer{0.0};
|
||||
};
|
||||
|
||||
// voltage relay: tells whether the supply is within a range, e.g. to recognize the supply system of a multi-system
|
||||
// vehicle. the simulator gives the vehicles only the voltage of the catenary, so the systems are told apart by it
|
||||
class el_voltage_relay final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
ok,
|
||||
u
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
min_voltage,
|
||||
max_voltage,
|
||||
delay
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_voltage_relay(block_setup const &Setup)
|
||||
: block(Setup), m_minvoltage(number(Setup, param::min_voltage)), m_maxvoltage(number(Setup, param::max_voltage)), m_delay(number(Setup, param::delay))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
// the relay draws no current
|
||||
auto const voltage{forward_in(Frame, port::in)};
|
||||
auto const inrange{voltage > 0.0 && voltage >= m_minvoltage && (m_maxvoltage <= 0.0 || voltage <= m_maxvoltage)};
|
||||
m_timer = inrange ? m_timer + Frame.dt : 0.0;
|
||||
set_bool(Frame, port::ok, inrange && m_timer >= m_delay);
|
||||
set(Frame, port::u, voltage);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.voltage_relay", "Voltage relay",
|
||||
"ok is set when the voltage was within min_voltage and max_voltage (0: no upper limit) for the delay. Draws no current; used e.g. to recognize "
|
||||
"the supply system.",
|
||||
block_origin::primitive)};
|
||||
type.ports = {physical_in("in", domain::high_voltage), signal_out("ok", value_type::boolean), signal_out("u", value_type::real, "V")};
|
||||
type.params = {number_param("min_voltage", 2200.0, "V"), number_param("max_voltage", 4000.0, "V"), number_param("delay", 0.5, "s")};
|
||||
type.create = make_block<el_voltage_relay>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_minvoltage;
|
||||
double const m_maxvoltage;
|
||||
double const m_delay;
|
||||
double m_timer{0.0};
|
||||
};
|
||||
|
||||
// main transformer of an ac vehicle, or of the ac branch of a multi-system one, with the rectifier behind it implied.
|
||||
// the output voltage is the input voltage times the ratio; the current drawn from the input follows from the power
|
||||
class el_transformer final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
out
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
ratio,
|
||||
eff
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_transformer(block_setup const &Setup)
|
||||
: block(Setup), m_ratio(std::max(0.0, number(Setup, param::ratio))), m_efficiency(std::clamp(number(Setup, param::eff), 0.01, 1.0))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
set_forward(Frame, port::out, forward_in(Frame, port::in) * m_ratio);
|
||||
add_back(Frame, port::in, back_out(Frame, port::out) * m_ratio / m_efficiency);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.transformer", "Transformer",
|
||||
"Output voltage is the input voltage times the ratio (out/in); the input current is the output current times ratio / eff. For ac vehicles "
|
||||
"and the ac branches of multi-system vehicles, with the rectifier implied.",
|
||||
block_origin::primitive)};
|
||||
type.ports = {physical_in("in", domain::high_voltage), physical_out("out", domain::high_voltage)};
|
||||
type.params = {number_param("ratio", 0.12, ""), number_param("eff", 0.97, "")};
|
||||
type.create = make_block<el_transformer>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_ratio;
|
||||
double const m_efficiency;
|
||||
};
|
||||
|
||||
// battery of the low voltage circuits; its switch is the Battery of the body
|
||||
class el_battery final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
on,
|
||||
charge,
|
||||
nn,
|
||||
ok,
|
||||
u
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
voltage,
|
||||
capacity,
|
||||
min_voltage
|
||||
};
|
||||
};
|
||||
|
||||
explicit el_battery(block_setup const &Setup)
|
||||
: block(Setup), m_charger(is_connected(Setup, port::charge)), m_voltage(number(Setup, param::voltage)), m_capacity(std::max(0.1, number(Setup, param::capacity))),
|
||||
m_minvoltage(number(Setup, param::min_voltage)), m_charge(m_capacity)
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const on{get_bool(Frame, port::on)};
|
||||
if (on)
|
||||
{
|
||||
// current of the loads in the previous step, A
|
||||
m_charge -= std::max(0.0, back_out(Frame, port::nn)) * Frame.dt / 3600.0;
|
||||
}
|
||||
if (m_charger && forward_in(Frame, port::charge) > 0.0)
|
||||
{
|
||||
// charged at a tenth of the capacity per hour
|
||||
m_charge += 0.1 * m_capacity * Frame.dt / 3600.0;
|
||||
}
|
||||
m_charge = std::clamp(m_charge, 0.0, m_capacity);
|
||||
// open circuit voltage drops with the charge
|
||||
auto const voltage{on ? m_voltage * (0.85 + 0.15 * m_charge / m_capacity) : 0.0};
|
||||
body.Battery = on;
|
||||
body.BatteryVoltage = voltage;
|
||||
set_forward(Frame, port::nn, voltage);
|
||||
set_bool(Frame, port::ok, on && voltage >= m_minvoltage);
|
||||
set(Frame, port::u, voltage);
|
||||
}
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "charge";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_charge;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("el.battery", "Battery",
|
||||
"Battery of the low voltage circuits. ok tells whether its voltage is above min_voltage; it's charged when the charge input has voltage.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {signal_in("on", value_type::boolean), physical_in("charge", domain::aux_ac, true), physical_out("nn", domain::low_voltage),
|
||||
signal_out("ok", value_type::boolean), signal_out("u", value_type::real, "V")};
|
||||
type.params = {number_param("voltage", 110.0, "V"), number_param("capacity", 100.0, "Ah"), number_param("min_voltage", 80.0, "V")};
|
||||
type.single = true;
|
||||
type.create = make_block<el_battery>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_charger;
|
||||
double const m_voltage;
|
||||
double const m_capacity;
|
||||
double const m_minvoltage;
|
||||
double m_charge;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_electric_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(el_pantograph::describe());
|
||||
Catalog.add(el_junction::describe());
|
||||
Catalog.add(el_hsb::describe());
|
||||
Catalog.add(el_contactor::describe());
|
||||
Catalog.add(el_converter::describe());
|
||||
Catalog.add(el_battery::describe());
|
||||
Catalog.add(el_voltage_relay::describe());
|
||||
Catalog.add(el_transformer::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
468
vehicle/graph/blocks/interfaces.cpp
Normal file
468
vehicle/graph/blocks/interfaces.cpp
Normal file
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_body.h"
|
||||
#include "vehicle/graph/graph_instance.h"
|
||||
#include "McZapkie/MOVER.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
// standard interfaces: between the vehicle and its neighbours (brake pipe, feed pipe, train heating), and between the
|
||||
// vehicle and the AI. the neighbours may be .fiz vehicles, so these blocks work through the couplers of the vehicle body
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
TMoverParameters &vehicle(frame const &Frame)
|
||||
{
|
||||
return *Frame.body->mover;
|
||||
}
|
||||
|
||||
block_type make_type(std::string Id, std::string Name, std::string Description)
|
||||
{
|
||||
block_type type;
|
||||
type.id = std::move(Id);
|
||||
type.category = "interfaces";
|
||||
type.name = std::move(Name);
|
||||
type.description = std::move(Description);
|
||||
type.origin = block_origin::interface_profile;
|
||||
return type;
|
||||
}
|
||||
|
||||
param_def side_param()
|
||||
{
|
||||
return choice_param("side", {"front", "rear"}, "front");
|
||||
}
|
||||
|
||||
// end cock of the brake pipe, or of the feed pipe, at one end of the vehicle
|
||||
class if_cock final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
pipe,
|
||||
cock
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
side,
|
||||
cock
|
||||
};
|
||||
};
|
||||
|
||||
if_cock(block_setup const &Setup, bool const Brakepipe)
|
||||
: block(Setup), m_brakepipe(Brakepipe), m_side(choice(Setup, param::side) == 0 ? end::front : end::rear), m_switched(is_connected(Setup, port::cock)),
|
||||
m_open(boolean(Setup, param::cock))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const open{m_switched ? get_bool(Frame, port::cock) : m_open};
|
||||
auto &coupler{vehicle(Frame).Couplers[m_side]};
|
||||
if (m_brakepipe)
|
||||
{
|
||||
coupler.BrakePipeCock = open;
|
||||
}
|
||||
else
|
||||
{
|
||||
coupler.FeedPipeCock = open;
|
||||
}
|
||||
}
|
||||
|
||||
static block_type describe(bool const Brakepipe)
|
||||
{
|
||||
auto type{Brakepipe ? make_type("if.pg", "Brake pipe coupling",
|
||||
"End cock of the brake pipe at one end of the vehicle. Open (the default), the brake pipe exchanges air with the neighbouring vehicle.")
|
||||
: make_type("if.pz", "Feed pipe coupling",
|
||||
"End cock (yellow) of the feed pipe at one end of the vehicle. Closed by default; open, the feed pipe exchanges air with the neighbour.")};
|
||||
type.ports = {Brakepipe ? physical_in("pg", domain::brake_pipe) : physical_in("pz", domain::feed_pipe), signal_in("cock", value_type::boolean, 0.0, "", true)};
|
||||
type.params = {side_param(), boolean_param("cock", Brakepipe)};
|
||||
if (Brakepipe)
|
||||
{
|
||||
type.create = [](block_setup const &Setup) -> std::unique_ptr<block> { return std::make_unique<if_cock>(Setup, true); };
|
||||
}
|
||||
else
|
||||
{
|
||||
type.create = [](block_setup const &Setup) -> std::unique_ptr<block> { return std::make_unique<if_cock>(Setup, false); };
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_brakepipe;
|
||||
int const m_side;
|
||||
bool const m_switched;
|
||||
bool const m_open;
|
||||
};
|
||||
|
||||
// train heating supply through the couplers. the voltage comes from the main circuit of the vehicle (Mains)
|
||||
class if_heat final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
wn,
|
||||
on,
|
||||
i
|
||||
};
|
||||
};
|
||||
|
||||
explicit if_heat(block_setup const &Setup) : block(Setup), m_side(choice(Setup, 0) == 0 ? end::front : end::rear) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const voltage{forward_in(Frame, port::wn)};
|
||||
auto const on{get_bool(Frame, port::on)};
|
||||
body.HeatingAllow = on;
|
||||
body.Heating = on && voltage > 0.0;
|
||||
auto const current{std::abs(body.Couplers[m_side].power_high.current)};
|
||||
add_back(Frame, port::wn, current);
|
||||
set(Frame, port::i, current);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("if.heat", "Train heating",
|
||||
"Train heating supply through the coupler at one end: on connects the main circuit (behind the line breaker) to the heating line. i is the "
|
||||
"current the train draws.")};
|
||||
type.ports = {physical_in("wn", domain::high_voltage), signal_in("on", value_type::boolean, 1.0, "", true), signal_out("i", value_type::real, "A")};
|
||||
type.params = {side_param()};
|
||||
type.create = make_block<if_heat>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
int const m_side;
|
||||
};
|
||||
|
||||
// intents of the AI driver, and the feedback it reads. graph vehicles aren't driven by the AI yet: the outputs stay
|
||||
// at rest until the AI adapter of the graph vehicles is in place
|
||||
class if_ai final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
ready,
|
||||
i_ratio,
|
||||
slip,
|
||||
pg,
|
||||
zg,
|
||||
bc,
|
||||
vmax,
|
||||
dir,
|
||||
traction,
|
||||
ed_brake,
|
||||
brake,
|
||||
emergency,
|
||||
local_brake,
|
||||
prepare,
|
||||
shutdown,
|
||||
doors_left,
|
||||
doors_right,
|
||||
lights,
|
||||
horn,
|
||||
pantographs,
|
||||
sand
|
||||
};
|
||||
};
|
||||
|
||||
explicit if_ai(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const &intents{Frame.body->ai};
|
||||
set(Frame, port::dir, intents.dir);
|
||||
set(Frame, port::traction, intents.traction);
|
||||
set(Frame, port::ed_brake, intents.ed_brake);
|
||||
set(Frame, port::brake, intents.brake);
|
||||
set_bool(Frame, port::emergency, intents.emergency);
|
||||
set(Frame, port::local_brake, intents.local_brake);
|
||||
set_bool(Frame, port::prepare, intents.prepare);
|
||||
set_bool(Frame, port::shutdown, intents.shutdown);
|
||||
set_bool(Frame, port::doors_left, intents.doors_left);
|
||||
set_bool(Frame, port::doors_right, intents.doors_right);
|
||||
set(Frame, port::lights, intents.lights);
|
||||
set_bool(Frame, port::horn, intents.horn);
|
||||
set_bool(Frame, port::pantographs, intents.pantographs);
|
||||
set_bool(Frame, port::sand, intents.sand);
|
||||
auto &feedback{Frame.body->ai_reply};
|
||||
feedback.ready = get_bool(Frame, port::ready);
|
||||
feedback.i_ratio = get(Frame, port::i_ratio);
|
||||
feedback.slip = get_bool(Frame, port::slip);
|
||||
feedback.pg = get(Frame, port::pg);
|
||||
feedback.zg = get(Frame, port::zg);
|
||||
feedback.bc = get(Frame, port::bc);
|
||||
feedback.vmax = get(Frame, port::vmax);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("if.ai", "AI interface",
|
||||
"What the AI driver wants (outputs) and what it needs to know (inputs). The vehicle's graph translates the intents to its controls. Not "
|
||||
"used by the simulator yet: the AI doesn't drive graph vehicles.")};
|
||||
type.ports = {signal_in("ready", value_type::boolean, 0.0, "", true),
|
||||
signal_in("i_ratio", value_type::real, 0.0, "", true),
|
||||
signal_in("slip", value_type::boolean, 0.0, "", true),
|
||||
signal_in("pg", value_type::real, 0.0, "bar", true),
|
||||
signal_in("zg", value_type::real, 0.0, "bar", true),
|
||||
signal_in("bc", value_type::real, 0.0, "bar", true),
|
||||
signal_in("vmax", value_type::real, 0.0, "km/h", true),
|
||||
signal_out("dir", value_type::real),
|
||||
signal_out("traction", value_type::real),
|
||||
signal_out("ed_brake", value_type::real),
|
||||
signal_out("brake", value_type::real),
|
||||
signal_out("emergency", value_type::boolean),
|
||||
signal_out("local_brake", value_type::real),
|
||||
signal_out("prepare", value_type::boolean),
|
||||
signal_out("shutdown", value_type::boolean),
|
||||
signal_out("doors_left", value_type::boolean),
|
||||
signal_out("doors_right", value_type::boolean),
|
||||
signal_out("lights", value_type::integer),
|
||||
signal_out("horn", value_type::boolean),
|
||||
signal_out("pantographs", value_type::boolean),
|
||||
signal_out("sand", value_type::boolean)};
|
||||
type.single = true;
|
||||
type.create = make_block<if_ai>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
// control line of the multiple unit control: a named train wire shared by the coupled graph vehicles. every vehicle
|
||||
// publishes its contribution in its body state (body_state::lines); the others read what it published last, so the
|
||||
// values pass between the vehicles with up to one step of delay. the line ends at a vehicle without a graph, at
|
||||
// a vehicle without the line, or at a coupling without the required flag
|
||||
class if_line final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
active,
|
||||
out,
|
||||
vehicles,
|
||||
drivers
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
name,
|
||||
combine,
|
||||
reverse,
|
||||
pair,
|
||||
coupling
|
||||
};
|
||||
};
|
||||
enum class combination
|
||||
{
|
||||
max,
|
||||
min,
|
||||
sum,
|
||||
maxabs,
|
||||
any
|
||||
};
|
||||
|
||||
explicit if_line(block_setup const &Setup)
|
||||
: block(Setup), m_name(text(Setup, param::name)), m_pair(text(Setup, param::pair).empty() ? m_name : text(Setup, param::pair)),
|
||||
m_combine(static_cast<combination>(std::max(0, choice(Setup, param::combine)))), m_reverse(boolean(Setup, param::reverse)),
|
||||
m_flag(coupling_flag(choice(Setup, param::coupling))), m_drives(is_connected(Setup, port::in))
|
||||
{
|
||||
m_hints.fill(0);
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &lines{Frame.body->lines};
|
||||
if (m_entry < 0)
|
||||
{
|
||||
// the entry of the line in this vehicle, shared with the other blocks of the same line
|
||||
m_entry = find(lines, m_name, 0);
|
||||
if (m_entry < 0)
|
||||
{
|
||||
lines.push_back({m_name, 0.0, false});
|
||||
m_entry = static_cast<int>(lines.size()) - 1;
|
||||
}
|
||||
}
|
||||
auto &own{lines[m_entry]};
|
||||
if (m_drives)
|
||||
{
|
||||
own.value = get(Frame, port::in);
|
||||
own.active = get_bool(Frame, port::active);
|
||||
}
|
||||
m_drivers = 0;
|
||||
m_vehicles = 1;
|
||||
if (own.active)
|
||||
{
|
||||
add(own.value);
|
||||
}
|
||||
auto const *body{Frame.body->mover};
|
||||
for (int side = 0; side < 2; ++side)
|
||||
{
|
||||
walk(*body, side, m_hints.data() + side * max_vehicles);
|
||||
}
|
||||
set(Frame, port::out, m_drivers > 0 ? m_result : 0.0);
|
||||
set(Frame, port::vehicles, m_vehicles);
|
||||
set(Frame, port::drivers, m_drivers);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("if.line", "Train line",
|
||||
"Named control line of the multiple unit control, shared by the coupled vehicles with the line (the coupling needs the selected flag). out "
|
||||
"combines the values of the vehicles driving the line (in connected and active set), in the orientation of this vehicle: with reverse the "
|
||||
"value of a vehicle facing the other way changes sign, and such a vehicle gives its pair line (if set) in place of this one.")};
|
||||
type.ports = {signal_in("in", value_type::real, 0.0, "", true), signal_in("active", value_type::boolean, 1.0, "", true), signal_out("out", value_type::real),
|
||||
signal_out("vehicles", value_type::integer), signal_out("drivers", value_type::integer)};
|
||||
type.params = {text_param("name", "linia"), choice_param("combine", {"max", "min", "sum", "maxabs", "or"}, "max"), boolean_param("reverse", false), text_param("pair"),
|
||||
choice_param("coupling", {"control", "permanent", "coupler", "highvoltage", "heating", "power110v", "power24v", "power3x400v"}, "control")};
|
||||
type.create = make_block<if_line>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
// longest chain of vehicles followed in each direction, also the guard against coupling loops
|
||||
static constexpr int max_vehicles{64};
|
||||
|
||||
static int coupling_flag(int const Choice)
|
||||
{
|
||||
static int const flags[]{coupling::control, coupling::permanent, coupling::coupler, coupling::highvoltage, coupling::heating, coupling::power110v, coupling::power24v,
|
||||
coupling::power3x400v};
|
||||
return flags[std::clamp(Choice, 0, static_cast<int>(std::size(flags)) - 1)];
|
||||
}
|
||||
|
||||
// index of the line with the specified name, checking the hint first, or -1
|
||||
static int find(std::vector<train_line_value> const &Lines, std::string const &Name, int const Hint)
|
||||
{
|
||||
if (Hint >= 0 && Hint < static_cast<int>(Lines.size()) && Lines[Hint].name == Name)
|
||||
{
|
||||
return Hint;
|
||||
}
|
||||
for (std::size_t idx = 0; idx < Lines.size(); ++idx)
|
||||
{
|
||||
if (Lines[idx].name == Name)
|
||||
{
|
||||
return static_cast<int>(idx);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void add(double const Value)
|
||||
{
|
||||
if (m_drivers == 0)
|
||||
{
|
||||
m_result = (m_combine == combination::any ? (Value != 0.0 ? 1.0 : 0.0) : Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (m_combine)
|
||||
{
|
||||
case combination::max:
|
||||
m_result = std::max(m_result, Value);
|
||||
break;
|
||||
case combination::min:
|
||||
m_result = std::min(m_result, Value);
|
||||
break;
|
||||
case combination::sum:
|
||||
m_result += Value;
|
||||
break;
|
||||
case combination::maxabs:
|
||||
m_result = (std::abs(Value) > std::abs(m_result) ? Value : m_result);
|
||||
break;
|
||||
case combination::any:
|
||||
m_result = (m_result != 0.0 || Value != 0.0 ? 1.0 : 0.0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
++m_drivers;
|
||||
}
|
||||
|
||||
// follows the coupled vehicles from the specified side of the vehicle. a neighbour coupled by the same side as the one
|
||||
// leading to it (front to front, rear to rear) faces the other way
|
||||
void walk(TMoverParameters const &Body, int Side, int *Hints)
|
||||
{
|
||||
auto const *current{&Body};
|
||||
auto facing{1}; // orientation of the current vehicle relative to this one
|
||||
for (int count = 0; count < max_vehicles; ++count)
|
||||
{
|
||||
auto const &coupler{current->Couplers[Side]};
|
||||
if (coupler.Connected == nullptr || (coupler.CouplingFlag & m_flag) == 0 || coupler.Connected == &Body)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const *neighbour{coupler.Connected};
|
||||
auto const connectedside{coupler.ConnectedNr};
|
||||
facing = (Side != connectedside ? facing : -facing);
|
||||
if (neighbour->Graph == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const &lines{neighbour->Graph->body().lines};
|
||||
auto const index{find(lines, facing > 0 ? m_name : m_pair, Hints[count])};
|
||||
if (index < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Hints[count] = index;
|
||||
++m_vehicles;
|
||||
auto const &line{lines[index]};
|
||||
if (line.active)
|
||||
{
|
||||
add(m_reverse && facing < 0 ? -line.value : line.value);
|
||||
}
|
||||
current = neighbour;
|
||||
Side = 1 - connectedside;
|
||||
}
|
||||
}
|
||||
|
||||
std::string const m_name;
|
||||
std::string const m_pair;
|
||||
combination const m_combine;
|
||||
bool const m_reverse;
|
||||
int const m_flag;
|
||||
bool const m_drives;
|
||||
int m_entry{-1};
|
||||
std::array<int, 2 * max_vehicles> m_hints;
|
||||
double m_result{0.0};
|
||||
int m_drivers{0};
|
||||
int m_vehicles{1};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_interface_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(if_cock::describe(true));
|
||||
Catalog.add(if_cock::describe(false));
|
||||
Catalog.add(if_heat::describe());
|
||||
Catalog.add(if_ai::describe());
|
||||
Catalog.add(if_line::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
278
vehicle/graph/blocks/lua_block.cpp
Normal file
278
vehicle/graph/blocks/lua_block.cpp
Normal 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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_format.h"
|
||||
#include "vehicle/graph/graph_lua.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#ifdef WITH_LUA
|
||||
#include <lua.hpp>
|
||||
#endif
|
||||
|
||||
// function block defined by a lua script. its ports and numeric parameters come from the script
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool resolve_lua_block(node_desc const &Node, std::vector<port_def> &Ports, std::vector<param_def> &Params, std::string &Error)
|
||||
{
|
||||
if (false == lua_available())
|
||||
{
|
||||
Error = "lua blocks need a simulator built with lua support (WITH_LUA)";
|
||||
return false;
|
||||
}
|
||||
if (Node.script.empty())
|
||||
{
|
||||
Error = "lua block without a script";
|
||||
return false;
|
||||
}
|
||||
lua_block_definition definition;
|
||||
if (false == parse_lua_block(Node.script, Node.id, definition, Error))
|
||||
{
|
||||
Error = "lua: " + Error;
|
||||
return false;
|
||||
}
|
||||
Ports = std::move(definition.ports);
|
||||
Params = std::move(definition.params);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef WITH_LUA
|
||||
|
||||
class lua_block final : public block
|
||||
{
|
||||
public:
|
||||
explicit lua_block(block_setup const &Setup) : block(Setup), m_host(Setup.lua), m_nodeid(Setup.node_id)
|
||||
{
|
||||
for (std::size_t idx = 0; idx < Setup.ports.size(); ++idx)
|
||||
{
|
||||
auto const &port{Setup.ports[idx]};
|
||||
(port.direction == port_direction::in ? m_inputs : m_outputs).push_back({idx, port.key, port.type});
|
||||
}
|
||||
if (m_host == nullptr || m_host->state() == nullptr)
|
||||
{
|
||||
fail("no lua state");
|
||||
return;
|
||||
}
|
||||
std::string error;
|
||||
m_definition = m_host->load_block(Setup.script, Setup.node_id, error);
|
||||
if (m_definition < 0)
|
||||
{
|
||||
fail(error);
|
||||
return;
|
||||
}
|
||||
auto *L{m_host->state()};
|
||||
auto const top{lua_gettop(L)};
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_definition);
|
||||
auto const definition{lua_gettop(L)};
|
||||
lua_getfield(L, definition, "rate");
|
||||
auto const rate{lua_isnumber(L, -1) ? lua_tonumber(L, -1) : 0.0};
|
||||
lua_pop(L, 1);
|
||||
m_period = (rate > 0.0 ? 1.0 / rate : 0.0);
|
||||
// self: copy of the state table, and the parameters with values from the graph for the numeric ones
|
||||
lua_newtable(L);
|
||||
auto const self{lua_gettop(L)};
|
||||
lua_getfield(L, definition, "state");
|
||||
if (lua_istable(L, -1))
|
||||
{
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0)
|
||||
{
|
||||
lua_pushvalue(L, -2);
|
||||
lua_insert(L, -2);
|
||||
lua_rawset(L, self);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, definition, "params");
|
||||
if (lua_istable(L, -1))
|
||||
{
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0)
|
||||
{
|
||||
if (lua_type(L, -2) == LUA_TSTRING && lua_type(L, -1) == LUA_TNUMBER)
|
||||
{
|
||||
auto const paramindex{find_param(Setup.param_defs, std::string{"lua."} + lua_tostring(L, -2))};
|
||||
if (paramindex >= 0)
|
||||
{
|
||||
lua_pop(L, 1);
|
||||
lua_pushnumber(L, Setup.params[paramindex].number);
|
||||
}
|
||||
}
|
||||
lua_pushvalue(L, -2);
|
||||
lua_insert(L, -2);
|
||||
lua_rawset(L, self);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
m_self = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
lua_newtable(L);
|
||||
m_in = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
lua_newtable(L);
|
||||
m_out = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
lua_settop(L, top);
|
||||
}
|
||||
|
||||
~lua_block() override
|
||||
{
|
||||
if (m_host != nullptr)
|
||||
{
|
||||
m_host->release(m_definition);
|
||||
m_host->release(m_self);
|
||||
m_host->release(m_in);
|
||||
m_host->release(m_out);
|
||||
}
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
if (m_fault)
|
||||
{
|
||||
// outputs keep their last values
|
||||
return;
|
||||
}
|
||||
auto dt{Frame.dt};
|
||||
if (m_period > 0.0)
|
||||
{
|
||||
m_accumulated += Frame.dt;
|
||||
if (m_accumulated + 1e-9 < m_period)
|
||||
{
|
||||
return;
|
||||
}
|
||||
dt = m_accumulated;
|
||||
m_accumulated = 0.0;
|
||||
}
|
||||
auto *L{m_host->state()};
|
||||
auto const top{lua_gettop(L)};
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_in);
|
||||
for (auto const &input : m_inputs)
|
||||
{
|
||||
auto const value{get(Frame, input.port)};
|
||||
switch (input.type)
|
||||
{
|
||||
case value_type::boolean:
|
||||
lua_pushboolean(L, value > 0.5);
|
||||
break;
|
||||
case value_type::integer:
|
||||
lua_pushnumber(L, std::round(value));
|
||||
break;
|
||||
default:
|
||||
lua_pushnumber(L, value);
|
||||
break;
|
||||
}
|
||||
lua_setfield(L, -2, input.key.c_str());
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_definition);
|
||||
lua_getfield(L, -1, "update");
|
||||
lua_remove(L, -2);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_self);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_in);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_out);
|
||||
lua_pushnumber(L, dt);
|
||||
std::string error;
|
||||
if (false == m_host->call(4, error))
|
||||
{
|
||||
lua_settop(L, top);
|
||||
fail(error);
|
||||
return;
|
||||
}
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_out);
|
||||
for (auto const &output : m_outputs)
|
||||
{
|
||||
lua_getfield(L, -1, output.key.c_str());
|
||||
auto value{0.0};
|
||||
if (lua_isboolean(L, -1))
|
||||
{
|
||||
value = (lua_toboolean(L, -1) ? 1.0 : 0.0);
|
||||
}
|
||||
else if (lua_type(L, -1) == LUA_TNUMBER)
|
||||
{
|
||||
value = lua_tonumber(L, -1);
|
||||
if (output.type == value_type::integer)
|
||||
{
|
||||
value = std::round(value);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
set(Frame, output.port, value);
|
||||
}
|
||||
lua_settop(L, top);
|
||||
}
|
||||
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "fault";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_fault ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
struct port_entry
|
||||
{
|
||||
std::size_t port;
|
||||
std::string key;
|
||||
value_type type;
|
||||
};
|
||||
|
||||
void fail(std::string const &Reason)
|
||||
{
|
||||
// a failing block stops, and the rest of the vehicle keeps working
|
||||
m_fault = true;
|
||||
log_message("lua [" + m_nodeid + "]: block stopped: " + Reason);
|
||||
}
|
||||
|
||||
lua_host *m_host;
|
||||
std::string m_nodeid;
|
||||
std::vector<port_entry> m_inputs;
|
||||
std::vector<port_entry> m_outputs;
|
||||
int m_definition{-1};
|
||||
int m_self{-1};
|
||||
int m_in{-1};
|
||||
int m_out{-1};
|
||||
double m_period{0.0};
|
||||
double m_accumulated{0.0};
|
||||
bool m_fault{false};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_script_blocks(catalog &Catalog)
|
||||
{
|
||||
block_type type;
|
||||
type.id = "lua.block";
|
||||
type.category = "script";
|
||||
type.name = "Lua block";
|
||||
type.description = "Function block defined by a lua 5.1 script: block \"name\" { inputs, outputs, params, state, rate, update = function(self, i, o, dt) ... end }.";
|
||||
type.origin = block_origin::script;
|
||||
type.dynamic_ports = true;
|
||||
type.resolve = resolve_lua_block;
|
||||
#ifdef WITH_LUA
|
||||
type.create = make_block<lua_block>;
|
||||
#else
|
||||
// graphs with lua blocks are rejected when loaded, so blocks of this type are never created
|
||||
type.create = nullptr;
|
||||
#endif
|
||||
Catalog.add(std::move(type));
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
186
vehicle/graph/blocks/module.cpp
Normal file
186
vehicle/graph/blocks/module.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_format.h"
|
||||
|
||||
// modules: graphs of logic made once and used by many vehicles. a node of type "module" names the file of the module;
|
||||
// when the vehicle graph is loaded, the node is replaced with the blocks of the module (expand_modules), connected
|
||||
// through the module's interface blocks, module.input and module.output, one per port of the module.
|
||||
// the interface blocks work also on their own, so a module file can be tried with -graph-run: an input gives its
|
||||
// default value (forced values come from the script), an output shows the value it receives
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::vector<std::string> interface_types()
|
||||
{
|
||||
std::vector<std::string> result{"bool", "int", "float"};
|
||||
for (std::size_t idx = 1; idx < static_cast<std::size_t>(domain::count_); ++idx)
|
||||
{
|
||||
result.emplace_back(info(static_cast<domain>(idx)).id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<param_def> interface_params(bool const Input)
|
||||
{
|
||||
std::vector<param_def> params{choice_param("type", interface_types(), "float")};
|
||||
if (Input)
|
||||
{
|
||||
params.push_back(number_param("default", 0.0));
|
||||
}
|
||||
params.push_back(text_param("label"));
|
||||
return params;
|
||||
}
|
||||
|
||||
// the port of an interface block, of the type chosen by its "type" parameter
|
||||
bool interface_port(node_desc const &Node, bool const Input, std::vector<port_def> &Ports, std::vector<param_def> &Params, std::string &Error)
|
||||
{
|
||||
std::string type{"float"};
|
||||
for (auto const ¶m : Node.params)
|
||||
{
|
||||
if (param.first == "type")
|
||||
{
|
||||
type = param.second.text;
|
||||
}
|
||||
}
|
||||
// the input of the module is an output of the interface block inside it, and the other way round
|
||||
auto const key{Input ? "out" : "in"};
|
||||
if (type == "bool" || type == "int" || type == "float")
|
||||
{
|
||||
auto const valuetype{type == "bool" ? value_type::boolean : type == "int" ? value_type::integer : value_type::real};
|
||||
Ports = {Input ? signal_out(key, valuetype) : signal_in(key, valuetype, 0.0, "", true)};
|
||||
}
|
||||
else
|
||||
{
|
||||
domain dom;
|
||||
if (false == parse_domain(type, dom) || dom == domain::signal)
|
||||
{
|
||||
Error = "unknown type \"" + type + "\" of the module port; expected bool, int, float or a physical domain";
|
||||
return false;
|
||||
}
|
||||
Ports = {Input ? physical_out(key, dom) : physical_in(key, dom, true)};
|
||||
}
|
||||
Params = interface_params(Input);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool resolve_input(node_desc const &Node, std::vector<port_def> &Ports, std::vector<param_def> &Params, std::string &Error)
|
||||
{
|
||||
return interface_port(Node, true, Ports, Params, Error);
|
||||
}
|
||||
|
||||
bool resolve_output(node_desc const &Node, std::vector<port_def> &Ports, std::vector<param_def> &Params, std::string &Error)
|
||||
{
|
||||
return interface_port(Node, false, Ports, Params, Error);
|
||||
}
|
||||
|
||||
bool resolve_module(node_desc const &, std::vector<port_def> &, std::vector<param_def> &, std::string &Error)
|
||||
{
|
||||
// expand_modules replaces these nodes when the file is loaded, so one left here means the module couldn't be read
|
||||
Error = "the module wasn't expanded; graphs with modules have to be loaded from a file, next to the module files";
|
||||
return false;
|
||||
}
|
||||
|
||||
class module_input final : public block
|
||||
{
|
||||
public:
|
||||
explicit module_input(block_setup const &Setup) : block(Setup), m_signal(Setup.ports[0].dom == domain::signal), m_value(number(Setup, 1)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
if (m_signal)
|
||||
{
|
||||
set(Frame, 0, m_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
set_forward(Frame, 0, m_value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_signal;
|
||||
double const m_value;
|
||||
};
|
||||
|
||||
class module_output final : public block
|
||||
{
|
||||
public:
|
||||
explicit module_output(block_setup const &Setup) : block(Setup), m_signal(Setup.ports[0].dom == domain::signal), m_connected(is_connected(Setup, 0)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
if (false == m_connected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_value = m_signal ? get(Frame, 0) : forward_in(Frame, 0);
|
||||
}
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "value";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_signal;
|
||||
bool const m_connected;
|
||||
double m_value{0.0};
|
||||
};
|
||||
|
||||
block_type interface_type(bool const Input)
|
||||
{
|
||||
block_type type;
|
||||
type.id = Input ? "module.input" : "module.output";
|
||||
type.category = "module";
|
||||
type.name = Input ? "Module input" : "Module output";
|
||||
type.description = Input ? "Input of a module: the value given to the module node in the vehicle graph. On its own it gives the default value."
|
||||
: "Output of a module: the value the module node gives in the vehicle graph.";
|
||||
type.origin = block_origin::primitive;
|
||||
type.dynamic_ports = true;
|
||||
type.resolve = Input ? resolve_input : resolve_output;
|
||||
type.params = interface_params(Input);
|
||||
type.create = Input ? make_block<module_input> : make_block<module_output>;
|
||||
return type;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_module_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(interface_type(true));
|
||||
Catalog.add(interface_type(false));
|
||||
|
||||
block_type type;
|
||||
type.id = std::string{module_type};
|
||||
type.category = "module";
|
||||
type.name = "Module";
|
||||
type.description = "Graph of logic from another file (a module), used as one block. Its ports are the module.input and module.output blocks "
|
||||
"of the module; the file is given relative to the vehicle graph.";
|
||||
type.origin = block_origin::primitive;
|
||||
type.dynamic_ports = true;
|
||||
type.resolve = resolve_module;
|
||||
type.params = {text_param("file")};
|
||||
type.create = nullptr;
|
||||
Catalog.add(std::move(type));
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
801
vehicle/graph/blocks/pneumatic.cpp
Normal file
801
vehicle/graph/blocks/pneumatic.cpp
Normal file
@@ -0,0 +1,801 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_body.h"
|
||||
#include "McZapkie/MOVER.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
// air supply and brakes. the blocks drive the brake equipment of the vehicle body (TMoverParameters): its brake pipe and
|
||||
// feed pipe, which exchange air with the neighbouring vehicles, and the distributor and driver's brake valves created
|
||||
// by CheckLocomotiveParameters from the parameters of these blocks, with the same code as for the .fiz vehicles.
|
||||
// the sequence of the operations follows TMoverParameters::UpdatePipePressure and UpdateScndPipePressure
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// cross-section of the feed pipe connection, as in TMoverParameters::UpdateScndPipePressure
|
||||
double constexpr feed_pipe_section{0.5067};
|
||||
|
||||
TMoverParameters &vehicle(frame const &Frame)
|
||||
{
|
||||
return *Frame.body->mover;
|
||||
}
|
||||
|
||||
block_type make_type(std::string Id, std::string Category, std::string Name, std::string Description, block_origin Origin)
|
||||
{
|
||||
block_type type;
|
||||
type.id = std::move(Id);
|
||||
type.category = std::move(Category);
|
||||
type.name = std::move(Name);
|
||||
type.description = std::move(Description);
|
||||
type.origin = Origin;
|
||||
return type;
|
||||
}
|
||||
|
||||
// compressor of the main reservoir
|
||||
class pn_compressor final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
power,
|
||||
power_hv,
|
||||
on,
|
||||
air,
|
||||
run
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
flow,
|
||||
max_pressure,
|
||||
min_voltage,
|
||||
power
|
||||
};
|
||||
};
|
||||
|
||||
explicit pn_compressor(block_setup const &Setup)
|
||||
: block(Setup), m_aux(is_connected(Setup, port::power)), m_hv(is_connected(Setup, port::power_hv)), m_flow(number(Setup, param::flow)),
|
||||
m_maxpressure(number(Setup, param::max_pressure)), m_minvoltage(number(Setup, param::min_voltage)), m_power(number(Setup, param::power))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
// without supply connections the compressor is assumed to be powered
|
||||
auto powered{false == m_aux && false == m_hv};
|
||||
auto const auxvoltage{m_aux ? forward_in(Frame, port::power) : 0.0};
|
||||
auto const hvvoltage{m_hv ? forward_in(Frame, port::power_hv) : 0.0};
|
||||
powered = powered || (m_aux && auxvoltage >= m_minvoltage && auxvoltage > 0.0) || (m_hv && hvvoltage >= m_minvoltage && hvvoltage > 0.0);
|
||||
auto const run{powered && get_bool(Frame, port::on) && body.VeselVolume > 0.0 && m_maxpressure > 0.0};
|
||||
body.CompressorAllow = get_bool(Frame, port::on);
|
||||
body.CompressorFlag = run;
|
||||
if (run)
|
||||
{
|
||||
// TMoverParameters::CompressorCheck, compressor working at steady pace
|
||||
body.CompressedVolume += m_flow * (2.0 * m_maxpressure - body.Compressor) / m_maxpressure * Frame.dt;
|
||||
if (m_aux && auxvoltage > 0.0)
|
||||
{
|
||||
add_back(Frame, port::power, m_power);
|
||||
}
|
||||
else if (m_hv && hvvoltage > 0.0)
|
||||
{
|
||||
add_back(Frame, port::power_hv, m_power / hvvoltage);
|
||||
}
|
||||
}
|
||||
set_forward(Frame, port::air, body.Compressor);
|
||||
set_bool(Frame, port::run, run);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("pn.compressor", "pneumatics", "Compressor",
|
||||
"Compressor of the main reservoir. Without a supply connected it's assumed to be powered; with both connected either one is enough.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {physical_in("power", domain::aux_ac, true), physical_in("power_hv", domain::high_voltage, true), signal_in("on", value_type::boolean),
|
||||
physical_out("air", domain::air), signal_out("run", value_type::boolean)};
|
||||
type.params = {fiz(number_param("flow", 0.05, "m3/s"), "Brake:", "CompressorSpeed"), number_param("max_pressure", 10.0, "bar"), number_param("min_voltage", 0.0, "V"),
|
||||
number_param("power", 10000.0, "W")};
|
||||
type.single = true;
|
||||
type.create = make_block<pn_compressor>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_aux;
|
||||
bool const m_hv;
|
||||
double const m_flow;
|
||||
double const m_maxpressure;
|
||||
double const m_minvoltage;
|
||||
double const m_power;
|
||||
};
|
||||
|
||||
// pressure switch of the compressor: asks for air below the lower pressure, until the upper one is reached
|
||||
class pn_governor final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
p,
|
||||
need
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
min,
|
||||
max
|
||||
};
|
||||
};
|
||||
|
||||
explicit pn_governor(block_setup const &Setup) : block(Setup), m_min(number(Setup, param::min)), m_max(number(Setup, param::max)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const pressure{get(Frame, port::p)};
|
||||
if (pressure < m_min)
|
||||
{
|
||||
m_need = true;
|
||||
}
|
||||
else if (pressure > m_max)
|
||||
{
|
||||
m_need = false;
|
||||
}
|
||||
vehicle(Frame).CompressorGovernorLock = false == m_need;
|
||||
}
|
||||
void commit(frame &Frame) override
|
||||
{
|
||||
set_bool(Frame, port::need, m_need);
|
||||
}
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "need";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_need ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("pn.governor", "pneumatics", "Pressure governor", "Pressure switch of the compressor: on below min, off above max.", block_origin::primitive)};
|
||||
type.ports = {signal_in("p", value_type::real, 0.0, "bar"), signal_out("need", value_type::boolean)};
|
||||
type.params = {fiz(number_param("min", 7.5, "bar"), "Brake:", "MinCP"), fiz(number_param("max", 8.5, "bar"), "Brake:", "MaxCP")};
|
||||
type.deferred = true;
|
||||
type.create = make_block<pn_governor>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_min;
|
||||
double const m_max;
|
||||
bool m_need{true};
|
||||
};
|
||||
|
||||
// main reservoir of the vehicle, and its connection with the feed pipe
|
||||
class pn_reservoir final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
out,
|
||||
pz,
|
||||
p
|
||||
};
|
||||
};
|
||||
|
||||
explicit pn_reservoir(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const dt{Frame.dt};
|
||||
if (body.VeselVolume > 0.0)
|
||||
{
|
||||
// TMoverParameters::CompressorCheck: safety valve and leaks
|
||||
body.Compressor = body.CompressedVolume / body.VeselVolume;
|
||||
body.EmergencyValveOpen = body.Compressor > (body.EmergencyValveOpen ? body.EmergencyValveOff : body.EmergencyValveOn);
|
||||
if (body.EmergencyValveOpen)
|
||||
{
|
||||
body.CompressedVolume -= PF(0, body.Compressor, body.EmergencyValveArea) * dt;
|
||||
}
|
||||
body.CompressedVolume = std::max(0.0, body.CompressedVolume - dt * body.AirLeakRate * 0.1);
|
||||
body.Compressor = body.CompressedVolume / body.VeselVolume;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Compressor = 0.0;
|
||||
}
|
||||
set_forward(Frame, port::out, body.Compressor);
|
||||
set_forward(Frame, port::pz, body.ScndPipePress);
|
||||
set(Frame, port::p, body.Compressor);
|
||||
}
|
||||
|
||||
// the feed pipe, after the other blocks took their air from it (TMoverParameters::UpdateScndPipePressure)
|
||||
void late_update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const dt{Frame.dt};
|
||||
// the end cocks (if.pz) are checked by the exchange
|
||||
auto const exchange{body.ScndPipeExchange(dt)};
|
||||
if (body.VeselVolume > 0.0)
|
||||
{
|
||||
auto const flow{PF(body.Compressor, body.ScndPipePress, feed_pipe_section) * dt};
|
||||
body.CompressedVolume += flow / 1000.0;
|
||||
body.Pipe2->Flow(-flow);
|
||||
}
|
||||
body.Pipe2->Flow(exchange);
|
||||
body.Pipe2->Act();
|
||||
body.ScndPipePress = body.Pipe2->P();
|
||||
if (body.ScndPipePress < -1.0)
|
||||
{
|
||||
body.ScndPipePress = -1.0;
|
||||
body.Pipe2->CreatePress(-1.0);
|
||||
body.Pipe2->Act();
|
||||
}
|
||||
if (body.CompressedVolume < 0.0)
|
||||
{
|
||||
body.CompressedVolume = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("pn.reservoir", "pneumatics", "Main reservoir",
|
||||
"The main reservoir of the vehicle. It feeds the feed pipe (pz), which the brake valves and the neighbouring vehicles take air from.",
|
||||
block_origin::simulator)};
|
||||
type.ports = {physical_in("in", domain::air, true), physical_out("out", domain::air), physical_out("pz", domain::feed_pipe), signal_out("p", value_type::real, "bar")};
|
||||
type.params = {fiz(number_param("volume", 0.0, "m3"), "Brake:", "Vv")};
|
||||
type.single = true;
|
||||
type.late = true;
|
||||
type.create = make_block<pn_reservoir>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
// brake pipe of the vehicle
|
||||
class br_pipe final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
in,
|
||||
vent,
|
||||
dist,
|
||||
couplers,
|
||||
p
|
||||
};
|
||||
};
|
||||
|
||||
explicit br_pipe(block_setup const &Setup) : block(Setup), m_trainlength(std::max(0.0, number(Setup, 0))) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const dt{Frame.dt};
|
||||
if (Frame.body->standalone && m_trainlength > 0.0 && false == m_extended)
|
||||
{
|
||||
// without the simulator there are no neighbours: the pipe gets the volume of the train instead
|
||||
auto const pressure{body.Pipe->P()};
|
||||
body.Pipe->CreateCap((std::max(body.Dim.L, 14.0) + 0.5 + m_trainlength) * body.Spg);
|
||||
body.Pipe->CreatePress(pressure);
|
||||
m_extended = true;
|
||||
}
|
||||
// emergency valve: the vent input, and the emergency brakes the simulator applies to every vehicle
|
||||
body.EmergencyValveFlow = 0.0;
|
||||
if (get_bool(Frame, port::vent) || body.RadioStopFlag || body.AlarmChainFlag)
|
||||
{
|
||||
body.EmergencyValveFlow = PF(0, body.PipePress, 0.15) * dt;
|
||||
}
|
||||
body.Pipe->Flow(-body.EmergencyValveFlow);
|
||||
body.Pipe->Flow(-body.PipePress * 0.001 * dt);
|
||||
body.Pipe->Act();
|
||||
body.PipePress = body.Pipe->P();
|
||||
publish(Frame, body.PipePress);
|
||||
}
|
||||
|
||||
// exchange with the neighbours, after the distributor took its air
|
||||
void late_update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
// the end cocks (if.pg) are checked by the exchange
|
||||
body.Pipe->Flow(body.GetDVc(Frame.dt));
|
||||
body.Pipe->Act();
|
||||
body.PipePress = body.Pipe->P();
|
||||
if (body.PipePress < -1.0)
|
||||
{
|
||||
body.PipePress = -1.0;
|
||||
body.Pipe->CreatePress(-1.0);
|
||||
body.Pipe->Act();
|
||||
}
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("br.pipe", "brakes", "Brake pipe",
|
||||
"The brake pipe of the vehicle, connected through the couplers with the neighbouring vehicles. vent opens the emergency valve. "
|
||||
"preview_train_length adds the pipe of a train of that length when the graph runs without the simulator.",
|
||||
block_origin::native)};
|
||||
type.ports = {physical_in("in", domain::brake_pipe, true), signal_in("vent", value_type::boolean, 0.0, "", true), physical_out("dist", domain::brake_pipe),
|
||||
physical_out("couplers", domain::brake_pipe), signal_out("p", value_type::real, "bar")};
|
||||
type.params = {number_param("preview_train_length", 0.0, "m")};
|
||||
type.single = true;
|
||||
type.late = true;
|
||||
type.create = make_block<br_pipe>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double const m_trainlength; // without the simulator: length of the brake pipe of the train behind the vehicle
|
||||
bool m_extended{false};
|
||||
|
||||
void publish(frame &Frame, double const Pressure)
|
||||
{
|
||||
set_forward(Frame, port::dist, Pressure);
|
||||
set_forward(Frame, port::couplers, Pressure);
|
||||
set(Frame, port::p, Pressure);
|
||||
}
|
||||
};
|
||||
|
||||
// driver's brake valve
|
||||
class br_handle final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
zg,
|
||||
pos,
|
||||
emerg,
|
||||
reductor,
|
||||
pg,
|
||||
p_ctl
|
||||
};
|
||||
};
|
||||
|
||||
explicit br_handle(block_setup const &Setup) : block(Setup), m_reductor(is_connected(Setup, port::reductor)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const dt{Frame.dt};
|
||||
auto position{get(Frame, port::pos)};
|
||||
if (get_bool(Frame, port::emerg))
|
||||
{
|
||||
position = body.Handle->GetPos(bh_EB);
|
||||
}
|
||||
body.BrakeCtrlPosR = position;
|
||||
body.BrakeCtrlPos = static_cast<int>(std::floor(position + 0.5));
|
||||
// brake pipe lock in the emergency state, per the .fiz parameters (LPOn, LPOff, HandlePipeUnlockPos, EmergencyCutsOffHandle)
|
||||
body.LockPipe = body.PipePress < (body.LockPipe ? body.LockPipeOff : body.LockPipeOn);
|
||||
auto const lock{(body.LockPipe && false == body.UnlockPipe && position > body.HandleUnlock) || (body.EmergencyCutsOffHandle && body.EmergencyValveFlow > 0)};
|
||||
auto const supply{lock ? body.PipePress + 0.00001 : body.ScndPipePress};
|
||||
body.Handle->SetReductor(m_reductor ? get(Frame, port::reductor) : body.BrakeCtrlPos2);
|
||||
auto const flow{body.Handle->GetPF(position, body.PipePress, supply, dt, body.EqvtPipePress)};
|
||||
if (flow < 0)
|
||||
{
|
||||
if (body.Compressor > body.ScndPipePress)
|
||||
{
|
||||
body.CompressedVolume += flow / 1500.0;
|
||||
body.Pipe2->Flow(flow / 3.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
body.Pipe2->Flow(flow);
|
||||
}
|
||||
}
|
||||
body.Pipe->Flow(-flow);
|
||||
// normalised as in UpdatePipePressure, for the sounds of the valve
|
||||
body.dpMainValve = flow / (std::max(body.Dim.L, 1.0) * std::max(body.Spg, 0.01) * 20) / (100.0 * dt);
|
||||
set_forward(Frame, port::pg, body.PipePress);
|
||||
set(Frame, port::p_ctl, body.Handle->GetCP());
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("br.handle", "brakes", "Driver's brake valve",
|
||||
"Driver's brake valve (TDriverHandle). pos is the position of the handle in the valve's own scale, e.g. -1..6 for FV4a; emerg moves it to the "
|
||||
"emergency position. It takes air from the feed pipe of the main reservoir.",
|
||||
block_origin::simulator)};
|
||||
type.ports = {physical_in("zg", domain::air),
|
||||
signal_in("pos", value_type::real),
|
||||
signal_in("emerg", value_type::boolean, 0.0, "", true),
|
||||
signal_in("reductor", value_type::real, 0.0, "bar", true),
|
||||
physical_out("pg", domain::brake_pipe),
|
||||
signal_out("p_ctl", value_type::real, "bar")};
|
||||
type.params = {
|
||||
fiz(choice_param("type", {"FV4a", "MHZ_EN57", "MHZ_K5P", "MHZ_K8P", "MHZ_6P", "M394", "Knorr", "St113", "FVel6", "FVE408", "D2", "Westinghouse", "test"}, "FV4a"), "Cntrl.",
|
||||
"BrakeHandle"),
|
||||
fiz(boolean_param("auto_overload", false), "Cntrl.", "HAO"),
|
||||
fiz(boolean_param("manual_overload", false), "Cntrl.", "HMO"),
|
||||
fiz(number_param("gdp1", 0.0), "Cntrl.", "HGDP1"),
|
||||
fiz(number_param("gdp2", 0.0), "Cntrl.", "HGDP2"),
|
||||
fiz(number_param("overload_max", 0.0, "bar"), "Cntrl.", "OMP"),
|
||||
fiz(number_param("overload_decrease", 0.0, "bar/s"), "Cntrl.", "OPD"),
|
||||
fiz(number_param("lock_on", -1.0, "bar"), "Brake:", "LPOn"),
|
||||
fiz(number_param("lock_off", -1.0, "bar"), "Brake:", "LPOff"),
|
||||
fiz(number_param("unlock_pos", -3.0), "Brake:", "HandlePipeUnlockPos"),
|
||||
fiz(boolean_param("emergency_cuts_off", false), "Brake:", "EmergencyCutsOffHandle"),
|
||||
};
|
||||
type.single = true;
|
||||
type.create = make_block<br_handle>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_reductor;
|
||||
};
|
||||
|
||||
// independent (local) brake valve
|
||||
class br_local_handle final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
zg,
|
||||
pos,
|
||||
bc
|
||||
};
|
||||
};
|
||||
|
||||
explicit br_local_handle(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const position{std::clamp(get(Frame, port::pos), 0.0, 1.0)};
|
||||
body.LocalBrakePosA = position;
|
||||
body.dpLocalValve = body.LocHandle->GetPF(position, body.Hamulec->GetBCP(), body.ScndPipePress, Frame.dt, 0);
|
||||
body.LocBrakePress = body.LocHandle->GetCP();
|
||||
set_forward(Frame, port::bc, body.LocBrakePress);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("br.local_handle", "brakes", "Independent brake valve",
|
||||
"Independent (local) brake valve of the locomotive. pos 0..1 is the position of the handle; bc is the pressure it sets for the distributor.",
|
||||
block_origin::simulator)};
|
||||
type.ports = {physical_in("zg", domain::air), signal_in("pos", value_type::real), physical_out("bc", domain::air)};
|
||||
type.params = {fiz(choice_param("type", {"FD1", "Knorr", "Westinghouse"}, "FD1"), "Cntrl.", "LocBrakeHandle")};
|
||||
type.single = true;
|
||||
type.create = make_block<br_local_handle>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
// distributor (brake valve) of the vehicle, with its auxiliary reservoir and brake cylinders
|
||||
class br_distributor final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
pg,
|
||||
feed,
|
||||
local,
|
||||
ed,
|
||||
release,
|
||||
cyl,
|
||||
bc,
|
||||
aux,
|
||||
crp
|
||||
};
|
||||
};
|
||||
|
||||
explicit br_distributor(block_setup const &Setup)
|
||||
: block(Setup), m_local(is_connected(Setup, port::local)), m_ed(is_connected(Setup, port::ed)), m_release(is_connected(Setup, port::release))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const dt{Frame.dt};
|
||||
auto &brake{*body.Hamulec};
|
||||
brake.ForceLeak(dt * body.AirLeakRate * 0.25);
|
||||
if (m_local)
|
||||
{
|
||||
body.LocBrakePress = forward_in(Frame, port::local);
|
||||
brake.SetLBP(body.LocBrakePress);
|
||||
}
|
||||
// load dependent brake force, as in TMoverParameters::UpdatePipePressure
|
||||
switch (body.BrakeValve)
|
||||
{
|
||||
case TBrakeValve::K:
|
||||
case TBrakeValve::W:
|
||||
case TBrakeValve::KE:
|
||||
case TBrakeValve::EStED:
|
||||
case TBrakeValve::ESt3AL2:
|
||||
case TBrakeValve::NESt3:
|
||||
case TBrakeValve::ESt4:
|
||||
case TBrakeValve::ESt3:
|
||||
{
|
||||
brake.PLC(body.MBPM < 2 ? body.MaxBrakePress[body.LoadFlag] : body.TotalMass - body.Mred);
|
||||
break;
|
||||
}
|
||||
case TBrakeValve::EP2:
|
||||
case TBrakeValve::EP1:
|
||||
{
|
||||
brake.PLC(body.TotalMass - body.Mred);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_ed)
|
||||
{
|
||||
brake.SetED(std::clamp(get(Frame, port::ed), 0.0, 1.0));
|
||||
}
|
||||
if (m_release)
|
||||
{
|
||||
brake.Releaser(get_bool(Frame, port::release) ? 1 : 0);
|
||||
}
|
||||
body.CntrlPipePress = brake.GetVRP();
|
||||
// the brake pipe
|
||||
auto const connected{(brake.GetBrakeStatus() & b_dmg) == b_dmg ? 0.0 : 1.0};
|
||||
body.Pipe->Flow(connected * brake.GetPF(connected * body.PipePress, dt, body.Vel));
|
||||
if (body.ASBType == 128)
|
||||
{
|
||||
brake.ASB(int(body.SlippingWheels && body.Vel > 1) * (1 + 2 * int(body.nrot_eps < -0.01)));
|
||||
}
|
||||
// the feed pipe
|
||||
body.Pipe2->Flow(brake.GetHPFlow(body.ScndPipePress, dt));
|
||||
|
||||
body.BrakePress = brake.GetBCP();
|
||||
body.Volume = brake.GetBRP();
|
||||
set_forward(Frame, port::cyl, body.BrakePress);
|
||||
set(Frame, port::bc, body.BrakePress);
|
||||
set(Frame, port::aux, body.Volume);
|
||||
set(Frame, port::crp, brake.GetCRP());
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("br.distributor", "brakes", "Distributor",
|
||||
"Distributor of the automatic brake (TBrake), with its auxiliary reservoir and the brake cylinders. local is the pressure of the independent "
|
||||
"brake valve; ed (0..1) is the share of the electrodynamic brake for the blending of the EStED type; release operates the releaser.",
|
||||
block_origin::simulator)};
|
||||
type.ports = {physical_in("pg", domain::brake_pipe),
|
||||
physical_in("feed", domain::feed_pipe, true),
|
||||
physical_in("local", domain::air, true),
|
||||
signal_in("ed", value_type::real, 0.0, "", true),
|
||||
signal_in("release", value_type::boolean, 0.0, "", true),
|
||||
physical_out("cyl", domain::air),
|
||||
signal_out("bc", value_type::real, "bar"),
|
||||
signal_out("aux", value_type::real, "bar"),
|
||||
signal_out("crp", value_type::real, "bar")};
|
||||
type.params = {
|
||||
fiz(choice_param("type", {"W", "W_Lu_L", "W_Lu_XR", "W_Lu_VI", "K", "Kg", "Kp", "Kss", "Kkg", "Kkp", "Kks", "Hikp1", "Hikss", "Hikg1", "KE", "SW", "EStED", "NESt3", "ESt3",
|
||||
"LSt", "ESt4", "ESt3AL2", "EP1", "EP2", "M483", "CV1_L_TR", "CV1", "CV1_R"},
|
||||
"ESt3"),
|
||||
"Brake:", "BrakeValve"),
|
||||
fiz(choice_param("mode", {"G", "P", "R", "GP", "PR", "GPR", "GPR+Mg", "PR+Mg"}, "GP"), "Cntrl.", "BrakeDelays"),
|
||||
fiz(number_param("max_bp", 3.8, "bar"), "Brake:", "MaxBP"),
|
||||
fiz(number_param("max_lbp", 0.0, "bar"), "Brake:", "MaxLBP"),
|
||||
fiz(number_param("tare_max_bp", 0.0, "bar"), "Brake:", "TareMaxBP"),
|
||||
fiz(number_param("max_asbp", 0.0, "bar"), "Brake:", "MaxASBP"),
|
||||
fiz(number_param("cyl_count", 2.0), "Brake:", "BCN"),
|
||||
fiz(number_param("cyl_radius", 0.178, "m"), "Brake:", "BCR"),
|
||||
fiz(number_param("cyl_dist", 0.1, "m"), "Brake:", "BCD"),
|
||||
fiz(number_param("volume", 0.0, "l"), "Brake:", "BVV"),
|
||||
fiz(number_param("rm", 1.0), "Brake:", "RM"),
|
||||
fiz(number_param("rv", 55.0, "km/h"), "Brake:", "RV"),
|
||||
fiz(number_param("valve_size", 0.0), "Brake:", "Size"),
|
||||
fiz(number_param("delay1", 0.0, "s"), "Cntrl.", "BDelay1"),
|
||||
fiz(number_param("delay2", 0.0, "s"), "Cntrl.", "BDelay2"),
|
||||
fiz(number_param("delay3", 0.0, "s"), "Cntrl.", "BDelay3"),
|
||||
fiz(number_param("delay4", 0.0, "s"), "Cntrl.", "BDelay4"),
|
||||
fiz(choice_param("asb", {"none", "manual", "automatic", "yes"}, "none"), "Cntrl.", "ASB"),
|
||||
};
|
||||
type.single = true;
|
||||
type.create = make_block<br_distributor>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_local;
|
||||
bool const m_ed;
|
||||
bool const m_release;
|
||||
};
|
||||
|
||||
// brake rigging: from the cylinder pressure to the brake force at the wheels
|
||||
class br_rigging final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
bc,
|
||||
spring,
|
||||
force
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
bcm,
|
||||
bcs,
|
||||
bsa,
|
||||
bre,
|
||||
nbpa,
|
||||
material
|
||||
};
|
||||
};
|
||||
|
||||
explicit br_rigging(block_setup const &Setup) : block(Setup), m_material(make_friction_material(material_id(text(Setup, param::material)))) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
// the cylinders are described by the distributor (BCR, BCN), the rigging by this block; both are in the body
|
||||
brake_rigging_setup setup;
|
||||
setup.press_to_force = body.P2FTrans;
|
||||
setup.cylinder_spring = body.BrakeCylSpring;
|
||||
setup.cylinder_mult = body.BrakeCylMult[0];
|
||||
setup.slack_adjuster = body.BrakeSlckAdj;
|
||||
setup.rigging_efficiency = body.BrakeRigEff;
|
||||
setup.cylinder_count = body.BrakeCylNo;
|
||||
setup.axles = body.NAxles;
|
||||
setup.blocks_per_axle = body.NBpA;
|
||||
auto const shoeforce{brake_rigging_shoe_force(setup, std::max(0.0, get(Frame, port::spring)), get(Frame, port::bc), body.Ntotal)};
|
||||
auto const friction{m_material->GetFC(shoeforce, body.Vel)};
|
||||
body.UnitBrakeForce = friction * shoeforce * 1000.0;
|
||||
auto const force{body.UnitBrakeForce * body.NAxles * std::max(1, body.NBpA)};
|
||||
Frame.body->brake_force += force;
|
||||
set(Frame, port::force, force);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("br.rigging", "brakes", "Brake rigging",
|
||||
"Brake rigging and shoes or discs: the brake force from the cylinder pressure (bc) and the force of the spring brake (spring, kN). The force is "
|
||||
"applied at the wheels of the vehicle.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {signal_in("bc", value_type::real, 0.0, "bar"), signal_in("spring", value_type::real, 0.0, "kN", true), signal_out("force", value_type::real, "N")};
|
||||
type.params = {
|
||||
fiz(number_param("bcm", 1.0), "Brake:", "BCM"),
|
||||
fiz(number_param("bcs", 0.0, "kN"), "Brake:", "BCS"),
|
||||
fiz(number_param("bsa", 0.0, "kN"), "Brake:", "BSA"),
|
||||
fiz(number_param("bre", 1.0), "Brake:", "BRE"),
|
||||
fiz(number_param("nbpa", 1.0), "Brake:", "NBpA"),
|
||||
fiz(choice_param("material", {"P10", "P10-Bg", "P10-Bgu", "P10yBg", "P10yBgu", "FR513", "FR510", "Cosid", "Disk1", "Disk1+Mg", "Disk2"}, "P10"), "Brake:", "BM"),
|
||||
};
|
||||
type.single = true;
|
||||
type.create = make_block<br_rigging>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
static int material_id(std::string const &Material)
|
||||
{
|
||||
// as in TMoverParameters::LoadFIZ_Brake, without the magnetic track brake
|
||||
if (Material == "P10-Bg")
|
||||
return bp_P10Bg;
|
||||
if (Material == "P10-Bgu")
|
||||
return bp_P10Bgu;
|
||||
if (Material == "FR513")
|
||||
return bp_FR513;
|
||||
if (Material == "FR510")
|
||||
return bp_FR510;
|
||||
if (Material == "Cosid")
|
||||
return bp_Cosid;
|
||||
if (Material == "P10yBg")
|
||||
return bp_P10yBg;
|
||||
if (Material == "P10yBgu")
|
||||
return bp_P10yBgu;
|
||||
if (Material == "Disk1" || Material == "Disk1+Mg")
|
||||
return bp_D1;
|
||||
if (Material == "Disk2")
|
||||
return bp_D2;
|
||||
return bp_P10;
|
||||
}
|
||||
|
||||
std::shared_ptr<TFricMat> m_material;
|
||||
};
|
||||
|
||||
// spring (parking) brake
|
||||
class pn_spring_brake final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
release,
|
||||
feed,
|
||||
force,
|
||||
applied
|
||||
};
|
||||
};
|
||||
|
||||
explicit pn_spring_brake(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto &spring{body.SpringBrake};
|
||||
spring.Activate = false == get_bool(Frame, port::release);
|
||||
body.UpdateSpringBrake(Frame.dt);
|
||||
// the force the spring adds to the shoes, as in TMoverParameters::BrakeForce
|
||||
auto const force{spring.IsReady ? std::max(0.0, spring.MinForcePressure - spring.Cylinder->P()) * spring.MaxBrakeForce : 0.0};
|
||||
set(Frame, port::force, force);
|
||||
set_bool(Frame, port::applied, spring.IsActive);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("pn.spring_brake", "brakes", "Spring brake",
|
||||
"Spring (parking) brake, released with air from the feed pipe. force (kN) is meant for the spring input of the brake rigging.",
|
||||
block_origin::extracted)};
|
||||
type.ports = {signal_in("release", value_type::boolean), physical_in("feed", domain::air, true), signal_out("force", value_type::real, "kN"),
|
||||
signal_out("applied", value_type::boolean)};
|
||||
type.params = {
|
||||
fiz(number_param("volume", 1.0, "l"), "SpringBrake:", "Volume"),
|
||||
fiz(number_param("mbf", 0.0, "kN"), "SpringBrake:", "MBF"),
|
||||
fiz(number_param("max_sp", 0.0, "bar"), "SpringBrake:", "MaxSP"),
|
||||
fiz(number_param("reset_p", 0.0, "bar"), "SpringBrake:", "ResetP"),
|
||||
fiz(number_param("min_fp", 0.1, "bar"), "SpringBrake:", "MinFP"),
|
||||
fiz(number_param("press_off", -1.0, "bar"), "SpringBrake:", "PressOff"),
|
||||
fiz(number_param("press_on", -2.0, "bar"), "SpringBrake:", "PressOn"),
|
||||
fiz(number_param("valve_on_area", 0.0), "SpringBrake:", "ValveOnArea"),
|
||||
fiz(number_param("valve_off_area", 0.0), "SpringBrake:", "ValveOffArea"),
|
||||
fiz(number_param("valve_pnb_area", 0.0), "SpringBrake:", "ValvePNBArea"),
|
||||
};
|
||||
type.single = true;
|
||||
type.create = make_block<pn_spring_brake>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_pneumatic_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(pn_compressor::describe());
|
||||
Catalog.add(pn_governor::describe());
|
||||
Catalog.add(pn_reservoir::describe());
|
||||
Catalog.add(br_handle::describe());
|
||||
Catalog.add(br_local_handle::describe());
|
||||
Catalog.add(br_pipe::describe());
|
||||
Catalog.add(br_distributor::describe());
|
||||
Catalog.add(br_rigging::describe());
|
||||
Catalog.add(pn_spring_brake::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
149
vehicle/graph/blocks/security.cpp
Normal file
149
vehicle/graph/blocks/security.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_body.h"
|
||||
#include "McZapkie/MOVER.h"
|
||||
|
||||
// vigilance device and cab signalling (CA/SHP). the block drives the TSecuritySystem of the vehicle body, configured by
|
||||
// TSecuritySystem::load from the parameters of the block, so the simulator's own uses of it (cab signal magnets of the
|
||||
// track, the reset commands of the cab, the AI) keep working with graph vehicles
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class sec_ca final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
v,
|
||||
power,
|
||||
reset,
|
||||
reset_shp,
|
||||
active,
|
||||
lamp,
|
||||
buzzer,
|
||||
brake,
|
||||
shp_lamp
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
aware,
|
||||
cab_signal,
|
||||
separate_ack,
|
||||
sifa,
|
||||
aware_delay,
|
||||
sound_delay,
|
||||
emergency_delay,
|
||||
min_speed,
|
||||
max_hold_time,
|
||||
radio_stop,
|
||||
cab_dependent
|
||||
};
|
||||
};
|
||||
|
||||
explicit sec_ca(block_setup const &Setup) : block(Setup), m_separate(boolean(Setup, param::separate_ack)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &security{Frame.body->mover->SecuritySystem};
|
||||
auto const active{get_bool(Frame, port::active)};
|
||||
if (m_first || active != m_active)
|
||||
{
|
||||
security.set_enabled(active);
|
||||
m_active = active;
|
||||
m_first = false;
|
||||
}
|
||||
auto const reset{get_bool(Frame, port::reset)};
|
||||
if (reset && false == m_reset)
|
||||
{
|
||||
security.acknowledge_press();
|
||||
}
|
||||
else if (false == reset && m_reset)
|
||||
{
|
||||
security.acknowledge_release();
|
||||
}
|
||||
m_reset = reset;
|
||||
auto const resetshp{get_bool(Frame, port::reset_shp)};
|
||||
if (resetshp && false == m_resetshp)
|
||||
{
|
||||
security.cabsignal_reset();
|
||||
}
|
||||
m_resetshp = resetshp;
|
||||
|
||||
security.update(Frame.dt, get(Frame, port::v), get_bool(Frame, port::power), Frame.body->cab);
|
||||
|
||||
set_bool(Frame, port::lamp, m_separate ? security.is_vigilance_blinking() : security.is_blinking());
|
||||
set_bool(Frame, port::buzzer, security.is_beeping() || security.is_cabsignal_beeping());
|
||||
set_bool(Frame, port::brake, security.is_braking());
|
||||
set_bool(Frame, port::shp_lamp, security.is_cabsignal_blinking());
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
block_type type;
|
||||
type.id = "sec.ca";
|
||||
type.category = "security";
|
||||
type.name = "Vigilance device (CA/SHP)";
|
||||
type.description = "Vigilance device and cab signalling (TSecuritySystem). v in km/h; brake is meant for the vent input of the brake pipe. The cab signal is "
|
||||
"set by the magnets of the track, as for the .fiz vehicles.";
|
||||
type.origin = block_origin::simulator;
|
||||
type.ports = {signal_in("v", value_type::real, 0.0, "km/h"),
|
||||
signal_in("power", value_type::boolean),
|
||||
signal_in("reset", value_type::boolean),
|
||||
signal_in("reset_shp", value_type::boolean, 0.0, "", true),
|
||||
signal_in("active", value_type::boolean, 1.0, "", true),
|
||||
signal_out("lamp", value_type::boolean),
|
||||
signal_out("buzzer", value_type::boolean),
|
||||
signal_out("brake", value_type::boolean),
|
||||
signal_out("shp_lamp", value_type::boolean)};
|
||||
// the first four make AwareSystem; a negative min_speed leaves the default of 10% of vmax
|
||||
type.params = {
|
||||
boolean_param("aware", true),
|
||||
boolean_param("cab_signal", false),
|
||||
boolean_param("separate_ack", false),
|
||||
boolean_param("sifa", false),
|
||||
fiz(number_param("aware_delay", 30.0, "s"), "Security:", "AwareDelay"),
|
||||
fiz(number_param("sound_delay", 5.0, "s"), "Security:", "SoundSignalDelay"),
|
||||
fiz(number_param("emergency_delay", 5.0, "s"), "Security:", "EmergencyBrakeDelay"),
|
||||
number_param("min_speed", -1.0, "km/h"),
|
||||
fiz(number_param("max_hold_time", 1.5, "s"), "Security:", "MaxHoldTime"),
|
||||
fiz(boolean_param("radio_stop", false), "Security:", "RadioStop"),
|
||||
fiz(boolean_param("cab_dependent", false), "Security:", "CabDependent"),
|
||||
};
|
||||
type.single = true;
|
||||
type.create = make_block<sec_ca>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_separate;
|
||||
bool m_first{true};
|
||||
bool m_active{false};
|
||||
bool m_reset{false};
|
||||
bool m_resetshp{false};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_security_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(sec_ca::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
424
vehicle/graph/blocks/vehicle.cpp
Normal file
424
vehicle/graph/blocks/vehicle.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
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 "vehicle/graph/graph_catalog.h"
|
||||
#include "vehicle/graph/graph_body.h"
|
||||
#include "McZapkie/MOVER.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
// the vehicle body: its motion, doors, lights, sander and the animated parts of its model, which stay native in the simulator
|
||||
namespace vgraph
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
TMoverParameters &vehicle(frame const &Frame)
|
||||
{
|
||||
return *Frame.body->mover;
|
||||
}
|
||||
|
||||
block_type make_type(std::string Id, std::string Name, std::string Description)
|
||||
{
|
||||
block_type type;
|
||||
type.id = std::move(Id);
|
||||
type.category = "vehicle";
|
||||
type.name = std::move(Name);
|
||||
type.description = std::move(Description);
|
||||
type.origin = block_origin::native;
|
||||
return type;
|
||||
}
|
||||
|
||||
class veh_state final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
v,
|
||||
v_signed,
|
||||
accel,
|
||||
wheel_rpm,
|
||||
slipping,
|
||||
cab,
|
||||
time
|
||||
};
|
||||
};
|
||||
|
||||
explicit veh_state(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const &body{*Frame.body};
|
||||
set(Frame, port::v, std::abs(body.velocity) * 3.6);
|
||||
set(Frame, port::v_signed, body.velocity);
|
||||
set(Frame, port::accel, body.acceleration);
|
||||
set(Frame, port::wheel_rpm, std::abs(body.wheel_speed) * 60.0);
|
||||
set_bool(Frame, port::slipping, body.slipping);
|
||||
set(Frame, port::cab, body.cab);
|
||||
set(Frame, port::time, Frame.time);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("veh.state", "Vehicle state",
|
||||
"Motion of the vehicle: speed (km/h), signed speed (m/s, positive towards the front, cab 1), acceleration, wheel revolutions, the occupied "
|
||||
"cab (1, -1, 0) and the time of the graph.")};
|
||||
type.ports = {signal_out("v", value_type::real, "km/h"), signal_out("v_signed", value_type::real, "m/s"), signal_out("accel", value_type::real, "m/s2"),
|
||||
signal_out("wheel_rpm", value_type::real, "1/min"), signal_out("slipping", value_type::boolean), signal_out("cab", value_type::integer),
|
||||
signal_out("time", value_type::real, "s")};
|
||||
type.create = make_block<veh_state>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
// passenger doors, operated by the driver through the graph; the doors of the vehicle body move them and play their sounds
|
||||
class veh_doors final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
open_left,
|
||||
open_right,
|
||||
lock,
|
||||
closed_left,
|
||||
closed_right,
|
||||
all_closed
|
||||
};
|
||||
};
|
||||
|
||||
explicit veh_doors(block_setup const &Setup) : block(Setup), m_lock(is_connected(Setup, port::lock)) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &doors{vehicle(Frame).Doors};
|
||||
operate(doors.instances[side::left], get_bool(Frame, port::open_left));
|
||||
operate(doors.instances[side::right], get_bool(Frame, port::open_right));
|
||||
if (m_lock)
|
||||
{
|
||||
doors.has_lock = true;
|
||||
doors.lock_enabled = get_bool(Frame, port::lock);
|
||||
}
|
||||
// the state from the last update of the doors; they move after the graph
|
||||
auto const closedleft{doors.instances[side::left].is_closed};
|
||||
auto const closedright{doors.instances[side::right].is_closed};
|
||||
set_bool(Frame, port::closed_left, closedleft);
|
||||
set_bool(Frame, port::closed_right, closedright);
|
||||
set_bool(Frame, port::all_closed, closedleft && closedright);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("veh.doors", "Doors",
|
||||
"Passenger doors of the vehicle. They open while the open input is on and close when it goes off; lock blocks them above the lock speed.")};
|
||||
type.ports = {signal_in("open_left", value_type::boolean, 0.0, "", true),
|
||||
signal_in("open_right", value_type::boolean, 0.0, "", true),
|
||||
signal_in("lock", value_type::boolean, 0.0, "", true),
|
||||
signal_out("closed_left", value_type::boolean),
|
||||
signal_out("closed_right", value_type::boolean),
|
||||
signal_out("all_closed", value_type::boolean)};
|
||||
// open_time and close_time are turned into the speeds of the .fiz with the range
|
||||
type.params = {
|
||||
number_param("open_time", 2.0, "s"),
|
||||
number_param("close_time", 3.0, "s"),
|
||||
fiz(choice_param("type", {"shift", "rotate", "fold", "plug"}, "rotate"), "Doors:", "DoorOpenMethod"),
|
||||
fiz(number_param("range", 1.0, "m/deg"), "Doors:", "DoorMaxShiftL"),
|
||||
fiz(number_param("range_out", 0.0, "m"), "Doors:", "DoorMaxShiftPlug"),
|
||||
fiz(number_param("open_delay", 0.0, "s"), "Doors:", "DoorOpenDelay"),
|
||||
fiz(number_param("close_delay", 0.0, "s"), "Doors:", "DoorCloseDelay"),
|
||||
fiz(number_param("lock_speed", 10.0, "km/h"), "Doors:", "DoorLockSpeed"),
|
||||
};
|
||||
type.single = true;
|
||||
type.create = make_block<veh_doors>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class Door_> static void operate(Door_ &Door, bool const Open)
|
||||
{
|
||||
Door.remote_open = Open;
|
||||
Door.remote_close = false == Open;
|
||||
}
|
||||
|
||||
bool const m_lock;
|
||||
};
|
||||
|
||||
class veh_lights final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
front,
|
||||
rear,
|
||||
dim
|
||||
};
|
||||
};
|
||||
|
||||
explicit veh_lights(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
body.iLights[end::front] = static_cast<int>(std::lround(get(Frame, port::front)));
|
||||
body.iLights[end::rear] = static_cast<int>(std::lround(get(Frame, port::rear)));
|
||||
Frame.body->headlights_dimmed = get_bool(Frame, port::dim);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("veh.lights", "Head and tail lights",
|
||||
"Lights at the front and the rear end of the vehicle, as bit masks of the lights (1 left, 4 upper, 16 right white; 2 left, 32 right red...).")};
|
||||
type.ports = {signal_in("front", value_type::integer, 0.0, "", true), signal_in("rear", value_type::integer, 0.0, "", true), signal_in("dim", value_type::boolean, 0.0, "", true)};
|
||||
type.single = true;
|
||||
type.create = make_block<veh_lights>;
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
class veh_sander final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
on,
|
||||
sanding
|
||||
};
|
||||
};
|
||||
|
||||
explicit veh_sander(block_setup const &Setup) : block(Setup) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto &body{vehicle(Frame)};
|
||||
auto const on{get_bool(Frame, port::on)};
|
||||
if (on != m_on)
|
||||
{
|
||||
body.Sandbox(on, range_t::local);
|
||||
m_on = on;
|
||||
}
|
||||
set_bool(Frame, port::sanding, body.SandDose);
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("veh.sander", "Sander", "Sands the rails while on, until the sand (body sand_capacity) runs out.")};
|
||||
type.ports = {signal_in("on", value_type::boolean), signal_out("sanding", value_type::boolean)};
|
||||
type.single = true;
|
||||
type.create = make_block<veh_sander>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_on{false};
|
||||
};
|
||||
|
||||
// placement of the animated submodels of the vehicle's external model: the model of the vehicle body or its low poly
|
||||
// interior. the vehicle (DynObj) reads these parameters by their keys and applies the block's state to the submodels
|
||||
// right before the vehicle is drawn, so keep the keys and the choice ids
|
||||
param_def model_param()
|
||||
{
|
||||
return choice_param("model", {"vehicle", "lowpoly"}, "vehicle");
|
||||
}
|
||||
|
||||
// submodel of the external model rotated or moved by the value, like the TGauge of a cab control: the input is clamped
|
||||
// to min..max, followed with the smoothing of TGauge (its friction), and gives offset + scale * value degrees
|
||||
// (rotation about the submodel's y axis) or millimetres (shift along its z axis)
|
||||
class veh_anim final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
value,
|
||||
anim
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
model,
|
||||
submodel,
|
||||
animation,
|
||||
min,
|
||||
max,
|
||||
scale,
|
||||
offset,
|
||||
smoothing
|
||||
};
|
||||
};
|
||||
|
||||
explicit veh_anim(block_setup const &Setup)
|
||||
: block(Setup), m_min(std::min(number(Setup, param::min), number(Setup, param::max))), m_max(std::max(number(Setup, param::min), number(Setup, param::max))),
|
||||
m_scale(number(Setup, param::scale)), m_offset(number(Setup, param::offset)), m_smoothing(std::max(0.0, number(Setup, param::smoothing)))
|
||||
{
|
||||
}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto const target{std::clamp(get(Frame, port::value), m_min, m_max)};
|
||||
if (m_initialized && m_smoothing > 0.0 && Frame.dt < 0.5 * m_smoothing)
|
||||
{
|
||||
// the same as TGauge::Update
|
||||
m_value += Frame.dt * (target - m_value) / m_smoothing;
|
||||
if (std::abs(target - m_value) <= 0.0001)
|
||||
{
|
||||
m_value = target;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the first step starts at the input, so the part doesn't move when the vehicle appears
|
||||
m_value = target;
|
||||
m_initialized = true;
|
||||
}
|
||||
set(Frame, port::anim, animation());
|
||||
}
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
char const *state_name(std::size_t Index) const override
|
||||
{
|
||||
return Index == 0 ? "value" : "anim";
|
||||
}
|
||||
double state_value(std::size_t Index) const override
|
||||
{
|
||||
return Index == 0 ? m_value : animation();
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("veh.anim", "Vehicle model animation",
|
||||
"Rotates or moves a submodel of the vehicle's external model (or its low poly interior) by the value, like a cab control: offset + scale * value, "
|
||||
"in degrees or millimetres, smoothed. Each vehicle shows its own state, although the model is shared.")};
|
||||
type.ports = {signal_in("value", value_type::real), signal_out("anim", value_type::real)};
|
||||
type.params = {
|
||||
model_param(),
|
||||
text_param("submodel"),
|
||||
choice_param("animation", {"rotate", "move"}, "rotate"),
|
||||
number_param("min", 0.0),
|
||||
number_param("max", 1.0),
|
||||
number_param("scale", 90.0, "deg/mm"),
|
||||
number_param("offset", 0.0, "deg/mm"),
|
||||
number_param("smoothing", 0.5, "s"),
|
||||
};
|
||||
type.create = make_block<veh_anim>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
double animation() const
|
||||
{
|
||||
return m_offset + m_scale * m_value;
|
||||
}
|
||||
|
||||
double const m_min;
|
||||
double const m_max;
|
||||
double const m_scale;
|
||||
double const m_offset;
|
||||
double const m_smoothing;
|
||||
double m_value{0.0};
|
||||
bool m_initialized{false};
|
||||
};
|
||||
|
||||
// submodel of the external model shown while the input is on, like the TButton of a lamp: submodel visible when on,
|
||||
// the optional submodel_off visible when off
|
||||
class veh_visible final : public block
|
||||
{
|
||||
public:
|
||||
struct port
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
on,
|
||||
shown
|
||||
};
|
||||
};
|
||||
struct param
|
||||
{
|
||||
enum : std::size_t
|
||||
{
|
||||
model,
|
||||
submodel,
|
||||
submodel_off,
|
||||
blink,
|
||||
blink_period
|
||||
};
|
||||
};
|
||||
|
||||
explicit veh_visible(block_setup const &Setup) : block(Setup), m_blink(boolean(Setup, param::blink)), m_blinkperiod(std::max(0.01, number(Setup, param::blink_period))) {}
|
||||
|
||||
void update(frame &Frame) override
|
||||
{
|
||||
auto on{get_bool(Frame, port::on)};
|
||||
if (on && m_blink)
|
||||
{
|
||||
// in step with the lamps of the cab, which blink with the time of the graph too
|
||||
on = std::fmod(Frame.time, m_blinkperiod) < 0.5 * m_blinkperiod;
|
||||
}
|
||||
m_shown = on;
|
||||
set_bool(Frame, port::shown, m_shown);
|
||||
}
|
||||
std::size_t state_count() const override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
char const *state_name(std::size_t) const override
|
||||
{
|
||||
return "on";
|
||||
}
|
||||
double state_value(std::size_t) const override
|
||||
{
|
||||
return m_shown ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
static block_type describe()
|
||||
{
|
||||
auto type{make_type("veh.visible", "Vehicle model element visibility",
|
||||
"Shows a submodel of the vehicle's external model (or its low poly interior) while the input is on, and the optional off submodel while "
|
||||
"it's off, e.g. a lamp, a sign or a gadget. Each vehicle shows its own state, although the model is shared.")};
|
||||
type.ports = {signal_in("on", value_type::boolean), signal_out("shown", value_type::boolean)};
|
||||
type.params = {
|
||||
model_param(), text_param("submodel"), text_param("submodel_off"), boolean_param("blink", false), number_param("blink_period", 1.0, "s"),
|
||||
};
|
||||
type.create = make_block<veh_visible>;
|
||||
return type;
|
||||
}
|
||||
|
||||
private:
|
||||
bool const m_blink;
|
||||
double const m_blinkperiod;
|
||||
bool m_shown{false};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void register_vehicle_blocks(catalog &Catalog)
|
||||
{
|
||||
Catalog.add(veh_state::describe());
|
||||
Catalog.add(veh_doors::describe());
|
||||
Catalog.add(veh_lights::describe());
|
||||
Catalog.add(veh_sander::describe());
|
||||
Catalog.add(veh_anim::describe());
|
||||
Catalog.add(veh_visible::describe());
|
||||
}
|
||||
|
||||
} // namespace vgraph
|
||||
Reference in New Issue
Block a user