diff --git a/Libs/CMS_CORE_Library.dll b/Libs/CMS_CORE_Library.dll index e7212f06..ee051536 100644 Binary files a/Libs/CMS_CORE_Library.dll and b/Libs/CMS_CORE_Library.dll differ diff --git a/Step.Config/ServerConfig.cs b/Step.Config/ServerConfig.cs index 1c143297..1a2e5c27 100644 --- a/Step.Config/ServerConfig.cs +++ b/Step.Config/ServerConfig.cs @@ -35,6 +35,8 @@ namespace Step.Config public static List ProductionScadaSchema = new List(); public static List ConfiguredScadaSchema = new List(); + public static List SubscribedScada = new List(); + public static List MacrosConfig; } } \ No newline at end of file diff --git a/Step.Config/ServerConfigController.cs b/Step.Config/ServerConfigController.cs index 39e130af..78c40c86 100644 --- a/Step.Config/ServerConfigController.cs +++ b/Step.Config/ServerConfigController.cs @@ -185,6 +185,7 @@ namespace Step.Config StreamReader sr = new StreamReader(SCADA_DIRECTORY + file.Name); XmlSerializer xmlSerializer = new XmlSerializer(typeof(ScadaSchemaModel)); ScadaSchemaModel schema = xmlSerializer.Deserialize(sr) as ScadaSchemaModel; + schema.Id = i++; // Setup incremental ids schema.Layers = schema.Layers.Select(x => new ScadaSchemaLayerModel() { diff --git a/Step.Core/ThreadsFunctions.cs b/Step.Core/ThreadsFunctions.cs index 79b1096d..41227899 100644 --- a/Step.Core/ThreadsFunctions.cs +++ b/Step.Core/ThreadsFunctions.cs @@ -11,6 +11,8 @@ using System.Threading; using TeamDev.SDK.MVVM; using static Step.Model.Constants; using static Step.Utils.ExceptionManager; +using static Step.Config.ServerConfig; +using System.Linq; public static class ThreadsFunctions { @@ -734,12 +736,14 @@ public static class ThreadsFunctions // Check if client is connected if (ncHandler.numericalControl.NC_IsConnected()) { + List scadaToRead = ProductionScadaSchema.Concat(SubscribedScada).ToList(); + // Get new data from PLC - libraryError = ncHandler.ReadScadaData(out List scadas); + libraryError = ncHandler.ReadScadasData(scadaToRead, out List scadas); if (libraryError.IsError()) ManageLibraryError(libraryError); - // MessageServices.Current.Publish(NC_MAGAZINE_IS_ACTIVE, null, status); + MessageServices.Current.Publish(SEND_SCADA_DATA, null, scadas); } else RestoreConnection(); diff --git a/Step.Model/Constants.cs b/Step.Model/Constants.cs index 552075fb..b0a42b40 100644 --- a/Step.Model/Constants.cs +++ b/Step.Model/Constants.cs @@ -186,6 +186,7 @@ namespace Step.Model public const string NC_MAGAZINE_IS_ACTIVE = "UPDATE_NC_MAGAZINE_STATUS"; public const string SEND_QUEUE_DATA = "SEND_QUEUE_DATA"; public const string SEND_M155_DATA = "SEND_M155_DATA"; + public const string SEND_SCADA_DATA = "SEND_SCADA_DATA"; public const string BROADCAST_DATA = "BROADCAST_DATA"; diff --git a/Step.Model/DTOModels/AlarmModels/DTOAlarmHistoricModel.cs b/Step.Model/DTOModels/AlarmModels/DTOAlarmHistoricModel.cs index 4d673513..d8e00014 100644 --- a/Step.Model/DTOModels/AlarmModels/DTOAlarmHistoricModel.cs +++ b/Step.Model/DTOModels/AlarmModels/DTOAlarmHistoricModel.cs @@ -1,6 +1,7 @@ using CMS_CORE_Library.Utils; using Step.Model.DatabaseModels; using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -32,7 +33,10 @@ namespace Step.Model.DTOModels.AlarmModels { List processes = new List(); - bool[] statusBits = Nc_Utils.IntToBits(obj.Processes); + bool[] statusBits = new BitArray(new int[] { obj.Processes }) + .Cast() + .ToArray(); + for (int i = 0; i < 8; i++) { if (statusBits[i]) diff --git a/Step.Model/DTOModels/Scada/DTOScadaModel.cs b/Step.Model/DTOModels/Scada/DTOScadaModel.cs index 7fed2795..56ff7e2b 100644 --- a/Step.Model/DTOModels/Scada/DTOScadaModel.cs +++ b/Step.Model/DTOModels/Scada/DTOScadaModel.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; +using System.Linq; namespace Step.Model.DTOModels.Scada { public class DTOScadaModel { + public int Id { get; set; } public string Name { get; set; } public bool IsInProductionPage { get; set; } public List Buttons { get; set; } @@ -19,54 +21,160 @@ namespace Step.Model.DTOModels.Scada ProgressBars = new List(); Inputs = new List(); } - } - - public class DTOScadaLayerModel - { - public int Id { get; set; } - public List Buttons { get; set; } - public List Images { get; set; } - public List ProgressBars { get; set; } - public List Inputs { get; set; } - - public DTOScadaLayerModel() + public override bool Equals(object obj) { - Buttons = new List(); - Images = new List(); - ProgressBars = new List(); - Inputs = new List(); + var item = obj as DTOScadaModel; + if (item == null) + return false; + + if (item.Buttons.Count() != Buttons.Count() || item.Images.Count() != Images.Count() || item.ProgressBars.Count() != ProgressBars.Count() || item.Inputs.Count() != Inputs.Count()) + return false; + + // Compare lists of buttons, images, progressbar and inputs + bool listAreEquals = item.Buttons.All(Buttons.Contains); + if (!listAreEquals) + return false; + + listAreEquals = item.Images.All(Images.Contains); + if (!listAreEquals) + return false; + + listAreEquals = item.ProgressBars.All(ProgressBars.Contains); + if (!listAreEquals) + return false; + + listAreEquals = item.Inputs.All(Inputs.Contains); + if (!listAreEquals) + return false; + + return true; } } + //public class DTOScadaLayerModel + //{ + // public int Id { get; set; } + // public List Buttons { get; set; } + // public List Images { get; set; } + // public List ProgressBars { get; set; } + // public List Inputs { get; set; } + + // public DTOScadaLayerModel() + // { + // Buttons = new List(); + // Images = new List(); + // ProgressBars = new List(); + // Inputs = new List(); + // } + //} + public class DTOScadaButtonModel { public int Id { get; set; } public bool Enabled { get; set; } + + public override bool Equals(object obj) + { + var item = obj as DTOScadaButtonModel; + if (item == null) + return false; + + if (item.Id != Id) + return false; + + if (item.Enabled != Enabled) + return false; + + return true; + } } public class DTOScadaImageModel { public int Id { get; set; } public bool IsVisible { get; set; } + + public override bool Equals(object obj) + { + var item = obj as DTOScadaImageModel; + if (item == null) + return false; + + + if (item.Id != Id) + return false; + + if (item.IsVisible != IsVisible) + return false; + + return true; + } } public class DTOScadaProgressBarModel { public int Id { get; set; } public byte Value { get; set; } + + public override bool Equals(object obj) + { + var item = obj as DTOScadaProgressBarModel; + if (item == null) + return false; + + if (item.Id != Id) + return false; + + if (item.Value != Value) + return false; + + return true; + } } public class DTOScadaInputModel { public int Id { get; set; } public DTOScadaValueModel Value { get; set; } + + public DTOScadaInputModel() + { + Id = 0; + Value = new DTOScadaValueModel(); + } + + public override bool Equals(object obj) + { + var item = obj as DTOScadaInputModel; + if (item == null) + return false; + + if (item.Id != Id) + return false; + + return item.Value.Equals(Value); + } } - + public class DTOScadaValueModel { public bool IsVisible { get; set; } public object Value { get; set; } - } + public override bool Equals(object obj) + { + var item = obj as DTOScadaValueModel; + if (item == null) + return false; + + if (item.IsVisible != IsVisible) + return false; + + if (!item.Value.Equals(Value)) + return false; + + return true; + } + } } \ No newline at end of file diff --git a/Step.Model/DTOModels/Scada/ScadaSchemaModel.cs b/Step.Model/DTOModels/Scada/ScadaSchemaModel.cs index 5bf50694..5905ddfb 100644 --- a/Step.Model/DTOModels/Scada/ScadaSchemaModel.cs +++ b/Step.Model/DTOModels/Scada/ScadaSchemaModel.cs @@ -6,6 +6,8 @@ namespace Step.Model.DTOModels.Scada [XmlRoot("scada")] public class ScadaSchemaModel { + public int Id { get; set; } + [XmlElement("name")] public string Name { get; set; } [XmlElement("backgroundImagePath")] diff --git a/Step.NC/NcHandler.cs b/Step.NC/NcHandler.cs index 4da543c3..97de7c3d 100644 --- a/Step.NC/NcHandler.cs +++ b/Step.NC/NcHandler.cs @@ -158,7 +158,7 @@ namespace Step.NC fileInfo = new DTOActiveImageAndNameDataModel() { - Image = SupportFunctions.FindImageBase64String(IMAGES_PATH, name), + Image = SupportFunctions.FindImageBase64String(PART_PRG_IMAGES, name), Name = Path.GetFileName(name) // TODO: Get From NC }; } @@ -193,7 +193,7 @@ namespace Step.NC // Set image base 64 string foreach (string ext in VALID_IMAGE_EXTENSIONS) { - string imagePath = IMAGES_PATH + "/" + fileInfo.Name + ext; + string imagePath = PART_PRG_IMAGES + "/" + fileInfo.Name + ext; if (File.Exists(imagePath)) { fileInfo.PreviewBase64 = "data:image/" + ext + ";base64," + Convert.ToBase64String(File.ReadAllBytes(imagePath)); @@ -2377,106 +2377,141 @@ namespace Step.NC } #endregion Osai/Fanuc Tools - - public CmsError ReadScadaData(out List scadas) + + public CmsError ReadScadasData(List schemasToRead, out List scadas) { CmsError cmsError = NO_ERROR; scadas = new List(); - DTOScadaModel scada = new DTOScadaModel(); - foreach (var prodScada in ProductionScadaSchema) + List alreadyReadScada = new List(); + + foreach (var schema in schemasToRead) { - scada = new DTOScadaModel() + // Avoid duplicated reads + if (!alreadyReadScada.Contains(schema.Id)) { - Name = prodScada.Name, - IsInProductionPage = prodScada.IsInProductionPage, - Buttons = new List() - }; + // Read one schema + cmsError = ReadScadaData(schema, out DTOScadaModel scadaValue); + if (cmsError.IsError()) + return cmsError; - // Cycle inside layers - foreach (var layer in prodScada.Layers) - { - object val = null; - object val2 = null; - // Cycle - for (int i = 0; i < layer.Buttons.Count(); i++) - { - if(layer.Buttons.ElementAt(i) != null) - { - // Read enabled from PLC - cmsError = numericalControl.PLC_RScadaValue(layer.Buttons[i].Status.MemEnabledIndex, SCADA_MEM_TYPE.BOOL, ref val); - if (cmsError.IsError()) - return cmsError; - // Populate & add new object into scada model - scada.Buttons.Add(new DTOScadaButtonModel() - { - Id = layer.Buttons[i].Id, - Enabled = Convert.ToBoolean(val) - }); - } - - - if (layer.Images.ElementAt(i) != null) - { - // Read isVisible from PLC - cmsError = numericalControl.PLC_RScadaValue(layer.Images[i].Status.MemVisibleIndex, SCADA_MEM_TYPE.BOOL, ref val); - if (cmsError.IsError()) - return cmsError; - // Populate & add new object into scada model - scada.Images.Add(new DTOScadaImageModel() - { - Id = layer.Images[i].Id, - IsVisible = Convert.ToBoolean(val) - }); - } - - - if(layer.ProgressBars.ElementAt(i) != null) - { - // Read value from PLC - cmsError = numericalControl.PLC_RScadaValue(layer.ProgressBars[i].MemValueIndex, SCADA_MEM_TYPE.BYTE, ref val); - if (cmsError.IsError()) - return cmsError; - // Populate & add new object into scada model - scada.ProgressBars.Add(new DTOScadaProgressBarModel() - { - Id = layer.ProgressBars[i].Id, - Value = Convert.ToByte(val) - }); - } - - if (layer.Inputs.ElementAt(i) != null && layer.Inputs[i].Status.Action == "read") - { - // Read isVisible from PLC - cmsError = numericalControl.PLC_RScadaValue(layer.Inputs[i].Status.MemEnabledIndex, SCADA_MEM_TYPE.BOOL, ref val); - if (cmsError.IsError()) - return cmsError; - - // Read value from PLC - cmsError = numericalControl.PLC_RScadaValue(layer.Inputs[i].Status.MemValueIndex, GetMemTypeFromString(layer.Inputs[i].Status.Type), ref val2); - if (cmsError.IsError()) - return cmsError; - - // Populate & add new object into scada model - scada.Inputs.Add(new DTOScadaInputModel() - { - Id = layer.Inputs[i].Id, - Value = new DTOScadaValueModel() - { - IsVisible = Convert.ToBoolean(val), - Value = val2 - } - }); - } - } + // Add value to return list + scadas.Add(scadaValue); + // Add id to read scada + alreadyReadScada.Add(schema.Id); } - - scadas.Add(scada); } return cmsError; } + public CmsError ReadScadaData(ScadaSchemaModel scadaSchema, out DTOScadaModel scadaValue) + { + CmsError cmsError = NO_ERROR; + scadaValue = new DTOScadaModel() + { + Id = scadaSchema.Id, + Name = scadaSchema.Name, + IsInProductionPage = scadaSchema.IsInProductionPage, + Buttons = new List() + }; + + // Cycle inside layers + foreach (var layer in scadaSchema.Layers) + { + object val = null; + object val2 = null; + // Cycle through elements, use the max number of elements between layer components in order to be optimized + for (int i = 0; i < GetMaxCountBetweenLayerComponents(layer); i++) + { + if (layer.Buttons.ElementAt(i) != null) + { + // Read enabled from PLC + cmsError = numericalControl.PLC_RScadaValue(layer.Buttons[i].Status.MemEnabledIndex, SCADA_MEM_TYPE.BOOL, ref val); + if (cmsError.IsError()) + return cmsError; + // Populate & add new object into scada model + scadaValue.Buttons.Add(new DTOScadaButtonModel() + { + Id = layer.Buttons[i].Id, + Enabled = Convert.ToBoolean(val) + }); + } + + if (layer.Images.ElementAt(i) != null) + { + // Read isVisible from PLC + cmsError = numericalControl.PLC_RScadaValue(layer.Images[i].Status.MemVisibleIndex, SCADA_MEM_TYPE.BOOL, ref val); + if (cmsError.IsError()) + return cmsError; + // Populate & add new object into scada model + scadaValue.Images.Add(new DTOScadaImageModel() + { + Id = layer.Images[i].Id, + IsVisible = Convert.ToBoolean(val) + }); + } + + if (layer.ProgressBars.ElementAt(i) != null) + { + // Read value from PLC + cmsError = numericalControl.PLC_RScadaValue(layer.ProgressBars[i].MemValueIndex, SCADA_MEM_TYPE.BYTE, ref val); + if (cmsError.IsError()) + return cmsError; + // Populate & add new object into scada model + scadaValue.ProgressBars.Add(new DTOScadaProgressBarModel() + { + Id = layer.ProgressBars[i].Id, + Value = Convert.ToByte(val) + }); + } + + if (layer.Inputs.ElementAt(i) != null && layer.Inputs[i].Status.Action == "read") + { + // Read isVisible from PLC + cmsError = numericalControl.PLC_RScadaValue(layer.Inputs[i].Status.MemEnabledIndex, SCADA_MEM_TYPE.BOOL, ref val); + if (cmsError.IsError()) + return cmsError; + + // Read value from PLC + cmsError = numericalControl.PLC_RScadaValue(layer.Inputs[i].Status.MemValueIndex, GetMemTypeFromString(layer.Inputs[i].Status.Type), ref val2); + if (cmsError.IsError()) + return cmsError; + + // Populate & add new object into scada model + scadaValue.Inputs.Add(new DTOScadaInputModel() + { + Id = layer.Inputs[i].Id, + Value = new DTOScadaValueModel() + { + IsVisible = Convert.ToBoolean(val), + Value = val2 + } + }); + } + } + } + + return cmsError; + } + + private int GetMaxCountBetweenLayerComponents(ScadaSchemaLayerModel layer) + { + // Choose which list contains the maximum number of elements + int max = layer.Buttons.Count(); + + if (layer.Images.Count() > max) + max = layer.Images.Count(); + + if (layer.Inputs.Count() > max) + max = layer.Inputs.Count(); + + if (layer.ProgressBars.Count() > max) + max = layer.ProgressBars.Count(); + + return max; + } + private SCADA_MEM_TYPE GetMemTypeFromString(string memType) { memType = memType.ToUpper(); diff --git a/Step/Controllers/WebApi/ScadaController.cs b/Step/Controllers/WebApi/ScadaController.cs index e654cb63..bb82034b 100644 --- a/Step/Controllers/WebApi/ScadaController.cs +++ b/Step/Controllers/WebApi/ScadaController.cs @@ -19,29 +19,61 @@ namespace Step.Controllers.WebApi { using (NcHandler ncHandler = new NcHandler()) { - ncHandler.Connect(); - CmsError cmsError = ncHandler.ReadScadaData(out List values); - if (cmsError.IsError()) - return BadRequest(cmsError.localizationKey); - - - ScadaDataAndSchema data = new ScadaDataAndSchema() - { - schemes = ConfiguredScadaSchema, - values = values - }; - - return Ok(data); + return Ok(ConfiguredScadaSchema); } } - [Route("production/list")] + [Route("subscribe/{scadaId:int}")] + public IHttpActionResult SubscribeScada(int scadaId) + { + // Select schema + ScadaSchemaModel selectedScadaSchema = ConfiguredScadaSchema + .Where(x => x.Id == scadaId) + .FirstOrDefault(); + + if (selectedScadaSchema == null) + return BadRequest(); + // Add to the subscriber list + SubscribedScada.Add(selectedScadaSchema); + + using (NcHandler ncHandler = new NcHandler()) + { + ncHandler.Connect(); + // Read the selected scada data + CmsError cmsError = ncHandler.ReadScadaData(selectedScadaSchema, out DTOScadaModel values); + if (cmsError.IsError()) + return BadRequest(cmsError.localizationKey); + + return Ok(values); + } + } + + [Route("unsubscribe/{scadaId:int}")] + public IHttpActionResult UnsubscribeScada(int scadaId) + { + using (NcHandler ncHandler = new NcHandler()) + { + // Find scada by id + ScadaSchemaModel scadaToRemove = SubscribedScada + .Where(x => x.Id == scadaId) + .FirstOrDefault(); + + if (scadaToRemove == null) + return BadRequest(); + // Remove from the subiscriber list + SubscribedScada.Remove(scadaToRemove); + + return Ok(); + } + } + + [Route("production")] public IHttpActionResult GetProductionPageList() { using(NcHandler ncHandler = new NcHandler()) { ncHandler.Connect(); - CmsError cmsError = ncHandler.ReadScadaData(out List values); + CmsError cmsError = ncHandler.ReadScadasData(ProductionScadaSchema, out List values); if (cmsError.IsError()) return BadRequest(cmsError.localizationKey); diff --git a/Step/Listeners/ListenersHandler.cs b/Step/Listeners/ListenersHandler.cs index 498292b4..fa76fdb5 100644 --- a/Step/Listeners/ListenersHandler.cs +++ b/Step/Listeners/ListenersHandler.cs @@ -90,8 +90,12 @@ namespace Step.Listeners { SignalRListener.SendM155Data(a); })); + infos.Add(MessageServices.Current.Subscribe(SEND_SCADA_DATA, (a, b) => + { + SignalRListener.SendScadaData(a); + })); - // Database + //////////////////////////// Database infos.Add(MessageServices.Current.Subscribe(UPDATE_TOOLS_DATA, (a, b) => { SignalRDatabaseHandler.UpdateToolsData(a); diff --git a/Step/Listeners/SignalR/SignalRListener.cs b/Step/Listeners/SignalR/SignalRListener.cs index 3d636350..44ed73b6 100644 --- a/Step/Listeners/SignalR/SignalRListener.cs +++ b/Step/Listeners/SignalR/SignalRListener.cs @@ -3,6 +3,7 @@ using Step.Config; using Step.Controllers.SignalR; using Step.Model.DTOModels; using Step.Model.DTOModels.AlarmModels; +using Step.Model.DTOModels.Scada; using Step.Model.DTOModels.ToolModels; using Step.Utils; using System; @@ -244,6 +245,19 @@ namespace Step.Listeners.SignalR } } + public static void SendScadaData(object scada) + { + List scadaVal = scada as List; + + if (!LastScadaData.SequenceEqual(scadaVal)) + { + LastScadaData = scadaVal; + + var context = GlobalHost.ConnectionManager.GetHubContext(); + context.Clients.Group("ncData").scadaData(scadaVal); + } + } + private volatile static object _broadcastlock = new Object(); public static void BroadcastData() diff --git a/Step/Listeners/SignalRStaticObjects.cs b/Step/Listeners/SignalRStaticObjects.cs index dfa6669c..715a8b77 100644 --- a/Step/Listeners/SignalRStaticObjects.cs +++ b/Step/Listeners/SignalRStaticObjects.cs @@ -1,5 +1,6 @@ using Step.Model.DTOModels; using Step.Model.DTOModels.AlarmModels; +using Step.Model.DTOModels.Scada; using Step.Model.DTOModels.ToolModels; using System.Collections.Generic; @@ -22,6 +23,7 @@ namespace Step.Listeners public static Dictionary LastNcMagazineIsActive = new Dictionary(); public static List LastPartProgramQueue = new List(); public static List LastM155Data = new List(); + public static List LastScadaData = new List(); public static bool LastIsNcConnected = false; }