/// <summary> /// Restituisce se la risorsa ha un attributo attivo che abilita /// la procedura di intestatura /// </summary> /// <param name="resource">Risorsa di cui valutare gli attributi</param> /// <param name="attributeName">Attributo da ricercare</param> /// <returns>Se l'attributo è presente, attivo e con valore 1</returns> public static bool HasActiveBooleanAttribute(this IMesResource resource, string attributeName) { if (resource == null || resource.Settings.ResourceAttributes == null) { return(false); } var outResult = false; var isValid = resource.Settings.ResourceAttributes.TryGetActiveAttribute(attributeName, ValueContainerType.Boolean, out outResult); return(isValid && outResult); }
/// <summary> /// Recupera le informazioni delle lavorazioni pianificate su Nicim /// per la macchina specificata /// NB: i dati sono generati dallo SCHEDULATORE di NICIM che è un modulo /// opzionale e solo pochi clienti lo hanno /// </summary> /// <param name="resource">Macchina per cui si vogliono le lavorazioni</param> /// <returns>Elenco lavorazioni pianificate per la risorsa</returns> private IList <NicimWorkPlanItem> GetResourceNicimWorkPlanItems(IMesResource resource) { var nicimRepeater = this.GetNicimRepeater(); if (nicimRepeater == null || !nicimRepeater.DirectAccessEnabled) { return(new List <NicimWorkPlanItem>()); } var workPlanItems = nicimRepeater.LoadWorkPlan(resource); return(workPlanItems == null ? new List <NicimWorkPlanItem>() : workPlanItems.ToList()); }
/// <summary> /// Verifica se è presente un attributo di tipo stringa e ne restituisce il valore /// </summary> /// <param name="resource">Risorsa di cui valutare gli attributi</param> /// <param name="attributeName">Attributo da ricercare</param> /// <param name="attributeValue">Valore estratto dall'attributo</param> /// <returns>Se l'attributo è presente e con un valore valido</returns> public static bool TryGetActiveResourceStringAttribute(this IMesResource resource, string attributeName, out string attributeValue) { attributeValue = string.Empty; if (resource == null || resource.Settings.ResourceAttributes == null) { return(false); } var isValid = resource.Settings .ResourceAttributes .TryGetActiveAttribute(attributeName, ValueContainerType.String, out attributeValue); return(isValid); }
/// <summary> /// Recupera le informazioni delle lavorazioni in corso su Nicim /// per la macchina specificata /// </summary> /// <param name="resource">Macchina per cui si vogliono le lavorazioni</param> /// <returns>Elenco lavorazioni in corso per la risorsa</returns> private IList <NicimRunningOperation> GetResourceNicimRunningOperations(IMesResource resource) { var nicimRepeater = this.GetNicimRepeater(); if (nicimRepeater == null || !nicimRepeater.DirectAccessEnabled) { return(new List <NicimRunningOperation>()); } //operazioni attive sulla risorsa specificata var runningOperations = nicimRepeater.LoadRunningOperations(resource); ////operazioni attive su TUTTE le risorse //var allRunningOperations = nicimRepeater.LoadRunningOperations(); //l'oggetto NicimRunningOperation ha i vari campi della vista Nicim //e una proprietà CustomFields che contiene l'elenco dei 10 campi //personalizzati return(runningOperations == null ? new List <NicimRunningOperation>() : runningOperations.ToList()); }
/// <summary> /// Invio alla macchina specificata un ordine di produzione, se non in marcia /// </summary> private void WriteValuesOnPowerDevice(IMesResource resource, string orderValue, int qtyValue) { if (resource == null) { throw new ArgumentNullException(nameof(resource)); } if (string.IsNullOrWhiteSpace(orderValue)) { throw new ArgumentException("Value cannot be null or whitespace.", nameof(orderValue)); } if (qtyValue <= 0) { throw new ArgumentOutOfRangeException(nameof(qtyValue)); } var dvcService = this._MesManager.ServiceManager.GetService <IDvcIntegrationService>(); if (dvcService == null || !dvcService.Enabled) { this._MesManager.ApplicationMainLogger.WriteMessage(MessageLevel.Info, false, LOGSOURCE, "WriteValuesOnPowerDevice(): PowerDevice integration not available"); return; } const string dvcInstance = "localhost"; const string qtyAddress = @"{OPCV1}{TESTOPC}{storage.numeric.reg02}"; //quantità prevista const string orderAddress = @"{OPCV1}{TESTOPC}{storage.string.reg01}"; //ordine di lavoro const string runningMachineAddress = @"{OPCV1}{TESTOPC}{storage.bool.reg01}"; //bool - macchina in marcia /* * prima di tutto leggo il valore da PowerDevice di macchina in marcia * se non lo trovo a false\zero interrompo l'operazione */ var isRunningReadResult = dvcService.ReadAddressValue(runningMachineAddress, dvcInstance) as DvcReadOperationSuccess; if (isRunningReadResult?.AddressValue == null || !isRunningReadResult.AddressValue.IsValid || string.IsNullOrWhiteSpace(isRunningReadResult.AddressValue.ValueAsString)) { this._MesManager.ApplicationMainLogger.WriteMessage(MessageLevel.Warning, false, LOGSOURCE, "WriteValuesOnPowerDevice(): cannot read if machine is running"); return; } if (isRunningReadResult.AddressValue.ValueAsString.ToLowerInvariant() != Boolean.FalseString.ToLowerInvariant() && isRunningReadResult.AddressValue.ValueAsString.ToLowerInvariant() != "0") { this._MesManager.ApplicationMainLogger.WriteMessage(MessageLevel.Warning, false, LOGSOURCE, "WriteValuesOnPowerDevice(): cannot write because machine is running"); return; } /* * NB: i valori numerici devono essere convertiti in stringa, * se decimali il separatore è sempre il punto */ var nfi = (NumberFormatInfo)CultureInfo.CurrentCulture.NumberFormat.Clone(); nfi.NumberDecimalSeparator = "."; nfi.NumberGroupSeparator = string.Empty; var qtyValueAsString = qtyValue.ToString(nfi); /* * procediamo alla scrittura */ var orderWriteResponse = dvcService.SetAddressValue(orderAddress, dvcInstance, orderValue); var qtyWriteResponse = dvcService.SetAddressValue(qtyAddress, dvcInstance, qtyValueAsString); if (orderWriteResponse == null || qtyWriteResponse == null) { this._MesManager.ApplicationMainLogger.WriteMessage(MessageLevel.Warning, false, LOGSOURCE, "WriteValuesOnPowerDevice(): null response from PowerDevice integration"); return; } if (orderWriteResponse.Success && qtyWriteResponse.Success) { this._MesManager.ApplicationMainLogger.WriteMessage(MessageLevel.Info, false, LOGSOURCE, "WriteValuesOnPowerDevice(): write completed"); return; } this._MesManager.ApplicationMainLogger.WriteMessage(MessageLevel.Warning, false, LOGSOURCE, "WriteValuesOnPowerDevice(): bad response from PowerDevice integration! " + "ORDER = {0} \\ {1}", orderWriteResponse.Success, qtyWriteResponse.Success); }
/// <summary> /// Carica i dati di produzione per una pressofusione /// </summary> /// <param name="resource">Risorsa associata alla pressa</param> private void CheckMachineProduction(IMesResource resource) { /* * La tabella PRODUZ ha un Id e timestamp che usiamo per caricare solo * i nuovi record rispetto all'ultimo che abbiamo processato. * Le informazioni da passare da un ciclo all'altro vengono memorizzate in un * oggetto MachineProductionMemento sulla tabella SH97_RepositoryValues * */ if (resource == null) { throw new ArgumentNullException(nameof(resource)); } //abilitazione tramite attributo var enabled = resource.Settings .ResourceAttributes .GetActiveAttributeValueOrDefault <bool>(LocalConstants.RES_ATTR_DIECASTINGPROD_ENABLED, ValueContainerType.Boolean); if (!enabled) { this._MesLogger.WriteMessage(MessageLevel.Diagnostics, false, LOGRSOURCE, "CheckMachineProduction(): resource {0} not enabled", resource.Name); return; } //mi serve anche nome istanza SQL, anche in questo caso gestito tramite attributo. //Il nome del DB invece è cablato, mi aspetto che possa essere spostato, non rinominato var sqlInstanceName = resource.Settings .ResourceAttributes .GetActiveAttributeValueOrDefault <string>(LocalConstants.RES_ATTR_DIECASTINGPROD_SQL, ValueContainerType.String); if (string.IsNullOrWhiteSpace(sqlInstanceName)) { this._MesLogger.WriteMessage(MessageLevel.Error, true, LOGRSOURCE, "CheckMachineProduction(): Bad SQL instance name resource {0}", resource.Name); return; } this._MesLogger.WriteMessage(MessageLevel.Diagnostics, false, LOGRSOURCE, "CheckMachineProduction(): start for resource {0}", resource.Name); var dbUserName = string.Empty; //TODO: inserire qui user e psw per accesso sql. in DieCastingDataAccessHub sono rpevisti dei default var dbPassword = string.Empty; var dataAccess = new DieCastingDataAccessHub(sqlInstanceName, dbUserName, dbPassword, this._MesLogger); if (!dataAccess.CheckConnection()) { //db non raggiungibile this._MesManager.ApplicationMainLogger.WriteMessage(MessageLevel.Warning, true, LOGRSOURCE, "CheckMachineProduction(): DATABASE NOT AVAILABLE for {1} [{0}]", sqlInstanceName, resource.Name); return; } var localTime = DateTime.Now; //recupero la data del server SQL per gestire la differenza, //utile se l'istanza MS SQL è direttamente sul PC della macchina var machineTime = dataAccess.GetServerTime(); if (machineTime == DateTime.MinValue) { machineTime = localTime; } var timeDifference = localTime - machineTime; var productionMemento = this.LoadResourceMemento(resource.Name); var productionStrokes = dataAccess.GetProduction(productionMemento.LastReadRecordNumber); var funnelEvents = new List <DataUnitEvent>(); var notRunningAtLast = false; var lastTimestamp = DateTime.Now; if (productionStrokes.Count > 0) { /* * Cicliamo sui record acquisiti, ognuno dei quali rappresenta una battuta della pressa. * Per ogni stampata creaiamo una Fine\Versamento, e subito dopo un inizio. * Se l'ultimo record processato segnala che la macchina non era in automatico, * creo una sospensione. */ foreach (var productionStroke in productionStrokes) { var normalizedStrokeTime = productionStroke.Timestamp.Add(timeDifference); if (normalizedStrokeTime < productionMemento.LastReadStrokeTimestamp) { this._MesLogger.WriteMessage(MessageLevel.Diagnostics, false, LOGRSOURCE, "CheckMachineProduction(): stroke in the past for resource {0}", resource.Name); continue; } if (!productionStroke.MachineIsRunning) { notRunningAtLast = true; lastTimestamp = normalizedStrokeTime; this._MesLogger.WriteMessage(MessageLevel.Warning, false, LOGRSOURCE, "CheckMachineProduction(): stroke with resource {0} not running", resource.Name); continue; } notRunningAtLast = false; this._MesLogger.WriteMessage(MessageLevel.Info, false, LOGRSOURCE, "CheckMachineProduction(): NEW STROKE for resource {0} - {2} - {1}", resource.Name, normalizedStrokeTime.ToString("G"), productionStroke.MachineCycleNumber); var bareQty = 1; //un record per ogni stampata, contiamo le battute var goodQty = productionStroke.IsGood ? bareQty : 0; var rejectedQty = !productionStroke.IsGood ? bareQty : 0; //creo un done e poi uno start var doneEvent = new ProductDoneEvent(resource.Name, normalizedStrokeTime.ToUniversalTime(), productionStroke.Article, goodQty, rejectedQty, 0, productionStroke.MachineCycleNumber.ToString(), 0); funnelEvents.Add(doneEvent); var startEvent = new ArticleStartedEvent(resource.Name, normalizedStrokeTime.ToUniversalTime(), productionStroke.Article, 0, string.Empty, 0, string.Empty, 100, productionStroke.Order ?? string.Empty); funnelEvents.Add(startEvent); //per definizione numero ciclo e timestamp sono maggiori del precedente productionMemento.LastReadStrokeTimestamp = normalizedStrokeTime; productionMemento.LastReadRecordNumber = productionStroke.MachineCycleNumber; productionMemento.LastReadStrokeId = productionStroke.StrokeId; } } else { this._MesLogger.WriteMessage(MessageLevel.Diagnostics, false, LOGRSOURCE, "CheckMachineProduction(): no strokes for resource {0}", resource.Name); } if (notRunningAtLast && resource.Status.IsWorkingState()) { //se l'ultimo record rilevato non ha macchina in automatico var suspension = new GenericSuspensionEvent(resource.Name, lastTimestamp.ToUniversalTime(), string.Empty); funnelEvents.Add(suspension); this._MesLogger.WriteMessage(MessageLevel.Info, true, LOGRSOURCE, "CheckMachineProduction(): setting suspension for resource NOT AUTO MODE {0} - {1}", resource.Name, lastTimestamp.ToString("G")); } //gli eventi di produzione creati vengono inseriti in un "imbuto" //che li mette su una coda di elaborazione asincrona if (funnelEvents.Count > 0) { this._MesManager.DataInputFunnel.EnqueueEvents(funnelEvents); } productionMemento.LastProcessingTime = DateTime.Now; //memorizza i dati di elaborazione this.SaveResourceMemento(resource.Name, productionMemento); }