diff --git a/AirMonitor/AirZone.cs b/AirMonitor/AirZone.cs index 7a5225d..9d4c3df 100644 --- a/AirMonitor/AirZone.cs +++ b/AirMonitor/AirZone.cs @@ -9,30 +9,38 @@ namespace IngameScript { public class AirZone { - public bool Triggered = false; + public bool Triggered { get; private set; } = false; + public string Name { get; private set; } + public List Vents { get; private set; } private MyIni _ini; - private List _vents; + private List _doors; + private List _displays; private PrefixedConsole _console; private Sequencer _sequencer; + private const float TriggerLevel = 0.75F; public AirZone(string zoneName, MyIni ini, IConsole console) { + Name = zoneName; _ini = ini; _console = new PrefixedConsole(console, zoneName); _sequencer = new Sequencer(zoneName, _console); - _vents = new List(); + Vents = new List(); + _doors = new List(); + _displays = new List(); } public void AddBlock(IMyTerminalBlock block) { if (block is IMyAirVent) { - _vents.Add(block as IMyAirVent); + Vents.Add(block as IMyAirVent); } else if (block is IMyDoor) { + _doors.Add(block as IMyDoor); SequenceableDoor wrapped = SequenceableFactory.MakeSequenceable(block as IMyDoor, _ini, "airMonitor") as SequenceableDoor; wrapped.Step = 0; wrapped.DeployOpen = false; @@ -47,28 +55,49 @@ namespace IngameScript // TODO: support for arbitrary (sub-)sequences here could be powerful } + public void Reset() + { + _console.Print("Resetting sensor."); + Triggered = false; + } + // This enumerator should run forever. public IEnumerator Monitor() { while (true) { - foreach (IMyAirVent vent in _vents) + foreach (IMyAirVent vent in Vents) { if (vent.GetOxygenLevel() < TriggerLevel) Triggered = true; } if (Triggered == true) { + _console.Print($"Low pressure alarm triggered."); // close the doors IEnumerator job = _sequencer.RunSequence(true); while (job.MoveNext()) yield return true; - // noop until manually reset - while (Triggered) yield return true; + while (Triggered) + { + // if any of the doors become re-enabled, reset and unlock all of them; + // think of it as a player manually overriding the safety protocols. + // Note that this means resets propagate across zones that share doors, + // which will likely be all of them. Which is a *little* bit of a + // bug, but it's better than never re-triggering + foreach (IMyDoor door in _doors) + { + if (door.Enabled) + { + Reset(); + break; + } + } + yield return true; + } - // open the doors - job = _sequencer.RunSequence(false); - while (job.MoveNext()) yield return true; + // unlock the doors, but we'll leave them closed. + foreach (IMyDoor door in _doors) { door.Enabled = true; } } yield return true; diff --git a/AirMonitor/Program.cs b/AirMonitor/Program.cs index 4e97772..0d2156b 100644 --- a/AirMonitor/Program.cs +++ b/AirMonitor/Program.cs @@ -25,9 +25,13 @@ namespace IngameScript private MyIni _ini; private Console _console; private MyCommandLine _cli; + private int _totalTicks = 0; + private Dictionary _zones; private List> _jobs; - private int _totalTicks = 0; + private List _displays; + private StringBuilder _displayBuffer; + private List _oxygenTanks; public Program() { @@ -36,6 +40,9 @@ namespace IngameScript _console = new Console(this, _ini); _zones = new Dictionary(); _jobs = new List>(); + _displays = new List(); + _displayBuffer = new StringBuilder(); + _oxygenTanks = new List(); // Find all tagged objects and build out zones List blocks = new List(); @@ -44,6 +51,34 @@ namespace IngameScript { _ini.TryParse(block.CustomData); string[] zones = new string[] { }; + + // TODO: how do we display text on e.g. decorative console blocks? + // They have configurable screens but the docs say they don't implement this. + // Meanwhile *every* functional block seems to implement IMyTextSurfaceProvider... + // It'd probably be safe to just check SurfaceCount... experiment with this later + if (block is IMyTextSurface) + { + _console.Print($"Adding monitoring display '{block.CustomName}'"); + _displays.Add(block as IMyTextSurface); + } + if (_ini.Get("airMonitor", "display").ToString() != "") + { + int displayIndex = _ini.Get("airMonitor", "display").ToInt32(); + IMyTextSurfaceProvider provider = block as IMyTextSurfaceProvider; + if (provider.SurfaceCount <= displayIndex) + { + _console.Print($"Invalid display index '{displayIndex}' in block '{block.CustomName}'"); + } + else + { + _displays.Add(provider.GetSurface(displayIndex)); + } + } + if (block is IMyGasTank) + { + _oxygenTanks.Add(block as IMyGasTank); + } + if (_ini.Get("airMonitor", "zones").ToString() != "") { zones = _ini.Get("airMonitor", "zones").ToString().Split(','); @@ -63,9 +98,11 @@ namespace IngameScript } } - _console.Print($"Found {_zones.Count} zones."); - foreach (AirZone zone in _zones.Values) + _console.Print($"Found {_zones.Count} zones:"); + foreach (KeyValuePair kvp in _zones) { + AirZone zone = kvp.Value; + _console.Print(kvp.Key); _jobs.Add(zone.Monitor()); } Runtime.UpdateFrequency |= UpdateFrequency.Update100; @@ -85,12 +122,42 @@ namespace IngameScript _console.Print($"Resetting {zone}."); if (_zones.ContainsKey(zone)) { - _zones[zone].Triggered = false; + _zones[zone].Reset(); } } } } + // write diagnostics + if (_displays.Count != 0) + { + _displayBuffer.Clear(); + _displayBuffer.Append("AIR PRESSURE REPORT\n\n"); + foreach (AirZone zone in _zones.Values) + { + _displayBuffer.Append(zone.Name); + _displayBuffer.Append(": "); + _displayBuffer.Append(zone.Triggered ? "ALARM TRIPPED " : "NOMINAL "); + foreach (IMyAirVent vent in zone.Vents) + { + _displayBuffer.Append((int)(vent.GetOxygenLevel() * 100F)); + _displayBuffer.Append("% "); + } + + _displayBuffer.Append("\n"); + } + + _displayBuffer.Append("\n"); + _displayBuffer.Append("OXYGEN TANK LEVELS\n"); + foreach (IMyGasTank tank in _oxygenTanks) + { + _displayBuffer.Append((int)(tank.FilledRatio * 100)); + _displayBuffer.Append("% "); + } + + foreach (IMyTextSurface display in _displays) display.WriteText(_displayBuffer.ToString()); + } + foreach (IEnumerator job in _jobs) { if (job.MoveNext()) continue;