17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 11:37:29 +01:00
Files
maszyna/vehicle/graph/blocks/electric.cpp

659 lines
20 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "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