using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Xml; using Aitex.Core.Common; using Aitex.Core.RT.DataCenter; using Aitex.Core.RT.Device; using Aitex.Core.RT.Event; using Aitex.Core.RT.SCCore; using Aitex.Core.Util; using Aitex.Sorter.Common; using MECF.Framework.Common.Equipment; using MECF.Framework.Common.SubstrateTrackings; using MECF.Framework.RT.EquipmentLibrary.HardwareUnits.Robot; using Virgo_DCommon; using Virgo_DRT.Devices; using Virgo_DRT.Devices.YASKAWA; using Virgo_DRT.Modules; using System.Diagnostics; using Aitex.Core.Common.DeviceData; namespace Virgo_DRT.Device { class EfemCommunicationBase { protected readonly AsyncSocket _socket = new AsyncSocket(""); protected EfemCommunicationBase(string sIP) { string ip_address = sIP; if (SC.GetValue("System.IsSimulatorMode")) { ip_address = "127.0.0.1:13001"; } _socket.Connect(ip_address); _socket.OnDataChanged += Hardware_OnDataChanged; _socket.OnErrorHappened += Hardware_OnErrorHappened; //if (!_socket.IsConnected) //{ // EV.PostAlarmLog(ModuleName.EFEM.ToString(), "Cannot connect to EFEM"); //} } private void Hardware_OnDataChanged(string message) { Singleton.Instance.EFEM.PostMsg(EfemEntity.MSG.RecHwMsg, message); } private void Hardware_OnErrorHappened(ErrorEventArgs args) { } public virtual void SendTo(string str) { } public virtual void SendTo(IEfemMessage yas_msg) { } } class EfemBase : BaseDevice, IDevice { // Fields // protected volatile LinkedList _actions = new LinkedList(); protected EfemCommunicationBase _comm; protected IMessageHandler _msgHandler; public virtual ILoadport this[ModuleName mod] { get { throw new ApplicationException(); } } // Properties // public new ModuleName Module { get; set; } public OnlineFlag OnlineFlag { get; set; } public bool CommunicationConnected { get; protected set; } public DeviceState Status { get; set; } public bool HasActions => _actions.Any(x => x.Status == ActionStatus.Pending); public EfemCommunicationBase Comm => _comm; public IMessageHandler MsgHandler => _msgHandler; // Constructor // protected EfemBase(XmlElement xmlNode = null) { } public bool Initialize() { return true; } public void Monitor() { throw new NotImplementedException(); } public void Terminate() { throw new NotImplementedException(); } public void Reset() { throw new NotImplementedException(); } // Methods // public void ExecuteAction() { if (!_actions.Any()) { Debug.WriteLine("No Action in the EFEM Queue"); return; } if (_actions.All(x => x.Status != ActionStatus.Pending)) { Debug.Print("NO pending Action in EFEM Queue"); return; } var nextAction = _actions.First(a => a.Status == ActionStatus.Pending); if (nextAction != null) { Debug.WriteLine($"{nextAction.GetType()} 开始执行"); nextAction.Execute(); } } public void ClearActions() { _actions?.Clear(); } public void AddAction(ActionBase cmd) { _actions.AddLast(cmd); } public void UpdateStatus(ushort Id, ActionStatus st) { var cur = _actions.First(x => x.ID == Id); if (null == cur) throw new ApplicationException($"NO {Id} action in the queue"); cur.Status = st; if (st == ActionStatus.Completed) { cur.OnPostWork(); } } public virtual void ReceiveMessage(string sRec) { throw new NotImplementedException(); } public void SetOnline(bool online) { this.OnlineFlag = online ? OnlineFlag.Online : OnlineFlag.Offline; } public virtual void SetOnline(ModuleName mod, bool online) { } public virtual void SetBusy(ModuleName mod, bool online) { } } } namespace Virgo_DRT.Device.YASKAWA { sealed class MessageHandler : IMessageHandler { // Fields // private readonly IList _msgQueue = new List(); private readonly EfemBase _efem; // Properties // public bool IsCompleted { get { if (_msgQueue.Count <= 0) return false; return EfemMessage.MsgHead.INF == _msgQueue[_msgQueue.Count - 1].Head; } } public event EventHandler CommandUpdated; public event EventHandler EventUpdated; public event EventHandler ErrorOccurred; // Constructor // public MessageHandler(EfemBase device) { _efem = device; } public void Send(IEfemMessage msg) { _efem.Comm.SendTo(msg); _msgQueue.Add(msg as EfemMessage); } public void ReceiveMessage(string sRec) { string[] msgs = sRec.Split('\r'); foreach (var msg in msgs) { if (string.IsNullOrWhiteSpace(msg)) continue; EfemMessage rec_msg = msg.ToMessage(); switch (rec_msg.Head) { case EfemMessage.MsgHead.ACK: _msgQueue.Add(rec_msg); OnCommandUpdated(new EfemActionArgs { Module = rec_msg.Port, CommandType = rec_msg.Operation, Status = ActionStatus.RecACK }); break; case EfemMessage.MsgHead.INF: OnCommandUpdated(new EfemActionArgs { Module = rec_msg.Port, CommandType = rec_msg.Operation, Status = ActionStatus.RecINF }); // 收到INF之后发送ACK确认 string strACK = rec_msg.RawString.Replace("INF", "ACK"); _efem.Comm.SendTo(strACK); EfemMessage ack_msg = strACK.ToMessage(); ack_msg.Direct = MsgDirection.To; _msgQueue.Add(ack_msg); OnCommandUpdated(new EfemActionArgs { Module = rec_msg.Port, CommandType = rec_msg.Operation, Status = ActionStatus.Completed, Data = rec_msg.Data.Count > 0 ? rec_msg.Data.First() : string.Empty }); break; case EfemMessage.MsgHead.EVT: OnEventUpdated(new EfemEventArgs { EvtStr = rec_msg.ToParamString(), Module = rec_msg.Port, CommandType = rec_msg.Operation, DataList = rec_msg.Data }); break; case EfemMessage.MsgHead.NAK: OnErrorOccurred(new EfemErrorArgs { Factor = rec_msg.Factor, Description = Constant.FactorString[rec_msg.Factor] }); break; case EfemMessage.MsgHead.CAN: OnErrorOccurred(new EfemErrorArgs { Factor = rec_msg.Factor, Description = Constant.FactorString[rec_msg.Factor], Message = rec_msg.Data[0] }); break; case EfemMessage.MsgHead.ABS: OnErrorOccurred(new EfemErrorArgs { Factor = rec_msg.Factor, Description = $"{rec_msg.Data[0]}, {rec_msg.Data[1]}" }); break; } } } private void OnCommandUpdated(EfemActionArgs args) { CommandUpdated?.Invoke(this, args); } private void OnEventUpdated(EfemEventArgs args) { EventUpdated?.Invoke(this, args); } private void OnErrorOccurred(EfemErrorArgs args) { ErrorOccurred?.Invoke(this, args); } } sealed class EfemComm : EfemCommunicationBase { public EfemComm() : base(SC.GetStringValue("EFEM.IPAddress")) { } public override void SendTo(string str) { _socket.Write(str + '\r'); } public override void SendTo(IEfemMessage msg) { if (msg is EfemMessage yas_msg) { //string str = EfemParser.Instance.TranslateBack(yas_msg); string str = yas_msg.ToString(); if (string.IsNullOrEmpty(str)) throw new ApplicationException("Yaskawa message translation error"); yas_msg.RawString = str; SendTo(str); } } } /// /// EFEM object class /// sealed class Efem : EfemBase, IEfem { //---------------------------------Fields---------------------------------------- // private readonly JetPM[] _pm = new JetPM[2]; private readonly Loadport[] _LPMs = new Loadport[2]; private readonly SignalTower _signalT = new SignalTower(); //private readonly CoolingStage[] _aligner = new CoolingStage[2]; private string _robotMoveAction; private LidState _CassetteDoor; private LidState _SideDoor; private F_TRIG _bSysVacPressure1 = new F_TRIG(); private F_TRIG _bSysCompressedAirPressure = new F_TRIG(); private R_TRIG _bFlowGaugeSensor = new R_TRIG(); private R_TRIG _bLeakageSensor = new R_TRIG(); private F_TRIG _bDiffPressureSensorSetting1 = new F_TRIG(); private F_TRIG _bDiffPressureSensorSetting2 = new F_TRIG(); private F_TRIG _bIonizerAlarm = new F_TRIG(); private F_TRIG _bFFuAlarm = new F_TRIG(); private F_TRIG _bAreaSensor = new F_TRIG(); private F_TRIG _bModeSwitch = new F_TRIG(); private RD_TRIG _bCassetteDoorTrig = new RD_TRIG(); private RD_TRIG _bSideDoorTrig = new RD_TRIG(); public override ILoadport this[ModuleName mod] { get { if (!ModuleHelper.IsLoadPort(mod)) throw new ApplicationException($"{mod} is NOT Loadport"); return _LPMs[mod - ModuleName.LP1]; } } //---------------------------------Properties------------------------------------ // public LidState CassetteDoor { get => _CassetteDoor; set { _CassetteDoor = value; _bCassetteDoorTrig.CLK = _CassetteDoor == LidState.Close; if (_bCassetteDoorTrig.T) { EV.PostWarningLog(Module.ToString(), "前门被打开"); } if (_bCassetteDoorTrig.R) { EV.PostInfoLog(Module.ToString(), "前门被关闭"); } } } public LidState DoorSwitch { get => _SideDoor; set { _SideDoor = value; _bSideDoorTrig.CLK = _SideDoor == LidState.Close; if (_bSideDoorTrig.T) { EV.PostWarningLog(Module.ToString(), "侧门被打开"); } if (_bSideDoorTrig.R) { EV.PostInfoLog(Module.ToString(), "侧门被关闭"); } } } // Constructor // public Efem() { Module = ModuleName.EfemRobot; _pm[0] = DEVICE.GetDevice(ModuleName.PMA.ToString()); _pm[1] = DEVICE.GetDevice(ModuleName.PMB.ToString()); _comm = new EfemComm(); _LPMs[0] = new Loadport(ModuleName.LP1, this); _LPMs[1] = new Loadport(ModuleName.LP2, this); _msgHandler = new MessageHandler(this); _msgHandler.CommandUpdated += MsgOnCommandUpdated; _msgHandler.EventUpdated += MsgOnEventUpdated; _msgHandler.ErrorOccurred += MsgOnErrorOccurred; CarrierManager.Instance.SubscribeLocation(ModuleName.LP1.ToString(), 1); CarrierManager.Instance.SubscribeLocation(ModuleName.LP2.ToString(), 1); WaferManager.Instance.SubscribeLocation(ModuleName.EfemRobot, 1); WaferManager.Instance.SubscribeLocation(ModuleName.Aligner1, 1); WaferManager.Instance.SubscribeLocation(ModuleName.Aligner2, 1); WaferManager.Instance.SubscribeLocation(ModuleName.LP1, 25); WaferManager.Instance.SubscribeLocation(ModuleName.LP2, 25); DATA.Subscribe("EfemRobot.RobotMoveAction", () => _robotMoveAction); DATA.Subscribe("LP1.WaferSize", () => _LPMs[0].WaferSize.ToString()); DATA.Subscribe("LP1.CassetteState", () => _LPMs[0].HasCassette ? LoadportCassetteState.Normal : LoadportCassetteState.Absent); DATA.Subscribe("LP2.WaferSize", () => _LPMs[1].WaferSize.ToString()); DATA.Subscribe("LP2.CassetteState", () => _LPMs[1].HasCassette ? LoadportCassetteState.Normal : LoadportCassetteState.Absent); DATA.Subscribe("Aligner1.WaferSize", () => WaferManager.Instance.GetWafer(ModuleName.Aligner1, 0).Size.ToString()); DATA.Subscribe("Aligner2.WaferSize", () => WaferManager.Instance.GetWafer(ModuleName.Aligner2, 0).Size.ToString()); DATA.Subscribe("EfemRobot.WaferSize", () => WaferManager.Instance.GetWafer(ModuleName.EfemRobot, 0).Size.ToString()); DATA.Subscribe("EFEM.CassetteDoor", () => CassetteDoor); _bCassetteDoorTrig.CLK = true; _bSideDoorTrig.CLK = true; } // Methods // public void HomeAll() { AddAction(new HomeAllAction(this, ModuleName.EFEM)); AddAction(new OrgshAction(this, ModuleName.EFEM)); } public void Home(ModuleName mod) { AddAction(new HomeModuleAction(this, mod)); } public void ClearError() { AddAction(new ClearErrorAction(this)); } public void AbortRobot() { AddAction(new AbortAction(this)); } public void Pick(MoveParam mp) { if (ModuleHelper.IsPm(mp.SrcModule)) { JetPM pM = GetPM(mp.SrcModule); if (pM.IsSlitDoorClosed) { EV.PostAlarmLog(pM.Module.ToString(), "Slit 门必须打开"); return; } AddAction(new PinAction(mp.SrcModule, pM, MovementPosition.Up, false)); AddAction(new ExtendAction(this, new ExtendParam { Module = mp.SrcModule, Pos = ExtendPos.GB })); _robotMoveAction = string.Format($"{mp.SrcModule}.Picking"); AddAction(new PinAction(mp.SrcModule, pM, MovementPosition.Down, true)); AddAction(new ExtendAction(this, new ExtendParam { Module = mp.SrcModule, Pos = ExtendPos.G4 })); } else { _robotMoveAction = string.Format($"{mp.SrcModule}.Picking"); AddAction(new PickAction(this, mp)); } } public void Place(MoveParam mp) { if (ModuleHelper.IsPm(mp.DestModule)) { JetPM pM = GetPM(mp.DestModule); if (pM.IsSlitDoorClosed) { EV.PostAlarmLog(pM.Module.ToString(), "Slit 门必须打开"); return; } AddAction(new ExtendAction(this, new ExtendParam { Module = mp.DestModule, Pos = ExtendPos.PB })); _robotMoveAction = string.Format($"{mp.DestModule}.Placing"); AddAction(new PinAction(mp.DestModule, pM, MovementPosition.Up, true)); AddAction(new ExtendAction(this, new ExtendParam { Module = mp.DestModule, Pos = ExtendPos.P4 })); AddAction(new PinAction(mp.DestModule, pM, MovementPosition.Down, false)); } else { _robotMoveAction = string.Format($"{mp.DestModule}.Placing"); AddAction(new PlaceAction(this, mp)); } } public void Extend(ExtendParam ep) { AddAction(new ExtendAction(this, ep)); } public void Retract(ExtendParam ep) { AddAction(new ExtendAction(this, ep)); } public void Map(ModuleName mod) { _LPMs[mod - ModuleName.LP1].Map(); } public void SetPinUp(ModuleName mod) { AddAction(new LiftAction(this, mod)); } public void Align(ModuleName mod, float delayTime, WaferSize size) { AddAction(new AlignAction(this, mod, size)); } public void SetLamp(LightType light, LightStatus status) { //AddAction(new LedAction(this, light, status)); } public void TurnOffAllLamps() { //AddAction(new LedAction(this, LightType.RED, LightStatus.OFF)); //AddAction(new LedAction(this, LightType.YELLOW, LightStatus.OFF)); //AddAction(new LedAction(this, LightType.GREEN, LightStatus.OFF)); //AddAction(new LedAction(this, LightType.BUZZER1, LightStatus.OFF)); } public override void ReceiveMessage(string sRec) { _msgHandler.ReceiveMessage(sRec); } public override void SetOnline(ModuleName mod, bool online) { this[mod].SetOnline(online); } public override void SetBusy(ModuleName mod, bool busy) { this[mod].Status = DeviceState.Busy; } //--------------------------------Constructor------------------------------------ // private JetPM GetPM(ModuleName mod) { if (!ModuleHelper.IsPm(mod)) throw new ArgumentException("Module argument error"); return _pm[mod - ModuleName.PMA]; } //----------------------------------Private Method------------------------------- // private void MsgOnEventUpdated(object sender, EventArgs e) { if (!(e is EfemEventArgs)) return; EfemEventArgs eArg = e as EfemEventArgs; switch (eArg.CommandType) { case EfemOperation.SigStatus: // EVT:SIGSTAT/Parameter/DATA1/DATA2; string sParam = eArg.DataList[0]; // "SYSTEM" or "Pn" // DATA1 & DATA2 int nData1 = Convert.ToInt32(eArg.DataList[1], 16); int nData2 = Convert.ToInt32(eArg.DataList[2], 16); BitArray baData1 = new BitArray(new int[] { nData1}); BitArray baData2 = new BitArray(new int[] { nData2}); if (0 == string.Compare(sParam, Constant.SYS, true)) { // EVT:SIGSTAT/System/00000000/00000004; // DATA1 _bSysVacPressure1.CLK = baData1[0]; // bit 0 _bSysCompressedAirPressure.CLK = baData1[2]; // bit 2 _bFlowGaugeSensor.CLK = baData1[4]; // bit 4 _bLeakageSensor.CLK = baData1[5]; // bit 5 this.DoorSwitch = baData1[6] ? LidState.Close : LidState.Open; // bit 6 //bDrivePower = baData1[7]; // bit 7 _bDiffPressureSensorSetting1.CLK = baData1[8]; // bit 8 _bDiffPressureSensorSetting2.CLK = baData1[9]; // bit 9 _bIonizerAlarm.CLK = baData1[10]; // bit 10 _bFFuAlarm.CLK = baData1[11]; // bit 11 _bAreaSensor.CLK = baData1[12]; // bit 12 _bModeSwitch.CLK = baData1[13]; // bit 13 this.CassetteDoor = baData1[15] ? LidState.Close : LidState.Open; // bit 15 if (_bSysVacPressure1.Q) EV.PostWarningLog(Module.ToString(), "真空报错"); if (_bSysCompressedAirPressure.Q) EV.PostWarningLog(Module.ToString(), "压缩空气报错"); // DATA2 _signalT.ChangeLightStatus(LightType.RED, baData2[0] ? LightStatus.ON : baData2[5] ? LightStatus.BLINK : LightStatus.OFF); _signalT.ChangeLightStatus(LightType.GREEN, baData2[1] ? LightStatus.ON : baData2[6] ? LightStatus.BLINK : LightStatus.OFF); _signalT.ChangeLightStatus(LightType.YELLOW, baData2[2] ? LightStatus.ON : baData2[7] ? LightStatus.BLINK : LightStatus.OFF); _signalT.ChangeLightStatus(LightType.BLUE, baData2[3] ? LightStatus.ON : baData2[8] ? LightStatus.BLINK : LightStatus.OFF); _signalT.ChangeLightStatus(LightType.WHITE, baData2[4] ? LightStatus.ON : baData2[9] ? LightStatus.BLINK : LightStatus.OFF); _signalT.ChangeLightStatus(LightType.BUZZER1, baData2[10] ? LightStatus.ON : LightStatus.OFF); /* EFEM 程序中目前没有实现 _RobotErr.CLK = baData2[27]; // bit 27 bool bArmNotExtendLLA = baData2[30]; // bit 30 bool bArmNotExtendLLB = baData2[31]; // bit 31 */ } // system event else { _LPMs[eArg.Module - ModuleName.LP1].HandleEvent(eArg); } // FOUP EVENT break; case EfemOperation.GetWaferInfo: _LPMs[eArg.Module - ModuleName.LP1].HandleEvent(eArg); break; default: break; } } private void MsgOnCommandUpdated(object sender, EventArgs e) { EfemActionArgs arg = e as EfemActionArgs; if (arg == null) throw new ArgumentNullException("Argument is Null"); if (arg.CommandType == EfemOperation.Ready) { this.CommunicationConnected = true; Singleton.Instance.EFEM.PostMsg(EfemEntity.MSG.CommReady); return; } EfemActionBase action = null; foreach (var item in _actions) { if (item is EfemActionBase a1) { if (a1.Type == arg.CommandType && item.Status != ActionStatus.Completed) { action = a1; break; } } } if (action == null) { EV.PostAlarmLog(arg.Module.ToString(), $"NO activated {arg.CommandType} in the queue"); throw new ApplicationException($"NO activated {arg.CommandType} in the queue"); } // 更新 action 状态 action.Status = arg.Status; if (arg.Status == ActionStatus.Completed) { ModuleName mod = action.Module; // Map 命令比较特别, module 是LPX但是用robot做mapping if (mod == ModuleName.EFEM || mod == ModuleName.EfemRobot || ModuleHelper.IsAligner(mod) || arg.CommandType == EfemOperation.Map) { Singleton.Instance.EFEM.PostMsg(EfemEntity.MSG.ActionDone, arg.CommandType); } else if (ModuleHelper.IsLoadPort(mod)) { Singleton.Instance.EFEM.NotifyLP(mod, LoadportEntity.MSG.ActionDone); } _robotMoveAction = string.Format($"System.None"); action.OnPostWork(arg.Data); } } private void MsgOnErrorOccurred(object sender, EventArgs e) { if (e is EfemErrorArgs errArg) { this.Status = DeviceState.Error; if (this._actions.Count > 0) this._actions.RemoveLast(); EV.PostAlarmLog(Module.ToString(), $"{errArg.Description}, [{errArg.Message}], [{errArg.Factor}]"); } } } /// /// Load port object class /// sealed class Loadport : ILoadport { private readonly EfemBase _efem; //---------------------------------Properties------------------------------------ // public OnlineFlag OnlineFlag { get; set; } public ModuleName Module { get; set; } public DeviceState Status { get; set; } public WaferSize WaferSize { get; set; } public WaferStatus[] WaferInfo { get; set; } public bool HasCassette { get; set; } public bool Protrusion { get; set; } public bool IsMapped { get; set; } // Constructor // public Loadport(ModuleName mod, EfemBase efem) { Module = mod; _efem = efem; } // Methods // public void Home() { _efem.AddAction(new HomeAllAction(_efem, Module)); } public void Map() { _efem.AddAction(new MapAction(_efem, Module)); } public void HandleEvent(EfemEventArgs eArg) { switch (eArg.CommandType) { case EfemOperation.GetWaferInfo: string sWaferInfo = eArg.DataList[0]; for (byte index = 0; index < sWaferInfo.Length; index++) { WaferStatus st = (WaferStatus)int.Parse(sWaferInfo.Substring(index, 1)); WaferManager.Instance.CreateWafer(this.Module, index, st); WaferManager.Instance.UpdateWaferSize(this.Module, index, WaferSize); if (st == WaferStatus.Normal) { EV.PostInfoLog(this.Module.ToString(), $"Found Wafer on Slot {index+1} {WaferSize}"); } } this.IsMapped = true; break; case EfemOperation.SigStatus: // EVT: SIGSTAT/P2/00000381/00000000; string sParam = eArg.DataList[0]; ModuleName mod = sParam.ToModule(); if (!ModuleHelper.IsLoadPort(mod)) return; // DATA1 & DATA2 int nData1 = Convert.ToInt32(eArg.DataList[1], 16); int nData2 = Convert.ToInt32(eArg.DataList[2], 16); BitArray baData1 = new BitArray(new int[] { nData1 }); BitArray baData2 = new BitArray(new int[] { nData2 }); // wafer size this.WaferSize = !baData1[6] ? WaferSize.WS3 : !baData1[7] ? WaferSize.WS4 : !baData1[8] ? WaferSize.WS6 : WaferSize.WS0; // placement & present bool bPlacement = baData1[0]; // bit 0 bool bPresence = !baData1[1]; // bit 1 bool bArrived = bPlacement && bPresence; if (HasCassette) { if (!bArrived) { this.HasCassette = false; this.IsMapped = false; EV.PostInfoLog(mod.ToString(), "Cassette 拿走"); WaferManager.Instance.DeleteWafer(this.Module, 0, 25); } } else { if (bArrived) { this.HasCassette = true; EV.PostInfoLog(mod.ToString(), $"Cassette {WaferSize} 放好"); } } this.Protrusion = !baData1[9]; if (Protrusion) { EV.PostWarningLog(Module.ToString(), "发现 wafer 突出"); } // DATA2, loadport上面的LED 灯, 暂时不需要用到 break; default: break; } } public void SetOnline(bool online) { OnlineFlag = online ? OnlineFlag.Online : OnlineFlag.Offline; } } sealed class SignalTower { // Fields // private readonly Dictionary _lights = new Dictionary(5); public SignalTower() { _lights.Add(LightType.RED, LightStatus.OFF); _lights.Add(LightType.YELLOW, LightStatus.OFF); _lights.Add(LightType.BLUE, LightStatus.OFF); _lights.Add(LightType.GREEN, LightStatus.OFF); _lights.Add(LightType.WHITE, LightStatus.OFF); _lights.Add(LightType.BUZZER1, LightStatus.OFF); DATA.Subscribe($"{ModuleName.EFEM}.SignalTower", () => { return new AITSignalTowerData { IsGreenLightOn = _lights[LightType.GREEN] == LightStatus.ON, IsRedLightOn = _lights[LightType.RED] == LightStatus.ON, IsYellowLightOn = _lights[LightType.YELLOW] == LightStatus.ON, IsWhiteLightOn = _lights[LightType.WHITE] == LightStatus.ON, IsBlueLightOn = _lights[LightType.BLUE] == LightStatus.ON, IsBuzzerOn = _lights[LightType.BUZZER1] == LightStatus.ON, }; }); } // Methods // public void ChangeLightStatus(LightType light, LightStatus st) { if (!_lights.ContainsKey(light)) throw new ApplicationException($"NO {light} configured"); _lights[light] = st; } } }