using MapoSDK; using Opc.Ua; using System; using System.Collections.Generic; using System.Linq; using System.Net.NetworkInformation; namespace IOB_WIN_NEXT { public enum IcoelStatus { Allarme = 0, Stop = 1, Manuale = 2, Automatico = 3 } public class DatiMesIcoel { #region Public Constructors /// /// Inizializzazione classe con dati RAW da byte[] /// /// Flusso di dati RAW da tradurre... public DatiMesIcoel(byte[] rawData) { // solo se i dati sono esattamente 115... if (rawData.Length >= 115) { Calibratrice_L1 = new MesItemStatus(rawData.Skip(0).Take(7).ToArray()); Calibratrice_L2 = new MesItemStatus(rawData.Skip(7).Take(7).ToArray()); Dewatering_L1 = new MesItemStatus(rawData.Skip(14).Take(7).ToArray()); Dewatering_L2 = new MesItemStatus(rawData.Skip(21).Take(7).ToArray()); Precalibro_L1 = new MesItemStatus(rawData.Skip(28).Take(7).ToArray()); Precalibro_L2 = new MesItemStatus(rawData.Skip(35).Take(7).ToArray()); Taglierina_L1 = new MesItemStatus(rawData.Skip(42).Take(7).ToArray()); Taglierina_L2 = new MesItemStatus(rawData.Skip(49).Take(7).ToArray()); NastroTaglierina_L1 = new MesItemStatus(rawData.Skip(56).Take(7).ToArray()); NastroTaglierina_L2 = new MesItemStatus(rawData.Skip(63).Take(7).ToArray()); Elevatore_L1 = new MesItemStatus(rawData.Skip(70).Take(7).ToArray()); Elevatore_L2 = new MesItemStatus(rawData.Skip(77).Take(7).ToArray()); ImmergitoreBins_L1 = new MesItemStatus(rawData.Skip(84).Take(7).ToArray()); ImmergitoreBins_L2 = new MesItemStatus(rawData.Skip(91).Take(7).ToArray()); ImmergitoreCasse_L1 = new MesItemStatus(rawData.Skip(98).Take(7).ToArray()); ImmergitoreCasse_L2 = new MesItemStatus(rawData.Skip(105).Take(7).ToArray()); Varie = new PlantStatus(rawData.Skip(112).Take(3).ToArray()); } } #endregion Public Constructors #region Public Properties public MesItemStatus Calibratrice_L1 { get; set; } public MesItemStatus Calibratrice_L2 { get; set; } public MesItemStatus Dewatering_L1 { get; set; } public MesItemStatus Dewatering_L2 { get; set; } public MesItemStatus Elevatore_L1 { get; set; } public MesItemStatus Elevatore_L2 { get; set; } public MesItemStatus ImmergitoreBins_L1 { get; set; } public MesItemStatus ImmergitoreBins_L2 { get; set; } public MesItemStatus ImmergitoreCasse_L1 { get; set; } public MesItemStatus ImmergitoreCasse_L2 { get; set; } public MesItemStatus NastroTaglierina_L1 { get; set; } public MesItemStatus NastroTaglierina_L2 { get; set; } public MesItemStatus Precalibro_L1 { get; set; } public MesItemStatus Precalibro_L2 { get; set; } public MesItemStatus Taglierina_L1 { get; set; } public MesItemStatus Taglierina_L2 { get; set; } public PlantStatus Varie { get; set; } #endregion Public Properties #region Public Methods public override bool Equals(object obj) { // Object is not a GaugeModel instance if (!(obj is DatiMesIcoel item)) return false; if (Calibratrice_L1 != item.Calibratrice_L1) return false; if (Calibratrice_L2 != item.Calibratrice_L2) return false; if (Dewatering_L1 != item.Dewatering_L1) return false; if (Dewatering_L2 != item.Dewatering_L2) return false; if (Precalibro_L1 != item.Precalibro_L1) return false; if (Precalibro_L2 != item.Precalibro_L2) return false; if (Taglierina_L1 != item.Taglierina_L1) return false; if (Taglierina_L2 != item.Taglierina_L2) return false; if (NastroTaglierina_L1 != item.NastroTaglierina_L1) return false; if (NastroTaglierina_L2 != item.NastroTaglierina_L2) return false; if (Elevatore_L1 != item.Elevatore_L1) return false; if (Elevatore_L2 != item.Elevatore_L2) return false; if (ImmergitoreBins_L1 != item.ImmergitoreBins_L1) return false; if (ImmergitoreBins_L2 != item.ImmergitoreBins_L2) return false; if (ImmergitoreCasse_L1 != item.ImmergitoreCasse_L1) return false; if (ImmergitoreCasse_L2 != item.ImmergitoreCasse_L2) return false; if (Varie != item.Varie) return false; return true; } public override int GetHashCode() { return base.GetHashCode(); } #endregion Public Methods } public class IobOpcUaOmronIcoel : IobOpcUaOmron { #region Public Constructors /// /// Estende l'init della classe base, impiegando il pacchetto Nuget OPC-UA foundation con la /// gestione specifica per Omron (es ICOEL) https://github.com/OPCFoundation/UA-.NETStandard /// /// /// public IobOpcUaOmronIcoel(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf) { // inizializzo classe base... if (!string.IsNullOrEmpty(getOptPar("CHANGE_ODL_MODE"))) { CHANGE_ODL_MODE = getOptPar("CHANGE_ODL_MODE"); } sendKeyRichiesta = true; doByteRead = true; lgInfo($"Avviato IobOpcUaOmronIcoel | encodeReadData: {doByteRead}"); } #endregion Public Constructors #region Public Methods /// /// Processo i task richiesti e li elimino dalla coda 1:1 /// /// public override Dictionary executeTasks(Dictionary task2exe) { // uso metodo base x ora return base.executeTasks(task2exe); } /// /// Effettua vero processing contapezzi /// public override void processContapezzi() { #if false if (utils.CRB("enableContapezzi")) { // check condizione validazione if (checkMultiCondition(opcUaParams.condCountEnabled) || opcUaParams.condCountEnabled.checkList.Count == 0) { // cerco parametro contapezzi... string currPzCount = getDataItemValue(opcUaParams.keyPartCount); // se ho un contapezzi... processo... if (!string.IsNullOrEmpty(currPzCount)) { int newVal = -1; bool fatto = Int32.TryParse(currPzCount, out newVal); if (fatto) { // gestione decremento contapezzi: viene "messo via" solo SE c'è un // effettivo decremento contapezzi... if (newVal < contapezziPLC) { pzCountResetted = true; // incremento contatore richiesta countKeyRichiesta = countKeyRichiesta + 1; // log lgInfo("Contapezzi resettato (PLC) --> pzCountResetted = true"); } // salvo nuovo valore contapezziPLC contapezziPLC = newVal > -1 ? newVal : contapezziPLC; } else { lgError($"Errore in decodifica valore contapezzi, valore rilevato: {currPzCount}"); } } else { lgError("Errore in decodifica valore contapezzi, valore vuoto!"); } } if (CHANGE_ODL_MODE == "PZCOUNT_RESET") { // controllo comunque, se è ZERO il contapezzi, e sul server è maggiore il // valore x ODL e NON abilitato il trigger reset --> abilito trigger... if (!pzCountResetted && contapezziPLC == 0 && contapezziIOB > 0) { pzCountResetted = true; // incremento contatore richiesta countKeyRichiesta = countKeyRichiesta + 1; // log lgInfo("Contapezzi resettato (PLC==0 e IOB>PLC) --> pzCountResetted = true"); } } } #endif } /// /// Effettua reset del contapezzi, NON POSSIBILE in questa versione /// /// public override bool resetcontapezziPLC() { bool answ = false; return answ; } /// /// Effettua IMPOSTAZIONE FORZATA del contapezzi, NON POSSIBILE in questa versione /// /// public override bool setcontapezziPLC(int newPzCount) { bool answ = false; return answ; } #endregion Public Methods #region Internal Methods /// /// Verifica ed invia variazioni DAL FORMATO RAW data (byte[]) --> esplode oggetti e li /// testa 1:1 /// /// /// /// internal override bool checkAndSendRaw(Opc.Ua.Client.MonitoredItem MonIt, byte[] NotifyValue, bool forceSend) { bool changed = false; if (MonIt != null) { if (NotifyValue != null && NotifyValue.Length > 0) { // verifico variazione "globale" if (!lastData.Equals(currData)) { // effettuo test/invio x ogni info changed = changed || testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L1", lastData.Calibratrice_L1, currData.Calibratrice_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Calibratrice_L2", lastData.Calibratrice_L2, currData.Calibratrice_L2, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Dewatering_L1", lastData.Dewatering_L1, currData.Dewatering_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Dewatering_L2", lastData.Dewatering_L2, currData.Dewatering_L2, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Elevatore_L1", lastData.Elevatore_L1, currData.Elevatore_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Elevatore_L2", lastData.Elevatore_L2, currData.Elevatore_L2, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L1", lastData.ImmergitoreBins_L1, currData.ImmergitoreBins_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreBins_L2", lastData.ImmergitoreBins_L2, currData.ImmergitoreBins_L2, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L1", lastData.ImmergitoreCasse_L1, currData.ImmergitoreCasse_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "ImmergitoreCasse_L2", lastData.ImmergitoreCasse_L2, currData.ImmergitoreCasse_L2, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L1", lastData.NastroTaglierina_L1, currData.NastroTaglierina_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "NastroTaglierina_L2", lastData.NastroTaglierina_L2, currData.NastroTaglierina_L2, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Precalibro_L1", lastData.Precalibro_L1, currData.Precalibro_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Precalibro_L2", lastData.Precalibro_L2, currData.Precalibro_L2, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Taglierina_L1", lastData.Taglierina_L1, currData.Taglierina_L1, forceSend); changed = changed || testSendDataBlock(MonIt.StartNodeId, "Taglierina_L2", lastData.Taglierina_L2, currData.Taglierina_L2, forceSend); // salvo lastData... lastData = currData; } } else { lgError($"checkAndSend ERROR | MonIt: {MonIt.DisplayName} | NotifyValue Null!!!"); } } else { lgError("checkAndSend ERROR: MonIt null"); } return changed; } internal override void UA_ref_eh_MonItChange(object sender, opcUaMonitItemChange e) { base.UA_ref_eh_MonItChange(sender, e); } #endregion Internal Methods #region Protected Properties /// /// Valore corrente dei dati ICOEL (traduzione JIT da byte[]) /// protected DatiMesIcoel currData { get { byte[] rawByte = new byte[115]; // se ho dati raw decodifico... if (byteRawData != null) { if (byteRawData.Length >= 115) { rawByte = byteRawData; } } DatiMesIcoel answ = new DatiMesIcoel(rawByte); return answ; } } /// /// Indica se abbia stato POWER ON (multicondizione) /// protected override bool hasPowerOn { get { // da rivedere return true; //return checkMultiCondition(opcUaParams.condPowerOn); } } /// /// Indica se abbia stato MANUAL (condizioni varie, es stopped) /// protected override bool isManual { get { return false; //return checkMultiCondition(opcUaParams.condManual); } } /// /// Indica se abbia stato READY (condizioni varie, es ausiliari OK) /// protected override bool isReady { get { return false; //return checkMultiCondition(opcUaParams.condReady); } } /// /// Ultima versione validata delle info x confronto /// protected DatiMesIcoel lastData { get; set; } = new DatiMesIcoel(new byte[115]); #endregion Protected Properties #region Protected Methods /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO /// protected override void decodeToBaseBitmap() { DateTime adesso = DateTime.Now; // init a zero... B_input = 0; /* ----------------------------------------------------- * STATE MACHINE 60 STD / SIMULA *------------------------------------------------------ * bitmap MAPO * B0: POWER_ON * B1: RUN * B2: pzCount * B3: allarme * B4: manuale * B5: SlowTC (NON gestito qui) * B6: warm-up / cool-down / setup * B7: emergenza ARMATA (1=ok, 0 = premuta) ---------------------------------------------------- */ // se valido il check ping lo eseguo... altrimenti lo do x buono bool checkPing = !opcUaParams.pingAsPowerOn; string currRun = "N.A."; if (!checkPing) { checkPing = (testPingMachine == IPStatus.Success); } // bit 0 (poweron) imposto a 1 SE pingo + PowerOn=="ON"... bool powerOnOk = checkPing && hasPowerOn; // procedo SOLO SE mi da ping OK... if (checkPing) { B_input = powerOnOk ? 1 : 0; // decodifico da currData if (currData.Varie.InEmergenza) { B_input += (1 << 7); } if (currData.Varie.InMarcia) { B_input += (1 << 1); } if (currData.Varie.InStop) { B_input += (1 << 3); } } // controllo se sono poweroff e se non ho dati buoni da > 2 minuti --> disconnetto if (!powerOnOk && adesso.Subtract(lastCurrent).TotalMinutes > 2) { tryDisconnect(); } // solo se non ho veto check int vFactor = 2; if (vetoCheckStatus < adesso) { lgDebug($"Stato variabili checkPing: {testPingMachine}"); // imposto veto per vetoSeconds... vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor); } // log opzionale! if (verboseLog) { lgDebug($"Trasformazione checkPing: {checkPing} | hasPowerOn: {hasPowerOn} | B_input: {B_input} | currRun = {currRun}"); } } /// /// Effettua vera scrittura parametri /// /// protected override void plcWriteParams(ref List updatedPar) { } /// /// effettua verifica del datablock icoel invianod eventualmente i dati variati /// /// /// /// /// /// /// protected bool testSendDataBlock(NodeId startNodeId, string blockName, MesItemStatus currBlock, MesItemStatus newBlock, bool forceSend) { bool changed = false; // verifica globale blocchi old/new... if (!currBlock.Equals(newBlock)) { // creo un nuovo monitoredItem se non ci fosse x ogni variabile dell'oggetto... changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Stato", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Stato}", forceSend); changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Velocita", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Velocita}", forceSend); changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_Termico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.Termico}", forceSend); changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_MagnetoTermico", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.MagnetoTermico}", forceSend); changed = changed || testSendProperty(new Opc.Ua.Client.MonitoredItem() { DisplayName = $"{blockName}_AvariaInverter", NodeClass = NodeClass.Variable, StartNodeId = startNodeId }, $"{newBlock.AvariaInverter}", forceSend); // spostare sotto in checkAndSendRaw ??? FIXME todo if (changed) { lgTrace($"Invio variazione dataitem per {dataItemMem.Count} elementi"); sendDataItemListToServer(startNodeId); } else { lgTrace("Nessuna variazione DataItem da trasmettere"); } } return changed; } /// /// Verifico se salvare e inviare proprietà specificata /// /// protected bool testSendProperty(Opc.Ua.Client.MonitoredItem MonIt, string NotifyValue, bool forceSend) { bool changed = false; DateTime locTStamp = DateTime.Now; string sVal = ""; string descr = ""; descr = itemTranslation("OPC", MonIt.DisplayName); sVal = $"Change: {locTStamp.ToString()} | descr: {descr} | Id: {MonIt.StartNodeId} | Val: {NotifyValue}"; lgInfo($"TSP | {sVal}"); changed = checkSaveValue(MonIt, NotifyValue, false); // cerco se non sia un dato filtrato in FLUXLOG... bool isFiltered = opcUaParams.fluxLogVeto.Contains(MonIt.DisplayName); if (isFiltered) { lgTrace($"TSP | NON ACCODATO sample per {MonIt.DisplayName} - trovato VETO in fluxLogVeto", false); } else { if (changed || forceSend) { accodaFLog(sVal, qEncodeFLog(descr, $"{NotifyValue}")); } else { lgTrace($"TSP | NON ACCODATO sample per {MonIt.DisplayName} - verifica variazione ha dato esito negativo", false); } } return changed; } #endregion Protected Methods } public class MesItemStatus { #region Public Constructors public MesItemStatus(byte[] rawData) { Stato = (IcoelStatus)BitConverter.ToUInt16(rawData, 0); Velocita = BitConverter.ToUInt16(rawData, 2); Termico = !rawData.Skip(4).Take(1).FirstOrDefault().Equals(0); MagnetoTermico = !rawData.Skip(5).Take(1).FirstOrDefault().Equals(0); AvariaInverter = !rawData.Skip(6).Take(1).FirstOrDefault().Equals(0); } #endregion Public Constructors #region Public Properties public bool AvariaInverter { get; set; } = false; public bool MagnetoTermico { get; set; } = false; public IcoelStatus Stato { get; set; } = 0; public bool Termico { get; set; } = false; public UInt16 Velocita { get; set; } = 0; #endregion Public Properties #region Public Methods public override bool Equals(object obj) { // Object is not a GaugeModel instance if (!(obj is MesItemStatus item)) return false; if (Stato != item.Stato) return false; if (Velocita != item.Velocita) return false; if (Termico != item.Termico) return false; if (MagnetoTermico != item.MagnetoTermico) return false; if (AvariaInverter != item.AvariaInverter) return false; return true; } public override int GetHashCode() { return base.GetHashCode(); } /// /// Converte un singolo item in un array di byte per scrittura su PLC S7 /// /// public byte[] serialize() { byte[] answ = new byte[7]; Buffer.BlockCopy(BitConverter.GetBytes((short)Stato), 0, answ, 0, 2); Buffer.BlockCopy(BitConverter.GetBytes(Velocita), 0, answ, 2, 2); Buffer.BlockCopy(BitConverter.GetBytes(Termico), 0, answ, 4, 1); Buffer.BlockCopy(BitConverter.GetBytes(MagnetoTermico), 0, answ, 5, 1); Buffer.BlockCopy(BitConverter.GetBytes(AvariaInverter), 0, answ, 6, 1); return answ; } #endregion Public Methods } public class PlantStatus { #region Public Constructors public PlantStatus(byte[] rawData) { InMarcia = !rawData.Skip(0).Take(1).FirstOrDefault().Equals(0); InEmergenza = !rawData.Skip(1).Take(1).FirstOrDefault().Equals(0); InStop = !rawData.Skip(2).Take(1).FirstOrDefault().Equals(0); //var valore = BitConverter.ToUInt16(rawData, 0); //byte b = rawData.Skip(0).Take(1).FirstOrDefault(); //InMarcia = (b & (1 << (1 - 1))) != 0; //InEmergenza = (b & (1 << (2 - 1))) != 0; //InStop = (b & (1 << (3 - 1))) != 0; ; } #endregion Public Constructors #region Public Properties public bool InEmergenza { get; set; } = false; public bool InMarcia { get; set; } = false; public bool InStop { get; set; } = false; #endregion Public Properties #region Public Methods public override bool Equals(object obj) { // Object is not a GaugeModel instance if (!(obj is PlantStatus item)) return false; if (InMarcia != item.InMarcia) return false; if (InEmergenza != item.InEmergenza) return false; if (InStop != item.InStop) return false; return true; } public override int GetHashCode() { return base.GetHashCode(); } #endregion Public Methods } }