mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-11 16:57:11 +01:00
feat(blackbox): the cab panel as fits the vehicle, every switch the driver worked in the events
- The rest of the cab record is read when written: what drives the vehicle, the doors' permits and departure signal, the brakes of the whole train, a diesel's oil and temperatures, the cruise control and the rest. - Cab panel: the traction as fits what drives the vehicle (voltage and current of an electric one; revolutions, oil pressure and temperatures of a diesel; clutch or torque converter of a mechanical or hydraulic one; nothing for a draisine), the equalizing reservoir, the main reservoir pipe and the most braked cylinder of the train, the brakes' setting and mode, a group of the doors, the pumps of a diesel where an electric vehicle has its pantographs, the hand brake, the releaser, the horn's tone, the radio's channel, the integrated handle of a unit. - Events: every switch on and off, doors opened and closed, the departure signal, the hand brake, the brakes' setting, the radio channel, the cruise control's speed; moving with the doors open and an overheating engine as faults. - A vehicle with no lamps at an end (a draisine) is not judged on its lights, and one which never had a voltage shows none.
This commit is contained in:
@@ -111,8 +111,8 @@ public sealed class DefaultBlackBoxStrings : IBlackBoxStrings
|
||||
["DevHeating"] = "Train heating",
|
||||
["DevRadio"] = "Radio",
|
||||
["DevShuntMode"] = "Shunting mode",
|
||||
["DevDoorsLeft"] = "Left doors",
|
||||
["DevDoorsRight"] = "Right doors",
|
||||
["DevDoorsLeft"] = "Left doors open",
|
||||
["DevDoorsRight"] = "Right doors open",
|
||||
["DevSanding"] = "Sanding",
|
||||
["DevHorn"] = "Horn",
|
||||
["DevSpringBrake"] = "Spring brake",
|
||||
@@ -201,6 +201,50 @@ public sealed class DefaultBlackBoxStrings : IBlackBoxStrings
|
||||
["CabGroupDriving"] = "Driving",
|
||||
["CabGroupSafety"] = "Safety",
|
||||
["CabLightsWant"] = "{0}, should be {1}",
|
||||
["CabLightsNone"] = "no lamps",
|
||||
["CabBrakeSetting"] = "setting {0}",
|
||||
["CabEqualizing"] = "Equalizing reservoir",
|
||||
["CabFeedPipe"] = "Main reservoir pipe",
|
||||
["CabConsistCylinder"] = "Cylinders, train max",
|
||||
["CabPower"] = "Power",
|
||||
["CabSpeedControl"] = "Cruise control",
|
||||
["CabOilPressure"] = "Oil pressure",
|
||||
["CabWater"] = "Water",
|
||||
["CabOil"] = "Oil",
|
||||
["CabEngine"] = "Engine",
|
||||
["CabNoDrive"] = "no drive of its own",
|
||||
["CabClutch"] = "Clutch",
|
||||
["CabConverterFill"] = "Torque converter",
|
||||
["CabIntegrated"] = "Driving handle",
|
||||
["CabGroupDoors"] = "Doors",
|
||||
["DevDoorPermitLeft"] = "Left doors permitted",
|
||||
["DevDoorPermitRight"] = "Right doors permitted",
|
||||
["DevDepartureSignal"] = "Departure signal",
|
||||
["DevDoorStep"] = "Steps",
|
||||
["DevDoorLock"] = "Door lock",
|
||||
["DevDoorsLocked"] = "Doors locked",
|
||||
["DevBrakeReleaser"] = "Brake releaser",
|
||||
["DevFuelPump"] = "Fuel pump",
|
||||
["DevOilPump"] = "Oil pump",
|
||||
["DevMotorBlowers"] = "Motor fans",
|
||||
["DevEngineHot"] = "Engine overheating",
|
||||
["DevSpeedControl"] = "Cruise control",
|
||||
["DevRadioChannel"] = "Radio, channel {0}",
|
||||
["DevHandBrake"] = "Hand brake",
|
||||
["DevHornTone"] = "Horn ({0})",
|
||||
["HornLow"] = "low",
|
||||
["HornHigh"] = "high",
|
||||
["HornBoth"] = "both",
|
||||
["EventDeviceOn"] = "{0}: on",
|
||||
["EventDeviceOff"] = "{0}: off",
|
||||
["EventDoorsLeftClosed"] = "Left doors closed",
|
||||
["EventDoorsRightClosed"] = "Right doors closed",
|
||||
["EventMovingDoorsOpen"] = "Moving with the doors open ({0:0} km/h)",
|
||||
["EventHandBrakeOn"] = "Hand brake applied",
|
||||
["EventHandBrakeOff"] = "Hand brake released",
|
||||
["EventBrakeSetting"] = "Brake setting: {0}",
|
||||
["EventRadioChannel"] = "Radio: channel {0}",
|
||||
["EventSpeedControlSet"] = "Cruise control: {0:0} km/h",
|
||||
["DevVigilance"] = "CA",
|
||||
["DevCabSignal"] = "SHP",
|
||||
["DevSecurityBraking"] = "CA/SHP braking",
|
||||
@@ -338,8 +382,8 @@ public sealed class DefaultBlackBoxStrings : IBlackBoxStrings
|
||||
["DevHeating"] = "Ogrzewanie",
|
||||
["DevRadio"] = "Radiotelefon",
|
||||
["DevShuntMode"] = "Jazda manewrowa",
|
||||
["DevDoorsLeft"] = "Drzwi lewe",
|
||||
["DevDoorsRight"] = "Drzwi prawe",
|
||||
["DevDoorsLeft"] = "Drzwi lewe otwarte",
|
||||
["DevDoorsRight"] = "Drzwi prawe otwarte",
|
||||
["DevSanding"] = "Piasecznica",
|
||||
["DevHorn"] = "Syrena",
|
||||
["DevSpringBrake"] = "Hamulec sprężynowy",
|
||||
@@ -428,6 +472,50 @@ public sealed class DefaultBlackBoxStrings : IBlackBoxStrings
|
||||
["CabGroupDriving"] = "Jazda",
|
||||
["CabGroupSafety"] = "Bezpieczeństwo",
|
||||
["CabLightsWant"] = "{0}, a ma być {1}",
|
||||
["CabLightsNone"] = "brak lamp",
|
||||
["CabBrakeSetting"] = "nastawa {0}",
|
||||
["CabEqualizing"] = "Zbiornik czasowy",
|
||||
["CabFeedPipe"] = "Przewód zasilający",
|
||||
["CabConsistCylinder"] = "Cylindry, skład max",
|
||||
["CabPower"] = "Moc",
|
||||
["CabSpeedControl"] = "Tempomat",
|
||||
["CabOilPressure"] = "Ciśn. oleju",
|
||||
["CabWater"] = "Woda",
|
||||
["CabOil"] = "Olej",
|
||||
["CabEngine"] = "Silnik",
|
||||
["CabNoDrive"] = "brak własnego napędu",
|
||||
["CabClutch"] = "Sprzęgło",
|
||||
["CabConverterFill"] = "Przekładnia hydr.",
|
||||
["CabIntegrated"] = "Zadajnik",
|
||||
["CabGroupDoors"] = "Drzwi",
|
||||
["DevDoorPermitLeft"] = "Zezwolenie lewe",
|
||||
["DevDoorPermitRight"] = "Zezwolenie prawe",
|
||||
["DevDepartureSignal"] = "Sygnał odjazdu",
|
||||
["DevDoorStep"] = "Stopień",
|
||||
["DevDoorLock"] = "Blokada drzwi",
|
||||
["DevDoorsLocked"] = "Drzwi zablokowane",
|
||||
["DevBrakeReleaser"] = "Odluźniacz",
|
||||
["DevFuelPump"] = "Pompa paliwa",
|
||||
["DevOilPump"] = "Pompa oleju",
|
||||
["DevMotorBlowers"] = "Wentylatory silników",
|
||||
["DevEngineHot"] = "Przegrzanie silnika",
|
||||
["DevSpeedControl"] = "Tempomat",
|
||||
["DevRadioChannel"] = "Radiotelefon, kanał {0}",
|
||||
["DevHandBrake"] = "Hamulec ręczny",
|
||||
["DevHornTone"] = "Syrena ({0})",
|
||||
["HornLow"] = "niski",
|
||||
["HornHigh"] = "wysoki",
|
||||
["HornBoth"] = "oba",
|
||||
["EventDeviceOn"] = "{0}: wł.",
|
||||
["EventDeviceOff"] = "{0}: wył.",
|
||||
["EventDoorsLeftClosed"] = "Drzwi lewe zamknięte",
|
||||
["EventDoorsRightClosed"] = "Drzwi prawe zamknięte",
|
||||
["EventMovingDoorsOpen"] = "Jazda z otwartymi drzwiami ({0:0} km/h)",
|
||||
["EventHandBrakeOn"] = "Hamulec ręczny zaciągnięty",
|
||||
["EventHandBrakeOff"] = "Hamulec ręczny zwolniony",
|
||||
["EventBrakeSetting"] = "Nastawa hamulca: {0}",
|
||||
["EventRadioChannel"] = "Radiotelefon: kanał {0}",
|
||||
["EventSpeedControlSet"] = "Tempomat: {0:0} km/h",
|
||||
["DevVigilance"] = "CA",
|
||||
["DevCabSignal"] = "SHP",
|
||||
["DevSecurityBraking"] = "Hamowanie CA/SHP",
|
||||
|
||||
@@ -178,7 +178,7 @@ public sealed class BlackBoxWindow : Window
|
||||
cabCard.Margin = new Thickness(0, UiKit.Gap, 0, 0);
|
||||
var sideColumn = new DockPanel();
|
||||
// On a short window the cab scrolls, so the list above keeps room for a few rows.
|
||||
sideColumn.SizeChanged += (_, e) => cabCard.MaxHeight = Math.Max(160, e.NewSize.Height - 200);
|
||||
sideColumn.SizeChanged += (_, e) => cabCard.MaxHeight = Math.Max(160, e.NewSize.Height - 160);
|
||||
DockPanel.SetDock(cabCard, Dock.Bottom);
|
||||
sideColumn.Children.Add(cabCard);
|
||||
sideColumn.Children.Add(UiKit.Frame(side));
|
||||
|
||||
@@ -9,26 +9,32 @@ using Avalonia.Media;
|
||||
namespace Eu07.BlackBox.Views;
|
||||
|
||||
/// <summary>
|
||||
/// The cab of the user's train at the moment played: the speed against the AI's, the
|
||||
/// accelerations, the controllers, the pressures of the brakes as gauges, the traction,
|
||||
/// the lights at the head and the tail of the train with what they are to show, and a
|
||||
/// lamp for every device in three groups (power, driving, safety), the alerter (CA) and
|
||||
/// the cab signalling (SHP) apart.
|
||||
/// The cab of the user's train at the moment played, for checking what the driver did: the
|
||||
/// speed against the AI's, the accelerations, the controllers, the brakes (the pressures as
|
||||
/// gauges, the setting), the traction as fits what drives the vehicle (the voltage and the
|
||||
/// current of an electric one, the oil and the temperatures of a diesel), the lights at the
|
||||
/// head and the tail of the train with what they are to show, and a lamp for every device in
|
||||
/// groups (power, driving, safety, doors), the alerter (CA) and the cab signalling (SHP) apart.
|
||||
/// </summary>
|
||||
public sealed class CabPanel : Control
|
||||
{
|
||||
private const double Pad = 14;
|
||||
private const double LampRow = 15;
|
||||
private const double LampRow = 14;
|
||||
private const int DoorColumns = 3;
|
||||
|
||||
private Recording? _recording;
|
||||
private double _time;
|
||||
private int _measured = -1;
|
||||
private int _controllerTop = 1;
|
||||
private readonly HashSet<int> _powered = new();
|
||||
private readonly HashSet<int> _feedPipe = new();
|
||||
private readonly HashSet<int> _integrated = new();
|
||||
private readonly HashSet<int> _hydraulic = new();
|
||||
|
||||
public CabPanel()
|
||||
{
|
||||
ClipToBounds = true;
|
||||
Height = 506;
|
||||
Height = 650;
|
||||
}
|
||||
|
||||
public IBlackBoxStrings Strings { get; set; } = new DefaultBlackBoxStrings();
|
||||
@@ -67,6 +73,7 @@ public sealed class CabPanel : Control
|
||||
}
|
||||
Measure(recording);
|
||||
var culture = CultureInfo.CurrentCulture;
|
||||
var extra = s.Extra;
|
||||
|
||||
// The vehicle at the left; who drives, from which cab, at the right.
|
||||
double chipX = right;
|
||||
@@ -105,37 +112,64 @@ public sealed class CabPanel : Control
|
||||
double column = width / 4;
|
||||
Value(context, Strings["CabController"], s.MainController.ToString(culture), Pad, top);
|
||||
Steps(context, s.MainController, _controllerTop, Pad, top + 34, column - 14);
|
||||
Value(context, Strings["CabShunt"], s.ScndController.ToString(culture), Pad + column, top);
|
||||
if (extra is { } handle && _integrated.Contains(s.Vehicle))
|
||||
Value(context, Strings["CabIntegrated"], (handle.IntegratedController * 100).ToString("+0;−0;0", culture) + " %", Pad + column, top);
|
||||
else
|
||||
Value(context, Strings["CabShunt"], s.ScndController.ToString(culture), Pad + column, top);
|
||||
Value(context, Strings["CabReverser"], Strings[s.Reverser > 0 ? "CabForward" : s.Reverser < 0 ? "CabBackward" : "CabNeutral"], Pad + 2 * column, top);
|
||||
Value(context, Strings["CabTrainBrake"], s.TrainBrake.ToString("0.0", culture), Pad + 3 * column, top);
|
||||
|
||||
// The brakes, as gauges.
|
||||
top = Section(context, Strings["CabSectionBrakes"], Pad, 180, right);
|
||||
// The brakes, as gauges, the setting (G, P, R) at the right of the title.
|
||||
string? setting = extra is { } e && (e.BrakeDelay | e.BrakeMode) != 0
|
||||
? string.Format(culture, Strings["CabBrakeSetting"], string.Join(" · ", new[] { BrakeSetting(e.BrakeDelay), BrakeMode(e.BrakeMode) }.Where(t => t.Length > 0)))
|
||||
: null;
|
||||
top = Section(context, Strings["CabSectionBrakes"], Pad, 180, right, setting);
|
||||
const double gauge = 17;
|
||||
Gauge(context, Strings["CabPipe"], s.BrakePipe, 6, 5, $"{s.BrakePipe:0.00} bar", Palette.Station, Pad, top, width);
|
||||
Gauge(context, Strings["CabCylinder"], s.BrakeCylinder, 6, null, $"{s.BrakeCylinder:0.0} bar", Palette.Brakes, Pad, top + 18, width);
|
||||
Gauge(context, Strings["CabReservoir"], s.MainReservoir, 10, null, $"{s.MainReservoir:0.0} bar", Palette.Controller, Pad, top + 36, width);
|
||||
Gauge(context, Strings["CabLocalBrake"], s.LocalBrake, 1, null, $"{s.LocalBrake * 100:0} %", Palette.Brakes, Pad, top + 54, width);
|
||||
double y = top + gauge;
|
||||
if (extra is { } x1)
|
||||
{
|
||||
// where the driver's brake valve takes the pipe
|
||||
Gauge(context, Strings["CabEqualizing"], x1.EqualizingReservoir, 6, null, $"{x1.EqualizingReservoir:0.00} bar", Palette.Target, Pad, y, width);
|
||||
y += gauge;
|
||||
}
|
||||
Gauge(context, Strings["CabCylinder"], s.BrakeCylinder, 6, null, $"{s.BrakeCylinder:0.0} bar", Palette.Brakes, Pad, y, width);
|
||||
y += gauge;
|
||||
if (extra is { } x3)
|
||||
{
|
||||
// the most braked vehicle of the train
|
||||
Gauge(context, Strings["CabConsistCylinder"], x3.ConsistBrakeCylinder, 6, null, $"{x3.ConsistBrakeCylinder:0.0} bar", Palette.Brakes, Pad, y, width);
|
||||
y += gauge;
|
||||
}
|
||||
Gauge(context, Strings["CabReservoir"], s.MainReservoir, 10, null, $"{s.MainReservoir:0.0} bar", Palette.Controller, Pad, y, width);
|
||||
y += gauge;
|
||||
if (extra is { } x2)
|
||||
{
|
||||
bool has = _feedPipe.Contains(s.Vehicle);
|
||||
Gauge(context, Strings["CabFeedPipe"], has ? x2.FeedPipe : 0, 10, null, has ? $"{x2.FeedPipe:0.0} bar" : "—", Palette.Controller, Pad, y, width);
|
||||
y += gauge;
|
||||
}
|
||||
Gauge(context, Strings["CabLocalBrake"], s.LocalBrake, 1, null, $"{s.LocalBrake * 100:0} %", Palette.Brakes, Pad, y, width);
|
||||
|
||||
// The traction at the left, the lights of both ends of the train at the right, one under the other.
|
||||
// The traction at the left, as fits what drives the vehicle; the lights of both ends of the train at the right.
|
||||
double half = Pad + width / 2;
|
||||
top = Section(context, Strings["CabSectionDrive"], Pad, 272, half - 10);
|
||||
Section(context, Strings["CabLights"], half + 4, 272, right);
|
||||
double third = (half - 10 - Pad) / 3;
|
||||
Value(context, Strings["CabVoltage"], $"{s.Voltage:0} V", Pad, top);
|
||||
Value(context, Strings["CabCurrent"], $"{s.MotorCurrent:0} A", Pad + third, top);
|
||||
Value(context, Strings["CabRpm"], $"{s.EngineRpm:0}", Pad + 2 * third, top);
|
||||
double driveTop = y + gauge + 4;
|
||||
top = Section(context, Strings["CabSectionDrive"], Pad, driveTop, half - 10);
|
||||
Section(context, Strings["CabLights"], half + 4, driveTop, right);
|
||||
DrawDrive(context, s, top, (half - 10 - Pad) / 3, culture);
|
||||
double row = top;
|
||||
foreach (var (key, lights, required, isRight) in new[]
|
||||
foreach (var (key, lights, equipped, required, isRight) in new[]
|
||||
{
|
||||
("CabLightsHead", s.LightsHead, HeadCodes.RequiredHead(s), HeadCodes.HeadRight(s)),
|
||||
("CabLightsTail", s.LightsTail, HeadCodes.RequiredTail(s), HeadCodes.TailRight(s))
|
||||
("CabLightsHead", s.LightsHead, s.EquippedHead, HeadCodes.RequiredHead(s), HeadCodes.HeadRight(s)),
|
||||
("CabLightsTail", s.LightsTail, s.EquippedTail, HeadCodes.RequiredTail(s), HeadCodes.TailRight(s))
|
||||
})
|
||||
{
|
||||
DrawFace(context, lights, new Point(half + 4 + 17, row + 15));
|
||||
// the end, and under it the signal it shows: green as it is to be (with the lamps the vehicle has),
|
||||
// red otherwise with the one it is to show
|
||||
var shown = HeadCodes.Of(lights);
|
||||
var text = isRight == false && required is { } want
|
||||
var text = HeadCodes.NoLamps(equipped) ? Strings["CabLightsNone"]
|
||||
: isRight == false && required is { } want
|
||||
? string.Format(culture, Strings["CabLightsWant"], HeadCodes.Name(shown), HeadCodes.Name(want))
|
||||
: isRight == true && required is { } met ? HeadCodes.Name(met) : HeadCodes.Name(shown);
|
||||
var brush = isRight is null ? Palette.DimText : isRight.Value ? Palette.Good : Palette.Bad;
|
||||
@@ -148,63 +182,187 @@ public sealed class CabPanel : Control
|
||||
}
|
||||
|
||||
// The devices, in three groups side by side: lit as their lamps are in the cab, the ones off faint.
|
||||
top = Section(context, Strings["CabSectionDevices"], Pad, 350, right);
|
||||
var groups = Devices(s);
|
||||
top = Section(context, Strings["CabSectionDevices"], Pad, top + 76, right);
|
||||
var groups = Devices(s, culture);
|
||||
var widths = new double[groups.Length];
|
||||
for (int g = 0; g < groups.Length; g++)
|
||||
{
|
||||
widths[g] = MapView.Text(Strings[groups[g].Title], Palette.DimText, 10, FontWeight.SemiBold).Width;
|
||||
foreach (var (key, _) in groups[g].Lamps)
|
||||
widths[g] = Math.Max(widths[g], 14 + MapView.Text(Strings[key], Palette.Text, 10.5).Width);
|
||||
foreach (var (text, _) in groups[g].Lamps)
|
||||
widths[g] = Math.Max(widths[g], 14 + MapView.Text(text, Palette.Text, 10.5).Width);
|
||||
}
|
||||
// each group as wide as its longest name, what is left shared between them
|
||||
double spare = (width - widths.Sum()) / groups.Length;
|
||||
double x = Pad;
|
||||
double x = Pad, bottom = top;
|
||||
for (int g = 0; g < groups.Length; g++)
|
||||
{
|
||||
double groupWidth = Math.Max(20, widths[g] + spare);
|
||||
using (context.PushClip(new Rect(x, top, groupWidth, size.Height - top)))
|
||||
{
|
||||
context.DrawText(MapView.Text(Strings[groups[g].Title], Palette.DimText, 10, FontWeight.SemiBold), new Point(x, top));
|
||||
double y = top + 16;
|
||||
foreach (var (key, brush) in groups[g].Lamps)
|
||||
double lampY = top + 16;
|
||||
foreach (var (text, brush) in groups[g].Lamps)
|
||||
{
|
||||
Led(context, new Point(x + 4, y + 7), brush);
|
||||
context.DrawText(MapView.Text(Strings[key], brush is null ? Palette.FaintText : Palette.Text, 10.5), new Point(x + 14, y));
|
||||
y += LampRow;
|
||||
Lamp(context, text, brush, x, lampY);
|
||||
lampY += LampRow;
|
||||
}
|
||||
bottom = Math.Max(bottom, lampY);
|
||||
}
|
||||
x += groupWidth;
|
||||
}
|
||||
|
||||
// The doors under them, across: open, permitted, the departure signal, the steps and the lock.
|
||||
top = Math.Max(bottom, top + 16 + 9 * LampRow) + 6;
|
||||
context.DrawText(MapView.Text(Strings["CabGroupDoors"], Palette.DimText, 10, FontWeight.SemiBold), new Point(Pad, top));
|
||||
var doors = Doors(s);
|
||||
double doorColumn = width / DoorColumns;
|
||||
for (int i = 0; i < doors.Length; i++)
|
||||
{
|
||||
double doorX = Pad + i % DoorColumns * doorColumn;
|
||||
using (context.PushClip(new Rect(doorX, top, doorColumn, size.Height - top)))
|
||||
Lamp(context, doors[i].Text, doors[i].Lit, doorX, top + 16 + i / DoorColumns * LampRow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The title of a part of the panel, with a line from it to the right; the top of the part returned.</summary>
|
||||
private static double Section(DrawingContext context, string title, double x, double y, double right)
|
||||
/// <summary>
|
||||
/// The traction, in two rows of three, as fits what drives the vehicle: the voltage, the current and the
|
||||
/// revolutions of an electric one, the power and the cruise control under; the revolutions and the oil
|
||||
/// pressure of a diesel, its temperatures under. A recording made before the engine was written: the first
|
||||
/// row of an electric one, no voltage for a vehicle which never had any.
|
||||
/// </summary>
|
||||
private void DrawDrive(DrawingContext context, CabSample s, double top, double third, CultureInfo culture)
|
||||
{
|
||||
void At(int column, int row, string label, string value, IBrush? brush = null) =>
|
||||
Value(context, label, value, Pad + column * third, top + row * 36, brush);
|
||||
|
||||
string voltage = _powered.Contains(s.Vehicle) ? $"{s.Voltage:0} V" : "—";
|
||||
if (s.Extra is not { } e)
|
||||
{
|
||||
At(0, 0, Strings["CabVoltage"], voltage);
|
||||
At(1, 0, Strings["CabCurrent"], $"{s.MotorCurrent:0} A");
|
||||
At(2, 0, Strings["CabRpm"], $"{s.EngineRpm:0}");
|
||||
return;
|
||||
}
|
||||
string oil = string.Format(culture, "{0:0.00} MPa", e.OilPressure);
|
||||
switch (e.Engine)
|
||||
{
|
||||
case EngineKind.Electric:
|
||||
case EngineKind.ElectricInduction:
|
||||
At(0, 0, Strings["CabVoltage"], voltage);
|
||||
At(1, 0, Strings["CabCurrent"], $"{s.MotorCurrent:0} A");
|
||||
At(2, 0, Strings["CabRpm"], $"{s.EngineRpm:0}");
|
||||
At(0, 1, Strings["CabPower"], $"{e.EnginePower:0} kW");
|
||||
At(1, 1, Strings["CabSpeedControl"], s.Has(CabDevices.SpeedControl) ? $"{e.SpeedControlKmh:0} km/h" : "—");
|
||||
break;
|
||||
case EngineKind.DieselElectric:
|
||||
case EngineKind.Diesel:
|
||||
At(0, 0, Strings["CabRpm"], $"{s.EngineRpm:0}");
|
||||
if (e.Engine == EngineKind.DieselElectric)
|
||||
At(1, 0, Strings["CabCurrent"], $"{s.MotorCurrent:0} A");
|
||||
else if (_hydraulic.Contains(s.Vehicle))
|
||||
At(1, 0, Strings["CabConverterFill"], $"{e.ConverterFill * 100:0} %");
|
||||
else
|
||||
At(1, 0, Strings["CabClutch"], $"{e.Clutch * 100:0} %");
|
||||
At(2, 0, Strings["CabOilPressure"], oil);
|
||||
// the temperatures, warm when the engine is too hot
|
||||
var heat = s.Has(CabDevices.EngineHot) ? Palette.Bad : null;
|
||||
At(0, 1, Strings["CabWater"], $"{e.WaterTemperature:0} °C", heat);
|
||||
At(1, 1, Strings["CabOil"], $"{e.OilTemperature:0} °C", heat);
|
||||
At(2, 1, Strings["CabEngine"], $"{e.EngineTemperature:0} °C", heat);
|
||||
break;
|
||||
case EngineKind.Steam:
|
||||
At(0, 0, Strings["CabRpm"], $"{s.EngineRpm:0}");
|
||||
break;
|
||||
default:
|
||||
context.DrawText(MapView.Text(Strings["CabNoDrive"], Palette.DimText, 11), new Point(Pad, top + 2));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The setting of the brakes from its bits: G, P, R, R+Mg.</summary>
|
||||
public static string BrakeSetting(byte bits)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if ((bits & 1) != 0)
|
||||
parts.Add("G");
|
||||
if ((bits & 2) != 0)
|
||||
parts.Add("P");
|
||||
if ((bits & 4) != 0)
|
||||
parts.Add("R");
|
||||
if ((bits & 8) != 0)
|
||||
parts.Add("Mg");
|
||||
return string.Join("+", parts);
|
||||
}
|
||||
|
||||
/// <summary>The mode of the brakes from its bits: PS, PN, EP, MED.</summary>
|
||||
public static string BrakeMode(byte bits)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if ((bits & 1) != 0)
|
||||
parts.Add("PS");
|
||||
if ((bits & 2) != 0)
|
||||
parts.Add("PN");
|
||||
if ((bits & 4) != 0)
|
||||
parts.Add("EP");
|
||||
if ((bits & 8) != 0)
|
||||
parts.Add("MED");
|
||||
return string.Join("+", parts);
|
||||
}
|
||||
|
||||
/// <summary>The title of a part of the panel, with a line from it to the right (and a note at its end); the top of the part returned.</summary>
|
||||
private static double Section(DrawingContext context, string title, double x, double y, double right, string? note = null)
|
||||
{
|
||||
var text = MapView.Text(title.ToUpper(CultureInfo.CurrentCulture), Palette.DimText, 9, FontWeight.SemiBold);
|
||||
context.DrawText(text, new Point(x, y));
|
||||
double end = right;
|
||||
if (note is not null)
|
||||
{
|
||||
var noted = MapView.Text(note, Palette.Text, 10, FontWeight.SemiBold);
|
||||
end = right - noted.Width - 8;
|
||||
context.DrawText(noted, new Point(right - noted.Width, y + (text.Height - noted.Height) / 2));
|
||||
}
|
||||
double line = Math.Round(y + text.Height / 2) + 0.5;
|
||||
if (x + text.Width + 8 < right)
|
||||
context.DrawLine(new Pen(Palette.Grid, 1), new Point(x + text.Width + 8, line), new Point(right, line));
|
||||
if (x + text.Width + 8 < end)
|
||||
context.DrawLine(new Pen(Palette.Grid, 1), new Point(x + text.Width + 8, line), new Point(end, line));
|
||||
return y + 17;
|
||||
}
|
||||
|
||||
/// <summary>The highest step of the controller used in the recording, for its bar.</summary>
|
||||
/// <summary>
|
||||
/// What is the same all the recording long for each vehicle: the highest step of the controller used, for
|
||||
/// its bar, and which vehicles ever had a voltage, a main reservoir pipe, an integrated controller moved off
|
||||
/// nought and a torque converter filled, to show the field shunting, the clutch or none for the others.
|
||||
/// </summary>
|
||||
private void Measure(Recording recording)
|
||||
{
|
||||
if (_measured == recording.Revision)
|
||||
return;
|
||||
int top = 1;
|
||||
_powered.Clear();
|
||||
_feedPipe.Clear();
|
||||
_integrated.Clear();
|
||||
_hydraulic.Clear();
|
||||
foreach (var sample in recording.Cab)
|
||||
{
|
||||
top = Math.Max(top, sample.MainController);
|
||||
if (sample.Voltage > 1)
|
||||
_powered.Add(sample.Vehicle);
|
||||
if (sample.Extra is not { } e)
|
||||
continue;
|
||||
if (e.FeedPipe > 0.1f)
|
||||
_feedPipe.Add(sample.Vehicle);
|
||||
if (Math.Abs(e.IntegratedController) > 0.01f)
|
||||
_integrated.Add(sample.Vehicle);
|
||||
if (e.ConverterFill > 0.01f)
|
||||
_hydraulic.Add(sample.Vehicle);
|
||||
}
|
||||
_controllerTop = top;
|
||||
_measured = recording.Revision;
|
||||
}
|
||||
|
||||
private static void Value(DrawingContext context, string label, string value, double x, double y)
|
||||
private static void Value(DrawingContext context, string label, string value, double x, double y, IBrush? brush = null)
|
||||
{
|
||||
context.DrawText(MapView.Text(label, Palette.DimText, 10), new Point(x, y));
|
||||
context.DrawText(MapView.Text(value, Palette.Text, 14, FontWeight.SemiBold), new Point(x, y + 13));
|
||||
context.DrawText(MapView.Text(value, brush ?? Palette.Text, 14, FontWeight.SemiBold), new Point(x, y + 13));
|
||||
}
|
||||
|
||||
/// <summary>The steps of the controller as a row of cells, lit up to the one it stands at.</summary>
|
||||
@@ -269,14 +427,14 @@ public sealed class CabPanel : Control
|
||||
{
|
||||
context.DrawRectangle(null, new Pen(Palette.Grid, 1), new Rect(center.X - 17, center.Y - 15, 34, 22), 5, 5);
|
||||
bool rearEnd = lights.HasFlag(Lights.RearEndSignals);
|
||||
Lamp(context, center + new Vector(0, -8), lights.HasFlag(Lights.HeadlightUpper), false, false);
|
||||
Lamp(context, center + new Vector(-10, 1), lights.HasFlag(Lights.HeadlightLeft) || lights.HasFlag(Lights.AuxiliaryLeft),
|
||||
lights.HasFlag(Lights.RedMarkerLeft) || rearEnd, lights.HasFlag(Lights.HighBeamLeft));
|
||||
Lamp(context, center + new Vector(10, 1), lights.HasFlag(Lights.HeadlightRight) || lights.HasFlag(Lights.AuxiliaryRight),
|
||||
lights.HasFlag(Lights.RedMarkerRight) || rearEnd, lights.HasFlag(Lights.HighBeamRight));
|
||||
Light(context, center + new Vector(0, -8), lights.HasFlag(Lights.HeadlightUpper), false, false);
|
||||
Light(context, center + new Vector(-10, 1), lights.HasFlag(Lights.HeadlightLeft) || lights.HasFlag(Lights.AuxiliaryLeft),
|
||||
lights.HasFlag(Lights.RedMarkerLeft) || rearEnd, lights.HasFlag(Lights.HighBeamLeft));
|
||||
Light(context, center + new Vector(10, 1), lights.HasFlag(Lights.HeadlightRight) || lights.HasFlag(Lights.AuxiliaryRight),
|
||||
lights.HasFlag(Lights.RedMarkerRight) || rearEnd, lights.HasFlag(Lights.HighBeamRight));
|
||||
}
|
||||
|
||||
private static void Lamp(DrawingContext context, Point center, bool white, bool red, bool high)
|
||||
private static void Light(DrawingContext context, Point center, bool white, bool red, bool high)
|
||||
{
|
||||
var fill = white ? Palette.LightWhite : red ? Palette.LightRed : Palette.LampOff;
|
||||
double radius = high ? 4.5 : 3.5;
|
||||
@@ -285,55 +443,114 @@ public sealed class CabPanel : Control
|
||||
context.DrawEllipse(fill, null, center, radius, radius);
|
||||
}
|
||||
|
||||
private static void Led(DrawingContext context, Point center, IBrush? lit)
|
||||
/// <summary>A device's lamp and its name: lit in its colour, the name bright; off, both faint.</summary>
|
||||
private static void Lamp(DrawingContext context, string text, IBrush? lit, double x, double y)
|
||||
{
|
||||
var center = new Point(x + 4, y + 7);
|
||||
if (lit is not null)
|
||||
context.DrawEllipse(Palette.Faded(lit, 0.3), null, center, 6, 6);
|
||||
context.DrawEllipse(lit ?? Palette.LampOff, null, center, 3.5, 3.5);
|
||||
context.DrawText(MapView.Text(text, lit is null ? Palette.FaintText : Palette.Text, 10.5), new Point(x + 14, y));
|
||||
}
|
||||
|
||||
/// <summary>Every device, with the colour of its lamp if it was on, in the groups the panel shows.</summary>
|
||||
private static (string Title, (string Key, IBrush? Lit)[] Lamps)[] Devices(CabSample s)
|
||||
/// <summary>
|
||||
/// Every device but the doors, with the colour of its lamp if it was on, in the groups the panel shows. A
|
||||
/// diesel has its pumps where an electric vehicle has its pantographs; a recording made before the engine
|
||||
/// was written has the devices it had then.
|
||||
/// </summary>
|
||||
private (string Title, (string Text, IBrush? Lit)[] Lamps)[] Devices(CabSample s, CultureInfo culture)
|
||||
{
|
||||
IBrush? On(CabDevices device, IBrush colour) => s.Has(device) ? colour : null;
|
||||
IBrush? Flag(CabFlags flag, IBrush colour) => (s.Flags & flag) != 0 ? colour : null;
|
||||
(string, IBrush?) On(string key, CabDevices device, IBrush colour) => (Strings[key], s.Has(device) ? colour : null);
|
||||
(string, IBrush?) Flag(string key, CabFlags flag, IBrush colour) => (Strings[key], (s.Flags & flag) != 0 ? colour : null);
|
||||
IBrush? Security(SecurityFlags flag, IBrush colour) => s.Has(flag) ? colour : null;
|
||||
var extra = s.Extra;
|
||||
bool diesel = extra is { Engine: EngineKind.Diesel or EngineKind.DieselElectric };
|
||||
(string, IBrush?) Horn()
|
||||
{
|
||||
if (!s.Has(CabDevices.Horn))
|
||||
return (Strings["DevHorn"], null);
|
||||
string? tone = extra?.Horn switch
|
||||
{
|
||||
1 => Strings["HornLow"],
|
||||
2 => Strings["HornHigh"],
|
||||
3 => Strings["HornBoth"],
|
||||
_ => null
|
||||
};
|
||||
return (tone is null ? Strings["DevHorn"] : string.Format(culture, Strings["DevHornTone"], tone), Palette.LampWarning);
|
||||
}
|
||||
bool electric = extra is null or { Engine: EngineKind.Electric or EngineKind.ElectricInduction };
|
||||
|
||||
var power = new[]
|
||||
var power = new List<(string, IBrush?)> { On("DevBattery", CabDevices.Battery, Palette.LampOn), On("DevMainBreaker", CabDevices.MainBreaker, Palette.LampOn) };
|
||||
if (electric)
|
||||
{
|
||||
("DevBattery", On(CabDevices.Battery, Palette.LampOn)),
|
||||
("DevMainBreaker", On(CabDevices.MainBreaker, Palette.LampOn)),
|
||||
("DevPantographFront", On(CabDevices.PantographFront, Palette.LampOn)),
|
||||
("DevPantographRear", On(CabDevices.PantographRear, Palette.LampOn)),
|
||||
("DevConverter", On(CabDevices.Converter, Palette.LampOn)),
|
||||
("DevCompressor", On(CabDevices.Compressor, Palette.LampOn)),
|
||||
("DevLineContactors", On(CabDevices.LineContactors, Palette.LampOn)),
|
||||
("DevHeating", On(CabDevices.Heating, Palette.LampOn))
|
||||
};
|
||||
var driving = new[]
|
||||
power.Add(On("DevPantographFront", CabDevices.PantographFront, Palette.LampOn));
|
||||
power.Add(On("DevPantographRear", CabDevices.PantographRear, Palette.LampOn));
|
||||
}
|
||||
if (diesel)
|
||||
{
|
||||
("DevShuntMode", On(CabDevices.ShuntMode, Palette.LampOn)),
|
||||
("DevRadio", On(CabDevices.Radio, Palette.LampOn)),
|
||||
("DevSanding", On(CabDevices.Sanding, Palette.LampWarning)),
|
||||
("DevHorn", On(CabDevices.Horn, Palette.LampWarning)),
|
||||
("DevDoorsLeft", On(CabDevices.DoorsLeft, Palette.LampWarning)),
|
||||
("DevDoorsRight", On(CabDevices.DoorsRight, Palette.LampWarning)),
|
||||
("DevSpringBrake", On(CabDevices.SpringBrake, Palette.LampWarning))
|
||||
power.Add(On("DevFuelPump", CabDevices.FuelPump, Palette.LampOn));
|
||||
power.Add(On("DevOilPump", CabDevices.OilPump, Palette.LampOn));
|
||||
}
|
||||
power.Add(On("DevConverter", CabDevices.Converter, Palette.LampOn));
|
||||
power.Add(On("DevCompressor", CabDevices.Compressor, Palette.LampOn));
|
||||
power.Add(On("DevLineContactors", CabDevices.LineContactors, Palette.LampOn));
|
||||
if (extra is not null)
|
||||
power.Add(On("DevMotorBlowers", CabDevices.MotorBlowers, Palette.LampOn));
|
||||
power.Add(On("DevHeating", CabDevices.Heating, Palette.LampOn));
|
||||
|
||||
// the radio with its channel
|
||||
var radio = extra is { RadioChannel: > 0 } r
|
||||
? (string.Format(culture, Strings["DevRadioChannel"], r.RadioChannel), s.Has(CabDevices.Radio) ? Palette.LampOn : null)
|
||||
: On("DevRadio", CabDevices.Radio, Palette.LampOn);
|
||||
var driving = new List<(string, IBrush?)>
|
||||
{
|
||||
On("DevShuntMode", CabDevices.ShuntMode, Palette.LampOn), radio,
|
||||
On("DevSanding", CabDevices.Sanding, Palette.LampWarning), Horn(),
|
||||
On("DevSpringBrake", CabDevices.SpringBrake, Palette.LampWarning)
|
||||
};
|
||||
if (extra is not null)
|
||||
{
|
||||
driving.Add((Strings["DevHandBrake"], extra.Value.HandBrake > 0 ? Palette.LampWarning : null));
|
||||
driving.Add(On("DevBrakeReleaser", CabDevices.BrakeReleaser, Palette.LampWarning));
|
||||
driving.Add(On("DevSpeedControl", CabDevices.SpeedControl, Palette.LampOn));
|
||||
}
|
||||
|
||||
var safety = new List<(string, IBrush?)>();
|
||||
if (s.Security != SecurityFlags.None || s.CabEnd != 0)
|
||||
{
|
||||
// A recording which tells them apart.
|
||||
safety.Add(("DevVigilance", Security(SecurityFlags.VigilanceBeeping, Palette.LampAlarm) ?? Security(SecurityFlags.Vigilance, Palette.Vigilance)));
|
||||
safety.Add(("DevCabSignal", Security(SecurityFlags.CabSignalBeeping, Palette.LampAlarm) ?? Security(SecurityFlags.CabSignal, Palette.CabSignal)));
|
||||
safety.Add(("DevSecurityBraking", Security(SecurityFlags.Braking, Palette.LampAlarm)));
|
||||
safety.Add((Strings["DevVigilance"], Security(SecurityFlags.VigilanceBeeping, Palette.LampAlarm) ?? Security(SecurityFlags.Vigilance, Palette.Vigilance)));
|
||||
safety.Add((Strings["DevCabSignal"], Security(SecurityFlags.CabSignalBeeping, Palette.LampAlarm) ?? Security(SecurityFlags.CabSignal, Palette.CabSignal)));
|
||||
safety.Add((Strings["DevSecurityBraking"], Security(SecurityFlags.Braking, Palette.LampAlarm)));
|
||||
}
|
||||
else
|
||||
safety.Add(("DevAlerter", Flag(CabFlags.AlerterBeeping, Palette.LampAlarm) ?? Flag(CabFlags.AlerterBlinking, Palette.LampWarning)));
|
||||
safety.Add(("DevWheelSlip", Flag(CabFlags.WheelSlip, Palette.LampAlarm)));
|
||||
safety.Add(("DevOverloadRelay", On(CabDevices.OverloadRelay, Palette.LampAlarm)));
|
||||
safety.Add(("DevRadioStop", Flag(CabFlags.RadioStop, Palette.LampAlarm)));
|
||||
safety.Add(("DevAlarmChain", Flag(CabFlags.AlarmChain, Palette.LampAlarm)));
|
||||
return [("CabGroupPower", power), ("CabGroupDriving", driving), ("CabGroupSafety", safety.ToArray())];
|
||||
safety.Add((Strings["DevAlerter"], (s.Flags & CabFlags.AlerterBeeping) != 0 ? Palette.LampAlarm
|
||||
: (s.Flags & CabFlags.AlerterBlinking) != 0 ? Palette.LampWarning : null));
|
||||
safety.Add(Flag("DevWheelSlip", CabFlags.WheelSlip, Palette.LampAlarm));
|
||||
safety.Add(On("DevOverloadRelay", CabDevices.OverloadRelay, Palette.LampAlarm));
|
||||
safety.Add(Flag("DevRadioStop", CabFlags.RadioStop, Palette.LampAlarm));
|
||||
safety.Add(Flag("DevAlarmChain", CabFlags.AlarmChain, Palette.LampAlarm));
|
||||
if (diesel)
|
||||
safety.Add(On("DevEngineHot", CabDevices.EngineHot, Palette.LampAlarm));
|
||||
return [("CabGroupPower", power.ToArray()), ("CabGroupDriving", driving.ToArray()), ("CabGroupSafety", safety.ToArray())];
|
||||
}
|
||||
|
||||
/// <summary>The doors of the vehicle the user is in, in rows of three: open, the lock, the permits, the steps, the departure signal.</summary>
|
||||
private (string Text, IBrush? Lit)[] Doors(CabSample s)
|
||||
{
|
||||
(string, IBrush?) On(string key, CabDevices device, IBrush colour) => (Strings[key], s.Has(device) ? colour : null);
|
||||
var doors = new List<(string, IBrush?)>
|
||||
{
|
||||
On("DevDoorsLeft", CabDevices.DoorsLeft, Palette.LampWarning), On("DevDoorsRight", CabDevices.DoorsRight, Palette.LampWarning)
|
||||
};
|
||||
if (s.Extra is null)
|
||||
return doors.ToArray();
|
||||
doors.Add(On("DevDoorLock", CabDevices.DoorLock, Palette.LampOn));
|
||||
doors.Add(On("DevDoorPermitLeft", CabDevices.DoorPermitLeft, Palette.LampOn));
|
||||
doors.Add(On("DevDoorPermitRight", CabDevices.DoorPermitRight, Palette.LampOn));
|
||||
doors.Add(On("DevDoorsLocked", CabDevices.DoorsLocked, Palette.LampOn));
|
||||
doors.Add(On("DevDoorStep", CabDevices.DoorStep, Palette.LampOn));
|
||||
doors.Add(On("DevDepartureSignal", CabDevices.DepartureSignal, Palette.LampWarning));
|
||||
return doors.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ public sealed class EventList : ListBox
|
||||
}
|
||||
AddCameras(recording, strings, events, culture);
|
||||
AddCouplings(recording, strings, events, culture);
|
||||
AddCabChanges(recording, strings, events, culture);
|
||||
foreach (var t in recording.Timetables)
|
||||
events.Add(new BlackBoxEvent(t.Time, string.Format(culture, strings["EventTimetable"], $"{t.Category} {t.Train}".Trim(),
|
||||
string.Format(culture, strings["Relation"], t.From, t.To)), Palette.Station, false));
|
||||
@@ -266,6 +267,85 @@ public sealed class EventList : ListBox
|
||||
_ => "CameraFree"
|
||||
};
|
||||
|
||||
/// <summary>The switches the driver worked, as the cab tells them: what came on and went off.</summary>
|
||||
private static readonly (CabDevices Device, string Key)[] Switched =
|
||||
[
|
||||
(CabDevices.MainBreaker, "DevMainBreaker"), (CabDevices.PantographFront, "DevPantographFront"),
|
||||
(CabDevices.PantographRear, "DevPantographRear"), (CabDevices.FuelPump, "DevFuelPump"), (CabDevices.OilPump, "DevOilPump"),
|
||||
(CabDevices.Converter, "DevConverter"), (CabDevices.Compressor, "DevCompressor"), (CabDevices.MotorBlowers, "DevMotorBlowers"),
|
||||
(CabDevices.Heating, "DevHeating"), (CabDevices.Radio, "DevRadio"), (CabDevices.ShuntMode, "DevShuntMode"),
|
||||
(CabDevices.SpringBrake, "DevSpringBrake"), (CabDevices.BrakeReleaser, "DevBrakeReleaser"),
|
||||
(CabDevices.DoorPermitLeft, "DevDoorPermitLeft"), (CabDevices.DoorPermitRight, "DevDoorPermitRight"),
|
||||
(CabDevices.DoorStep, "DevDoorStep"), (CabDevices.DoorLock, "DevDoorLock"), (CabDevices.SpeedControl, "DevSpeedControl")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// What the driver did in the cab, from one look to the next in the same vehicle: the switches on and off, the
|
||||
/// doors opened and closed, the departure signal, the hand brake, the brakes' setting, the radio's channel, the
|
||||
/// speed the cruise control is set to; and, as faults, moving with the doors open and the engine overheating.
|
||||
/// </summary>
|
||||
private static void AddCabChanges(Recording recording, IBlackBoxStrings strings, List<BlackBoxEvent> events, CultureInfo culture)
|
||||
{
|
||||
CabSample? last = null;
|
||||
bool movingOpen = false;
|
||||
foreach (var s in recording.Cab)
|
||||
{
|
||||
if (last is not { } b || b.Vehicle != s.Vehicle)
|
||||
{
|
||||
last = s;
|
||||
movingOpen = false;
|
||||
continue;
|
||||
}
|
||||
bool Came(CabDevices device) => !b.Has(device) && s.Has(device);
|
||||
bool Went(CabDevices device) => b.Has(device) && !s.Has(device);
|
||||
foreach (var (device, key) in Switched)
|
||||
{
|
||||
if (Came(device))
|
||||
events.Add(new BlackBoxEvent(s.Time, string.Format(culture, strings["EventDeviceOn"], strings[key]), Palette.LampOn, false));
|
||||
else if (Went(device))
|
||||
events.Add(new BlackBoxEvent(s.Time, string.Format(culture, strings["EventDeviceOff"], strings[key]), Palette.FaintText, false));
|
||||
}
|
||||
foreach (var (device, open, closed) in new[] { (CabDevices.DoorsLeft, "DevDoorsLeft", "EventDoorsLeftClosed"), (CabDevices.DoorsRight, "DevDoorsRight", "EventDoorsRightClosed") })
|
||||
{
|
||||
if (Came(device))
|
||||
events.Add(new BlackBoxEvent(s.Time, strings[open], Palette.LampWarning, false));
|
||||
else if (Went(device))
|
||||
events.Add(new BlackBoxEvent(s.Time, strings[closed], Palette.FaintText, false));
|
||||
}
|
||||
if (Came(CabDevices.DepartureSignal))
|
||||
events.Add(new BlackBoxEvent(s.Time, strings["DevDepartureSignal"], Palette.LampWarning, false));
|
||||
if (Came(CabDevices.EngineHot))
|
||||
events.Add(new BlackBoxEvent(s.Time, strings["DevEngineHot"], Palette.Bad, true));
|
||||
// moving with the doors open: once, until they are closed or the train stands
|
||||
bool doorsOpen = s.Has(CabDevices.DoorsLeft) || s.Has(CabDevices.DoorsRight);
|
||||
if (doorsOpen && s.AbsSpeedKmh > 2 && !movingOpen)
|
||||
{
|
||||
events.Add(new BlackBoxEvent(s.Time, string.Format(culture, strings["EventMovingDoorsOpen"], s.AbsSpeedKmh), Palette.Bad, true));
|
||||
movingOpen = true;
|
||||
}
|
||||
else if (!doorsOpen || s.AbsSpeedKmh < 0.5)
|
||||
movingOpen = false;
|
||||
if (b.Extra is { } x && s.Extra is { } y)
|
||||
{
|
||||
if (x.HandBrake <= 0 && y.HandBrake > 0)
|
||||
events.Add(new BlackBoxEvent(s.Time, strings["EventHandBrakeOn"], Palette.LampWarning, false));
|
||||
else if (x.HandBrake > 0 && y.HandBrake <= 0)
|
||||
events.Add(new BlackBoxEvent(s.Time, strings["EventHandBrakeOff"], Palette.FaintText, false));
|
||||
if (x.BrakeDelay != y.BrakeDelay || x.BrakeMode != y.BrakeMode)
|
||||
{
|
||||
string setting = string.Join(" · ", new[] { CabPanel.BrakeSetting(y.BrakeDelay), CabPanel.BrakeMode(y.BrakeMode) }.Where(t => t.Length > 0));
|
||||
events.Add(new BlackBoxEvent(s.Time, string.Format(culture, strings["EventBrakeSetting"], setting), Palette.Brakes, false));
|
||||
}
|
||||
if (x.RadioChannel != y.RadioChannel && y.RadioChannel > 0)
|
||||
events.Add(new BlackBoxEvent(s.Time, string.Format(culture, strings["EventRadioChannel"], y.RadioChannel), Palette.LampOn, false));
|
||||
// the speed set, while the cruise control holds one
|
||||
if (s.Has(CabDevices.SpeedControl) && (Came(CabDevices.SpeedControl) || Math.Abs(x.SpeedControlKmh - y.SpeedControlKmh) >= 1))
|
||||
events.Add(new BlackBoxEvent(s.Time, string.Format(culture, strings["EventSpeedControlSet"], y.SpeedControlKmh), Palette.Target, false));
|
||||
}
|
||||
last = s;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddCouplings(Recording recording, IBlackBoxStrings strings, List<BlackBoxEvent> events, CultureInfo culture)
|
||||
{
|
||||
string Name(int id) => recording.Vehicle(id)?.Name ?? id.ToString(culture);
|
||||
|
||||
@@ -165,6 +165,13 @@ public sealed class BlackBoxReader
|
||||
// The lamps the vehicles at the ends have, which a pattern is checked against, later again.
|
||||
if (r.Remaining >= 4)
|
||||
sample = sample with { EquippedHead = (Lights)r.U16(), EquippedTail = (Lights)r.U16() };
|
||||
// What drives it, the doors' permits and the rest, later again; the arguments are read in their order.
|
||||
if (r.Remaining >= 54)
|
||||
sample = sample with
|
||||
{
|
||||
Extra = new CabExtra((EngineKind)r.U8(), r.U8(), r.U8(), r.F32(), r.F32(), r.F32(), r.F32(), r.F32(),
|
||||
r.F32(), r.F32(), r.F32(), r.I8(), r.U8(), r.U8(), r.F32(), r.F32(), r.F32(), r.F32())
|
||||
};
|
||||
rec.AddCab(sample);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public static class HeadCodes
|
||||
};
|
||||
|
||||
/// <summary>Whether the head of the train showed what it was to; null when nothing tells it.</summary>
|
||||
public static bool? HeadRight(CabSample sample) => sample.Movement switch
|
||||
public static bool? HeadRight(CabSample sample) => NoLamps(sample.EquippedHead) ? null : sample.Movement switch
|
||||
{
|
||||
Movement.Train => Meets(sample.LightsHead, sample.EquippedHead, sample.LightHintHead, HeadCode.Pc1),
|
||||
Movement.Shunting => Shows(HeadCode.Tb1, sample.LightsHead, sample.EquippedHead),
|
||||
@@ -79,7 +79,7 @@ public static class HeadCodes
|
||||
};
|
||||
|
||||
/// <summary>Whether the tail of the train showed what it was to; null when nothing tells it.</summary>
|
||||
public static bool? TailRight(CabSample sample) => sample.Movement switch
|
||||
public static bool? TailRight(CabSample sample) => NoLamps(sample.EquippedTail) ? null : sample.Movement switch
|
||||
{
|
||||
Movement.Train => Meets(sample.LightsTail, sample.EquippedTail, sample.LightHintTail, HeadCode.Pc5),
|
||||
Movement.Shunting => Shows(HeadCode.Tb1, sample.LightsTail, sample.EquippedTail),
|
||||
@@ -87,6 +87,12 @@ public static class HeadCodes
|
||||
};
|
||||
|
||||
/// <summary>Whether both ends showed what they were to; null when nothing tells it.</summary>
|
||||
/// <summary>
|
||||
/// An end with no lamp at all, a draisine's: the simulator still asks it for a signal it can't show, so it
|
||||
/// is not judged. A recording made before the lamps were written can't tell.
|
||||
/// </summary>
|
||||
public static bool NoLamps(Lights? equipped) => equipped == Lights.None;
|
||||
|
||||
public static bool? Right(CabSample sample) =>
|
||||
HeadRight(sample) is { } head && TailRight(sample) is { } tail ? head && tail : null;
|
||||
|
||||
|
||||
@@ -79,9 +79,56 @@ public enum CabDevices : uint
|
||||
Heating = 0x2000,
|
||||
/// <summary>Applied.</summary>
|
||||
SpringBrake = 0x4000,
|
||||
ShuntMode = 0x8000
|
||||
ShuntMode = 0x8000,
|
||||
/// <summary>Sounding.</summary>
|
||||
DepartureSignal = 0x0001_0000,
|
||||
/// <summary>The doors may be opened.</summary>
|
||||
DoorPermitLeft = 0x0002_0000,
|
||||
DoorPermitRight = 0x0004_0000,
|
||||
/// <summary>The steps come out with the doors.</summary>
|
||||
DoorStep = 0x0008_0000,
|
||||
/// <summary>The doors lock above a speed.</summary>
|
||||
DoorLock = 0x0010_0000,
|
||||
/// <summary>Locked now.</summary>
|
||||
DoorsLocked = 0x0020_0000,
|
||||
/// <summary>Releasing the brakes of the vehicle.</summary>
|
||||
BrakeReleaser = 0x0040_0000,
|
||||
FuelPump = 0x0080_0000,
|
||||
OilPump = 0x0100_0000,
|
||||
/// <summary>The fans of the traction motors.</summary>
|
||||
MotorBlowers = 0x0200_0000,
|
||||
/// <summary>The diesel engine is over its temperature.</summary>
|
||||
EngineHot = 0x0400_0000,
|
||||
/// <summary>The cruise control holds a speed.</summary>
|
||||
SpeedControl = 0x0800_0000
|
||||
}
|
||||
|
||||
/// <summary>What drives the vehicle whose motors the cab drives.</summary>
|
||||
public enum EngineKind : byte
|
||||
{
|
||||
/// <summary>Nothing of its own: a draisine, a car.</summary>
|
||||
None = 0,
|
||||
Electric = 1,
|
||||
ElectricInduction = 2,
|
||||
/// <summary>With a mechanical or a hydraulic transmission.</summary>
|
||||
Diesel = 3,
|
||||
DieselElectric = 4,
|
||||
Steam = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The rest of a look at the user's train, written from the same version as the doors' permits: what drives it,
|
||||
/// the brakes' setting and mode, the hand brake, the radio, the horn, the pressures of the equalizing reservoir
|
||||
/// and the main reservoir pipe, a diesel's oil, temperatures, clutch and torque converter, the cruise control,
|
||||
/// the power and the integrated controller of a unit, the highest brake cylinder pressure of the train. The
|
||||
/// temperatures and the oil mean something for a diesel only.
|
||||
/// </summary>
|
||||
public readonly record struct CabExtra(EngineKind Engine, byte BrakeDelay, byte RadioChannel, float EqualizingReservoir,
|
||||
float FeedPipe, float OilPressure, float WaterTemperature, float OilTemperature,
|
||||
float EngineTemperature, float SpeedControlKmh, float EnginePower, sbyte HandBrake,
|
||||
byte BrakeMode, byte Horn, float IntegratedController, float Clutch, float ConverterFill,
|
||||
float ConsistBrakeCylinder);
|
||||
|
||||
/// <summary>The train protection: the alerter (CA) and the cab signalling (SHP) apart.</summary>
|
||||
[Flags]
|
||||
public enum SecurityFlags : byte
|
||||
@@ -319,7 +366,7 @@ public readonly record struct CabSample(double Time, int Vehicle, Position Posit
|
||||
Lights LightsHead, Lights LightsTail, sbyte CabEnd = 0,
|
||||
SecurityFlags Security = SecurityFlags.None, float LateralAcceleration = 0,
|
||||
Movement Movement = Movement.Unknown, short LightHintHead = -1, short LightHintTail = -1,
|
||||
Lights? EquippedHead = null, Lights? EquippedTail = null)
|
||||
Lights? EquippedHead = null, Lights? EquippedTail = null, CabExtra? Extra = null)
|
||||
{
|
||||
public bool Has(CabDevices device) => (Devices & device) == device;
|
||||
public bool Has(SecurityFlags flag) => (Security & flag) == flag;
|
||||
|
||||
@@ -108,7 +108,8 @@ internal sealed class BlackBoxFile
|
||||
float target = 40, float nextLimit = -1, float nextDistance = -1, float brakeCylinder = 0,
|
||||
uint devices = 0x0007, bool extended = true, sbyte cabEnd = 1, byte security = 0,
|
||||
float lateral = 0.2f, ushort lightsHead = 0x15, ushort lightsTail = 0x22, byte? movement = null,
|
||||
short hintHead = -1, short hintTail = -1, ushort? equippedHead = null, ushort? equippedTail = null) =>
|
||||
short hintHead = -1, short hintTail = -1, ushort? equippedHead = null, ushort? equippedTail = null,
|
||||
byte? engine = null, float oilTemperature = 70) =>
|
||||
Record(0x20, w =>
|
||||
{
|
||||
w.Write(time);
|
||||
@@ -159,6 +160,28 @@ internal sealed class BlackBoxFile
|
||||
{
|
||||
w.Write(head);
|
||||
w.Write(equippedTail ?? head);
|
||||
// what drives it and the rest: a recording made before them ends above
|
||||
if (engine is { } kind2)
|
||||
{
|
||||
w.Write(kind2);
|
||||
w.Write((byte)2); // brake setting P
|
||||
w.Write((byte)3); // radio channel
|
||||
w.Write(4.8f); // equalizing reservoir
|
||||
w.Write(8.0f); // main reservoir pipe
|
||||
w.Write(0.45f); // oil pressure
|
||||
w.Write(75.0f); // water
|
||||
w.Write(oilTemperature);
|
||||
w.Write(80.0f); // engine
|
||||
w.Write(0.0f); // cruise control
|
||||
w.Write(900.0f); // power
|
||||
w.Write((sbyte)0); // hand brake
|
||||
w.Write((byte)1); // brake mode
|
||||
w.Write((byte)0); // horn
|
||||
w.Write(0.5f); // integrated controller
|
||||
w.Write(1.0f); // clutch
|
||||
w.Write(0.0f); // torque converter
|
||||
w.Write(2.5f); // the highest brake cylinder of the train
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,34 @@ public class HeadCodeTests
|
||||
Assert.True(fault.TailWrong);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void What_drives_the_train_and_the_rest_are_read_when_written()
|
||||
{
|
||||
var f = new BlackBoxFile().Info();
|
||||
f.Cab(0, 1, 1000, 40, movement: 1, equippedHead: 0x15, equippedTail: 0x22, engine: 4, oilTemperature: 71.5f);
|
||||
f.Cab(0.5, 1, 1000, 40, movement: 1, equippedHead: 0x15, equippedTail: 0x22);
|
||||
var cab = BlackBoxReader.Read(f.End(1).Stream()).Cab;
|
||||
var extra = Assert.NotNull(cab[0].Extra);
|
||||
Assert.Equal(EngineKind.DieselElectric, extra.Engine);
|
||||
Assert.Equal(3, extra.RadioChannel);
|
||||
Assert.Equal(71.5f, extra.OilTemperature);
|
||||
Assert.Equal(1.0f, extra.Clutch);
|
||||
Assert.Equal(2.5f, extra.ConsistBrakeCylinder);
|
||||
// an older sample has none
|
||||
Assert.Null(cab[1].Extra);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_vehicle_with_no_lamps_is_not_judged()
|
||||
{
|
||||
var recording = DriveWith(0, 0, 0, 0);
|
||||
Assert.Null(HeadCodes.HeadRight(recording.Cab[0]));
|
||||
Assert.Null(HeadCodes.TailRight(recording.Cab[0]));
|
||||
var summary = recording.Summarize();
|
||||
Assert.Equal(0, summary.LightsJudgedSeconds);
|
||||
Assert.Empty(summary.LightsFaults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_vehicle_with_all_the_lamps_is_to_light_all_three_for_Pc1()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user