From a99db87ceee6f6fdfc589528c3648eb82fb5d44e Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 13 Feb 2026 12:30:15 +0100 Subject: [PATCH 1/7] Test Fanuc x Jetco 3029 (errore getPrgName) --- IOB-WIN-FANUC/DATA/CONF/FOV106.ini | 1 + IOB-WIN-FANUC/DATA/CONF/MAIN.ini | 5 +++-- IOB-WIN-FANUC/IOB-WIN-FANUC.csproj | 6 +++++- IOB-WIN-FANUC/Iob/Fanuc.cs | 4 +++- IOB-WIN-FANUC/postBuildTgt.bat | 5 +++-- IOB-WIN-FORM/Iob/Generic.Public.cs | 2 +- IOB-WIN-MTC/DATA/CONF/FOV062.json | 2 ++ 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/IOB-WIN-FANUC/DATA/CONF/FOV106.ini b/IOB-WIN-FANUC/DATA/CONF/FOV106.ini index ed4fa69c..e0b0a305 100644 --- a/IOB-WIN-FANUC/DATA/CONF/FOV106.ini +++ b/IOB-WIN-FANUC/DATA/CONF/FOV106.ini @@ -71,6 +71,7 @@ MIN_SEND_PZC_BLOCK=0 MAX_SEND_PZC_BLOCK=100 ; Disabilito conteggio Pezzi come richiesto DISABLE_PZCOUNT=TRUE +DISABLE_SEND_WDST=TRUE [BRANCH] NAME=master diff --git a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini index 7ea30958..5b02ed04 100644 --- a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini +++ b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini @@ -26,10 +26,11 @@ CLI_INST=SteamWareSim ;STARTLIST=3004 ;STARTLIST=3002 ;STARTLIST=GT575 -;STARTLIST=3029 +STARTLIST=3029 ;STARTLIST=3004,3005 ;STARTLIST=SIMUL_01 -STARTLIST=FOV107 +;STARTLIST=FOV106 +;STARTLIST=FOV107 MAXCNC=10 \ No newline at end of file diff --git a/IOB-WIN-FANUC/IOB-WIN-FANUC.csproj b/IOB-WIN-FANUC/IOB-WIN-FANUC.csproj index 5a82ef02..25e5d51b 100644 --- a/IOB-WIN-FANUC/IOB-WIN-FANUC.csproj +++ b/IOB-WIN-FANUC/IOB-WIN-FANUC.csproj @@ -162,6 +162,7 @@ PreserveNewest + Always @@ -177,6 +178,7 @@ Always + Always @@ -231,7 +233,9 @@ - + + Always + Always diff --git a/IOB-WIN-FANUC/Iob/Fanuc.cs b/IOB-WIN-FANUC/Iob/Fanuc.cs index 60e192a2..979c1e53 100644 --- a/IOB-WIN-FANUC/Iob/Fanuc.cs +++ b/IOB-WIN-FANUC/Iob/Fanuc.cs @@ -690,7 +690,9 @@ namespace IOB_WIN_FANUC.Iob try { // recupero nome programma MAIN - prgName = utils.purgedChar2String(FANUC_ref.getPrgNameMain()); + var is30 = FANUC_ref.Is30Series; + var rawName = FANUC_ref.getPrgNameMain(); + prgName = utils.purgedChar2String(rawName); // trimmo path del programma, ovvero "CNCMEMUSERPATH1" prgName = prgName.Replace(utils.CRS("basePrgMemPath"), ""); lgInfo("Current PROG: {0}", prgName); diff --git a/IOB-WIN-FANUC/postBuildTgt.bat b/IOB-WIN-FANUC/postBuildTgt.bat index 520cfb94..6f8f075e 100644 --- a/IOB-WIN-FANUC/postBuildTgt.bat +++ b/IOB-WIN-FANUC/postBuildTgt.bat @@ -48,8 +48,9 @@ REM ROBOCOPY %2 \\192.168.1.133\Steamware\IOB-WIN-FANUC-DEB /MIR REM ROBOCOPY %2 \\10.51.90.10\Steamware\IOB-WIN-FANUC-DEB /MIR REM ROBOCOPY %2 \\10.51.90.14\Steamware\IOB-WIN-FANUC-DEB /MIR REM ROBOCOPY %2 \\10.51.90.13\Steamware\IOB-WIN-FANUC-DEB /MIR -ROBOCOPY %2 \\10.51.90.15\Steamware\IOB-WIN-FANUC-DEB /MIR -REM ROBOCOPY %2 \\10.51.90.9\Steamware\IOB-WIN-FANUC-DEB /MIR + +REM ROBOCOPY %2 \\10.51.90.15\Steamware\IOB-WIN-FANUC-DEB /MIR +ROBOCOPY %2 \\10.51.90.9\Steamware\IOB-WIN-FANUC-DEB /MIR goto END diff --git a/IOB-WIN-FORM/Iob/Generic.Public.cs b/IOB-WIN-FORM/Iob/Generic.Public.cs index 9e603b62..d32741f7 100644 --- a/IOB-WIN-FORM/Iob/Generic.Public.cs +++ b/IOB-WIN-FORM/Iob/Generic.Public.cs @@ -1342,7 +1342,7 @@ namespace IOB_WIN_FORM.Iob else if (ciclo == gatherCycle.LF) { processCustomTaskLF(); - processOtherCounters(); + await processOtherCounters(); processProgram(); // verifico se devo gestire cambio ODL in modo automatico await ProcessAutoOdlAsync(); diff --git a/IOB-WIN-MTC/DATA/CONF/FOV062.json b/IOB-WIN-MTC/DATA/CONF/FOV062.json index c99e445c..14729269 100644 --- a/IOB-WIN-MTC/DATA/CONF/FOV062.json +++ b/IOB-WIN-MTC/DATA/CONF/FOV062.json @@ -17,6 +17,8 @@ } ], "fluxLogVeto": [ + "E_nblock1", + "E_nline1", "S_nactS1P1", "S_nloadS1P1", "S_nactUP1", From e05157d618616f35756115e85b0a95436ca54b91 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Tue, 17 Feb 2026 15:52:46 +0100 Subject: [PATCH 2/7] Modifiche gestione sigleThread WorkLoop FANUC --- IOB-UT-NEXT/Config/Base/IobDto.cs | 5 + IOB-UT-NEXT/Config/IobConfTree.cs | 4 +- IOB-UT-NEXT/IOB-UT-NEXT.csproj | 1 + IOB-UT-NEXT/SingleThreadTaskScheduler.cs | 50 ++ IOB-WIN-FANUC/App.config | 2 +- IOB-WIN-FANUC/Iob/Fanuc.cs | 995 +++++++++++++---------- IOB-WIN-FORM/Iob/Generic.Protected.cs | 594 +++++++------- IOB-WIN-FORM/Iob/Generic.Public.cs | 208 +++-- 8 files changed, 1036 insertions(+), 823 deletions(-) create mode 100644 IOB-UT-NEXT/SingleThreadTaskScheduler.cs diff --git a/IOB-UT-NEXT/Config/Base/IobDto.cs b/IOB-UT-NEXT/Config/Base/IobDto.cs index 3c8ba1ad..96b7984b 100644 --- a/IOB-UT-NEXT/Config/Base/IobDto.cs +++ b/IOB-UT-NEXT/Config/Base/IobDto.cs @@ -107,6 +107,11 @@ namespace IOB_UT_NEXT.Config.Base /// public bool EnabRedisQue { get; set; } = true; + /// + /// Forza esecuzione ExeTask (main loop) in singleThread + /// + public bool ExeSingleThread { get; set; } = false; + /// /// Versione software IOB /// diff --git a/IOB-UT-NEXT/Config/IobConfTree.cs b/IOB-UT-NEXT/Config/IobConfTree.cs index c3040514..6ccc9b61 100644 --- a/IOB-UT-NEXT/Config/IobConfTree.cs +++ b/IOB-UT-NEXT/Config/IobConfTree.cs @@ -125,6 +125,7 @@ namespace IOB_UT_NEXT.Config Customer = fIni.ReadString("TAGS", "Customer", "EgalWare"), EnabelPodlManFull = bool.Parse(fIni.ReadString("OPTPAR", "EnabelPodlManFull", "false")), EnabRedisQue = bool.Parse(fIni.ReadString("IOB", "EnableRedisQueue", "true")), + ExeSingleThread = bool.Parse(fIni.ReadString("OPTPAR", "ExeSingleThread", "false")), MaxErroriCheck = fIni.ReadInteger("OPTPAR", "MAX_ERR_CHECK", maxErrCheck), MaxPingRetry = fIni.ReadInteger("OPTPAR", "MAX_PING_RETRY", 5), MinDeltaSec = fIni.ReadInteger("IOB", "MinDeltaSec", 6), @@ -140,11 +141,11 @@ namespace IOB_UT_NEXT.Config exclOptPar.Add("DELAY_READ_PZ_COUNT"); exclOptPar.Add("DISABLE_SEND_WDST"); exclOptPar.Add("EnabelPodlManFull"); + exclOptPar.Add("ExeSingleThread"); exclOptPar.Add("MAX_ERR_CHECK"); exclOptPar.Add("MAX_PING_RETRY"); exclOptPar.Add("SEND_ALARM_RESET"); exclOptPar.Add("SEND_MACHINE_CONF"); - exclOptPar.Add("timerIntMs"); exclOptPar.Add("WAIT_REC_MSEC"); exclOptPar.Add("WATCHDOG_PERIOD"); @@ -155,6 +156,7 @@ namespace IOB_UT_NEXT.Config { int.TryParse(rawTimer, out uiTimer); } + exclOptPar.Add("timerIntMs"); // ultimo controllo: timer deve essere compreso tra 10 a 500 x evitare task troppo veloci o lenti... uiTimer = uiTimer < 10 ? 10 : uiTimer; uiTimer = uiTimer > 500 ? 500 : uiTimer; diff --git a/IOB-UT-NEXT/IOB-UT-NEXT.csproj b/IOB-UT-NEXT/IOB-UT-NEXT.csproj index 87a955cc..89dbf82c 100644 --- a/IOB-UT-NEXT/IOB-UT-NEXT.csproj +++ b/IOB-UT-NEXT/IOB-UT-NEXT.csproj @@ -202,6 +202,7 @@ + diff --git a/IOB-UT-NEXT/SingleThreadTaskScheduler.cs b/IOB-UT-NEXT/SingleThreadTaskScheduler.cs new file mode 100644 index 00000000..f9271624 --- /dev/null +++ b/IOB-UT-NEXT/SingleThreadTaskScheduler.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +public class SingleThreadTaskScheduler : TaskScheduler, IDisposable +{ + private readonly BlockingCollection _tasks = new BlockingCollection(); + private readonly Thread _thread; + + public SingleThreadTaskScheduler() + { + _thread = new Thread(() => + { + foreach (var task in _tasks.GetConsumingEnumerable()) + { + // Esegue il task sul thread dedicato + base.TryExecuteTask(task); + } + }); + + _thread.IsBackground = true; + _thread.Name = "SingleWorkerThread"; + _thread.Start(); + } + + protected override void QueueTask(Task task) + { + _tasks.Add(task); + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // Consente l'esecuzione inline solo se siamo già sul thread dedicato + return Thread.CurrentThread == _thread && base.TryExecuteTask(task); + } + + protected override IEnumerable GetScheduledTasks() + { + return _tasks.ToArray(); + } + + public override int MaximumConcurrencyLevel => 1; + + public void Dispose() + { + _tasks.CompleteAdding(); + } +} \ No newline at end of file diff --git a/IOB-WIN-FANUC/App.config b/IOB-WIN-FANUC/App.config index 2ec47542..298795a2 100644 --- a/IOB-WIN-FANUC/App.config +++ b/IOB-WIN-FANUC/App.config @@ -73,7 +73,7 @@ - + diff --git a/IOB-WIN-FANUC/Iob/Fanuc.cs b/IOB-WIN-FANUC/Iob/Fanuc.cs index 979c1e53..5dd79df2 100644 --- a/IOB-WIN-FANUC/Iob/Fanuc.cs +++ b/IOB-WIN-FANUC/Iob/Fanuc.cs @@ -8,17 +8,15 @@ using System; using System.Collections.Generic; using System.Net; using System.Net.NetworkInformation; +using System.Security.Policy; +using System.Threading; using System.Threading.Tasks; +using static EgwProxy.MultiCncLib.CNC.FANUC; namespace IOB_WIN_FANUC.Iob { public class Fanuc : Iob.GenericNext { - /// - /// Oggetto logger della classe - /// - private static Logger _logger = LogManager.GetCurrentClassLogger(); - #region Public Constructors /// @@ -70,7 +68,6 @@ namespace IOB_WIN_FANUC.Iob areaX = FormatAreaFanuc("AreaX", "AREAX"); areaY = FormatAreaFanuc("AreaY", "AREAY"); - lgInfo($"Set Mem | {areaD.areaName}: {areaD.startIdx} +{areaD.arraySize} | {areaR.areaName}: {areaR.startIdx} +{areaR.arraySize} | {areaX.areaName}: {areaX.startIdx} +{areaX.arraySize} | {areaY.areaName}: {areaY.startIdx} +{areaY.arraySize}"); lgInfoStartup($"Start init Adapter FANUC all'IP {IOBConfFull.Device.Connect.IpAddr}:{IOBConfFull.Device.Connect.Port} per IOB {IOBConfFull.General.CodIOB}"); @@ -87,13 +84,10 @@ namespace IOB_WIN_FANUC.Iob lgInfo("FANUC_ref da EgwProxy.MultiCncLib"); } - // disconnetto e connetto... - if (isVerboseLog) - { - lgInfo("FANUC: tryDisconnect"); - } - + lgInfo("FANUC: tryDisconnect"); tryDisconnect(); + Thread.Sleep(200); + lgInfo("FANUC: tryConnect"); tryConnect(); if (enablePzCountByApp) @@ -135,32 +129,6 @@ namespace IOB_WIN_FANUC.Iob lgInfo("End init Adapter FANUC"); } - public override async Task InitializeAsync() - { - // invio altri dati accessori... - await SendMachineConfAsync(); - } - - /// - /// Formatta area x memorie Fanuc da conf caricata - /// - /// - /// - /// - private memAreaFanuc FormatAreaFanuc(string aName, string mKey) - { - var newArea = new memAreaFanuc() { areaName = aName }; - if (IOBConfFull.Special.BankConf.AreaConf.ContainsKey(mKey)) - { - var thisConf = IOBConfFull.Special.BankConf.AreaConf[mKey]; - newArea.startIdx = thisConf.Start; - newArea.arraySize = thisConf.Size; - - } - - return newArea; - } - #endregion Public Constructors #region Public Methods @@ -275,7 +243,7 @@ namespace IOB_WIN_FANUC.Iob { lgInfo("All OK"); // invio aggiornamento x reset richiesta... test! - sendOptVal(item.Key, newVal); + _ = sendOptVal(item.Key, newVal); } else { @@ -331,7 +299,7 @@ namespace IOB_WIN_FANUC.Iob { lgInfo("All OK"); // invio aggiornamento x reset richiesta... test! - sendOptVal(item.Key, newVal); + _ = sendOptVal(item.Key, newVal); } else { @@ -438,6 +406,7 @@ namespace IOB_WIN_FANUC.Iob public bool FanucMemMacroRW(bool bWrite, Int32 memIndex, ref short Value) { bool answ = false; + string topic = $"FanucMemMacroRW.short.{memIndex}"; if (connectionOk) { if (FANUC_ref.Connected) @@ -451,23 +420,19 @@ namespace IOB_WIN_FANUC.Iob answ = FANUC_ref.F_RW_Macro_Short(bWrite, memIndex, ref zeroVal); // ora scrivo vero valore... answ = FANUC_ref.F_RW_Macro_Short(bWrite, memIndex, ref Value); - numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0; + tryReduceErrorCount(topic); } catch (Exception exc) { - numErroriCheck++; - lgError($"Eccezione in FanucMemMacroRW | Short | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + tryIncreaseErrorComm(topic); + lgError($"Eccezione in FanucMemMacroRW | Short | MemType: MACRO | memIndex: {memIndex} | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } } else { - numErroriCheck++; + connectionOk = false; } } - else - { - numErroriCheck++; - } parentForm.commPlcActive = false; return answ; } @@ -482,6 +447,7 @@ namespace IOB_WIN_FANUC.Iob public bool FanucMemMacroRW(bool bWrite, Int32 memIndex, ref double Value) { bool answ = false; + string topic = $"FanucMemMacroRW.double.{memIndex}"; if (connectionOk) { if (FANUC_ref.Connected) @@ -494,23 +460,19 @@ namespace IOB_WIN_FANUC.Iob answ = FANUC_ref.F_RW_Macro_Short(bWrite, memIndex, ref zeroVal); // ora scrivo vero valore... answ = FANUC_ref.F_RW_Macro_Double(bWrite, memIndex, ref Value); - numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0; + tryReduceErrorCount(topic); } catch (Exception exc) { - numErroriCheck++; - lgError($"Eccezione in FanucMemMacroRW | Double | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + tryIncreaseErrorComm(topic); + lgError($"Eccezione in FanucMemMacroRW | Double | MemType: MACRO | memIndex: {memIndex} | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } } else { - numErroriCheck++; + connectionOk = false; } } - else - { - numErroriCheck++; - } parentForm.commPlcActive = false; return answ; } @@ -526,6 +488,7 @@ namespace IOB_WIN_FANUC.Iob public bool FanucMemRW(bool bWrite, FANUC.MemType MemType, Int32 memIndex, ref byte Value) { bool answ = false; + string topic = $"FanucMemRW.{MemType}.byte.{memIndex}"; if (connectionOk) { if (FANUC_ref.Connected) @@ -534,23 +497,19 @@ namespace IOB_WIN_FANUC.Iob { parentForm.commPlcActive = true; answ = FANUC_ref.F_RW_Byte(bWrite, MemType, memIndex, ref Value); - numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0; + tryReduceErrorCount(topic); } catch (Exception exc) { - numErroriCheck++; - lgError($"Eccezione in FanucMemRW | Byte | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + tryIncreaseErrorComm(topic); + lgError($"Eccezione in FanucMemRW | Byte | MemType: {MemType} | memIndex: {memIndex} | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } } else { - numErroriCheck++; + connectionOk = false; } } - else - { - numErroriCheck++; - } parentForm.commPlcActive = false; return answ; } @@ -566,6 +525,7 @@ namespace IOB_WIN_FANUC.Iob public bool FanucMemRW(bool bWrite, FANUC.MemType MemType, Int32 memIndex, ref byte[] Value) { bool answ = false; + string topic = $"FanucMemRW.{MemType}.byte_array.{memIndex}"; if (connectionOk) { if (FANUC_ref.Connected) @@ -574,23 +534,19 @@ namespace IOB_WIN_FANUC.Iob { parentForm.commPlcActive = true; answ = FANUC_ref.F_RW_Byte(bWrite, MemType, memIndex, ref Value); - numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0; + tryReduceErrorCount(topic); } catch (Exception exc) { - numErroriCheck++; - lgError($"Eccezione in FanucMemRW | MultiByte | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + tryIncreaseErrorComm(topic); + lgError($"Eccezione in FanucMemRW | MultiByte | MemType: {MemType} | memIndex: {memIndex} | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } } else { - numErroriCheck++; + connectionOk = false; } } - else - { - numErroriCheck++; - } parentForm.commPlcActive = false; return answ; } @@ -601,6 +557,7 @@ namespace IOB_WIN_FANUC.Iob public override Dictionary getDynData() { Dictionary outVal = new Dictionary(); + string topic = $"FanucMemRW.getDynData"; // processo SOLO SE connected... if (connectionOk) { @@ -635,14 +592,14 @@ namespace IOB_WIN_FANUC.Iob outVal.Add(string.Format("POS_{0:00}", i), item.ToString()); } } + tryReduceErrorCount(topic); } catch (Exception exc) { - numErroriCheck++; + tryIncreaseErrorComm(topic); lgError($"Errore in getDynData | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } sw.Stop(); - numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0; } } // se supero soglia errori lettura --> disconnetto e resetto @@ -654,6 +611,10 @@ namespace IOB_WIN_FANUC.Iob connectionOk = false; tryDisconnect(); } + if (outVal.Count > 0) + { + tryReduceErrorCount(topic); + } return outVal; } @@ -686,6 +647,7 @@ namespace IOB_WIN_FANUC.Iob public override string getPrgName() { string prgName = ""; + string topic = $"FanucMemRW.getPrgName"; DateTime adesso = DateTime.Now; try { @@ -696,11 +658,11 @@ namespace IOB_WIN_FANUC.Iob // trimmo path del programma, ovvero "CNCMEMUSERPATH1" prgName = prgName.Replace(utils.CRS("basePrgMemPath"), ""); lgInfo("Current PROG: {0}", prgName); - numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : 0; + tryReduceErrorCount(topic); } catch (Exception exc) { - numErroriCheck++; + tryIncreaseErrorComm(topic); lgError($"Eccezione in recupero PRG NAME MAIN | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } // se supero soglia errori lettura --> disconnetto e resetto @@ -756,6 +718,12 @@ namespace IOB_WIN_FANUC.Iob return outVal; } + public override async Task InitializeAsync() + { + // invio altri dati accessori... + await SendMachineConfAsync(); + } + /// /// Effettua vero processing contapezzi: /// 6711: pezzi lavorati @@ -764,6 +732,7 @@ namespace IOB_WIN_FANUC.Iob /// public override void processContapezzi() { + string topic = $"processContapezzi"; if (enablePzCountByApp) { // procedo SOLO SE ho connessione... @@ -777,6 +746,7 @@ namespace IOB_WIN_FANUC.Iob // verifico quale modalità sia richiesta: STD (6711) oppure BIT // (Custom, con indicazione area) string memAddr = IOBConfFull.Device.PzCountMode; + topic = $"processContapezzi.{memAddr}"; if (memAddr.StartsWith("STD")) { // inizio verifica area memoria/parametro levando prima parte codice @@ -785,6 +755,7 @@ namespace IOB_WIN_FANUC.Iob // var di appoggio int cntAddr = 0; object outputVal = new object(); + bool done = false; // verifico se si tratta di lettura parametro... formato tipo STD.PAR.6711 if (memAddr.StartsWith("PAR.")) { @@ -796,7 +767,7 @@ namespace IOB_WIN_FANUC.Iob } // processo parametro contapezzi (lavorati) sw.Restart(); - FANUC_ref.F_RW_Param_Integer(false, cntAddr, 3, ref outputVal); + done = FANUC_ref.F_RW_Param_Integer(false, cntAddr, 3, ref outputVal); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("R{0}-PAR", 4), sw.ElapsedTicks); @@ -815,7 +786,7 @@ namespace IOB_WIN_FANUC.Iob // processo parametro sw.Restart(); double macroVal = 0; - FANUC_ref.F_Read_macro(cntAddr, ref macroVal); + done = FANUC_ref.F_Read_macro(cntAddr, ref macroVal); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("R{0}-MACRO", 5), sw.ElapsedTicks); @@ -842,19 +813,19 @@ namespace IOB_WIN_FANUC.Iob { case "B": byte valB = 0; - FANUC_ref.F_RW_Byte(false, areaCounter.mType, areaCounter.mPos, ref valB); + done = FANUC_ref.F_RW_Byte(false, areaCounter.mType, areaCounter.mPos, ref valB); outputVal = valB; break; case "D": ushort valW = 0; - FANUC_ref.F_RW_Word(false, areaCounter.mType, areaCounter.mPos, ref valW); + done = FANUC_ref.F_RW_Word(false, areaCounter.mType, areaCounter.mPos, ref valW); outputVal = valW; break; case "DW": uint valDW = 0; - FANUC_ref.F_RW_DWord(false, areaCounter.mType, areaCounter.mPos, ref valDW); + done = FANUC_ref.F_RW_DWord(false, areaCounter.mType, areaCounter.mPos, ref valDW); if (isVerboseLog) { lgInfo("[1] valDW contapezzi: {0}", valDW); @@ -885,12 +856,22 @@ namespace IOB_WIN_FANUC.Iob //} } sw.Stop(); + tryReduceErrorCount(topic); } } catch (Exception exc) { - lgError(exc, "Errore in contapezzi FANUC 02"); + tryIncreaseErrorComm(topic); + lgError($"Eccezione in contapezzi FANUC 02 | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + } + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; connectionOk = false; + tryDisconnect(); } } else @@ -905,6 +886,7 @@ namespace IOB_WIN_FANUC.Iob /// public override void processMode() { + string topic = $"FanucMemRW.processMode.G"; // processo SOLO SE connected... if (connectionOk) { @@ -912,13 +894,14 @@ namespace IOB_WIN_FANUC.Iob { if (utils.CRB("enableMode") && MemBlockG != null && MemBlockG.Length > 0) { + bool done = false; try { // leggo tutto da 0 a 43... int memIndex = 0; // controllo modalità lettura memoria sw.Restart(); - FanucMemRW(R, FANUC.MemType.G, memIndex, ref MemBlockG); + done = FanucMemRW(R, FANUC.MemType.G, memIndex, ref MemBlockG); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("R{0}-G-AREA", MemBlockG.Length), sw.ElapsedTicks); @@ -927,6 +910,7 @@ namespace IOB_WIN_FANUC.Iob sw.Stop(); // verifico modo con valore corrente, se cambia aggiorno... CNC_MODE newMode = decodeG43(MemBlockG[43]); + tryReduceErrorCount(topic); if (newMode != currMode) { // aggiorno! @@ -945,9 +929,17 @@ namespace IOB_WIN_FANUC.Iob } catch (Exception exc) { - lgError(exc, string.Format("Errore in process Mode G43: {0}{1}", Environment.NewLine, exc)); + tryIncreaseErrorComm(topic); + lgError($"Errore in process Mode G43 | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + } + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; connectionOk = false; - sw.Stop(); + tryDisconnect(); } } } @@ -959,74 +951,30 @@ namespace IOB_WIN_FANUC.Iob /// public override async Task processOtherCounters() { - // processo verifica ed invio 1:1 - await checkSendCounter("PzReq", memNamePzReq, memAddrPzReq); - await checkSendCounter("Articolo", memNameArt, memAddrArt); - await checkSendCounter("Commessa", memNameCom, memAddrCom); - // solo SE abilitato contapezzi... - if (IOBConfFull.Device.EnabPzCount) + string topic = $"FanucMemRW.processOtherCounters"; + try { - await checkSendCounter("PzCount", memNamePzCnt, memAddrPzCnt); - await checkSendCounter("FreeCounter", memNamePzCntFree, memAddrPzCntFree); - await checkSendCounter("PzCntTOT", memNamePzCntTot, memAddrPzCntTot); - } - await checkSendCounter("Cadenza", memNameCaden, memAddrCaden); - } - /// - /// Verifica se effettuare invio (a scadenza) del valore counter richiesto - /// - /// - /// - /// - private async Task checkSendCounter(string varName, string currMemName, string currMemAddr) - { - // gestione con ricerca in memoria Write / optPar... - if (string.IsNullOrEmpty(currMemAddr)) - { - lgTrace($"{varName} disabilitato | NO memConf.Write | NO optPar"); - } - else - { - try + // processo verifica ed invio 1:1 + await checkSendCounter("PzReq", memNamePzReq, memAddrPzReq); + await checkSendCounter("Articolo", memNameArt, memAddrArt); + await checkSendCounter("Commessa", memNameCom, memAddrCom); + // solo SE abilitato contapezzi... + if (IOBConfFull.Device.EnabPzCount) { - DateTime adesso = DateTime.Now; - int currPeriod = memPeriod(currMemName); - // se non ha periodo abilito invio - bool doSend = currPeriod == 0; - // altrimenti verifica - if (!doSend) - { - if (VetoCounterSend.ContainsKey(currMemName)) - { - // verifico scadenza... - if (VetoCounterSend[currMemName] < adesso) - { - VetoCounterSend[currMemName] = adesso.AddSeconds(currPeriod); - doSend = true; - lgInfo($"checkSendCounter | set veto for {varName} at {VetoCounterSend[currMemName]:HH:mm:ss}"); - } - } - else - { - VetoCounterSend.Add(currMemName, adesso.AddSeconds(currPeriod)); - doSend = true; - lgTrace($"{varName} | Veto still active until {VetoCounterSend[currMemName]:HH:mm:ss}"); - } - } - // per il counter verifico SE sia da inviare... - if (doSend) - { - await sendOptVal(currMemName, getValByMemAddr(currMemAddr)); - } - } - catch (Exception exc) - { - lgError(exc, $"Eccezione | processOtherCounters.checkSendCounter | {varName} | {currMemName} | {currMemAddr}{Environment.NewLine}{exc}"); + await checkSendCounter("PzCount", memNamePzCnt, memAddrPzCnt); + await checkSendCounter("FreeCounter", memNamePzCntFree, memAddrPzCntFree); + await checkSendCounter("PzCntTOT", memNamePzCntTot, memAddrPzCntTot); } + await checkSendCounter("Cadenza", memNameCaden, memAddrCaden); + tryReduceErrorCount(topic); + } + catch (Exception exc) + { + tryIncreaseErrorComm(topic); + lgError($"Errore in processOtherCounters: {Environment.NewLine}{exc}"); } } - /// /// Effettua lettura semafori principale Parametri da /// aggiornare x display in form @@ -1035,9 +983,16 @@ namespace IOB_WIN_FANUC.Iob { DateTime adesso = DateTime.Now; base.readSemafori(ref currDispData); + string topic = $"FanucMemRW.readSemafori"; // verifico non sia in veto invio iniziale... if (queueInEnabCurr) { + // se ho errori wait proporzionale... + if (numErroriCheck > 0) + { + int sleepTime = rndGen.Next(50, 100) + 10 * numErroriCheck; + Thread.Sleep(sleepTime); + } try { if (verboseLog) @@ -1045,13 +1000,14 @@ namespace IOB_WIN_FANUC.Iob lgInfo("inizio read semafori"); } + bool readOk = true; currDispData.semIn = Semaforo.SV; // ogni lettura inizia da SUA area inizio controllo area R: se ha dati (> 0 // byte) --> leggo! if (MemBlockR.Length > 0) { sw.Restart(); - FanucMemRW(R, FANUC.MemType.R, areaR.startIdx, ref MemBlockR); + readOk = readOk && FanucMemRW(R, FANUC.MemType.R, areaR.startIdx, ref MemBlockR); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("R{0}-R", MemBlockR.Length), sw.ElapsedTicks); @@ -1069,7 +1025,7 @@ namespace IOB_WIN_FANUC.Iob if (MemBlockX.Length > 0) { sw.Restart(); - FanucMemRW(R, FANUC.MemType.X, areaX.startIdx, ref MemBlockX); + readOk = readOk && FanucMemRW(R, FANUC.MemType.X, areaX.startIdx, ref MemBlockX); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("R{0}-X", MemBlockX.Length), sw.ElapsedTicks); @@ -1087,7 +1043,7 @@ namespace IOB_WIN_FANUC.Iob if (MemBlockY.Length > 0) { sw.Restart(); - FanucMemRW(R, FANUC.MemType.Y, areaY.startIdx, ref MemBlockY); + readOk = readOk && FanucMemRW(R, FANUC.MemType.Y, areaY.startIdx, ref MemBlockY); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("R{0}-Y", MemBlockY.Length), sw.ElapsedTicks); @@ -1105,13 +1061,28 @@ namespace IOB_WIN_FANUC.Iob // salvo il solo BYTE dell'input decifrando il semaforo... decodeToBitmap(); reportRawInput(ref currDispData); + // se tutte le letture OK decremento + if (readOk) + { + tryReduceErrorCount(topic); + } } catch (Exception exc) { + tryIncreaseErrorComm(topic); lgError(string.Format("Eccezione in readSemafori:{0}{1}", Environment.NewLine, exc)); - connectionOk = false; currDispData.semIn = Semaforo.SR; } + + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; + connectionOk = false; + tryDisconnect(); + } } else { @@ -1127,6 +1098,7 @@ namespace IOB_WIN_FANUC.Iob public override bool resetContapezziPLC(string codTav) { bool answ = false; + string topic = $"FanucMemRW.resetContapezziPLC"; // scrivo valore 0 x il contapezzi try { @@ -1136,6 +1108,7 @@ namespace IOB_WIN_FANUC.Iob // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con // indicazione area) string memAddr = IOBConfFull.Device.PzCountMode; + topic = $"FanucMemRW.resetContapezziPLC.{memAddr}"; if (memAddr.StartsWith("STD")) { // inizio verifica area memoria/parametro levando prima parte codice @@ -1158,7 +1131,7 @@ namespace IOB_WIN_FANUC.Iob // processo RESET contapezzi (lavorati) sw.Restart(); - FANUC_ref.F_RW_Param_Integer(true, cntAddr, 3, ref newVal); + answ = FANUC_ref.F_RW_Param_Integer(true, cntAddr, 3, ref newVal); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("W{0}-PAR", 4), sw.ElapsedTicks); @@ -1197,19 +1170,19 @@ namespace IOB_WIN_FANUC.Iob { case "B": byte valB = 0; - FANUC_ref.F_RW_Byte(true, areaCounter.mType, areaCounter.mPos, ref valB); + answ = FANUC_ref.F_RW_Byte(true, areaCounter.mType, areaCounter.mPos, ref valB); newVal = valB; break; case "D": ushort valW = 0; - FANUC_ref.F_RW_Word(true, areaCounter.mType, areaCounter.mPos, ref valW); + answ = FANUC_ref.F_RW_Word(true, areaCounter.mType, areaCounter.mPos, ref valW); newVal = valW; break; case "DW": uint valDW = 0; - FANUC_ref.F_RW_DWord(true, areaCounter.mType, areaCounter.mPos, ref valDW); + answ = FANUC_ref.F_RW_DWord(true, areaCounter.mType, areaCounter.mPos, ref valDW); break; default: @@ -1222,13 +1195,24 @@ namespace IOB_WIN_FANUC.Iob } sw.Stop(); answ = true; + tryReduceErrorCount(topic); } } catch (Exception exc) { - lgError(exc, "Errore in RESET contapezzi FANUC"); - connectionOk = false; + tryIncreaseErrorComm(topic); + lgError($"Errore in RESET contapezzi FANUC | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; + connectionOk = false; + tryDisconnect(); + } + return answ; } @@ -1271,6 +1255,7 @@ namespace IOB_WIN_FANUC.Iob /// public bool setNumArt(int valReq) { + string topic = $"FanucMemRW.setNumArt"; bool answ = false; string memAddr = memAddrArt; lgTrace($"INIT setNumArt | memAddr: {memAddr}"); @@ -1304,11 +1289,21 @@ namespace IOB_WIN_FANUC.Iob } } sw.Stop(); + tryReduceErrorCount(topic); } catch (Exception exc) { - lgError(exc, "Errore in setNumArt FANUC"); + tryIncreaseErrorComm(topic); + lgError($"Errore in setNumArtFANUC | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + } + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; connectionOk = false; + tryDisconnect(); } } return answ; @@ -1321,6 +1316,7 @@ namespace IOB_WIN_FANUC.Iob public bool setNumCom(int valReq) { bool answ = false; + string topic = $"FanucMemRW.setNumCom"; // verifico quale modalità sia richiesta string memAddr = memAddrCom; lgTrace($"INIT setNumCom | memAddr: {memAddr}"); @@ -1353,141 +1349,21 @@ namespace IOB_WIN_FANUC.Iob } } sw.Stop(); + tryReduceErrorCount(topic); } catch (Exception exc) { - lgError(exc, "Errore in setNumCom FANUC"); - connectionOk = false; + tryIncreaseErrorComm(topic); + lgError($"Errore in setNumCom | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); } - answ = true; - } - return answ; - } - - protected bool setPzCnt(int pzCnt) - { - bool answ = false; - // ...SE abilitato da conf IOB - if (IOBConfFull.Counters.EnableSetPzCount) - { - // scrivo valore del contapezzi - try + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) { - string memAddr = memAddrPzCnt; - lgTrace($"INIT setPzCnt | memAddr: {memAddr}"); - // contapezzi ATTUALE - if (!string.IsNullOrEmpty(memAddr)) - { - // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con - // indicazione area) - if (memAddr.StartsWith("STD")) - { - // inizio verifica area memoria/parametro levando prima parte codice - memAddr = memAddr.Replace("STD.", ""); - } - // var di appoggio - int cntAddr = 0; - // var contapezzi a zero.... - object newVal = new object(); - newVal = pzCnt; - // verifico se si tratta di lettura parametro... formato tipo STD.PAR.6711 - if (memAddr.StartsWith("PAR.")) - { - // recupero parametro... - int.TryParse(memAddr.Replace("PAR.", ""), out cntAddr); - if (cntAddr == 0) - { - cntAddr = 6711; - } + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); - // processo SET contapezzi (lavorati) - sw.Restart(); - FANUC_ref.F_RW_Param_Integer(true, cntAddr, 3, ref newVal); - if (utils.CRB("recTime")) - { - TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("W{0}-PAR", 4), sw.ElapsedTicks); - } - } - else if (memAddr.StartsWith("MACRO.")) - { - // recupero parametro... - int.TryParse(memAddr.Replace("MACRO.", ""), out cntAddr); - if (cntAddr == 0) - { - cntAddr = 9999; - } - lgTrace($"MACRO | Pz Write 01 | memAddr: {memAddr} | idx: {cntAddr}"); - // processo SET contapezzi (lavorati) - sw.Restart(); - // se > 2^16 scrivo con INT, sennò con double... - short valTrShr = 0; - double valTrDbl = 0; - if (pzCnt < Math.Pow(2, 16)) - { - valTrShr = (short)pzCnt; - answ = FanucMemMacroRW(true, cntAddr, ref valTrShr); - //answ = FANUC_ref.F_RW_Macro_Short(true, cntAddr, ref valTrShr); - lgTrace($"MACRO | Com Write 02 SHORT | memAddr: {memAddr} | pzCnt: {pzCnt} | valTrShr: {valTrShr}"); - } - // sennò double! - else - { - valTrDbl = (double)pzCnt; - answ = FanucMemMacroRW(true, cntAddr, ref valTrDbl); - //answ = FANUC_ref.F_RW_Macro_Double(true, cntAddr, ref valTrDbl); - lgTrace($"MACRO | Com Write 02 DOUBLE | memAddr: {memAddr} | pzCnt: {pzCnt} | valTrDbl: {valTrDbl}"); - } - if (utils.CRB("recTime")) - { - TimingData.addResult(IOBConfFull.General.CodIOB, "MACRO-SHORT", sw.ElapsedTicks); - } - } - // altrimenti se legge da area memoria specifica leggo da li... formto tipo STD.D.1604.DW - else - { - memAddressFanuc areaCounter = new memAddressFanuc(memAddr); - - if (isVerboseLog) - { - lgInfo("setPzComm [0] area memoria: {0}.{1}.{2}", areaCounter.mType, areaCounter.mPos, areaCounter.vType); - } - // leggo! - sw.Restart(); - // switch x tipo dati --> tipo lettura... e salvo ultimo conteggio rilevato - switch (areaCounter.vType) - { - case "B": - byte valB = (byte)pzCnt; - FANUC_ref.F_RW_Byte(true, areaCounter.mType, areaCounter.mPos, ref valB); - newVal = valB; - break; - - case "D": - ushort valW = (ushort)pzCnt; - FANUC_ref.F_RW_Word(true, areaCounter.mType, areaCounter.mPos, ref valW); - newVal = valW; - break; - - case "DW": - uint valDW = (uint)pzCnt; - FANUC_ref.F_RW_DWord(true, areaCounter.mType, areaCounter.mPos, ref valDW); - break; - - default: - break; - } - if (utils.CRB("recTime")) - { - TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("W-{0}.{1}.{2}", areaCounter.mType, areaCounter.mPos, areaCounter.vType), sw.ElapsedTicks); - } - } - sw.Stop(); - } - } - catch (Exception exc) - { - lgError($"Errore in SET PzReq Commessa FANUC{Environment.NewLine}{exc}"); + numErroriCheck = 0; connectionOk = false; + tryDisconnect(); } answ = true; } @@ -1500,6 +1376,7 @@ namespace IOB_WIN_FANUC.Iob /// public override bool setPzComm(int pzReq) { + string topic = $"FanucMemRW.setPzComm"; bool answ = false; // ...SE abilitato da conf IOB if (IOBConfFull.Counters.EnableSetPzReq) @@ -1536,7 +1413,7 @@ namespace IOB_WIN_FANUC.Iob // processo SET contapezzi (lavorati) sw.Restart(); - FANUC_ref.F_RW_Param_Integer(true, cntAddr, 3, ref newVal); + answ = FANUC_ref.F_RW_Param_Integer(true, cntAddr, 3, ref newVal); if (utils.CRB("recTime")) { TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("W{0}-PAR", 4), sw.ElapsedTicks); @@ -1592,19 +1469,19 @@ namespace IOB_WIN_FANUC.Iob { case "B": byte valB = (byte)pzReq; - FANUC_ref.F_RW_Byte(true, areaCounter.mType, areaCounter.mPos, ref valB); + answ = FANUC_ref.F_RW_Byte(true, areaCounter.mType, areaCounter.mPos, ref valB); newVal = valB; break; case "D": ushort valW = (ushort)pzReq; - FANUC_ref.F_RW_Word(true, areaCounter.mType, areaCounter.mPos, ref valW); + answ = FANUC_ref.F_RW_Word(true, areaCounter.mType, areaCounter.mPos, ref valW); newVal = valW; break; case "DW": uint valDW = (uint)pzReq; - FANUC_ref.F_RW_DWord(true, areaCounter.mType, areaCounter.mPos, ref valDW); + answ = FANUC_ref.F_RW_DWord(true, areaCounter.mType, areaCounter.mPos, ref valDW); break; default: @@ -1616,12 +1493,22 @@ namespace IOB_WIN_FANUC.Iob } } sw.Stop(); + tryReduceErrorCount(topic); } } catch (Exception exc) { - lgError($"Errore in SET PzReq Commessa FANUC{Environment.NewLine}{exc}"); + tryIncreaseErrorComm(topic); + lgError($"Errore in SET PzReq Commessa | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + } + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; connectionOk = false; + tryDisconnect(); } answ = true; } @@ -1673,10 +1560,19 @@ namespace IOB_WIN_FANUC.Iob { // ora provo connessione... parentForm.commPlcActive = true; + // se connesso disconnetto... + if (FANUC_ref.Connected) + { + FANUC_ref.Disconnect(ref szStatusConnection); + lgInfo($"FANUC_ref preliminary DISCONNECT | connStatus: {FANUC_ref.Connected}| szStatusConnection: {szStatusConnection}"); + // aspetto + Thread.Sleep(500); + } FANUC_ref.Connect(ref szStatusConnection); parentForm.commPlcActive = false; - lgInfo("szStatusConnection: " + szStatusConnection); - connectionOk = true; + lgInfo($"FANUC_ref CONNECT | connected: {FANUC_ref.Connected}| szStatusConnection: {szStatusConnection}"); + connectionOk = FANUC_ref.Connected; + // refresh stato allarmi!!! if (connectionOk) { @@ -1729,11 +1625,11 @@ namespace IOB_WIN_FANUC.Iob try { FANUC_ref.Disconnect(ref szStatusConnection); + Thread.Sleep(500); connectionOk = false; // resetto timing! TimingData.resetData(); - lgInfo(szStatusConnection); - lgInfo("Effettuata disconnessione adapter FANUC!"); + lgInfo($"Effettuata disconnessione adapter FANUC! | connectred: {FANUC_ref.Connected} | szStatusConnection: {szStatusConnection}"); } catch (Exception exc) { @@ -1749,7 +1645,7 @@ namespace IOB_WIN_FANUC.Iob #endregion Public Methods - #region Internal Fields + #region Internal Properties /// /// LookUpTable di decodifica da CNC a segnali tipo bitmap MAPO @@ -1759,7 +1655,7 @@ namespace IOB_WIN_FANUC.Iob get => IOBConfFull.Device.SigLUT; } - #endregion Internal Fields + #endregion Internal Properties #region Protected Fields @@ -1892,8 +1788,158 @@ namespace IOB_WIN_FANUC.Iob } } + protected bool setPzCnt(int pzCnt) + { + string topic = $"FanucMemRW.setPzCnt"; + bool answ = false; + // ...SE abilitato da conf IOB + if (IOBConfFull.Counters.EnableSetPzCount) + { + // scrivo valore del contapezzi + try + { + string memAddr = memAddrPzCnt; + lgTrace($"INIT setPzCnt | memAddr: {memAddr}"); + // contapezzi ATTUALE + if (!string.IsNullOrEmpty(memAddr)) + { + // verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom, con + // indicazione area) + if (memAddr.StartsWith("STD")) + { + // inizio verifica area memoria/parametro levando prima parte codice + memAddr = memAddr.Replace("STD.", ""); + } + // var di appoggio + int cntAddr = 0; + // var contapezzi a zero.... + object newVal = new object(); + newVal = pzCnt; + // verifico se si tratta di lettura parametro... formato tipo STD.PAR.6711 + if (memAddr.StartsWith("PAR.")) + { + // recupero parametro... + int.TryParse(memAddr.Replace("PAR.", ""), out cntAddr); + if (cntAddr == 0) + { + cntAddr = 6711; + } + + // processo SET contapezzi (lavorati) + sw.Restart(); + FANUC_ref.F_RW_Param_Integer(true, cntAddr, 3, ref newVal); + if (utils.CRB("recTime")) + { + TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("W{0}-PAR", 4), sw.ElapsedTicks); + } + } + else if (memAddr.StartsWith("MACRO.")) + { + // recupero parametro... + int.TryParse(memAddr.Replace("MACRO.", ""), out cntAddr); + if (cntAddr == 0) + { + cntAddr = 9999; + } + lgTrace($"MACRO | Pz Write 01 | memAddr: {memAddr} | idx: {cntAddr}"); + // processo SET contapezzi (lavorati) + sw.Restart(); + // se > 2^16 scrivo con INT, sennò con double... + short valTrShr = 0; + double valTrDbl = 0; + if (pzCnt < Math.Pow(2, 16)) + { + valTrShr = (short)pzCnt; + answ = FanucMemMacroRW(true, cntAddr, ref valTrShr); + //answ = FANUC_ref.F_RW_Macro_Short(true, cntAddr, ref valTrShr); + lgTrace($"MACRO | Com Write 02 SHORT | memAddr: {memAddr} | pzCnt: {pzCnt} | valTrShr: {valTrShr}"); + } + // sennò double! + else + { + valTrDbl = (double)pzCnt; + answ = FanucMemMacroRW(true, cntAddr, ref valTrDbl); + //answ = FANUC_ref.F_RW_Macro_Double(true, cntAddr, ref valTrDbl); + lgTrace($"MACRO | Com Write 02 DOUBLE | memAddr: {memAddr} | pzCnt: {pzCnt} | valTrDbl: {valTrDbl}"); + } + if (utils.CRB("recTime")) + { + TimingData.addResult(IOBConfFull.General.CodIOB, "MACRO-SHORT", sw.ElapsedTicks); + } + } + // altrimenti se legge da area memoria specifica leggo da li... formto tipo STD.D.1604.DW + else + { + memAddressFanuc areaCounter = new memAddressFanuc(memAddr); + + if (isVerboseLog) + { + lgInfo("setPzComm [0] area memoria: {0}.{1}.{2}", areaCounter.mType, areaCounter.mPos, areaCounter.vType); + } + // leggo! + sw.Restart(); + // switch x tipo dati --> tipo lettura... e salvo ultimo conteggio rilevato + switch (areaCounter.vType) + { + case "B": + byte valB = (byte)pzCnt; + FANUC_ref.F_RW_Byte(true, areaCounter.mType, areaCounter.mPos, ref valB); + newVal = valB; + break; + + case "D": + ushort valW = (ushort)pzCnt; + FANUC_ref.F_RW_Word(true, areaCounter.mType, areaCounter.mPos, ref valW); + newVal = valW; + break; + + case "DW": + uint valDW = (uint)pzCnt; + FANUC_ref.F_RW_DWord(true, areaCounter.mType, areaCounter.mPos, ref valDW); + break; + + default: + break; + } + if (utils.CRB("recTime")) + { + TimingData.addResult(IOBConfFull.General.CodIOB, string.Format("W-{0}.{1}.{2}", areaCounter.mType, areaCounter.mPos, areaCounter.vType), sw.ElapsedTicks); + } + } + sw.Stop(); + tryReduceErrorCount(topic); + } + } + catch (Exception exc) + { + tryIncreaseErrorComm(topic); + lgError($"Errore in SET PzCnt | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + } + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; + connectionOk = false; + tryDisconnect(); + } + answ = true; + } + return answ; + } + #endregion Protected Methods + #region Private Fields + + /// + /// Oggetto logger della classe + /// + private static Logger _logger = LogManager.GetCurrentClassLogger(); + + #endregion Private Fields + #region Private Properties /// @@ -2290,6 +2336,207 @@ namespace IOB_WIN_FANUC.Iob #region Private Methods + + /// + /// Calcola il valore bitmap segnale in uscita data la bVal (mem area + bit) + indice i + /// + /// Conf memoria da signalLUT + /// Indice del bit x calcolo out + /// Byte completo recuperato + /// Indice bit cercato + /// + private int CalcAreaBitOut(string bVal, int i, ref int byte2check, ref int bitNum) + { + // OUT valore + int outVal = 0; + // var accessorie + int byteNum = 0; + bool invSignal = false; + char area; + string memArea = ""; + string[] memIdx; + // di norma è segnale normale => 1, altrimenti inverse => 0... + // cerco se sia inverse (primo char "!") --> registro e elimino char... + invSignal = bVal.StartsWith("!"); + // tolgo comunque inversione... + bVal = bVal.Replace("!", ""); + // recupero area... + area = bVal[0]; + // altrimenti decodifico area... + memArea = bVal.Substring(1, bVal.Length - 1); + memIdx = memArea.Split('.'); + // calcolo bit e byte number... + int.TryParse(memIdx[0], out byteNum); + if (memIdx.Length > 1) + { + int.TryParse(memIdx[1], out bitNum); + } + // in base al nome cerco in una delle aree.. e prendo solo solo quel bit + // di quel byte... + switch (area) + { + case 'G': + byte2check = MemBlockG[byteNum]; + break; + + case 'R': + byte2check = MemBlockR[byteNum]; + break; + + case 'X': + byte2check = MemBlockX[byteNum]; + break; + + case 'Y': + byte2check = MemBlockY[byteNum]; + break; + + default: + break; + } + // a secondo che sia segnale normale o inverso... + if (invSignal) + { + // controllo se il bit sia NON attivo (basso)... == 0... + if ((byte2check & (1 << bitNum)) == 0) + { + outVal += 1 << i; + } + } + else + { + // controllo se il bit sia attivo (alto)... != 0 + if ((byte2check & (1 << bitNum)) != 0) + { + outVal += 1 << i; + } + } + return outVal; + } + + /// + /// Calcola valore bitmap segnale in uscita data condizione programName + /// + /// Conf search programma: nome e se inizia per % ricerca LIKE + /// Indice del bit x calcolo out + private int CalProgNameBitOut(string confTest, int i) + { + // OUT valore + int outVal = 0; + string prgNameTgt = ""; + // il valore da cercare è quello inserito dopo il segno pipe... + if (confTest.Contains("|")) + { + var prgData = confTest.Split('|'); + if (prgData.Length > 1) + { + prgNameTgt = prgData[1]; + } + } + // ora verifico se prog corrente è valido + if (!string.IsNullOrEmpty(currPrgName)) + { + // se contiene % allora cerco x like + if (prgNameTgt.Contains("%")) + { + // e se sia uguale al valore tgt senza % + if (currPrgName.Contains(prgNameTgt.Replace("%", ""))) + { + // inserisco bit! + outVal += 1 << i; + } + } + // altrimenti ricerca puntuale (prima versione) + else + { + // e se sia uguale al valore tgt + if (currPrgName == prgNameTgt) + { + // inserisco bit! + outVal += 1 << i; + } + } + } + return outVal; + } + + /// + /// Verifica se effettuare invio (a scadenza) del valore counter richiesto + /// + /// + /// + /// + private async Task checkSendCounter(string varName, string currMemName, string currMemAddr) + { + string topic = $"FanucMemRW.checkSendCounter.{currMemName}"; + // gestione con ricerca in memoria Write / optPar... + if (string.IsNullOrEmpty(currMemAddr)) + { + lgTrace($"{varName} disabilitato | currMemAddr vuoto | NO memConf.Read | NO optPar"); + } + else + { + try + { + DateTime adesso = DateTime.Now; + int currPeriod = memPeriod(currMemName); + // se non ha periodo abilito invio + bool doSend = currPeriod == 0; + string vetoType = "DEFAULT"; + if (currPeriod == 0) + { + // ma imposto cmq un poeriodo veto a 15 min... + currPeriod = 15 * 60; + } + else + { + vetoType = "EXPLICIT"; + } + + // controllo comunque scadenza... + if (VetoCounterSend.ContainsKey(currMemName)) + { + // verifico scadenza... + if (VetoCounterSend[currMemName] < adesso) + { + VetoCounterSend[currMemName] = adesso.AddSeconds(currPeriod); + doSend = true; + lgInfo($"checkSendCounter | set veto {vetoType} ({currPeriod}) for {varName} at {VetoCounterSend[currMemName]:HH:mm:ss}"); + } + } + else + { + VetoCounterSend.Add(currMemName, adesso.AddSeconds(currPeriod)); + doSend = true; + lgTrace($"{varName} | Veto {vetoType} still active until {VetoCounterSend[currMemName]:HH:mm:ss}"); + } + + // per il counter verifico SE sia da inviare... + if (doSend) + { + await sendOptVal(currMemName, getValByMemAddr(currMemAddr)); + tryReduceErrorCount(topic); + } + } + catch (Exception exc) + { + tryIncreaseErrorComm(topic); + lgError($"Eccezione | processOtherCounters.checkSendCounter | {varName} | {currMemName} | {currMemAddr} | numErroriCheck: {numErroriCheck}{Environment.NewLine}{exc}"); + // attesa 10ms post errore... + await Task.Delay(10); + } + // se supero soglia errori lettura --> disconnetto e resetto + if (numErroriCheck > maxErroriCheck) + { + lgError($"numErroriCheck: {numErroriCheck} --> disconnessione adapter con tryDisconnect"); + + numErroriCheck = 0; + connectionOk = false; + tryDisconnect(); + } + } + } + /// /// Effettua decodifica aree memoria alla bitmap usata x MAPO /// @@ -2487,129 +2734,6 @@ namespace IOB_WIN_FANUC.Iob } } - /// - /// Calcola valore bitmap segnale in uscita data condizione programName - /// - /// Conf search programma: nome e se inizia per % ricerca LIKE - /// Indice del bit x calcolo out - private int CalProgNameBitOut(string confTest, int i) - { - // OUT valore - int outVal = 0; - string prgNameTgt = ""; - // il valore da cercare è quello inserito dopo il segno pipe... - if (confTest.Contains("|")) - { - var prgData = confTest.Split('|'); - if (prgData.Length > 1) - { - prgNameTgt = prgData[1]; - } - } - // ora verifico se prog corrente è valido - if (!string.IsNullOrEmpty(currPrgName)) - { - // se contiene % allora cerco x like - if (prgNameTgt.Contains("%")) - { - // e se sia uguale al valore tgt senza % - if (currPrgName.Contains(prgNameTgt.Replace("%", ""))) - { - // inserisco bit! - outVal += 1 << i; - } - } - // altrimenti ricerca puntuale (prima versione) - else - { - // e se sia uguale al valore tgt - if (currPrgName == prgNameTgt) - { - // inserisco bit! - outVal += 1 << i; - } - } - } - return outVal; - } - - /// - /// Calcola il valore bitmap segnale in uscita data la bVal (mem area + bit) + indice i - /// - /// Conf memoria da signalLUT - /// Indice del bit x calcolo out - /// Byte completo recuperato - /// Indice bit cercato - /// - private int CalcAreaBitOut(string bVal, int i, ref int byte2check, ref int bitNum) - { - // OUT valore - int outVal = 0; - // var accessorie - int byteNum = 0; - bool invSignal = false; - char area; - string memArea = ""; - string[] memIdx; - // di norma è segnale normale => 1, altrimenti inverse => 0... - // cerco se sia inverse (primo char "!") --> registro e elimino char... - invSignal = bVal.StartsWith("!"); - // tolgo comunque inversione... - bVal = bVal.Replace("!", ""); - // recupero area... - area = bVal[0]; - // altrimenti decodifico area... - memArea = bVal.Substring(1, bVal.Length - 1); - memIdx = memArea.Split('.'); - // calcolo bit e byte number... - int.TryParse(memIdx[0], out byteNum); - if (memIdx.Length > 1) - { - int.TryParse(memIdx[1], out bitNum); - } - // in base al nome cerco in una delle aree.. e prendo solo solo quel bit - // di quel byte... - switch (area) - { - case 'G': - byte2check = MemBlockG[byteNum]; - break; - - case 'R': - byte2check = MemBlockR[byteNum]; - break; - - case 'X': - byte2check = MemBlockX[byteNum]; - break; - - case 'Y': - byte2check = MemBlockY[byteNum]; - break; - - default: - break; - } - // a secondo che sia segnale normale o inverso... - if (invSignal) - { - // controllo se il bit sia NON attivo (basso)... == 0... - if ((byte2check & (1 << bitNum)) == 0) - { - outVal += 1 << i; - } - } - else - { - // controllo se il bit sia attivo (alto)... != 0 - if ((byte2check & (1 << bitNum)) != 0) - { - outVal += 1 << i; - } - } - return outVal; - } - /// /// Dump area D della memoria /// @@ -2750,6 +2874,25 @@ namespace IOB_WIN_FANUC.Iob utils.WritePlain(mappaValori, nomeFile); } + /// + /// Formatta area x memorie Fanuc da conf caricata + /// + /// + /// + /// + private memAreaFanuc FormatAreaFanuc(string aName, string mKey) + { + var newArea = new memAreaFanuc() { areaName = aName }; + if (IOBConfFull.Special.BankConf.AreaConf.ContainsKey(mKey)) + { + var thisConf = IOBConfFull.Special.BankConf.AreaConf[mKey]; + newArea.startIdx = thisConf.Start; + newArea.arraySize = thisConf.Size; + } + + return newArea; + } + /// /// Recupera il valore INT dal nome del parametro per successivo processing /// @@ -2863,6 +3006,18 @@ namespace IOB_WIN_FANUC.Iob return answ; } + private void tryIncreaseErrorComm(string topic) + { + numErroriCheck++; + parentForm.ChangeErrorDelay(topic, numErroriCheck, 5); + } + + private void tryReduceErrorCount(string topic) + { + numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : numErroriCheck; + parentForm.ChangeErrorDelay(topic, numErroriCheck, -5); + } + #endregion Private Methods } } \ No newline at end of file diff --git a/IOB-WIN-FORM/Iob/Generic.Protected.cs b/IOB-WIN-FORM/Iob/Generic.Protected.cs index 31018f68..77a8f2c9 100644 --- a/IOB-WIN-FORM/Iob/Generic.Protected.cs +++ b/IOB-WIN-FORM/Iob/Generic.Protected.cs @@ -32,6 +32,11 @@ namespace IOB_WIN_FORM.Iob { #region Protected Fields + /// + /// Variabile numero errori vari (in lettura) --> se supera soglia maxErroriCheck --> disconnette + /// + protected static int numErroriCheck = 0; + protected bool _connOk = false; /// @@ -59,13 +64,6 @@ namespace IOB_WIN_FORM.Iob /// protected int B_output; -#if false - /// - /// Ultimo valore B_output inviato - /// - protected int B_output_sent = -1; -#endif - /// /// Vettore 32 BIT valori precedenti /// @@ -308,11 +306,6 @@ namespace IOB_WIN_FORM.Iob /// protected bool numArtCharTrim = false; - /// - /// Variabile numero errori vari (in lettura) --> se supera soglia maxErroriCheck --> disconnette - /// - protected int numErroriCheck = 0; - /// /// Timeout x ping al server /// @@ -678,16 +671,6 @@ namespace IOB_WIN_FORM.Iob } } - protected bool queueInEnabCurr - { - get => qInEnabCurr; - set - { - qInEnabCurr = value; - lgInfo($"SET queueInEnabCurr: {value} | {DateTime.Now:HHmmss}"); - } - } - /// /// Definizioni x replace in file ricette /// @@ -1026,13 +1009,6 @@ namespace IOB_WIN_FORM.Iob { get => $@"{urlCommandIob("remTask2Exe")}?taskName="; } - /// - /// URL per richiamo task da eseguire... - /// - protected string urlRemTask2ExeTav(string codTav) - { - return $@"{urlCommandIob("remTask2Exe")}|{codTav}?taskName="; - } /// /// URL per salvataggio dati PARAMETRI IOB... @@ -1139,31 +1115,6 @@ namespace IOB_WIN_FORM.Iob #region Protected Methods - /// - /// Verifico i dynData x validità: - /// - siano almeno 1 - /// - NON siano tutti identici - /// - /// - /// - protected bool checkValidDynData(Dictionary currDynData) - { - bool answ = false; - // conto num valori - int numVal = currDynData.Count; - bool dataPresent = numVal > 0; - if (dataPresent) - { - // prendo il primo valore.. - string firstVal = currDynData.FirstOrDefault().Value; - // conto val uguali al primo - int numEq = currDynData.Where(x => x.Value == firstVal).Count(); - // se solo 1 o almeno 1 è diverso è ok - answ = numVal == 1 || (numVal > numEq); - } - return answ; - } - /// /// Decodifica file MAP (caso .bit) /// @@ -1226,6 +1177,26 @@ namespace IOB_WIN_FORM.Iob } } + protected static long GetObjectSize(object genObj, bool isSerializable) + { + long result = 0; + if (isSerializable) + { + using (var stream = new MemoryStream()) + { + var formatter = new BinaryFormatter(); + formatter.Serialize(stream, genObj); + result = stream.Length; + } + } + else + { + string rawVal = System.Text.Json.JsonSerializer.Serialize(genObj, options); + result = rawVal.Length; + } + return result; + } + /// /// Decodifica valore della coda IN nel formato hasVeto[0]=dtEve hasVeto[1]=valore hasVeto[2]=counter /// @@ -1267,6 +1238,31 @@ namespace IOB_WIN_FORM.Iob { } } + /// + /// Verifico i dynData x validità: + /// - siano almeno 1 + /// - NON siano tutti identici + /// + /// + /// + protected bool checkValidDynData(Dictionary currDynData) + { + bool answ = false; + // conto num valori + int numVal = currDynData.Count; + bool dataPresent = numVal > 0; + if (dataPresent) + { + // prendo il primo valore.. + string firstVal = currDynData.FirstOrDefault().Value; + // conto val uguali al primo + int numEq = currDynData.Where(x => x.Value == firstVal).Count(); + // se solo 1 o almeno 1 è diverso è ok + answ = numVal == 1 || (numVal > numEq); + } + return answ; + } + /// /// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile /// @@ -1352,6 +1348,42 @@ namespace IOB_WIN_FORM.Iob } } + /// + /// Verifica se salvare (nel log) info dei FluxLog filtrati + /// - per scadenza valore VetoFlushFiltFL + /// - per un numero complessivo di eventi superiori a soglia + /// + /// numero minimo di eventi necessari al salvataggio (come somma complessiva) + protected void FiltFluxLogCheckSave(long minCount) + { + DateTime adesso = DateTime.Now; + bool doWrite = VetoFlushFiltFL < adesso; + // conteggio valori + if (!doWrite) + { + long numFL = DictFiltFLog.Sum(x => x.Value); + doWrite = numFL > minCount; + } + if (doWrite) + { + // scrivo log x ogni valore + string sep = "------------------------------------------------------------"; + _logger.Info(sep); + _logger.Info("- Paret0 Eventi FluxLog filtrati"); + _logger.Info(sep); + foreach (var item in DictFiltFLog.OrderByDescending(x => x.Value)) + { + // loggo! + _logger.Info($"{item.Key}: {item.Value}"); + } + _logger.Info(sep); + // reset dizionario + DictFiltFLog.Clear(); + // nuovo veto a 1h + VetoFlushFiltFL = adesso.AddHours(1); + } + } + /// /// Imposta eventuali altri valori default /// @@ -1740,7 +1772,7 @@ namespace IOB_WIN_FORM.Iob } answ = await utils.callUrlAsync(url2call); #if false - answ = utils.callUrlNow(url2call); + answ = utils.callUrlNow(url2call); #endif } return answ; @@ -3202,16 +3234,33 @@ namespace IOB_WIN_FORM.Iob catch (Exception ex) { lgError("ProcessAutoOdl | Crash nel ponte Sync/Async: " + ex.Message); - } + } #endif await utils.callUrlAsync(url2call); #if false - answ = utils.callUrlNow(url2call); + answ = utils.callUrlNow(url2call); #endif } return answ; } + /// + /// Salva nel dizionario il num di valori letti e filtrati/non inviati + /// + /// + protected void SaveFiltFluxLog(string codFlux) + { + if (DictFiltFLog.ContainsKey(codFlux)) + { + DictFiltFLog[codFlux]++; + } + else + { + DictFiltFLog.Add(codFlux, 1); + _logger.Info($"FluxLog | veto | {codFlux}"); + } + } + /// /// Invio reset allarmi all'avvio per sicurezza... /// @@ -3335,7 +3384,7 @@ namespace IOB_WIN_FORM.Iob lgInfo("chiamata URL " + urlSetM2IOB); await utils.callUrlAsync(urlSetM2IOB); #if false - utils.callUrlNow(urlSetM2IOB); + utils.callUrlNow(urlSetM2IOB); #endif } } @@ -3424,7 +3473,7 @@ namespace IOB_WIN_FORM.Iob lgInfo("chiamata URL " + url2call); await utils.callUrlAsync(url2call); #if false - utils.callUrlNow(url2call); + utils.callUrlNow(url2call); #endif } } @@ -3912,33 +3961,6 @@ namespace IOB_WIN_FORM.Iob } } - protected static long GetObjectSize(object genObj, bool isSerializable) - { - long result = 0; - if (isSerializable) - { - using (var stream = new MemoryStream()) - { - - var formatter = new BinaryFormatter(); - formatter.Serialize(stream, genObj); - result = stream.Length; - } - } - else - { - string rawVal = System.Text.Json.JsonSerializer.Serialize(genObj, options); - result = rawVal.Length; - } - return result; - } - - private static readonly JsonSerializerOptions options = new JsonSerializerOptions - { - ReferenceHandler = ReferenceHandler.IgnoreCycles, - WriteIndented = false // Optional: set to true for pretty-printing - }; - /// /// Traccia in redis l'attività di lettura dati /// @@ -4123,6 +4145,7 @@ namespace IOB_WIN_FORM.Iob } return fatto; } + /// /// Effettua chiamata MP-IO per tentare chiusura ODL specifico /// @@ -4448,6 +4471,7 @@ namespace IOB_WIN_FORM.Iob } raiseRefresh(currDispData); } + /// /// Versione sync chiamata setup PODL /// @@ -4542,6 +4566,14 @@ namespace IOB_WIN_FORM.Iob return $@"{urlMesServer}{IOBConfFull.MapoMes.ApiUrl(cmqReq)}{IOBConfFull.General.FilenameIOB}"; } + /// + /// URL per richiamo task da eseguire... + /// + protected string urlRemTask2ExeTav(string codTav) + { + return $@"{urlCommandIob("remTask2Exe")}|{codTav}?taskName="; + } + /// /// Verifica se il parametro passi il limite della DeadBand (globale o specifica se /// configurata) Il riferimento è al prec valore currProdData @@ -4595,6 +4627,12 @@ namespace IOB_WIN_FORM.Iob #region Private Fields + private static readonly JsonSerializerOptions options = new JsonSerializerOptions + { + ReferenceHandler = ReferenceHandler.IgnoreCycles, + WriteIndented = false // Optional: set to true for pretty-printing + }; + /// /// Oggetto logger della classe /// @@ -4610,64 +4648,6 @@ namespace IOB_WIN_FORM.Iob /// private Dictionary DictFiltFLog = new Dictionary(); - /// - /// Valore veto al salvataggio di valori filtrati in FluxLog se NON superato limite chiamate - /// - private DateTime VetoFlushFiltFL = DateTime.Now.AddHours(1); - - /// - /// Salva nel dizionario il num di valori letti e filtrati/non inviati - /// - /// - protected void SaveFiltFluxLog(string codFlux) - { - if (DictFiltFLog.ContainsKey(codFlux)) - { - DictFiltFLog[codFlux]++; - } - else - { - DictFiltFLog.Add(codFlux, 1); - _logger.Info($"FluxLog | veto | {codFlux}"); - } - } - - /// - /// Verifica se salvare (nel log) info dei FluxLog filtrati - /// - per scadenza valore VetoFlushFiltFL - /// - per un numero complessivo di eventi superiori a soglia - /// - /// numero minimo di eventi necessari al salvataggio (come somma complessiva) - protected void FiltFluxLogCheckSave(long minCount) - { - DateTime adesso = DateTime.Now; - bool doWrite = VetoFlushFiltFL < adesso; - // conteggio valori - if (!doWrite) - { - long numFL = DictFiltFLog.Sum(x => x.Value); - doWrite = numFL > minCount; - } - if (doWrite) - { - // scrivo log x ogni valore - string sep = "------------------------------------------------------------"; - _logger.Info(sep); - _logger.Info("- Paret0 Eventi FluxLog filtrati"); - _logger.Info(sep); - foreach (var item in DictFiltFLog.OrderByDescending(x => x.Value)) - { - // loggo! - _logger.Info($"{item.Key}: {item.Value}"); - } - _logger.Info(sep); - // reset dizionario - DictFiltFLog.Clear(); - // nuovo veto a 1h - VetoFlushFiltFL = adesso.AddHours(1); - } - } - private string LastDayCurr = ""; /// @@ -4685,6 +4665,17 @@ namespace IOB_WIN_FORM.Iob private Dictionary TrackDetStatsCount = new Dictionary(); private Dictionary> TrackDetValsCount = new Dictionary>(); +#if false + /// + /// Ultimo valore B_output inviato + /// + protected int B_output_sent = -1; +#endif + + /// + /// Valore veto al salvataggio di valori filtrati in FluxLog se NON superato limite chiamate + /// + private DateTime VetoFlushFiltFL = DateTime.Now.AddHours(1); /// /// Dizionario dei valori bloccati x evitare log eccessivo @@ -4720,88 +4711,6 @@ namespace IOB_WIN_FORM.Iob #region Private Properties - /// - /// Verifica se la IOB sia ENABLED (da server o Demo) - /// - public async Task CheckIobEnabled() - { - // 1. Controllo Veto (Sincrono) - if (dtVetoCheckIOB >= DateTime.Now) - return IobOnline; - - bool currentAnsw = false; - - if (DemoOut) - { - currentAnsw = (QueueIN.Count + QueueFLog.Count >= nMaxSend); - } - else - { - currentAnsw = await ExecuteIobCheckWithRetry(maxRetries: 5); -#if false - // 2. Chiamata asincrona con Retry (Ponte Sync/Async) - currentAnsw = Task.Run(async () => await ExecuteIobCheckWithRetry(maxRetries: 5)) - .GetAwaiter() - .GetResult(); -#endif - } - - // 3. Gestione Stato e Logica UI - UpdateIobState(currentAnsw); - - return currentAnsw; - } - - private async Task ExecuteIobCheckWithRetry(int maxRetries) - { - var rand = new Random(); - for (int i = 0; i <= maxRetries; i++) - { - try - { - if (i > 0) - { - // Al terzo tentativo fallito resetto i client - if (i == 3) resetWebClients(); - - int delay = i == 3 ? rand.Next(250, 1000) : rand.Next(250, 500); - await Task.Delay(delay); - } - - string callResp = await callUrl(urlIobEnabled, i < 3); // true per i primi tentativi - if (callResp == "OK") return true; - } - catch (Exception exc) - { - lgError($"Eccez /// Boolean abilitazione coda eventi IN /// @@ -4815,98 +4724,6 @@ namespace IOB_WIN_FORM.Iob get => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CurrProdData"); } - /// - /// Test ping all'indirizzo impostato nei parametri - /// - /// - private IPStatus GetPingStatus() - { - var pStatus = Task.Run(async () => await GetPingStatusAsync(maxPingRetry + 1)) - .GetAwaiter() - .GetResult(); - - return pStatus; - } - - /// - /// Test ping all'indirizzo impostato nei parametri - /// - /// Numero max tentativi (maxPingRetry + 1) - /// - private async Task GetPingStatusAsync(int maxAttempts) - { - // 1. Check disabilitazione - if (IOBConfFull.MapoMes.DisabPing) return IPStatus.Success; - - // 2. Estrazione Host/IP pulita - string host = IOBConfFull.MapoMes.IpAddr; - try - { - // Rimuove protocollo (http://, ftp://, etc) - if (host.Contains("://")) - host = new Uri(host).Host; - else if (host.Contains(":")) - host = host.Split(':')[0]; - } - catch { /* fallback al valore originale se Uri fallisce */ } - - // 3. Risoluzione Indirizzo - if (!IPAddress.TryParse(host, out IPAddress address)) - { - try - { - var addresses = Dns.GetHostAddresses(host); - if (addresses.Length > 0) address = addresses[0]; - } - catch (Exception ex) - { - lgError($"Impossibile risolvere DNS per {host}: {ex.Message}"); - return IPStatus.DestinationHostUnreachable; - } - } - - if (address == null) return IPStatus.Unknown; - - // 4. Ciclo di Ping con Retry - var rand = new Random(); - using (Ping pingSender = new Ping()) - { - for (int attempt = 1; attempt <= maxAttempts; attempt++) - { - try - { - // Timeout dinamico come nel tuo originale - int timeout = (attempt == 1) - ? rand.Next(200, 400) - : (pingServerMsTimeout * attempt / 2); - - PingReply reply = pingSender.Send(address, timeout); - - if (reply.Status == IPStatus.Success) - { - if (attempt > 1) lgInfo("Server PING OK dopo retry!"); - return IPStatus.Success; - } - - lgInfo($"Ping tent. {attempt} fallito: {reply.Status} (Timeout: {timeout}ms)"); - } - catch (Exception ex) - { - lgError($"Eccezione durante ping tent. {attempt}: {ex.Message}"); - } - - // Attesa prima del prossimo tentativo (se non è l'ultimo) - if (attempt < maxAttempts) - { - await Task.Delay(rand.Next(50, 200)); - } - } - } - - return IPStatus.TimedOut; - } - - /// /// URL di base del server MES da contattare /// @@ -5149,6 +4966,33 @@ namespace IOB_WIN_FORM.Iob return fatto; } + private async Task ExecuteIobCheckWithRetry(int maxRetries) + { + var rand = new Random(); + for (int i = 0; i <= maxRetries; i++) + { + try + { + if (i > 0) + { + // Al terzo tentativo fallito resetto i client + if (i == 3) resetWebClients(); + + int delay = i == 3 ? rand.Next(250, 1000) : rand.Next(250, 500); + await Task.Delay(delay); + } + + string callResp = await callUrl(urlIobEnabled, i < 3); // true per i primi tentativi + if (callResp == "OK") return true; + } + catch (Exception exc) + { + lgError($"Eccez /// Esegue filtraggio dati x bit blinking!!! /// @@ -5226,6 +5070,97 @@ namespace IOB_WIN_FORM.Iob demFactDynData = IOBConfFull.FluxLog.DemFactDynData; } + /// + /// Test ping all'indirizzo impostato nei parametri + /// + /// + private IPStatus GetPingStatus() + { + var pStatus = Task.Run(async () => await GetPingStatusAsync(maxPingRetry + 1)) + .GetAwaiter() + .GetResult(); + + return pStatus; + } + + /// + /// Test ping all'indirizzo impostato nei parametri + /// + /// Numero max tentativi (maxPingRetry + 1) + /// + private async Task GetPingStatusAsync(int maxAttempts) + { + // 1. Check disabilitazione + if (IOBConfFull.MapoMes.DisabPing) return IPStatus.Success; + + // 2. Estrazione Host/IP pulita + string host = IOBConfFull.MapoMes.IpAddr; + try + { + // Rimuove protocollo (http://, ftp://, etc) + if (host.Contains("://")) + host = new Uri(host).Host; + else if (host.Contains(":")) + host = host.Split(':')[0]; + } + catch { /* fallback al valore originale se Uri fallisce */ } + + // 3. Risoluzione Indirizzo + if (!IPAddress.TryParse(host, out IPAddress address)) + { + try + { + var addresses = Dns.GetHostAddresses(host); + if (addresses.Length > 0) address = addresses[0]; + } + catch (Exception ex) + { + lgError($"Impossibile risolvere DNS per {host}: {ex.Message}"); + return IPStatus.DestinationHostUnreachable; + } + } + + if (address == null) return IPStatus.Unknown; + + // 4. Ciclo di Ping con Retry + var rand = new Random(); + using (Ping pingSender = new Ping()) + { + for (int attempt = 1; attempt <= maxAttempts; attempt++) + { + try + { + // Timeout dinamico come nel tuo originale + int timeout = (attempt == 1) + ? rand.Next(200, 400) + : (pingServerMsTimeout * attempt / 2); + + PingReply reply = pingSender.Send(address, timeout); + + if (reply.Status == IPStatus.Success) + { + if (attempt > 1) lgInfo("Server PING OK dopo retry!"); + return IPStatus.Success; + } + + lgInfo($"Ping tent. {attempt} fallito: {reply.Status} (Timeout: {timeout}ms)"); + } + catch (Exception ex) + { + lgError($"Eccezione durante ping tent. {attempt}: {ex.Message}"); + } + + // Attesa prima del prossimo tentativo (se non è l'ultimo) + if (attempt < maxAttempts) + { + await Task.Delay(rand.Next(50, 200)); + } + } + } + + return IPStatus.TimedOut; + } + /// /// Verifica se il log di un dato errore sia permesso /// @@ -5632,7 +5567,6 @@ namespace IOB_WIN_FORM.Iob { lgError($"Errore in RecipeDoProcessPODL.callUrl: {ex.Message}"); } - } answ = true; } @@ -5982,6 +5916,28 @@ namespace IOB_WIN_FORM.Iob } } + private void UpdateIobState(bool newStatus) + { + // Log se lo stato è cambiato + if (IobOnline != newStatus) + { + lgInfo(newStatus ? "IOB ONLINE for server MP/IO" : "IOB OFFLINE for server MP/IO"); + IobOnline = newStatus; + if (newStatus) + { + lastIobOnline = DateTime.Now; + parentForm.commSrvActive = 2; // Stato Online + } + else + { + parentForm.commSrvActive = 1; // Stato Degradato/Offline + } + } + + // Imposto il veto per il prossimo controllo + dtVetoCheckIOB = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 5); + } + #endregion Private Methods } } \ No newline at end of file diff --git a/IOB-WIN-FORM/Iob/Generic.Public.cs b/IOB-WIN-FORM/Iob/Generic.Public.cs index d32741f7..300e7eeb 100644 --- a/IOB-WIN-FORM/Iob/Generic.Public.cs +++ b/IOB-WIN-FORM/Iob/Generic.Public.cs @@ -60,7 +60,6 @@ namespace IOB_WIN_FORM.Iob // initi oggetto TCMan tcMan = new TCMan(IobConfNew.TCDataConf.Lambda, IobConfNew.TCDataConf.MaxDelayFactor, IobConfNew.TCDataConf.MaxIncrPz); - lastConnectTry = DateTime.Now; lgInfo("Avvio preliminare AdapterGeneric"); @@ -81,7 +80,9 @@ namespace IOB_WIN_FORM.Iob // imposto veto invio per i prossimi sec dtVetoQueueIN = DateTime.Now.AddSeconds(vetoQueueIn); - lgInfoStartup($"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}"); + string msgVeto = $"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}"; + lgInfoStartup(msgVeto); + lgInfo(msgVeto); // invio info IOB SendM2IOB(); @@ -100,16 +101,6 @@ namespace IOB_WIN_FORM.Iob } } - /// - /// Area init asincrono (fare override async!o) - /// - /// - public virtual Task InitializeAsync() - { - // da usare per implementare logiche di init specifiche - return Task.CompletedTask; - } - #endregion Public Constructors #region Public Events @@ -335,6 +326,19 @@ namespace IOB_WIN_FORM.Iob } } + /// + /// Abilitazione coda segnali ingresso + /// + public bool queueInEnabCurr + { + get => qInEnabCurr; + set + { + qInEnabCurr = value; + lgInfo($"SET queueInEnabCurr: {value} | {DateTime.Now:HHmmss}"); + } + } + /// /// Finestra dei byte da mostrare di default x il RawDataInput /// @@ -432,7 +436,7 @@ namespace IOB_WIN_FORM.Iob enabled = IOBConfFull.Memory.mMapRead[codFlux].sendEnabled; if (!enabled) { - lgDebug($"accodaFLog : invio bloccato per conf aprametro sendEnabled | codFlux: {codFlux}"); + lgDebug($"accodaFLog : invio bloccato per conf parametro sendEnabled | codFlux: {codFlux}"); } else { @@ -631,6 +635,38 @@ namespace IOB_WIN_FORM.Iob } } + /// + /// Verifica se la IOB sia ENABLED (da server o Demo) + /// + public async Task CheckIobEnabled() + { + // 1. Controllo Veto (Sincrono) + if (dtVetoCheckIOB >= DateTime.Now) + return IobOnline; + + bool currentAnsw = false; + + if (DemoOut) + { + currentAnsw = (QueueIN.Count + QueueFLog.Count >= nMaxSend); + } + else + { + currentAnsw = await ExecuteIobCheckWithRetry(maxRetries: 5); +#if false + // 2. Chiamata asincrona con Retry (Ponte Sync/Async) + currentAnsw = Task.Run(async () => await ExecuteIobCheckWithRetry(maxRetries: 5)) + .GetAwaiter() + .GetResult(); +#endif + } + + // 3. Gestione Stato e Logica UI + UpdateIobState(currentAnsw); + + return currentAnsw; + } + /// /// Verifica se il server sia ALIVE (tramite PING) /// @@ -668,79 +704,17 @@ namespace IOB_WIN_FORM.Iob // evitando deadlock con il thread della UI bool isAlive = Task.Run(async () => await ExecuteApiCheckWithRetryAsync(maxRetries: 7)) .GetAwaiter() - .GetResult(); + .GetResult(); #endif bool isAlive = await ExecuteApiCheckWithRetryAsync(maxRetries: 7); - // 3. Gestione Stato (Sincrono) UpdateServerState(isAlive); return isAlive; } - /// - /// Test ping + api al server in modalità Async - /// - /// - /// - private async Task ExecuteApiCheckWithRetryAsync(int maxRetries) - { - var rand = new Random(); - for (int i = 0; i <= maxRetries; i++) - { - try - { - // Se non è il primo tentativo, resetta i client e aspetta - if (i > 0) - { - if (i == 4) resetWebClients(); // Reset specifico a metà tentativi - await Task.Delay(rand.Next(150, 500)); - } - - string resp = await callUrl(urlAlive, false); - if (resp == "OK") return true; - } - catch (Exception ex) - { - if (i == 0) lgError($"Errore API Check: {ex.Message}"); - } - } - return false; - } - - /// - /// Update stato server - /// - /// - private void UpdateServerState(bool currentAlive) - { - if (MPOnline != currentAlive) - { - MPOnline = currentAlive; - parentForm.commSrvActive = currentAlive ? 1 : 0; - - if (currentAlive) - { - lgInfo("SERVER ONLINE"); - dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 5); - } - else - { - lgError("SERVER OFFLINE"); - // Veto standard per server offline - dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 10); - utils.dtVetoSend = dtVetoPing; - } - } - else - { - // Se lo stato è invariato (es. era online e resta online), allunghiamo il prossimo controllo - dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 20); - } - } - /// /// Update visualizzaizone BIT in ingresso Parametri da /// aggiornare x display in form @@ -1022,7 +996,7 @@ namespace IOB_WIN_FORM.Iob catch (Exception ex) { lgError("ProcessOtherInfoAsync | Crash nel ponte Sync/Async: " + ex.Message); - } + } #endif break; @@ -1449,7 +1423,7 @@ namespace IOB_WIN_FORM.Iob if (adpTryRestart && !connectionOk) { // controllo se sia scaduto periodi di veto al tryConnect... - int waitRecMSec = utils.CRI("waitRecMSec") * 2; + int waitRecMSec = utils.CRI("waitRecMSec"); // cerco se ci sia un valore in ovverride x il singolo IOB... if (IOBConfFull.General.WaitRecMsec > 0) { @@ -1672,6 +1646,16 @@ namespace IOB_WIN_FORM.Iob return answ; } + /// + /// Area init asincrono (fare override async!o) + /// + /// + public virtual Task InitializeAsync() + { + // da usare per implementare logiche di init specifiche + return Task.CompletedTask; + } + /// /// Restituisce un payload in formato json della lista di valori ricevuta /// @@ -1977,7 +1961,6 @@ namespace IOB_WIN_FORM.Iob } } - /// /// Verifica e processing x gestione ODL automatica /// @@ -2426,7 +2409,6 @@ namespace IOB_WIN_FORM.Iob if (memMap != null && memMap.mMapRead.ContainsKey(item.Key) && !string.IsNullOrEmpty(item.Value)) { upsertKeyLP(item.Key, item.Value); - } } } @@ -3386,12 +3368,13 @@ namespace IOB_WIN_FORM.Iob // sistemo altri check avvio dtVetoQueueIN = DateTime.Now.AddSeconds(vetoQueueIn); queueInEnabCurr = false; - lgInfoStartup($"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}"); + string msgVeto = $"Impostato veto QUEUE-IN a {vetoQueueIn}s | fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}"; + lgInfoStartup(msgVeto); lgDebug($"startAdapter completed | veto queueIn impostato per {vetoQueueIn}s fino alle {dtVetoQueueIN:yyyy.MM.dd HH:mm:ss}"); } /// - /// ferma l'adapter... + /// Ferma l'adapter... /// /// /// indica se si debba tentare di riavviare l'adapter (con caduta connessione viene fermato @@ -3763,6 +3746,36 @@ namespace IOB_WIN_FORM.Iob #region Private Methods + /// + /// Test ping + api al server in modalità Async + /// + /// + /// + private async Task ExecuteApiCheckWithRetryAsync(int maxRetries) + { + var rand = new Random(); + for (int i = 0; i <= maxRetries; i++) + { + try + { + // Se non è il primo tentativo, resetta i client e aspetta + if (i > 0) + { + if (i == 4) resetWebClients(); // Reset specifico a metà tentativi + await Task.Delay(rand.Next(150, 500)); + } + + string resp = await callUrl(urlAlive, false); + if (resp == "OK") return true; + } + catch (Exception ex) + { + if (i == 0) lgError($"Errore API Check: {ex.Message}"); + } + } + return false; + } + /// /// Processing di una risposta raw di task2exe /// @@ -3790,6 +3803,37 @@ namespace IOB_WIN_FORM.Iob } } + /// + /// Update stato server + /// + /// + private void UpdateServerState(bool currentAlive) + { + if (MPOnline != currentAlive) + { + MPOnline = currentAlive; + parentForm.commSrvActive = currentAlive ? 1 : 0; + + if (currentAlive) + { + lgInfo("SERVER ONLINE"); + dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 5); + } + else + { + lgError("SERVER OFFLINE"); + // Veto standard per server offline + dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 10); + utils.dtVetoSend = dtVetoPing; + } + } + else + { + // Se lo stato è invariato (es. era online e resta online), allunghiamo il prossimo controllo + dtVetoPing = DateTime.Now.AddMilliseconds(baseUtils.nextPauseSendMSec * 20); + } + } + #endregion Private Methods } } \ No newline at end of file From 591fae86eed55f1f5ac9fc836957f62eada09e0c Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Tue, 17 Feb 2026 19:15:53 +0100 Subject: [PATCH 3/7] Update con prima separazione metodi pooled/single (da validare) --- IOB-UT-NEXT/Config/Base/IobDto.cs | 2 +- IOB-UT-NEXT/Config/IobConfTree.cs | 2 +- IOB-UT-NEXT/SingleThreadTaskScheduler.cs | 45 +- IOB-UT-NEXT/baseUtils.cs | 44 -- IOB-WIN-FANUC/DATA/CONF/3028.ini | 6 + IOB-WIN-FANUC/DATA/CONF/3029.ini | 4 + IOB-WIN-FANUC/DATA/CONF/MAIN.ini | 3 +- IOB-WIN-FANUC/Iob/Fanuc.cs | 15 +- IOB-WIN-FORM/AdapterForm.cs | 571 +++++++++++++++++++---- IOB-WIN-FORM/Iob/Generic.Protected.cs | 25 +- IOB-WIN-FORM/Iob/Generic.Public.cs | 209 +++++---- 11 files changed, 653 insertions(+), 273 deletions(-) diff --git a/IOB-UT-NEXT/Config/Base/IobDto.cs b/IOB-UT-NEXT/Config/Base/IobDto.cs index 96b7984b..7c9d4248 100644 --- a/IOB-UT-NEXT/Config/Base/IobDto.cs +++ b/IOB-UT-NEXT/Config/Base/IobDto.cs @@ -80,7 +80,7 @@ namespace IOB_UT_NEXT.Config.Base /// /// Soglia massima errori prima della disconnessione automatica in check /// - public int MaxErroriCheck { get; set; } = 100; + public int MaxErroriCheck { get; set; } = 50; /// /// Max tentativi ping permessi (default: 5) diff --git a/IOB-UT-NEXT/Config/IobConfTree.cs b/IOB-UT-NEXT/Config/IobConfTree.cs index 6ccc9b61..3497fe50 100644 --- a/IOB-UT-NEXT/Config/IobConfTree.cs +++ b/IOB-UT-NEXT/Config/IobConfTree.cs @@ -274,7 +274,7 @@ namespace IOB_UT_NEXT.Config PzTotMode = fIni.ReadString("OPTPAR", "PZGTOT_MODE", ""), ReadErrorMax = fIni.ReadInteger("OPTPAR", "READ_ERROR_MAX", 20), ReadErrorSleepTime = fIni.ReadInteger("OPTPAR", "READ_ERROR_SLEEP_TIME", 20000), - StartupVetoQueueIN = fIni.ReadInteger("OPTPAR", "VETO_QUEUE_IN", 10), + StartupVetoQueueIN = fIni.ReadInteger("OPTPAR", "VETO_QUEUE_IN", 6), Vendor = fIni.ReadString("MACHINE", "VENDOR", "STEAMWARE"), Connect = new ConnectionDto() { diff --git a/IOB-UT-NEXT/SingleThreadTaskScheduler.cs b/IOB-UT-NEXT/SingleThreadTaskScheduler.cs index f9271624..7c70f915 100644 --- a/IOB-UT-NEXT/SingleThreadTaskScheduler.cs +++ b/IOB-UT-NEXT/SingleThreadTaskScheduler.cs @@ -8,43 +8,40 @@ public class SingleThreadTaskScheduler : TaskScheduler, IDisposable { private readonly BlockingCollection _tasks = new BlockingCollection(); private readonly Thread _thread; + public SynchronizationContext SyncContext { get; private set; } - public SingleThreadTaskScheduler() + public SingleThreadTaskScheduler(string threadName) { + var tcs = new TaskCompletionSource(); _thread = new Thread(() => { + // Creiamo il contesto di sincronizzazione personalizzato + SyncContext = new SingleThreadSynchronizationContext(this); + SynchronizationContext.SetSynchronizationContext(SyncContext); + tcs.SetResult(true); + foreach (var task in _tasks.GetConsumingEnumerable()) { - // Esegue il task sul thread dedicato base.TryExecuteTask(task); } - }); - - _thread.IsBackground = true; - _thread.Name = "SingleWorkerThread"; + }) + { IsBackground = true, Name = threadName }; _thread.Start(); + tcs.Task.Wait(); // Aspetta che il thread sia pronto col suo contesto } - protected override void QueueTask(Task task) + private class SingleThreadSynchronizationContext : SynchronizationContext { - _tasks.Add(task); - } - - protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) - { - // Consente l'esecuzione inline solo se siamo già sul thread dedicato - return Thread.CurrentThread == _thread && base.TryExecuteTask(task); - } - - protected override IEnumerable GetScheduledTasks() - { - return _tasks.ToArray(); + private readonly SingleThreadTaskScheduler _sch; + public SingleThreadSynchronizationContext(SingleThreadTaskScheduler sch) => _sch = sch; + public override void Post(SendOrPostCallback d, object state) + => Task.Factory.StartNew(() => d(state), CancellationToken.None, TaskCreationOptions.None, _sch); } + protected override void QueueTask(Task task) => _tasks.Add(task); + protected override bool TryExecuteTaskInline(Task task, bool prev) + => Thread.CurrentThread == _thread && base.TryExecuteTask(task); + protected override IEnumerable GetScheduledTasks() => _tasks; public override int MaximumConcurrencyLevel => 1; - - public void Dispose() - { - _tasks.CompleteAdding(); - } + public void Dispose() => _tasks.CompleteAdding(); } \ No newline at end of file diff --git a/IOB-UT-NEXT/baseUtils.cs b/IOB-UT-NEXT/baseUtils.cs index 7df31da5..6554e009 100644 --- a/IOB-UT-NEXT/baseUtils.cs +++ b/IOB-UT-NEXT/baseUtils.cs @@ -351,36 +351,6 @@ namespace IOB_UT_NEXT } } - -#if false - /// - /// Versione async della chiamata ad URL - /// - /// - /// - public static string callUrlAsync(string URL) - { - // Chiamo in modalità task... - var resp = Task.Run(() => callUrl(URL)); - resp.Wait(); - return resp.Result; - } - - /// - /// Versione async della chiamata ad URL - /// - /// - /// - /// - public static string callUrlAsync(string URL, string payload) - { - // Chiamo in modalità task... - var resp = Task.Run(() => callUrl(URL, payload)); - resp.Wait(); - return resp.Result; - } -#endif - /// /// Effettua chiamata URL IMMEDIATAMENTE e restituisce risultato /// @@ -553,20 +523,6 @@ namespace IOB_UT_NEXT public static string CRS(string key) { return ConfigurationManager.AppSettings[key] ?? string.Empty; -#if false - string answ = ""; - if (ConfigurationManager.AppSettings.Count > 0) - { - try - { - - answ = ConfigurationManager.AppSettings[key].ToString(); - } - catch - { } - } - return answ; -#endif } /// diff --git a/IOB-WIN-FANUC/DATA/CONF/3028.ini b/IOB-WIN-FANUC/DATA/CONF/3028.ini index b92709f3..97c3ebcb 100644 --- a/IOB-WIN-FANUC/DATA/CONF/3028.ini +++ b/IOB-WIN-FANUC/DATA/CONF/3028.ini @@ -86,6 +86,12 @@ DISABLE_SEND_WDST=TRUE ; bit da scrivere come trace ad ogni check ;MEM_2_TRACE=|BIT3|BIT4|BIT5| PARAM_CONF=3028.json +; test timing & errori +timerIntMs=30 +ExeSingleThread=true +MAX_ERR_CHECK=10 +;WAIT_REC_MSEC=15000 + [BRANCH] NAME=master diff --git a/IOB-WIN-FANUC/DATA/CONF/3029.ini b/IOB-WIN-FANUC/DATA/CONF/3029.ini index 839a86e0..2ae80899 100644 --- a/IOB-WIN-FANUC/DATA/CONF/3029.ini +++ b/IOB-WIN-FANUC/DATA/CONF/3029.ini @@ -88,6 +88,10 @@ ENABLE_SEND_PZC_BLOCK=TRUE MIN_SEND_PZC_BLOCK=0 MAX_SEND_PZC_BLOCK=100 DISABLE_SEND_WDST=TRUE +; test timing & errori +timerIntMs=40 +;ExeSingleThread=true +MAX_ERR_CHECK=10 ; bit da scrivere come trace ad ogni check ;MEM_2_TRACE=|BIT3|BIT4|BIT5| PARAM_CONF=3029.json diff --git a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini index 5b02ed04..5fefa1bd 100644 --- a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini +++ b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini @@ -26,11 +26,12 @@ CLI_INST=SteamWareSim ;STARTLIST=3004 ;STARTLIST=3002 ;STARTLIST=GT575 -STARTLIST=3029 ;STARTLIST=3004,3005 ;STARTLIST=SIMUL_01 ;STARTLIST=FOV106 ;STARTLIST=FOV107 +;STARTLIST=3028 +STARTLIST=3029 MAXCNC=10 \ No newline at end of file diff --git a/IOB-WIN-FANUC/Iob/Fanuc.cs b/IOB-WIN-FANUC/Iob/Fanuc.cs index 5dd79df2..e0398487 100644 --- a/IOB-WIN-FANUC/Iob/Fanuc.cs +++ b/IOB-WIN-FANUC/Iob/Fanuc.cs @@ -59,6 +59,11 @@ namespace IOB_WIN_FANUC.Iob MemBlockX = new byte[xSize]; MemBlockY = new byte[ySize]; + // forzo letture single threaded x FANUC!!! + IOBConfFull.General.ExeSingleThread = true; + // x FANUC riduco num errori std x forzare riavvio rapido + maxErroriCheck = maxErroriCheck > 15 ? 15 : maxErroriCheck; + // loggo aree di memoria avviate... lgInfo($"Init area di memoria | MemBlockG: {gSize}b | MemBlockR: {rSize}b | MemBlockX: {xSize}b | MemBlockY: {ySize}b "); @@ -1520,6 +1525,7 @@ namespace IOB_WIN_FANUC.Iob /// public override void tryConnect() { + lgInfo("++++++++++++ CONNECT ++++++++++++"); if (!connectionOk) { // controllo che il ping sia stato tentato almeno pingTestSec fa... @@ -1619,6 +1625,7 @@ namespace IOB_WIN_FANUC.Iob /// public override void tryDisconnect() { + lgInfo("----------- DISCONNECT -----------"); if (connectionOk) { string szStatusConnection = ""; @@ -3008,13 +3015,19 @@ namespace IOB_WIN_FANUC.Iob private void tryIncreaseErrorComm(string topic) { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | numErroriCheck: {numErroriCheck}"); numErroriCheck++; parentForm.ChangeErrorDelay(topic, numErroriCheck, 5); } private void tryReduceErrorCount(string topic) { - numErroriCheck = numErroriCheck > 0 ? numErroriCheck-- : numErroriCheck; + numErroriCheck -= 1; + if (numErroriCheck>=0) + { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | numErroriCheck: {numErroriCheck}"); + } + numErroriCheck = numErroriCheck < 0 ? 0 : numErroriCheck; parentForm.ChangeErrorDelay(topic, numErroriCheck, -5); } diff --git a/IOB-WIN-FORM/AdapterForm.cs b/IOB-WIN-FORM/AdapterForm.cs index eeba20c4..74811dbc 100644 --- a/IOB-WIN-FORM/AdapterForm.cs +++ b/IOB-WIN-FORM/AdapterForm.cs @@ -275,7 +275,7 @@ namespace IOB_WIN_FORM { // update veto! dMonDisplVetoVeto[1] = adesso.AddMilliseconds(delayShowLogMs); - + if (!ShouldUpdateUI()) return; // se scaduto veto --> display! lblOutMessage.Text = String.Join(Environment.NewLine, dMonValues[1]); @@ -748,8 +748,14 @@ namespace IOB_WIN_FORM restart.Enabled = true; // 2026.01.02 fix timers al restart gather.Enabled = true; + + // 2026.02.17 controlo stato single thread exe... + doExeSingleThread = iobObj.IOBConfFull.General.ExeSingleThread; + +#if true // gestione worker StartWorker(); +#endif displayTaskAndLog("Start Timers", true); // inizializzo le scadenze dei timers... TimersVeto = new Dictionary(); @@ -788,6 +794,31 @@ namespace IOB_WIN_FORM } } + /// + /// Registra variazione error delay in caso di errori o successi + /// + /// + /// + /// + public void ChangeErrorDelay(string topic, int currCount, int delta) + { + _errorDelay += delta; + // se fosse negativo --> riporto a zero! + _errorDelay = _errorDelay < 0 ? 0 : _errorDelay; + // registro LOG variazione + if (delta > 0) + { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | +++ delay {_errorDelay} | {topic} | err: {currCount} | {DateTime.Now:HH:mm:ss.fff}"); + } + else + { + if (_errorDelay > 0) + { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] | --- delay {_errorDelay} | {topic} | err: {currCount} | {DateTime.Now:HH:mm:ss.fff}"); + } + } + } + /// /// mostra un testo sulla status bar + LOG /// @@ -1084,6 +1115,28 @@ namespace IOB_WIN_FORM base.Dispose(disposing); } + /// + /// Esegue logica di init async x IOB + /// + /// + protected async Task iobInitAsync() + { + try + { + this.Cursor = Cursors.WaitCursor; + await iobObj.InitializeAsync(); + this.Cursor = Cursors.Default; + } + catch (Exception ex) + { + lgError($"Inizializzazione fallita per {tipoScelto}: {ex.Message}"); + iobObj = null; // Evitiamo di usare un oggetto malfunzionante + return false; + } + + return true; + } + /// /// GEstione evento refresh /// @@ -1184,28 +1237,6 @@ namespace IOB_WIN_FORM } } - /// - /// Esegue logica di init async x IOB - /// - /// - protected async Task iobInitAsync() - { - try - { - this.Cursor = Cursors.WaitCursor; - await iobObj.InitializeAsync(); - this.Cursor = Cursors.Default; - } - catch (Exception ex) - { - lgError($"Inizializzazione fallita per {tipoScelto}: {ex.Message}"); - iobObj = null; // Evitiamo di usare un oggetto malfunzionante - return false; - } - - return true; - } - /// /// Init form (grafica) con helper x testi e varie /// @@ -1248,25 +1279,6 @@ namespace IOB_WIN_FORM nLine2show = utils.CRI("numRowConsole"); } - /// - /// Indica se debba aggiornare o meno la UI perché minimizzata o meno - /// - /// - private bool ShouldUpdateUI() - { - // 1. Controlla se la form stessa è minimizzata - if (this.WindowState == FormWindowState.Minimized) return false; - - // 2. Se è una MDI Child, controlla se il Padre è minimizzato - if (this.MdiParent != null && this.MdiParent.WindowState == FormWindowState.Minimized) - return false; - - // 3. (Opzionale) Controlla se la form è visibile - if (!this.Visible) return false; - - return true; - } - /// /// MOstra info su coda complessiva /// @@ -1296,15 +1308,44 @@ namespace IOB_WIN_FORM #region Private Fields + /// + /// Task factory single threaded + /// + private static readonly TaskFactory _singleFactory = new TaskFactory(_singleScheduler); + + /// + /// Scheduler single threaded + /// + private static readonly SingleThreadTaskScheduler _singleScheduler = new SingleThreadTaskScheduler("IOB-SINGLE"); + private readonly object _lock = new object(); + /// + /// Usato come semaforo x evitare doppio uso risorse threaded + /// + private readonly SemaphoreSlim _plcLock = new SemaphoreSlim(1, 1); + private CancellationTokenSource _cts; + /// + /// Ritardo per errori (extra nei cicli) + /// + private int _errorDelay = 0; + // Per thread-safety private bool _isSuspended = false; + private FormWindowState _lastState = FormWindowState.Minimized; + private Task _workerTask; + /// + /// Booleana per determinare se eseguire le chiamate DoExecTasksAsync su single thread (es FANUC) o dinamico (default) + /// + private bool doExeSingleThread = false; + + private bool stopForced = false; + /// /// Contatore chiamate timers x debug timing /// @@ -1373,7 +1414,7 @@ namespace IOB_WIN_FORM if (IOBConfFull == null || !utils.CRB("autoLoadConf")) { IOBConfFull = new IobConfTree(); - loadIobType(); + await loadIobType(); displayTaskAndLog("Waiting for config file selection"); } @@ -1424,11 +1465,13 @@ namespace IOB_WIN_FORM lgError($"AdapterForm: EXCEPTION in fase di chiamata URL di reboot:{iobObj.urlReboot}{Environment.NewLine}{exc}"); } +#if false // avvio timer secondario x esecuzione (periodo di base: VHF!!!) StartWorker(); +#endif displayTaskAndLog($"Main workerTimer set: {IOBConfFull.General.Timers.MsVHF}ms", true); } - displayTaskAndLog("Main Form OK", true); + displayTaskAndLog("Adapter Form OK", true); } private void btnForceAutoOdl_Click(object sender, EventArgs e) @@ -1512,8 +1555,10 @@ namespace IOB_WIN_FORM /// /// Verifica scadenza task /// - /// - private async Task checkScad() + /// Indica se siano abilitate operazioni Pool (Thread Safe) + /// Indica se siano abilitate le operazioni Single Thread + /// + private async Task checkScad(bool enabPool, bool enabSTID) { DateTime adesso = DateTime.Now; bool sendDone = false; @@ -1532,10 +1577,13 @@ namespace IOB_WIN_FORM { TimersCycleCount[item]++; Stopwatch sw = Stopwatch.StartNew(); - await iobObj.getAndSendAsync(item); + + // chiamata di processing x frequenza + await iobObj.getAndSendAsync(item, enabPool, enabSTID); sw.Stop(); sendDone = true; + // metto una piccola attesa se ho altre scadenze TimersCycleElaps[item] += sw.Elapsed.TotalMilliseconds; // metto nuova scadenza perturbata 10% TimersVeto[item] = adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0.1)); @@ -1592,42 +1640,68 @@ namespace IOB_WIN_FORM /// /// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound /// - private async Task DoExecTasks() + /// Richiesta esecuzone su single thread (es FANUC) + /// + private async Task DoExecTasksAsync(bool reqSingle) { - bool doLog = false; - // procedo! - if (iobObj.periodicLog) - { - doLog = true; - } + // Attende il proprio turno. Se un task è già in corso, questo aspetta. + await _plcLock.WaitAsync(); + bool doLog = iobObj.periodicLog; try { - // check esecuzione SendTask (MsVHF) COMUNQUE... - await iobObj.getAndSendAsync(gatherCycle.VHF); + // imposto variabili x esecuzione separata task Single Thread / Pooled Thread + bool reqSTE = iobObj.IOBConfFull.General.ExeSingleThread; + bool enabSTID = reqSTE ? reqSingle : true; + bool enabPool = !reqSTE; + + if (enabPool) + { + // check esecuzione SendTask (MsVHF) COMUNQUE... + await iobObj.getAndSendAsync(gatherCycle.VHF, enabPool, enabSTID); + } // eseguo cicli attivi SOLO se adapter è in EFFETTIVO running... if (iobObj.adpRunning) { if (iobObj.connectionOk) { - // se richiesto faccio memory DUMP INIZIALE! - if (iobObj.doStartMemDump) + DateTime adesso = DateTime.Now; + // verifico non ci sia veto comunicazioni lettura... + if (iobObj.queueInEnabCurr) { - lgInfo("Inizio dump memoria"); - iobObj.saveMemDump(dumpType.STARTUP); - // fatto! non ripeto... - iobObj.doStartMemDump = false; - lgInfo("Finito dump memoria"); + if (enabSTID) + { + // se richiesto faccio memory DUMP INIZIALE! + if (iobObj.doStartMemDump) + { + lgInfo("Inizio dump memoria"); + iobObj.saveMemDump(dumpType.STARTUP); + // fatto! non ripeto... + iobObj.doStartMemDump = false; + lgInfo("Finito dump memoria"); + } + } + if (enabPool) + { + // controllo se sia abilitato sampleDump della meoria (periodico) + if (iobObj.doSampleMemory) + { + checkSampleMem(); + } + } + + // MAIN: controllo TUTTE le scadenze... + await checkScad(enabPool, enabSTID); + + // wait opzionale in coda + if (utils.CRI("waitEndCycle") > 0) + { + await Task.Delay(utils.CRI("waitEndCycle")); + } } - // controllo se sia abilitato sampleDump della meoria (periodico) - if (iobObj.doSampleMemory) + else { - checkSampleMem(); - } - // controllo TUTTE le scadenze... - checkScad(); - if (utils.CRI("waitEndCycle") > 0) - { - await Task.Delay(utils.CRI("waitEndCycle")); + lgTrace($"VETO queueInEnabCurr | veto attivo | {adesso:yyyy.MM.dd HH:mm:ss}"); + iobObj.checkVetoQueueIn(); } } else @@ -1663,7 +1737,11 @@ namespace IOB_WIN_FORM } catch (Exception exc) { - lgError(string.Format("Eccezione in fase di gatherTick: {0}{1}", Environment.NewLine, exc)); + lgError($"Eccezione in fase di DoExecTasksAsync: {Environment.NewLine}{exc}"); + } + finally + { + _plcLock.Release(); } } @@ -1706,8 +1784,6 @@ namespace IOB_WIN_FORM lgError($"Errore in fermaTutto |stopTimer: {stopTimer} | tryRestart: {tryRestart} | forceDequeue: {forceDequeue} | updateForm: {updateForm}{Environment.NewLine}{ex.Message}"); } - - newDisplayData currDispData = new newDisplayData(); currDispData.semIn = Semaforo.SS; currDispData.semOut = Semaforo.SS; @@ -1812,7 +1888,7 @@ namespace IOB_WIN_FORM IOBConfFull.SaveYaml(fullPath.Replace("iob", "yaml")); // carico IOB - loadIobType(); + _ = loadIobType(); // invio file di conf! IOBConfFull.SendConfYaml(iobObj.urlSaveConfYaml); @@ -1859,7 +1935,7 @@ namespace IOB_WIN_FORM } var appVers = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; - loadIobType(); + _ = loadIobType(); // avvio macchina con adapter specificato... if (utils.CRB("autoStartOnLoad")) { @@ -1922,7 +1998,6 @@ namespace IOB_WIN_FORM _lastState = this.WindowState; } - private FormWindowState _lastState = FormWindowState.Minimized; /// /// Mostrata form /// @@ -2117,6 +2192,25 @@ namespace IOB_WIN_FORM } } + /// + /// Indica se debba aggiornare o meno la UI perché minimizzata o meno + /// + /// + private bool ShouldUpdateUI() + { + // 1. Controlla se la form stessa è minimizzata + if (this.WindowState == FormWindowState.Minimized) return false; + + // 2. Se è una MDI Child, controlla se il Padre è minimizzato + if (this.MdiParent != null && this.MdiParent.WindowState == FormWindowState.Minimized) + return false; + + // 3. (Opzionale) Controlla se la form è visibile + if (!this.Visible) return false; + + return true; + } + private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e) { } @@ -2139,6 +2233,24 @@ namespace IOB_WIN_FORM /// private void StartWorker() { + + // Versione 1 +#if false + _isSuspended = false; + _cts = new CancellationTokenSource(); + + // Usiamo TaskCreationOptions.LongRunning + // Questo suggerisce a .NET di creare un thread dedicato invece di usare il pool + _workerTask = Task.Factory.StartNew( + () => WorkerLoopAsync(_cts.Token), + _cts.Token, + TaskCreationOptions.LongRunning, + TaskScheduler.Default).Unwrap(); // Unwrap è necessario perché WorkerLoopAsync è Task + +#endif + + // versone 2 +#if false // rimozione isSuspended _isSuspended = false; // per prima cosa disattivo il disabled... @@ -2153,10 +2265,170 @@ namespace IOB_WIN_FORM _cts = new CancellationTokenSource(); // Assegniamo il Task alla variabile per poterne monitorare lo stato + if (doExeSingleThread) + { + // Facciamo partire tutto il loop sul thread dedicato fin dall'inizio + _workerTask = _singleFactory.StartNew( + () => WorkerLoopAsync(_cts.Token), + _cts.Token + ).Unwrap(); + } + else + { + _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); + } + } +#endif + + // versione 3 +#if false + if (doExeSingleThread) + { + _isSuspended = false; + _cts = new CancellationTokenSource(); + + // Creiamo un thread VERO, uno solo, che non morirà mai + Thread dedicatedThread = new Thread(() => + { + lgInfo($"[THREAD FISSO: {Thread.CurrentThread.ManagedThreadId}] Avviato"); + + while (!_cts.Token.IsCancellationRequested) + { + try + { + if (!_isSuspended) + { + // Eseguiamo il task asincrono in modo "bloccante" per questo thread + // Ma essendo un thread separato, la UI non se ne accorge! + DoExecTasksAsync().GetAwaiter().GetResult(); + } + + // Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF + int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; + if (_errorDelay > 0) currentDelay += _errorDelay; + + // Dorme senza rilasciare il thread al pool + Thread.Sleep(currentDelay); + + // Logga l'ID: vedrai che ora torna sempre lo stesso! + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Fine ciclo delay"); + } + catch (Exception ex) + { + lgError("Errore nel loop: " + ex.Message); + } + } + }); + + dedicatedThread.IsBackground = true; + dedicatedThread.Name = "SingleDedicatedThread"; + dedicatedThread.Start(); + } + else + { + // rimozione isSuspended + _isSuspended = false; + // per prima cosa disattivo il disabled... + lock (_lock) + { + // 1. Controllo se è già in esecuzione + if (_workerTask != null && !_workerTask.IsCompleted) + { + lgTrace("Worker già in esecuzione. Richiesta ignorata."); + return; + } + + _cts = new CancellationTokenSource(); + _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); + } + } +#endif + + // versione 4 + + if (doExeSingleThread) + { + lock (_threadLock) + { + // 1. Ferma eventuali worker precedenti + if (_cts != null) + { + _cts.Cancel(); + } + + // 2. Attendi che il thread precedente sia effettivamente uscito + if (_dedicatedThread != null && _dedicatedThread.IsAlive) + { + // Timeout breve per non freezare la UI, ma necessario per pulizia + _dedicatedThread.Join(500); + } + + _isSuspended = false; + _cts = new CancellationTokenSource(); + + // 3. Crea un thread NUOVO e DEDICATO + _dedicatedThread = new Thread(() => WorkerLoopSingolo(_cts.Token)) + { + IsBackground = true, + Name = "Single_Dedicated_Thread", + Priority = ThreadPriority.AboveNormal // Opzionale: dà precedenza ai dati PLC + }; + + _dedicatedThread.Start(); + lgInfo("--- WORKER FISICO AVVIATO ---"); + } + } +#if false + else + { + // rimozione isSuspended + _isSuspended = false; + // per prima cosa disattivo il disabled... + lock (_lock) + { + // 1. Controllo se è già in esecuzione + if (_workerTask != null && !_workerTask.IsCompleted) + { + lgTrace("Worker già in esecuzione. Richiesta ignorata."); + return; + } + + _cts = new CancellationTokenSource(); + _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); + } + } +#endif + + // avvio altro thread comunque... + // rimozione isSuspended + _isSuspended = false; + // per prima cosa disattivo il disabled... + lock (_lock) + { + // 1. Controllo se è già in esecuzione + if (_workerTask != null && !_workerTask.IsCompleted) + { + lgTrace("Worker già in esecuzione. Richiesta ignorata."); + return; + } + + _cts = new CancellationTokenSource(); _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); } + + } + + private Thread _dedicatedThread; + private readonly object _threadLock = new object(); + private bool _isProcessing = false; // Flag di sicurezza aggiuntivo + + +#if false + private BlockingCollection _workQueue = new BlockingCollection(); +#endif + /// /// fermata dell'adapter /// @@ -2170,8 +2442,6 @@ namespace IOB_WIN_FORM lgInfo("UNLOAD Adapter"); } - private bool stopForced = false; - // Metodo per fermare tutto (es. nel Form_Closing o Dispose) private async Task StopWorker() { @@ -2224,8 +2494,58 @@ namespace IOB_WIN_FORM checkAssignSize(); } - private async Task WorkerLoopAsync(CancellationToken ct) +#if false + /// + /// Loop di gestione worker + /// + /// + private void WorkerLoopDestined(CancellationToken ct) { + try + { + lgInfo($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Loop dedicato AVVIATO"); + + while (!ct.IsCancellationRequested) + { + if (!_isSuspended) + { + try + { + // Eseguiamo il task asincrono e ASPETTIAMO che finisca + // rimanendo su questo thread. + DoExecTasksAsync().GetAwaiter().GetResult(); + + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Task completato"); + } + catch (Exception ex) + { + lgError("Errore durante i task: " + ex.Message); + } + } + + int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; + if (_errorDelay > 0) currentDelay += _errorDelay; + + // Invece di await Task.Delay, usiamo il WaitHandle del CancellationToken + // Questo blocca il thread attuale per X millisecondi o finché non viene cancellato + ct.WaitHandle.WaitOne(currentDelay); + } + } + catch (Exception ex) + { + lgError("Errore fatale nel thread dedicato: " + ex.Message); + } + } +#endif + /// + /// Loop di gestione worker singolo + /// + /// + private void WorkerLoopSingolo(CancellationToken ct) + { + int threadId = Thread.CurrentThread.ManagedThreadId; + lgInfo($"[Thread {threadId}] Loop fisico entrato in esecuzione."); + try { while (!ct.IsCancellationRequested) @@ -2234,7 +2554,75 @@ namespace IOB_WIN_FORM { try { - await DoExecTasks(); + // Eseguiamo il lavoro asincrono in modo SINCRONO su questo thread. + // .GetAwaiter().GetResult() blocca questo thread finché il task non è finito. + DoExecTasksAsync(true).GetAwaiter().GetResult(); + } + catch (Exception ex) + { + lgError($"Errore nel task: {ex.Message}"); + } + } + else + { + // se non fosse stop richiesto da utente... + if (!stopForced) + { + DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec / 2); + // verifica scadenza controllo SE fosse offline x eseguire test riconnessione... + if (iobObj.adpTryRestart && (DateTime.Now > dtVeto) && iobObj.adpRunning && !iobObj.connectionOk) + { + lgTrace($"WorkerLoopAsync sospeso: tentativo riavvio periodico"); + lastStartTry = DateTime.Now; + iobObj.tryConnect(); + } + else + { + lgTrace($"WorkerLoopAsync sospeso: NON esegue task specifici | _isSuspended: {_isSuspended}"); + } + } + } + + // Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF + int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; + + // ATTESA: Non rilascia il thread al pool di .NET + // Si interrompe subito se ct viene cancellato + bool cancelled = ct.WaitHandle.WaitOne(currentDelay); + if (cancelled) break; + } + } + catch (Exception ex) + { + lgError($"FATAL: Il thread dedicato {threadId} è morto: {ex.Message}"); + } + finally + { + lgInfo($"[Thread {threadId}] Loop fisico terminato."); + } + } + + /// + /// Loop di gestione worker + /// + /// + /// + private async Task WorkerLoopAsync(CancellationToken ct) + { + try + { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Inizio loop"); + + while (!ct.IsCancellationRequested) + { + if (!_isSuspended) + { + try + { + int threadIdInt = Thread.CurrentThread.ManagedThreadId; + await DoExecTasksAsync(false); + lgTrace($"[Thread: {threadIdInt}] factory exec DoExecTasksAsync"); + } catch (Exception ex) { @@ -2261,13 +2649,24 @@ namespace IOB_WIN_FORM } } - // Calcolo del delay dinamico da Timers.MsVHF + // Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; - // Il Task.Delay accetta il CancellationToken. - // Se chiudi l'app mentre sta "dormendo", l'attesa si interrompe IMMEDIATAMENTE - // senza dover aspettare la fine del timer, velocizzando la chiusura. + // se ho error delay --> log! + if (_errorDelay > 0) + { + currentDelay += _errorDelay; + lgTrace($"WorkerLoopAsync | delay: {currentDelay}"); + } + + // 4. ATTESA INTERROMPIBILE + // Il delay asincrono permette al sistema di riutilizzare il thread se necessario, + // ma il 'while' garantisce che non inizieremo il prossimo DoExecTasksAsync prematuramente. + //await Task.Delay(currentDelay, ct).ConfigureAwait(false); await Task.Delay(currentDelay, ct); + + // Logga l'ID: vedrai che ora torna sempre lo stesso! + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Fine ciclo delay"); } lgInfo("Worker interrotto per cancellation token."); } diff --git a/IOB-WIN-FORM/Iob/Generic.Protected.cs b/IOB-WIN-FORM/Iob/Generic.Protected.cs index 77a8f2c9..96912234 100644 --- a/IOB-WIN-FORM/Iob/Generic.Protected.cs +++ b/IOB-WIN-FORM/Iob/Generic.Protected.cs @@ -1263,13 +1263,7 @@ namespace IOB_WIN_FORM.Iob return answ; } - /// - /// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile - /// - protected void checkVetoQueueIn() - { - queueInEnabCurr = dtVetoQueueIN < DateTime.Now; - } + /// /// Conversione string row in log generico @@ -3222,24 +3216,7 @@ namespace IOB_WIN_FORM.Iob url2call = $"{urlRemTask2ExeTav(codTav)}{taskName}"; } -#if false - - try - { - Task.Run(async () => answ = await utils.callUrlAsync(url2call)) - .GetAwaiter() - .GetResult(); - lgInfo($"Task2Exe.remTask2exe | {esitoTask} | chiamata URL {url2call} | answ: {answ}"); - } - catch (Exception ex) - { - lgError("ProcessAutoOdl | Crash nel ponte Sync/Async: " + ex.Message); - } -#endif await utils.callUrlAsync(url2call); -#if false - answ = utils.callUrlNow(url2call); -#endif } return answ; } diff --git a/IOB-WIN-FORM/Iob/Generic.Public.cs b/IOB-WIN-FORM/Iob/Generic.Public.cs index 300e7eeb..64619507 100644 --- a/IOB-WIN-FORM/Iob/Generic.Public.cs +++ b/IOB-WIN-FORM/Iob/Generic.Public.cs @@ -653,12 +653,6 @@ namespace IOB_WIN_FORM.Iob else { currentAnsw = await ExecuteIobCheckWithRetry(maxRetries: 5); -#if false - // 2. Chiamata asincrona con Retry (Ponte Sync/Async) - currentAnsw = Task.Run(async () => await ExecuteIobCheckWithRetry(maxRetries: 5)) - .GetAwaiter() - .GetResult(); -#endif } // 3. Gestione Stato e Logica UI @@ -698,14 +692,6 @@ namespace IOB_WIN_FORM.Iob if (dtVetoPing >= DateTime.Now) return MPOnline; if (DemoOut) return true; -#if false - // 2. Eseguo la parte asincrona forzando l'attesa - // Usiamo Task.Run per far girare il codice asincrono su un thread del pool - // evitando deadlock con il thread della UI - bool isAlive = Task.Run(async () => await ExecuteApiCheckWithRetryAsync(maxRetries: 7)) - .GetAwaiter() - .GetResult(); -#endif bool isAlive = await ExecuteApiCheckWithRetryAsync(maxRetries: 7); @@ -715,6 +701,14 @@ namespace IOB_WIN_FORM.Iob return isAlive; } + /// + /// Verifica veto coda QueueIN ed aggiorna abilitazione su variabile + /// + public void checkVetoQueueIn() + { + queueInEnabCurr = dtVetoQueueIN < DateTime.Now; + } + /// /// Update visualizzaizone BIT in ingresso Parametri da /// aggiornare x display in form @@ -1248,22 +1242,28 @@ namespace IOB_WIN_FORM.Iob } /// - /// effettua recupero dati ed invio valori modificati... + /// Effettua recupero dati ed invio valori modificati... /// - /// - public async Task getAndSendAsync(gatherCycle ciclo) + /// Ciclo di freq richiesta + /// Indica se siano abilitate operazioni Pool (Thread Safe) + /// Indica se siano abilitate le operazioni Single Thread + /// + public async Task getAndSendAsync(gatherCycle ciclo, bool enabPool, bool enabSTID) { // init obj display newDisplayData currDispData = new newDisplayData(); - // IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!! - try + if (enabPool) { - await TrySendValuesAsync(); - } - catch (Exception exc) - { - lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria"); - currDispData.semOut = Semaforo.SR; + // IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!! + try + { + await TrySendValuesAsync(); + } + catch (Exception exc) + { + lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria"); + currDispData.semOut = Semaforo.SR; + } } // controllo connessione/connettività if (connectionOk) @@ -1293,81 +1293,105 @@ namespace IOB_WIN_FORM.Iob bool showDebugData = false; if (ciclo == gatherCycle.VHF) { - processVHF(); + if (enabPool) + { + processVHF(); + } } // processing dati memoria (lettura, filtraggio, enqueque) else if (ciclo == gatherCycle.HF) { - processWhatchDog(); - processAllMemory(); + if (enabSTID) + { + processWhatchDog(); + processAllMemory(); + } } else if (ciclo == gatherCycle.MF) { - processMode(); - await processServerRequests(); - processCustomTaskMF(); - processOverride(); - processContapezzi(); - processCncAlarms(); - processDynData(); - processMem2Write(); - processAllMemory(); + if (enabSTID) + { + processMode(); + await processServerRequests(); + processCustomTaskMF(); + processOverride(); + processContapezzi(); + processCncAlarms(); + processDynData(); + processMem2Write(); + processAllMemory(); + } } else if (ciclo == gatherCycle.LF) { - processCustomTaskLF(); - await processOtherCounters(); - processProgram(); - // verifico se devo gestire cambio ODL in modo automatico - await ProcessAutoOdlAsync(); - // verifico se devo gestire auto generazione dossier quotidiana - ProcessAutoDossier(); - // effettua gestione import file se configurato... - await ProcessFileImportAsync(); - // effettua process ritorno ricette - await ProcessRecipeFileRetAsync(); + if (enabSTID) + { + processCustomTaskLF(); + await processOtherCounters(); + processProgram(); + } + if (enabPool) + { + // verifico se devo gestire cambio ODL in modo automatico + await ProcessAutoOdlAsync(); + // verifico se devo gestire auto generazione dossier quotidiana + ProcessAutoDossier(); + // effettua gestione import file se configurato... + await ProcessFileImportAsync(); + // effettua process ritorno ricette + await ProcessRecipeFileRetAsync(); + } } else if (ciclo == gatherCycle.VLF) { - if (utils.CRB("enableContapezzi")) + if (enabPool) { - // rilettura contapezzi da server... - lgTrace("Ciclo MsVLF: pzCntReload(true)"); - if (!isMulti) + if (utils.CRB("enableContapezzi")) { - pzCntReload(true); + // rilettura contapezzi da server... + lgTrace("Ciclo MsVLF: pzCntReload(true)"); + if (!isMulti) + { + pzCntReload(true); + } + // refresh associazione Macchina - IOB + await SendM2IobAsync(); + // invio altri dati accessori... + await SendMachineConfAsync(); } - // refresh associazione Macchina - IOB - await SendM2IobAsync(); - // invio altri dati accessori... - await SendMachineConfAsync(); - } - // recupero dati SETUP (sysinfo) e li invio/mostro se variati... - processSysInfo(); - // checkLogDir x shrink! - checkShrinkDir(); - // eventuale log! - if (utils.CRB("recTime")) - { - try + // checkLogDir x shrink! + checkShrinkDir(); + // eventuale log! + if (utils.CRB("recTime")) { - logTimeResults(); + try + { + logTimeResults(); + } + catch + { } } - catch - { } + processRecipeSyncArch(); } - processRecipeSyncArch(); - if (enableSlowData) + if (enabSTID) { - processSlowDataRead(); + // recupero dati SETUP (sysinfo) e li invio/mostro se variati... + processSysInfo(); + if (enableSlowData) + { + processSlowDataRead(); + } } } - // mostra eventuali altri dati di processo... - reportDataProc(); - if (showDebugData) + if (enabPool) { - // verifica se debba salvare e mostrare dati - checkSavePersDataLayer(); + // mostra eventuali altri dati di processo... + reportDataProc(); + if (showDebugData) + { + // verifica se debba salvare e mostrare dati + checkSavePersDataLayer(); + } } } catch (Exception exc) @@ -1402,21 +1426,24 @@ namespace IOB_WIN_FORM.Iob } else { - // anche se NON connesso alcuni task di bassa freq li eseguo... - if (ciclo == gatherCycle.LF) + if (enabPool) { - // verifico se devo gestire cambio ODL in modo automatico - await ProcessAutoOdlAsync(); - // verifico se devo gestire auto generazione dossier quotidiana - ProcessAutoDossier(); - // effettua gestione import file se configurato... - await ProcessFileImportAsync(); - // effettua process ritorno ricette - await ProcessRecipeFileRetAsync(); - } - else if (ciclo == gatherCycle.VLF) - { - processRecipeSyncArch(); + // anche se NON connesso alcuni task di bassa freq li eseguo... + if (ciclo == gatherCycle.LF) + { + // verifico se devo gestire cambio ODL in modo automatico + await ProcessAutoOdlAsync(); + // verifico se devo gestire auto generazione dossier quotidiana + ProcessAutoDossier(); + // effettua gestione import file se configurato... + await ProcessFileImportAsync(); + // effettua process ritorno ricette + await ProcessRecipeFileRetAsync(); + } + else if (ciclo == gatherCycle.VLF) + { + processRecipeSyncArch(); + } } // provo a riconnettere SE abilitato tryRestart... From cc4dbeb79c0678163e991c7673d8337d4026f055 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 18 Feb 2026 10:03:57 +0100 Subject: [PATCH 4/7] FANUC: - spostamento in 2 WorkerLoop (mono Thread e PooledThred - test 3029 Jetco OK --- IOB-UT-NEXT/Config/Base/IobDto.cs | 4 +- IOB-UT-NEXT/Config/IobConfTree.cs | 4 +- IOB-UT-NEXT/IOB-UT-NEXT.csproj | 1 + IOB-UT-NEXT/TimerMan.cs | 64 +++++ IOB-UT-NEXT/baseUtils.cs | 2 +- IOB-WIN-FANUC/DATA/CONF/3028.ini | 2 +- IOB-WIN-FANUC/DATA/CONF/3029.ini | 2 +- IOB-WIN-FANUC/Iob/Fanuc.cs | 2 +- IOB-WIN-FORM/AdapterForm.cs | 374 ++++++++++++++++++++++++++--- IOB-WIN-FORM/Iob/Generic.Public.cs | 352 ++++++++++++++++++++++++++- 10 files changed, 759 insertions(+), 48 deletions(-) create mode 100644 IOB-UT-NEXT/TimerMan.cs diff --git a/IOB-UT-NEXT/Config/Base/IobDto.cs b/IOB-UT-NEXT/Config/Base/IobDto.cs index 7c9d4248..394e810b 100644 --- a/IOB-UT-NEXT/Config/Base/IobDto.cs +++ b/IOB-UT-NEXT/Config/Base/IobDto.cs @@ -108,9 +108,9 @@ namespace IOB_UT_NEXT.Config.Base public bool EnabRedisQue { get; set; } = true; /// - /// Forza esecuzione ExeTask (main loop) in singleThread + /// Forza esecuzione WorkLoopMachine (verso macchina PLC/CNC) in SingleThread (vs ThreadPool) /// - public bool ExeSingleThread { get; set; } = false; + public bool MachWLoopSingleThread { get; set; } = true; /// /// Versione software IOB diff --git a/IOB-UT-NEXT/Config/IobConfTree.cs b/IOB-UT-NEXT/Config/IobConfTree.cs index 3497fe50..7c50677b 100644 --- a/IOB-UT-NEXT/Config/IobConfTree.cs +++ b/IOB-UT-NEXT/Config/IobConfTree.cs @@ -125,7 +125,7 @@ namespace IOB_UT_NEXT.Config Customer = fIni.ReadString("TAGS", "Customer", "EgalWare"), EnabelPodlManFull = bool.Parse(fIni.ReadString("OPTPAR", "EnabelPodlManFull", "false")), EnabRedisQue = bool.Parse(fIni.ReadString("IOB", "EnableRedisQueue", "true")), - ExeSingleThread = bool.Parse(fIni.ReadString("OPTPAR", "ExeSingleThread", "false")), + MachWLoopSingleThread = bool.Parse(fIni.ReadString("OPTPAR", "MachSingleThread", "false")), MaxErroriCheck = fIni.ReadInteger("OPTPAR", "MAX_ERR_CHECK", maxErrCheck), MaxPingRetry = fIni.ReadInteger("OPTPAR", "MAX_PING_RETRY", 5), MinDeltaSec = fIni.ReadInteger("IOB", "MinDeltaSec", 6), @@ -141,7 +141,7 @@ namespace IOB_UT_NEXT.Config exclOptPar.Add("DELAY_READ_PZ_COUNT"); exclOptPar.Add("DISABLE_SEND_WDST"); exclOptPar.Add("EnabelPodlManFull"); - exclOptPar.Add("ExeSingleThread"); + exclOptPar.Add("MachSingleThread"); exclOptPar.Add("MAX_ERR_CHECK"); exclOptPar.Add("MAX_PING_RETRY"); exclOptPar.Add("SEND_ALARM_RESET"); diff --git a/IOB-UT-NEXT/IOB-UT-NEXT.csproj b/IOB-UT-NEXT/IOB-UT-NEXT.csproj index 89dbf82c..f1a1f4cc 100644 --- a/IOB-UT-NEXT/IOB-UT-NEXT.csproj +++ b/IOB-UT-NEXT/IOB-UT-NEXT.csproj @@ -202,6 +202,7 @@ + diff --git a/IOB-UT-NEXT/TimerMan.cs b/IOB-UT-NEXT/TimerMan.cs new file mode 100644 index 00000000..d03f3244 --- /dev/null +++ b/IOB-UT-NEXT/TimerMan.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IOB_UT_NEXT +{ + /// + /// Classe di gestione info Timer per esecuzione ordinata processi secondo priorità + /// + public class TimerMan + { + #region Public Constructors + + /// + /// Init classe timer + /// + /// + public TimerMan(ContextType contesto) + { + Contesto = contesto; + } + + #endregion Public Constructors + + #region Public Enums + + /// + /// Tipologia contesto timer + /// + public enum ContextType + { + Machine = 0, + Server + } + + #endregion Public Enums + + #region Public Properties + + /// + /// Contesto del timer + /// + public ContextType Contesto { get; private set; } = ContextType.Machine; + + /// + /// Contatore chiamate timers x debug timing + /// + public Dictionary CycleCount { get; set; } = new Dictionary(); + + /// + /// Contatore durata exec x debug timing + /// + public Dictionary CycleElaps { get; set; } = new Dictionary(); + + /// + /// Dizionario scadenza timers interni x esecuzione dei vari task a differente frequenza di chiamata + /// + public Dictionary Veto { get; set; } = new Dictionary(); + + #endregion Public Properties + } +} \ No newline at end of file diff --git a/IOB-UT-NEXT/baseUtils.cs b/IOB-UT-NEXT/baseUtils.cs index 6554e009..159da62a 100644 --- a/IOB-UT-NEXT/baseUtils.cs +++ b/IOB-UT-NEXT/baseUtils.cs @@ -405,7 +405,7 @@ namespace IOB_UT_NEXT clientPayload = new WebClientWT(); } clientPayload.Headers.Add("user-agent", $"{CRS("appName")}-PAYLOAD"); - clientPayload.Headers.Add("Content-Type", "application/json"); + clientPayload.Headers.Add("Content-ContextType", "application/json"); try { // problema certificati locali... diff --git a/IOB-WIN-FANUC/DATA/CONF/3028.ini b/IOB-WIN-FANUC/DATA/CONF/3028.ini index 97c3ebcb..9e6504a9 100644 --- a/IOB-WIN-FANUC/DATA/CONF/3028.ini +++ b/IOB-WIN-FANUC/DATA/CONF/3028.ini @@ -88,7 +88,7 @@ DISABLE_SEND_WDST=TRUE PARAM_CONF=3028.json ; test timing & errori timerIntMs=30 -ExeSingleThread=true +MachSingleThread=true MAX_ERR_CHECK=10 ;WAIT_REC_MSEC=15000 diff --git a/IOB-WIN-FANUC/DATA/CONF/3029.ini b/IOB-WIN-FANUC/DATA/CONF/3029.ini index 2ae80899..5441f022 100644 --- a/IOB-WIN-FANUC/DATA/CONF/3029.ini +++ b/IOB-WIN-FANUC/DATA/CONF/3029.ini @@ -90,7 +90,7 @@ MAX_SEND_PZC_BLOCK=100 DISABLE_SEND_WDST=TRUE ; test timing & errori timerIntMs=40 -;ExeSingleThread=true +;MachSingleThread=true MAX_ERR_CHECK=10 ; bit da scrivere come trace ad ogni check ;MEM_2_TRACE=|BIT3|BIT4|BIT5| diff --git a/IOB-WIN-FANUC/Iob/Fanuc.cs b/IOB-WIN-FANUC/Iob/Fanuc.cs index e0398487..91697079 100644 --- a/IOB-WIN-FANUC/Iob/Fanuc.cs +++ b/IOB-WIN-FANUC/Iob/Fanuc.cs @@ -60,7 +60,7 @@ namespace IOB_WIN_FANUC.Iob MemBlockY = new byte[ySize]; // forzo letture single threaded x FANUC!!! - IOBConfFull.General.ExeSingleThread = true; + IOBConfFull.General.MachWLoopSingleThread = true; // x FANUC riduco num errori std x forzare riavvio rapido maxErroriCheck = maxErroriCheck > 15 ? 15 : maxErroriCheck; diff --git a/IOB-WIN-FORM/AdapterForm.cs b/IOB-WIN-FORM/AdapterForm.cs index 74811dbc..9772f820 100644 --- a/IOB-WIN-FORM/AdapterForm.cs +++ b/IOB-WIN-FORM/AdapterForm.cs @@ -1,5 +1,6 @@ using IOB_UT_NEXT; using IOB_UT_NEXT.Config; +using IOB_UT_NEXT.Config.Base; using MapoSDK; using Newtonsoft.Json; using NLog; @@ -750,24 +751,37 @@ namespace IOB_WIN_FORM gather.Enabled = true; // 2026.02.17 controlo stato single thread exe... - doExeSingleThread = iobObj.IOBConfFull.General.ExeSingleThread; + doMachSingleThread = iobObj.IOBConfFull.General.MachWLoopSingleThread; #if true // gestione worker StartWorker(); #endif displayTaskAndLog("Start Timers", true); + // inizializzo le scadenze dei timers... + DateTime adesso = DateTime.Now; + +#if false TimersVeto = new Dictionary(); TimersCycleCount = new Dictionary(); - TimersCycleElaps = new Dictionary(); - DateTime adesso = DateTime.Now; + TimersCycleElaps = new Dictionary(); +#endif foreach (gatherCycle item in Enum.GetValues(typeof(gatherCycle))) { - // setup iniziale deterministico + // setup iniziale deterministico dei 2 obj gestione + TimerMachine.Veto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); + TimerMachine.CycleCount.Add(item, 0); + TimerMachine.CycleElaps.Add(item, 0); + + TimerServer.Veto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); + TimerServer.CycleCount.Add(item, 0); + TimerServer.CycleElaps.Add(item, 0); +#if false TimersVeto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); TimersCycleCount.Add(item, 0); - TimersCycleElaps.Add(item, 0); + TimersCycleElaps.Add(item, 0); +#endif } sendStartFLog = utils.CRB("sendStartFLog"); displayTaskAndLog("Adapter Running...", true); @@ -1342,10 +1356,11 @@ namespace IOB_WIN_FORM /// /// Booleana per determinare se eseguire le chiamate DoExecTasksAsync su single thread (es FANUC) o dinamico (default) /// - private bool doExeSingleThread = false; + private bool doMachSingleThread = false; private bool stopForced = false; +#if false /// /// Contatore chiamate timers x debug timing /// @@ -1359,7 +1374,18 @@ namespace IOB_WIN_FORM /// /// Dizionario scadenza timers interni x esecuzione dei vari task a differente frequenza di chiamata /// - private Dictionary TimersVeto = new Dictionary(); + private Dictionary TimersVeto = new Dictionary(); +#endif + + /// + /// Obj gestione Timer per esecuzione Task con Machine + /// + private TimerMan TimerMachine = new TimerMan(TimerMan.ContextType.Machine); + + /// + /// Obj gestione Timer per esecuzione Task con Server + /// + private TimerMan TimerServer = new TimerMan(TimerMan.ContextType.Server); /// /// Dizionario dei valori bloccati x evitare log eccessivo @@ -1552,6 +1578,78 @@ namespace IOB_WIN_FORM } } + /// + /// Verifica scadenza task nell'oggetto ricevuto e processing secondo ContextType + /// + /// Indica se siano abilitate operazioni Pool (Thread Safe) + /// Indica se siano abilitate le operazioni Single Thread + /// + private async Task checkScad(TimerMan timerObj) + { + DateTime adesso = DateTime.Now; + bool sendDone = false; + // ciclo su tutti i timers enum superiori a VHF + var priorities = (gatherCycle[])Enum.GetValues(typeof(gatherCycle)); + // prendo solo > VHF e ordino discendenti x dare priorità ai più rari... + var ordered = priorities.Where(x => x > gatherCycle.VHF).OrderByDescending(x => x); + foreach (gatherCycle item in ordered) + { + if (timerObj.Veto.ContainsKey(item)) + { + if (timerObj.Veto[item] <= adesso) + { + // se non ho già processato a questo giro... + if (!sendDone) + { + timerObj.CycleCount[item]++; + Stopwatch sw = Stopwatch.StartNew(); + + // Swith del treno di esecuzione Contesto (Timer) e chiamata di processing x frequenza ... + if (timerObj.Contesto == TimerMan.ContextType.Machine) + { + await iobObj.doMachineTaskAsync(item); + } + else + { + await iobObj.doServerTaskAsync(item); + } + +#if false + await iobObj.getAndSendAsync(item, enabPool, enabSTID); +#endif + + sw.Stop(); + sendDone = true; + // metto una piccola attesa se ho altre scadenze + timerObj.CycleElaps[item] += sw.Elapsed.TotalMilliseconds; + // metto nuova scadenza perturbata 10% + timerObj.Veto[item] = adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0.1)); + } + // log + reset se ho contato ALMENO 100 exec o il tempo totale supera 1000msec + int limMs = 10000; + double totalMs = timerObj.CycleElaps.ContainsKey(item) ? timerObj.CycleElaps[item] : 0; + if (timerObj.CycleCount[item] >= 100 || totalMs > limMs) + { + int nCount = Math.Max(timerObj.CycleCount[item], 1); + lgDebug($"Timer.{timerObj.Contesto} | checkScad | {item} | {totalMs / nCount:F1}ms x {nCount}"); + timerObj.CycleCount[item] = 0; + timerObj.CycleElaps[item] = 0; + } + } + } + else + { + timerObj.Veto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); + timerObj.CycleCount.Add(item, 0); + if (!timerObj.CycleElaps.ContainsKey(item)) + { + timerObj.CycleElaps.Add(item, 0); + } + } + } + } + +#if false /// /// Verifica scadenza task /// @@ -1607,7 +1705,8 @@ namespace IOB_WIN_FORM if (!TimersCycleElaps.ContainsKey(item)) TimersCycleElaps.Add(item, 0); } } - } + } +#endif private void ChkEdit_CheckedChanged(object sender, EventArgs e) { @@ -1637,6 +1736,185 @@ namespace IOB_WIN_FORM { } + + /// + /// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound + /// + /// Richiesta esecuzone su single thread (es FANUC) + /// + private async Task DoExecMachineTasksAsync(bool reqSingle) + { + // Attende il proprio turno. Se un task è già in corso, questo aspetta. + await _plcLock.WaitAsync(); + bool doLog = iobObj.periodicLog; + try + { + // eseguo cicli attivi SOLO se adapter è in EFFETTIVO running... + if (iobObj.adpRunning) + { + if (iobObj.connectionOk) + { + DateTime adesso = DateTime.Now; + // verifico non ci sia veto comunicazioni lettura... + if (iobObj.queueInEnabCurr) + { + // se richiesto faccio memory DUMP INIZIALE! + if (iobObj.doStartMemDump) + { + lgInfo("Inizio dump memoria"); + iobObj.saveMemDump(dumpType.STARTUP); + // fatto! non ripeto... + iobObj.doStartMemDump = false; + lgInfo("Finito dump memoria"); + } + // controllo se sia abilitato sampleDump della meoria (periodico) + if (iobObj.doSampleMemory) + { + checkSampleMem(); + } + + // MAIN: controllo TUTTE le scadenze Machine... + await checkScad(TimerMachine); + + // wait opzionale in coda + if (utils.CRI("waitEndCycle") > 0) + { + await Task.Delay(utils.CRI("waitEndCycle")); + } + } + else + { + lgTrace($"VETO DoExecMachineTasksAsync.queueInEnabCurr | veto attivo | {adesso:yyyy.MM.dd HH:mm:ss}"); + iobObj.checkVetoQueueIn(); + } + } + else + { + // qui attende meno... + DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec / 2); + if (iobObj.adpTryRestart && (DateTime.Now > dtVeto)) + { + if (doLog) + { + lgInfo($"Retry Time Elapsed ({waitRecMSec / 2} ms)--> tryConnect"); + } + lastStartTry = DateTime.Now; + iobObj.tryConnect(); + } + } + } +#if false + else + { + if (doLog) + { + lgInfo("Adapter stopped"); + } + // verifico SE debba tentare il riavvio, ovvero NON running ma adpTryRestart + // e non ho riprovato x oltre waitRecMSec + DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec); + if (iobObj.adpTryRestart && (DateTime.Now > dtVeto)) + { + lastStartTry = DateTime.Now; + AvviaAdapter(chkForceDequeue.Checked); + } + } +#endif + } + catch (Exception exc) + { + lgError($"Eccezione in fase di DoExecMachineTasksAsync: {Environment.NewLine}{exc}"); + } + finally + { + _plcLock.Release(); + } + } + + + /// + /// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound + /// + /// Richiesta esecuzone su single thread (es FANUC) + /// + private async Task DoExecServerTasksAsync(bool reqSingle) + { + // Attende il proprio turno. Se un task è già in corso, questo aspetta. + await _plcLock.WaitAsync(); + bool doLog = iobObj.periodicLog; + try + { + // check esecuzione SendTask (MsVHF) COMUNQUE... + await iobObj.doServerTaskAsync(gatherCycle.VHF); + // eseguo cicli attivi SOLO se adapter è in EFFETTIVO running... + if (iobObj.adpRunning) + { + if (iobObj.connectionOk) + { + DateTime adesso = DateTime.Now; + // verifico non ci sia veto comunicazioni lettura... + if (iobObj.queueInEnabCurr) + { + + // MAIN: controllo TUTTE le scadenze Server... + await checkScad(TimerServer); + + // wait opzionale in coda + if (utils.CRI("waitEndCycle") > 0) + { + await Task.Delay(utils.CRI("waitEndCycle")); + } + } + else + { + lgTrace($"VETO DoExecServerTasksAsync.queueInEnabCurr | veto attivo | {adesso:yyyy.MM.dd HH:mm:ss}"); + iobObj.checkVetoQueueIn(); + } + } +#if false + else + { + // qui attende meno... + DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec / 2); + if (iobObj.adpTryRestart && (DateTime.Now > dtVeto)) + { + if (doLog) + { + lgInfo($"Retry Time Elapsed ({waitRecMSec / 2} ms)--> tryConnect"); + } + lastStartTry = DateTime.Now; + iobObj.tryConnect(); + } + } +#endif + } + else + { + if (doLog) + { + lgInfo("Adapter stopped"); + } + // verifico SE debba tentare il riavvio, ovvero NON running ma adpTryRestart + // e non ho riprovato x oltre waitRecMSec + DateTime dtVeto = lastStartTry.AddMilliseconds(waitRecMSec); + if (iobObj.adpTryRestart && (DateTime.Now > dtVeto)) + { + lastStartTry = DateTime.Now; + AvviaAdapter(chkForceDequeue.Checked); + } + } + } + catch (Exception exc) + { + lgError($"Eccezione DoExecServerTasksAsync: {Environment.NewLine}{exc}"); + } + finally + { + _plcLock.Release(); + } + } + +#if false /// /// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound /// @@ -1650,7 +1928,7 @@ namespace IOB_WIN_FORM try { // imposto variabili x esecuzione separata task Single Thread / Pooled Thread - bool reqSTE = iobObj.IOBConfFull.General.ExeSingleThread; + bool reqSTE = iobObj.IOBConfFull.General.MachWLoopSingleThread; bool enabSTID = reqSTE ? reqSingle : true; bool enabPool = !reqSTE; @@ -1743,7 +2021,8 @@ namespace IOB_WIN_FORM { _plcLock.Release(); } - } + } +#endif /// /// Ferma tutti i componenti adapter + update buttons @@ -2265,7 +2544,7 @@ namespace IOB_WIN_FORM _cts = new CancellationTokenSource(); // Assegniamo il Task alla variabile per poterne monitorare lo stato - if (doExeSingleThread) + if (doMachSingleThread) { // Facciamo partire tutto il loop sul thread dedicato fin dall'inizio _workerTask = _singleFactory.StartNew( @@ -2282,7 +2561,7 @@ namespace IOB_WIN_FORM // versione 3 #if false - if (doExeSingleThread) + if (doMachSingleThread) { _isSuspended = false; _cts = new CancellationTokenSource(); @@ -2346,7 +2625,8 @@ namespace IOB_WIN_FORM // versione 4 - if (doExeSingleThread) + // FixMe ToDo !!! togliere o fare alternativa pooled + if (doMachSingleThread || true) { lock (_threadLock) { @@ -2357,24 +2637,24 @@ namespace IOB_WIN_FORM } // 2. Attendi che il thread precedente sia effettivamente uscito - if (_dedicatedThread != null && _dedicatedThread.IsAlive) + if (_machineThread != null && _machineThread.IsAlive) { // Timeout breve per non freezare la UI, ma necessario per pulizia - _dedicatedThread.Join(500); + _machineThread.Join(500); } _isSuspended = false; _cts = new CancellationTokenSource(); // 3. Crea un thread NUOVO e DEDICATO - _dedicatedThread = new Thread(() => WorkerLoopSingolo(_cts.Token)) + _machineThread = new Thread(() => WorkerLoopMachine(_cts.Token)) { IsBackground = true, Name = "Single_Dedicated_Thread", Priority = ThreadPriority.AboveNormal // Opzionale: dà precedenza ai dati PLC }; - _dedicatedThread.Start(); + _machineThread.Start(); lgInfo("--- WORKER FISICO AVVIATO ---"); } } @@ -2416,11 +2696,11 @@ namespace IOB_WIN_FORM _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); } - + } - private Thread _dedicatedThread; + private Thread _machineThread; private readonly object _threadLock = new object(); private bool _isProcessing = false; // Flag di sicurezza aggiuntivo @@ -2537,34 +2817,42 @@ namespace IOB_WIN_FORM } } #endif + + private int idxWLM = 0; + private int idxWLS = 0; + /// - /// Loop di gestione worker singolo + /// Loop di gestione worker comunicazione con la macchina (single threaded) /// /// - private void WorkerLoopSingolo(CancellationToken ct) + private void WorkerLoopMachine(CancellationToken ct) { - int threadId = Thread.CurrentThread.ManagedThreadId; - lgInfo($"[Thread {threadId}] Loop fisico entrato in esecuzione."); + lgInfo($"[Thread {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] inizio WorkerLoopMachine"); try { while (!ct.IsCancellationRequested) { + // incremento indice x mostrare esecuzione... + idxWLM++; + idxWLM = idxWLM > 999 ? 0 : idxWLM; + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine"); if (!_isSuspended) { try { // Eseguiamo il lavoro asincrono in modo SINCRONO su questo thread. // .GetAwaiter().GetResult() blocca questo thread finché il task non è finito. - DoExecTasksAsync(true).GetAwaiter().GetResult(); + DoExecMachineTasksAsync(true).GetAwaiter().GetResult(); } catch (Exception ex) { - lgError($"Errore nel task: {ex.Message}"); + lgError($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] Errore nel task: {ex.Message}"); } } else { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine suspended"); // se non fosse stop richiesto da utente... if (!stopForced) { @@ -2572,13 +2860,13 @@ namespace IOB_WIN_FORM // verifica scadenza controllo SE fosse offline x eseguire test riconnessione... if (iobObj.adpTryRestart && (DateTime.Now > dtVeto) && iobObj.adpRunning && !iobObj.connectionOk) { - lgTrace($"WorkerLoopAsync sospeso: tentativo riavvio periodico"); + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine sospeso: tentativo riavvio periodico"); lastStartTry = DateTime.Now; iobObj.tryConnect(); } else { - lgTrace($"WorkerLoopAsync sospeso: NON esegue task specifici | _isSuspended: {_isSuspended}"); + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine sospeso: NON esegue task specifici | _isSuspended: {_isSuspended}"); } } } @@ -2586,19 +2874,22 @@ namespace IOB_WIN_FORM // Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine | pre-delay"); // ATTESA: Non rilascia il thread al pool di .NET // Si interrompe subito se ct viene cancellato bool cancelled = ct.WaitHandle.WaitOne(currentDelay); if (cancelled) break; + + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine | End"); } } catch (Exception ex) { - lgError($"FATAL: Il thread dedicato {threadId} è morto: {ex.Message}"); + lgError($"FATAL: Il thread dedicato {Thread.CurrentThread.ManagedThreadId} ({idxWLM}) è terminato: {ex.Message}"); } finally { - lgInfo($"[Thread {threadId}] Loop fisico terminato."); + lgInfo($"[Thread {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine terminato."); } } @@ -2611,26 +2902,29 @@ namespace IOB_WIN_FORM { try { - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Inizio loop"); + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] Inizio WorkerLoopAsync"); while (!ct.IsCancellationRequested) { + // incremento indice x mostrare esecuzione... + idxWLS++; + idxWLS = idxWLS > 999 ? 0 : idxWLS; + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync"); if (!_isSuspended) { try { - int threadIdInt = Thread.CurrentThread.ManagedThreadId; - await DoExecTasksAsync(false); - lgTrace($"[Thread: {threadIdInt}] factory exec DoExecTasksAsync"); + await DoExecServerTasksAsync(false); } catch (Exception ex) { - lgError("Errore durante i task: " + ex.Message); + lgError($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] Errore durante i task: " + ex.Message); } } else { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync suspended"); // se non fosse stop richiesto da utente... if (!stopForced) { @@ -2656,24 +2950,26 @@ namespace IOB_WIN_FORM if (_errorDelay > 0) { currentDelay += _errorDelay; - lgTrace($"WorkerLoopAsync | delay: {currentDelay}"); + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | delay: {currentDelay}"); } + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | pre-delay"); + // 4. ATTESA INTERROMPIBILE // Il delay asincrono permette al sistema di riutilizzare il thread se necessario, // ma il 'while' garantisce che non inizieremo il prossimo DoExecTasksAsync prematuramente. //await Task.Delay(currentDelay, ct).ConfigureAwait(false); await Task.Delay(currentDelay, ct); - // Logga l'ID: vedrai che ora torna sempre lo stesso! - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Fine ciclo delay"); + // Logga l'ID Thread + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | End"); } - lgInfo("Worker interrotto per cancellation token."); + lgInfo("WorkerLoopAsync interrotto per cancellation token."); } catch (OperationCanceledException) { // Eccezione normale quando si preme Stop/Cancel - lgInfo("Worker interrotto correttamente."); + lgInfo("WorkerLoopAsync interrotto correttamente."); } } diff --git a/IOB-WIN-FORM/Iob/Generic.Public.cs b/IOB-WIN-FORM/Iob/Generic.Public.cs index 64619507..67af56d3 100644 --- a/IOB-WIN-FORM/Iob/Generic.Public.cs +++ b/IOB-WIN-FORM/Iob/Generic.Public.cs @@ -1241,6 +1241,355 @@ namespace IOB_WIN_FORM.Iob return valTransl; } + /// + /// Effettua i task di comunicazione IN/OUT con la macchina + /// + /// + /// + public async Task doMachineTaskAsync(gatherCycle ciclo) + { + // init obj display + newDisplayData currDispData = new newDisplayData(); + // controllo connessione/connettività + if (connectionOk) + { + // controllo non sia già in esecuzione... + if (!adpCommAct) + { + // provo ad avviare + try + { + // imposto flag adapter running.. + adpCommAct = true; + adpStartRun = DateTime.Now; + } + catch (Exception exc) + { + string errore = $"Adapter NOT STARTED!!!{Environment.NewLine}{exc}"; + adpCommAct = false; + adpStartRun = DateTime.Now; + currDispData.newLiveLogData = errore; + } + if (adpCommAct) + { + // try / catch generale altrimenti segno che è disconnesso... + try + { + if (ciclo == gatherCycle.VHF) + { + //processVHF(); + } + // processing dati memoria (lettura, filtraggio, enqueque) + else if (ciclo == gatherCycle.HF) + { + processWhatchDog(); + processAllMemory(); + } + else if (ciclo == gatherCycle.MF) + { + processMode(); + await processServerRequests(); + processCustomTaskMF(); + processOverride(); + processContapezzi(); + processCncAlarms(); + processDynData(); + processMem2Write(); + //processAllMemory(); + } + else if (ciclo == gatherCycle.LF) + { + processCustomTaskLF(); + await processOtherCounters(); + processProgram(); + //// verifico se devo gestire cambio ODL in modo automatico + //await ProcessAutoOdlAsync(); + //// verifico se devo gestire auto generazione dossier quotidiana + //ProcessAutoDossier(); + //// effettua gestione import file se configurato... + //await ProcessFileImportAsync(); + //// effettua process ritorno ricette + //await ProcessRecipeFileRetAsync(); + } + else if (ciclo == gatherCycle.VLF) + { + //if (utils.CRB("enableContapezzi")) + //{ + // // rilettura contapezzi da server... + // lgTrace("Ciclo MsVLF: pzCntReload(true)"); + // if (!isMulti) + // { + // pzCntReload(true); + // } + // // refresh associazione Macchina - IOB + // await SendM2IobAsync(); + // // invio altri dati accessori... + // await SendMachineConfAsync(); + //} + //// checkLogDir x shrink! + //checkShrinkDir(); + //// eventuale log! + //if (utils.CRB("recTime")) + //{ + // try + // { + // logTimeResults(); + // } + // catch + // { } + //} + //processRecipeSyncArch(); + // recupero dati SETUP (sysinfo) e li invio/mostro se variati... + processSysInfo(); + if (enableSlowData) + { + processSlowDataRead(); + } + } + + //// mostra eventuali altri dati di processo... + //reportDataProc(); + //if (showDebugData) + //{ + // // verifica se debba salvare e mostrare dati + // checkSavePersDataLayer(); + //} + } + catch (Exception exc) + { + // segnalo eccezione e indico disconnesso... + lgError($"Exception doMachineTaskAsync | {ciclo} | fermo adapter{Environment.NewLine}{exc}"); + parentForm.fermaAdapter(true, false, true); + } + // tolgo flag running + adpCommAct = false; + } + else + { + if (periodicLog) + { + lgDebug("ADP not running..."); + } + } + } + else + { + // log ADP running + lgError("Non eseguo chiamata: ADP ancora in running"); + // se è bloccato da oltre maxSec lo sblocco... + if (DateTime.Now.Subtract(adpStartRun).TotalSeconds > utils.CRI("maxAdapterLockSec")) + { + // tolgo flag running + adpCommAct = false; + adpStartRun = DateTime.Now; + } + } + } + else + { + //// anche se NON connesso alcuni task di bassa freq li eseguo... + //if (ciclo == gatherCycle.LF) + //{ + // // verifico se devo gestire cambio ODL in modo automatico + // await ProcessAutoOdlAsync(); + // // verifico se devo gestire auto generazione dossier quotidiana + // ProcessAutoDossier(); + // // effettua gestione import file se configurato... + // await ProcessFileImportAsync(); + // // effettua process ritorno ricette + // await ProcessRecipeFileRetAsync(); + //} + //else if (ciclo == gatherCycle.VLF) + //{ + // processRecipeSyncArch(); + //} + + // provo a riconnettere SE abilitato tryRestart... + if (adpTryRestart && !connectionOk) + { + // controllo se sia scaduto periodi di veto al tryConnect... + int waitRecMSec = utils.CRI("waitRecMSec"); + // cerco se ci sia un valore in ovverride x il singolo IOB... + if (IOBConfFull.General.WaitRecMsec > 0) + { + waitRecMSec = IOBConfFull.General.WaitRecMsec; + } + DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); + if (DateTime.Now > dtVeto) + { + lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); + lastConnectTry = DateTime.Now; + tryConnect(); + } + } + currDispData.semIn = Semaforo.SR; + processDisconnectedTask(); + processMemoryDiscon(); + } + raiseRefresh(currDispData); + } + + + /// + /// Effettua i task di comunicazione IN/OUT con il server MAPO + /// + /// + /// + public async Task doServerTaskAsync(gatherCycle ciclo) + { + // init obj display + newDisplayData currDispData = new newDisplayData(); + // IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!! + try + { + await TrySendValuesAsync(); + } + catch (Exception exc) + { + lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria"); + currDispData.semOut = Semaforo.SR; + } + // controllo connessione/connettività + if (connectionOk) + { + // try / catch generale altrimenti segno che è disconnesso... + try + { + bool showDebugData = false; + if (ciclo == gatherCycle.VHF) + { + + processVHF(); + } + // processing dati memoria (lettura, filtraggio, enqueque) + else if (ciclo == gatherCycle.HF) + { + //processWhatchDog(); + //processAllMemory(); + } + else if (ciclo == gatherCycle.MF) + { + //processMode(); + //await processServerRequests(); + //processCustomTaskMF(); + //processOverride(); + //processContapezzi(); + //processCncAlarms(); + //processDynData(); + //processMem2Write(); + //processAllMemory(); + } + else if (ciclo == gatherCycle.LF) + { + //processCustomTaskLF(); + //await processOtherCounters(); + //processProgram(); + + // verifico se devo gestire cambio ODL in modo automatico + await ProcessAutoOdlAsync(); + // verifico se devo gestire auto generazione dossier quotidiana + ProcessAutoDossier(); + // effettua gestione import file se configurato... + await ProcessFileImportAsync(); + // effettua process ritorno ricette + await ProcessRecipeFileRetAsync(); + } + else if (ciclo == gatherCycle.VLF) + { + if (utils.CRB("enableContapezzi")) + { + // rilettura contapezzi da server... + lgTrace("Ciclo MsVLF: pzCntReload(true)"); + if (!isMulti) + { + pzCntReload(true); + } + // refresh associazione Macchina - IOB + await SendM2IobAsync(); + // invio altri dati accessori... + await SendMachineConfAsync(); + } + // checkLogDir x shrink! + checkShrinkDir(); + // eventuale log! + if (utils.CRB("recTime")) + { + try + { + logTimeResults(); + } + catch + { } + } + processRecipeSyncArch(); + + //// recupero dati SETUP (sysinfo) e li invio/mostro se variati... + //processSysInfo(); + //if (enableSlowData) + //{ + // processSlowDataRead(); + //} + } + + // mostra eventuali altri dati di processo... + reportDataProc(); + if (showDebugData) + { + // verifica se debba salvare e mostrare dati + checkSavePersDataLayer(); + } + } + catch (Exception exc) + { + // segnalo eccezione e indico disconnesso... + lgError($"Exception | doServerTaskAsync | {ciclo}{Environment.NewLine}{exc}"); + } + } + else + { + // anche se NON connesso alcuni task di bassa freq li eseguo... + if (ciclo == gatherCycle.LF) + { + // verifico se devo gestire cambio ODL in modo automatico + await ProcessAutoOdlAsync(); + // verifico se devo gestire auto generazione dossier quotidiana + ProcessAutoDossier(); + // effettua gestione import file se configurato... + await ProcessFileImportAsync(); + // effettua process ritorno ricette + await ProcessRecipeFileRetAsync(); + } + else if (ciclo == gatherCycle.VLF) + { + processRecipeSyncArch(); + } + + //// provo a riconnettere SE abilitato tryRestart... + //if (adpTryRestart && !connectionOk) + //{ + // // controllo se sia scaduto periodi di veto al tryConnect... + // int waitRecMSec = utils.CRI("waitRecMSec"); + // // cerco se ci sia un valore in ovverride x il singolo IOB... + // if (IOBConfFull.General.WaitRecMsec > 0) + // { + // waitRecMSec = IOBConfFull.General.WaitRecMsec; + // } + // DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); + // if (DateTime.Now > dtVeto) + // { + // lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); + // lastConnectTry = DateTime.Now; + // tryConnect(); + // } + //} + //currDispData.semIn = Semaforo.SR; + //processDisconnectedTask(); + //processMemoryDiscon(); + } + raiseRefresh(currDispData); + } + +#if false /// /// Effettua recupero dati ed invio valori modificati... /// @@ -1469,7 +1818,8 @@ namespace IOB_WIN_FORM.Iob processMemoryDiscon(); } raiseRefresh(currDispData); - } + } +#endif /// /// Effettua conversione da valore bitmap ai valori configurati From 6c3807b4969df3295020af569f9fad27575a620e Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 18 Feb 2026 10:49:10 +0100 Subject: [PATCH 5/7] Verifica errori contapezzi --- IOB-WIN-FANUC/App.config | 2 +- IOB-WIN-FANUC/DATA/CONF/3029.ini | 2 +- IOB-WIN-FORM/AdapterForm.cs | 139 +++++++++++++------------------ 3 files changed, 61 insertions(+), 82 deletions(-) diff --git a/IOB-WIN-FANUC/App.config b/IOB-WIN-FANUC/App.config index 298795a2..83967707 100644 --- a/IOB-WIN-FANUC/App.config +++ b/IOB-WIN-FANUC/App.config @@ -25,7 +25,7 @@ - + diff --git a/IOB-WIN-FANUC/DATA/CONF/3029.ini b/IOB-WIN-FANUC/DATA/CONF/3029.ini index 5441f022..e144567e 100644 --- a/IOB-WIN-FANUC/DATA/CONF/3029.ini +++ b/IOB-WIN-FANUC/DATA/CONF/3029.ini @@ -89,7 +89,7 @@ MIN_SEND_PZC_BLOCK=0 MAX_SEND_PZC_BLOCK=100 DISABLE_SEND_WDST=TRUE ; test timing & errori -timerIntMs=40 +;timerIntMs=40 ;MachSingleThread=true MAX_ERR_CHECK=10 ; bit da scrivere come trace ad ogni check diff --git a/IOB-WIN-FORM/AdapterForm.cs b/IOB-WIN-FORM/AdapterForm.cs index 9772f820..5335df60 100644 --- a/IOB-WIN-FORM/AdapterForm.cs +++ b/IOB-WIN-FORM/AdapterForm.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Eventing.Reader; using System.Drawing; using System.IO; using System.Linq; @@ -750,9 +751,6 @@ namespace IOB_WIN_FORM // 2026.01.02 fix timers al restart gather.Enabled = true; - // 2026.02.17 controlo stato single thread exe... - doMachSingleThread = iobObj.IOBConfFull.General.MachWLoopSingleThread; - #if true // gestione worker StartWorker(); @@ -1339,7 +1337,15 @@ namespace IOB_WIN_FORM /// private readonly SemaphoreSlim _plcLock = new SemaphoreSlim(1, 1); - private CancellationTokenSource _cts; + /// + /// Cancellation token x processi verso macchina + /// + private CancellationTokenSource _ctsMachine; + + /// + /// Cancellation tocken x processi verso server + /// + private CancellationTokenSource _ctsServer; /// /// Ritardo per errori (extra nei cicli) @@ -1353,11 +1359,6 @@ namespace IOB_WIN_FORM private Task _workerTask; - /// - /// Booleana per determinare se eseguire le chiamate DoExecTasksAsync su single thread (es FANUC) o dinamico (default) - /// - private bool doMachSingleThread = false; - private bool stopForced = false; #if false @@ -1740,9 +1741,8 @@ namespace IOB_WIN_FORM /// /// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound /// - /// Richiesta esecuzone su single thread (es FANUC) /// - private async Task DoExecMachineTasksAsync(bool reqSingle) + private async Task DoExecMachineTasksAsync() { // Attende il proprio turno. Se un task è già in corso, questo aspetta. await _plcLock.WaitAsync(); @@ -2625,66 +2625,43 @@ namespace IOB_WIN_FORM // versione 4 - // FixMe ToDo !!! togliere o fare alternativa pooled - if (doMachSingleThread || true) - { - lock (_threadLock) - { - // 1. Ferma eventuali worker precedenti - if (_cts != null) - { - _cts.Cancel(); - } - - // 2. Attendi che il thread precedente sia effettivamente uscito - if (_machineThread != null && _machineThread.IsAlive) - { - // Timeout breve per non freezare la UI, ma necessario per pulizia - _machineThread.Join(500); - } - - _isSuspended = false; - _cts = new CancellationTokenSource(); - - // 3. Crea un thread NUOVO e DEDICATO - _machineThread = new Thread(() => WorkerLoopMachine(_cts.Token)) - { - IsBackground = true, - Name = "Single_Dedicated_Thread", - Priority = ThreadPriority.AboveNormal // Opzionale: dà precedenza ai dati PLC - }; - - _machineThread.Start(); - lgInfo("--- WORKER FISICO AVVIATO ---"); - } - } -#if false - else + //// FixMe ToDo !!! togliere o fare alternativa pooled + //if (iobObj.IOBConfFull.General.MachWLoopSingleThread || true) + //{ + //} + lock (_threadLock) { // rimozione isSuspended _isSuspended = false; - // per prima cosa disattivo il disabled... - lock (_lock) + + // 1. Ferma eventuali worker precedenti + if (_ctsMachine != null) { - // 1. Controllo se è già in esecuzione - if (_workerTask != null && !_workerTask.IsCompleted) - { - lgTrace("Worker già in esecuzione. Richiesta ignorata."); - return; - } - - _cts = new CancellationTokenSource(); - _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); + _ctsMachine.Cancel(); + _ctsMachine.Dispose(); // Libera le risorse del vecchio token } - } -#endif - // avvio altro thread comunque... - // rimozione isSuspended - _isSuspended = false; - // per prima cosa disattivo il disabled... - lock (_lock) - { + // 2. Attendi che il thread precedente sia effettivamente uscito + if (_machineThread != null && _machineThread.IsAlive) + { + // Timeout breve per non freezare la UI, ma necessario per pulizia + _machineThread.Join(1000); + } + + _ctsMachine = new CancellationTokenSource(); + + // 3. Crea un thread NUOVO e DEDICATO + _machineThread = new Thread(() => WorkerLoopMachine(_ctsMachine.Token)) + { + IsBackground = true, + Name = "Single_Dedicated_Thread", + Priority = ThreadPriority.AboveNormal // Opzionale: dà precedenza ai dati PLC + }; + + _machineThread.Start(); + lgInfo("--- WORKER MACHINE AVVIATO ---"); + + // avvio altro thread comunque... // 1. Controllo se è già in esecuzione if (_workerTask != null && !_workerTask.IsCompleted) { @@ -2692,8 +2669,9 @@ namespace IOB_WIN_FORM return; } - _cts = new CancellationTokenSource(); - _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); + _ctsServer = new CancellationTokenSource(); + _workerTask = Task.Run(() => WorkerLoopAsync(_ctsServer.Token)); + lgInfo("--- WORKER SERVER AVVIATO ---"); } @@ -2702,7 +2680,6 @@ namespace IOB_WIN_FORM private Thread _machineThread; private readonly object _threadLock = new object(); - private bool _isProcessing = false; // Flag di sicurezza aggiuntivo #if false @@ -2725,9 +2702,9 @@ namespace IOB_WIN_FORM // Metodo per fermare tutto (es. nel Form_Closing o Dispose) private async Task StopWorker() { - if (_cts == null) return; + if (_ctsMachine == null) return; - _cts.Cancel(); + _ctsMachine.Cancel(); try { @@ -2740,10 +2717,10 @@ namespace IOB_WIN_FORM catch (OperationCanceledException) { /* Normale amministrazione */ } finally { - if (_cts != null) + if (_ctsMachine != null) { - _cts.Dispose(); - _cts = null; + _ctsMachine.Dispose(); + _ctsMachine = null; } if (_workerTask != null) { @@ -2836,14 +2813,15 @@ namespace IOB_WIN_FORM // incremento indice x mostrare esecuzione... idxWLM++; idxWLM = idxWLM > 999 ? 0 : idxWLM; - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine"); + //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine"); if (!_isSuspended) { try { // Eseguiamo il lavoro asincrono in modo SINCRONO su questo thread. // .GetAwaiter().GetResult() blocca questo thread finché il task non è finito. - DoExecMachineTasksAsync(true).GetAwaiter().GetResult(); + //await DoExecMachineTasksAsync(); + DoExecMachineTasksAsync().GetAwaiter().GetResult(); } catch (Exception ex) { @@ -2874,13 +2852,14 @@ namespace IOB_WIN_FORM // Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine | pre-delay"); + //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine | pre-delay"); + // ATTESA: Non rilascia il thread al pool di .NET // Si interrompe subito se ct viene cancellato bool cancelled = ct.WaitHandle.WaitOne(currentDelay); if (cancelled) break; - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine | End"); + //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine | End"); } } catch (Exception ex) @@ -2909,7 +2888,7 @@ namespace IOB_WIN_FORM // incremento indice x mostrare esecuzione... idxWLS++; idxWLS = idxWLS > 999 ? 0 : idxWLS; - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync"); + //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync"); if (!_isSuspended) { try @@ -2953,7 +2932,7 @@ namespace IOB_WIN_FORM lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | delay: {currentDelay}"); } - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | pre-delay"); + //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | pre-delay"); // 4. ATTESA INTERROMPIBILE // Il delay asincrono permette al sistema di riutilizzare il thread se necessario, @@ -2962,7 +2941,7 @@ namespace IOB_WIN_FORM await Task.Delay(currentDelay, ct); // Logga l'ID Thread - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | End"); + //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | End"); } lgInfo("WorkerLoopAsync interrotto per cancellation token."); } From 18e526f8b33fa8e1729b4c4cfa374c82abf9587b Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 18 Feb 2026 16:12:42 +0100 Subject: [PATCH 6/7] Fix test task2exe FANUC 3029 --- IOB-UT-NEXT/IOB-UT-NEXT.csproj | 1 + IOB-UT-NEXT/Iob/BaseObj.cs | 18 +- IOB-UT-NEXT/JobTask2Exe.cs | 65 ++ IOB-WIN-FORM/AdapterForm.cs | 307 ++++------ IOB-WIN-FORM/Iob/Generic.Protected.cs | 167 ++++- IOB-WIN-FORM/Iob/Generic.Public.cs | 843 +++++++++++++++----------- 6 files changed, 823 insertions(+), 578 deletions(-) create mode 100644 IOB-UT-NEXT/JobTask2Exe.cs diff --git a/IOB-UT-NEXT/IOB-UT-NEXT.csproj b/IOB-UT-NEXT/IOB-UT-NEXT.csproj index f1a1f4cc..239e9d06 100644 --- a/IOB-UT-NEXT/IOB-UT-NEXT.csproj +++ b/IOB-UT-NEXT/IOB-UT-NEXT.csproj @@ -200,6 +200,7 @@ + diff --git a/IOB-UT-NEXT/Iob/BaseObj.cs b/IOB-UT-NEXT/Iob/BaseObj.cs index 905b8850..39b181ee 100644 --- a/IOB-UT-NEXT/Iob/BaseObj.cs +++ b/IOB-UT-NEXT/Iob/BaseObj.cs @@ -238,6 +238,16 @@ namespace IOB_UT_NEXT.Iob /// public DataQueue QueueRawTransf;// = new DataQueue("000", "QueueRawTransf", false); + /// + /// Coda delle richieste dal server (Task2Exe) + /// + public DataQueue QueueSrvReq; + + /// + /// Coda delle risposte al server (Task2Exe) + /// + public DataQueue QueueSrvResp; + /// /// Coda valori LOG UTENTE (da non sottocampionare come samples)... /// @@ -621,16 +631,18 @@ namespace IOB_UT_NEXT.Iob { string codIob = IOBConfFull.General.FilenameIOB; bool useRedis = IOBConfFull.General.EnabRedisQue; - QueueAlarm = new DataQueue(codIob, "QueueAlarm", false, redisMan); // valutare se portare di nuovo in redis... QueueIN = new DataQueue(codIob, "QueueIN", useRedis, redisMan); + QueueSrvResp = new DataQueue(codIob, "QueueServResp", useRedis, redisMan); // fix a NON redis + QueueAlarm = new DataQueue(codIob, "QueueAlarm", false, redisMan); QueueFLog = new DataQueue(codIob, "QueueFLog", false, redisMan); //QueueFLog = new DataQueue(codIob, "QueueFLog", useRedis, redisMan); QueueMessages = new DataQueue(codIob, "QueueMessages", false, redisMan); - QueueRawTransf = new DataQueue(codIob, "QueueRawTransf", false, redisMan); - QueueULog = new DataQueue(codIob, "QueueULog", false, redisMan); QueuePing = new DataQueue(codIob, "QueuePing", false, redisMan); + QueueRawTransf = new DataQueue(codIob, "QueueRawTransf", false, redisMan); + QueueSrvReq = new DataQueue(codIob, "QueueServReq", false, redisMan); + QueueULog = new DataQueue(codIob, "QueueULog", false, redisMan); } #endregion Protected Methods diff --git a/IOB-UT-NEXT/JobTask2Exe.cs b/IOB-UT-NEXT/JobTask2Exe.cs new file mode 100644 index 00000000..da465db4 --- /dev/null +++ b/IOB-UT-NEXT/JobTask2Exe.cs @@ -0,0 +1,65 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IOB_UT_NEXT +{ + /// + /// Classe per gestione Job Task2Exe x una macchina + /// + public class JobTaskData + { + #region Public Constructors + + public JobTaskData(string codTav, string rawData) + { + CodTav = codTav; + RawData = rawData; + } + + #endregion Public Constructors + +#if false + public JobTaskData(string codTav, Dictionary newDict) + { + CodTav = codTav; + RawData = JsonConvert.SerializeObject(newDict); + } +#endif + + #region Public Properties + + /// + /// Codice tavola (empty = main) + /// + public string CodTav { get; private set; } = ""; + + /// + /// Dizionario in formato raw (da deserializzare + /// + public string RawData { get; private set; } = ""; + + #endregion Public Properties + + #region Public Methods + + /// + /// Dizionario dei Task associati + /// + public static Dictionary TaskDict(string rawData) + { + var answ = new Dictionary(); + if (!string.IsNullOrEmpty(rawData)) + { + answ = JsonConvert.DeserializeObject>(rawData) ?? new Dictionary(); + } + return answ; + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/IOB-WIN-FORM/AdapterForm.cs b/IOB-WIN-FORM/AdapterForm.cs index 5335df60..09919c3f 100644 --- a/IOB-WIN-FORM/AdapterForm.cs +++ b/IOB-WIN-FORM/AdapterForm.cs @@ -1580,12 +1580,10 @@ namespace IOB_WIN_FORM } /// - /// Verifica scadenza task nell'oggetto ricevuto e processing secondo ContextType + /// Verifica scadenza task nell'oggetto Pool /// - /// Indica se siano abilitate operazioni Pool (Thread Safe) - /// Indica se siano abilitate le operazioni Single Thread /// - private async Task checkScad(TimerMan timerObj) + private async Task checkScadPoolAsync() { DateTime adesso = DateTime.Now; bool sendDone = false; @@ -1595,56 +1593,102 @@ namespace IOB_WIN_FORM var ordered = priorities.Where(x => x > gatherCycle.VHF).OrderByDescending(x => x); foreach (gatherCycle item in ordered) { - if (timerObj.Veto.ContainsKey(item)) + if (TimerServer.Veto.ContainsKey(item)) { - if (timerObj.Veto[item] <= adesso) + if (TimerServer.Veto[item] <= adesso) { // se non ho già processato a questo giro... if (!sendDone) { - timerObj.CycleCount[item]++; + TimerServer.CycleCount[item]++; + Stopwatch sw = Stopwatch.StartNew(); - - // Swith del treno di esecuzione Contesto (Timer) e chiamata di processing x frequenza ... - if (timerObj.Contesto == TimerMan.ContextType.Machine) - { - await iobObj.doMachineTaskAsync(item); - } - else - { - await iobObj.doServerTaskAsync(item); - } - -#if false - await iobObj.getAndSendAsync(item, enabPool, enabSTID); -#endif - + await iobObj.doServerTaskAsync(item); sw.Stop(); + sendDone = true; // metto una piccola attesa se ho altre scadenze - timerObj.CycleElaps[item] += sw.Elapsed.TotalMilliseconds; + TimerServer.CycleElaps[item] += sw.Elapsed.TotalMilliseconds; // metto nuova scadenza perturbata 10% - timerObj.Veto[item] = adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0.1)); + TimerServer.Veto[item] = adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0.1)); } // log + reset se ho contato ALMENO 100 exec o il tempo totale supera 1000msec int limMs = 10000; - double totalMs = timerObj.CycleElaps.ContainsKey(item) ? timerObj.CycleElaps[item] : 0; - if (timerObj.CycleCount[item] >= 100 || totalMs > limMs) + double totalMs = TimerServer.CycleElaps.ContainsKey(item) ? TimerServer.CycleElaps[item] : 0; + if (TimerServer.CycleCount[item] >= 100 || totalMs > limMs) { - int nCount = Math.Max(timerObj.CycleCount[item], 1); - lgDebug($"Timer.{timerObj.Contesto} | checkScad | {item} | {totalMs / nCount:F1}ms x {nCount}"); - timerObj.CycleCount[item] = 0; - timerObj.CycleElaps[item] = 0; + int nCount = Math.Max(TimerServer.CycleCount[item], 1); + lgDebug($"Timer.{TimerServer.Contesto} | checkScadPoolAsync | {item} | {totalMs / nCount:F1}ms x {nCount}"); + TimerServer.CycleCount[item] = 0; + TimerServer.CycleElaps[item] = 0; } } } else { - timerObj.Veto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); - timerObj.CycleCount.Add(item, 0); - if (!timerObj.CycleElaps.ContainsKey(item)) + TimerServer.Veto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); + TimerServer.CycleCount.Add(item, 0); + if (!TimerServer.CycleElaps.ContainsKey(item)) { - timerObj.CycleElaps.Add(item, 0); + TimerServer.CycleElaps.Add(item, 0); + } + } + } + } + + + /// + /// Verifica scadenza task Machine + /// + /// + private void checkScadMachine() + { + DateTime adesso = DateTime.Now; + bool sendDone = false; + // ciclo su tutti i timers enum superiori a VHF + var priorities = (gatherCycle[])Enum.GetValues(typeof(gatherCycle)); + // prendo solo > VHF e ordino discendenti x dare priorità ai più rari... + var ordered = priorities.Where(x => x > gatherCycle.VHF).OrderByDescending(x => x); + foreach (gatherCycle item in ordered) + { + if (TimerMachine.Veto.ContainsKey(item)) + { + if (TimerMachine.Veto[item] <= adesso) + { + // se non ho già processato a questo giro... + if (!sendDone) + { + TimerMachine.CycleCount[item]++; + Stopwatch sw = Stopwatch.StartNew(); + + iobObj.doMachineTask(item); + + sw.Stop(); + sendDone = true; + // metto una piccola attesa se ho altre scadenze + TimerMachine.CycleElaps[item] += sw.Elapsed.TotalMilliseconds; + // metto nuova scadenza perturbata 10% + TimerMachine.Veto[item] = adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0.1)); + } + // log + reset se ho contato ALMENO 100 exec o il tempo totale supera 1000msec + int limMs = 10000; + double totalMs = TimerMachine.CycleElaps.ContainsKey(item) ? TimerMachine.CycleElaps[item] : 0; + if (TimerMachine.CycleCount[item] >= 100 || totalMs > limMs) + { + int nCount = Math.Max(TimerMachine.CycleCount[item], 1); + lgDebug($"Timer.{TimerMachine.Contesto} | checkScadMachine | {item} | {totalMs / nCount:F1}ms x {nCount}"); + TimerMachine.CycleCount[item] = 0; + TimerMachine.CycleElaps[item] = 0; + } + } + } + else + { + TimerMachine.Veto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); + TimerMachine.CycleCount.Add(item, 0); + if (!TimerMachine.CycleElaps.ContainsKey(item)) + { + TimerMachine.CycleElaps.Add(item, 0); } } } @@ -1742,10 +1786,10 @@ namespace IOB_WIN_FORM /// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound /// /// - private async Task DoExecMachineTasksAsync() + private void DoExecMachineTasks() { // Attende il proprio turno. Se un task è già in corso, questo aspetta. - await _plcLock.WaitAsync(); + _plcLock.Wait(); bool doLog = iobObj.periodicLog; try { @@ -1774,17 +1818,17 @@ namespace IOB_WIN_FORM } // MAIN: controllo TUTTE le scadenze Machine... - await checkScad(TimerMachine); + checkScadMachine(); // wait opzionale in coda if (utils.CRI("waitEndCycle") > 0) { - await Task.Delay(utils.CRI("waitEndCycle")); + Thread.Sleep(utils.CRI("waitEndCycle")); } } else { - lgTrace($"VETO DoExecMachineTasksAsync.queueInEnabCurr | veto attivo | {adesso:yyyy.MM.dd HH:mm:ss}"); + lgTrace($"VETO DoExecMachineTasks.queueInEnabCurr | veto attivo | {adesso:yyyy.MM.dd HH:mm:ss}"); iobObj.checkVetoQueueIn(); } } @@ -1823,7 +1867,7 @@ namespace IOB_WIN_FORM } catch (Exception exc) { - lgError($"Eccezione in fase di DoExecMachineTasksAsync: {Environment.NewLine}{exc}"); + lgError($"Eccezione in fase di DoExecMachineTasks: {Environment.NewLine}{exc}"); } finally { @@ -1857,7 +1901,7 @@ namespace IOB_WIN_FORM { // MAIN: controllo TUTTE le scadenze Server... - await checkScad(TimerServer); + await checkScadPoolAsync(); // wait opzionale in coda if (utils.CRI("waitEndCycle") > 0) @@ -2513,118 +2557,6 @@ namespace IOB_WIN_FORM private void StartWorker() { - // Versione 1 -#if false - _isSuspended = false; - _cts = new CancellationTokenSource(); - - // Usiamo TaskCreationOptions.LongRunning - // Questo suggerisce a .NET di creare un thread dedicato invece di usare il pool - _workerTask = Task.Factory.StartNew( - () => WorkerLoopAsync(_cts.Token), - _cts.Token, - TaskCreationOptions.LongRunning, - TaskScheduler.Default).Unwrap(); // Unwrap è necessario perché WorkerLoopAsync è Task - -#endif - - // versone 2 -#if false - // rimozione isSuspended - _isSuspended = false; - // per prima cosa disattivo il disabled... - lock (_lock) - { - // 1. Controllo se è già in esecuzione - if (_workerTask != null && !_workerTask.IsCompleted) - { - lgTrace("Worker già in esecuzione. Richiesta ignorata."); - return; - } - - _cts = new CancellationTokenSource(); - // Assegniamo il Task alla variabile per poterne monitorare lo stato - if (doMachSingleThread) - { - // Facciamo partire tutto il loop sul thread dedicato fin dall'inizio - _workerTask = _singleFactory.StartNew( - () => WorkerLoopAsync(_cts.Token), - _cts.Token - ).Unwrap(); - } - else - { - _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); - } - } -#endif - - // versione 3 -#if false - if (doMachSingleThread) - { - _isSuspended = false; - _cts = new CancellationTokenSource(); - - // Creiamo un thread VERO, uno solo, che non morirà mai - Thread dedicatedThread = new Thread(() => - { - lgInfo($"[THREAD FISSO: {Thread.CurrentThread.ManagedThreadId}] Avviato"); - - while (!_cts.Token.IsCancellationRequested) - { - try - { - if (!_isSuspended) - { - // Eseguiamo il task asincrono in modo "bloccante" per questo thread - // Ma essendo un thread separato, la UI non se ne accorge! - DoExecTasksAsync().GetAwaiter().GetResult(); - } - - // Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF - int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; - if (_errorDelay > 0) currentDelay += _errorDelay; - - // Dorme senza rilasciare il thread al pool - Thread.Sleep(currentDelay); - - // Logga l'ID: vedrai che ora torna sempre lo stesso! - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Fine ciclo delay"); - } - catch (Exception ex) - { - lgError("Errore nel loop: " + ex.Message); - } - } - }); - - dedicatedThread.IsBackground = true; - dedicatedThread.Name = "SingleDedicatedThread"; - dedicatedThread.Start(); - } - else - { - // rimozione isSuspended - _isSuspended = false; - // per prima cosa disattivo il disabled... - lock (_lock) - { - // 1. Controllo se è già in esecuzione - if (_workerTask != null && !_workerTask.IsCompleted) - { - lgTrace("Worker già in esecuzione. Richiesta ignorata."); - return; - } - - _cts = new CancellationTokenSource(); - _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); - } - } -#endif - - // versione 4 - //// FixMe ToDo !!! togliere o fare alternativa pooled //if (iobObj.IOBConfFull.General.MachWLoopSingleThread || true) //{ @@ -2751,51 +2683,13 @@ namespace IOB_WIN_FORM checkAssignSize(); } -#if false /// - /// Loop di gestione worker + /// Counter esecuzione WorkerLoopMachine /// - /// - private void WorkerLoopDestined(CancellationToken ct) - { - try - { - lgInfo($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Loop dedicato AVVIATO"); - - while (!ct.IsCancellationRequested) - { - if (!_isSuspended) - { - try - { - // Eseguiamo il task asincrono e ASPETTIAMO che finisca - // rimanendo su questo thread. - DoExecTasksAsync().GetAwaiter().GetResult(); - - lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId}] Task completato"); - } - catch (Exception ex) - { - lgError("Errore durante i task: " + ex.Message); - } - } - - int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; - if (_errorDelay > 0) currentDelay += _errorDelay; - - // Invece di await Task.Delay, usiamo il WaitHandle del CancellationToken - // Questo blocca il thread attuale per X millisecondi o finché non viene cancellato - ct.WaitHandle.WaitOne(currentDelay); - } - } - catch (Exception ex) - { - lgError("Errore fatale nel thread dedicato: " + ex.Message); - } - } -#endif - private int idxWLM = 0; + /// + /// Counter esecuzione WorkerLoopServer + /// private int idxWLS = 0; /// @@ -2805,7 +2699,7 @@ namespace IOB_WIN_FORM private void WorkerLoopMachine(CancellationToken ct) { lgInfo($"[Thread {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] inizio WorkerLoopMachine"); - + int validThread = Thread.CurrentThread.ManagedThreadId; try { while (!ct.IsCancellationRequested) @@ -2818,10 +2712,19 @@ namespace IOB_WIN_FORM { try { - // Eseguiamo il lavoro asincrono in modo SINCRONO su questo thread. - // .GetAwaiter().GetResult() blocca questo thread finché il task non è finito. - //await DoExecMachineTasksAsync(); - DoExecMachineTasksAsync().GetAwaiter().GetResult(); + // solo se il thread è valido... + if (Thread.CurrentThread.ManagedThreadId == validThread) + { + // Eseguiamo il lavoro asincrono in modo SINCRONO su questo thread. + // .GetAwaiter().GetResult() blocca questo thread finché il task non è finito. + //await DoExecMachineTasks(); + //DoExecMachineTasks().GetAwaiter().GetResult(); + DoExecMachineTasks(); + } + else + { + lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] | WorkerLoopMachine | NOT VALID, expecting {validThread}"); + } } catch (Exception ex) { @@ -2852,8 +2755,6 @@ namespace IOB_WIN_FORM // Calcolo del delay dinamico tra standard e suspended da Timers.MsVHF int currentDelay = _isSuspended ? 10 * IOBConfFull.General.Timers.MsVHF : IOBConfFull.General.Timers.MsVHF; - //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLM})] WorkerLoopMachine | pre-delay"); - // ATTESA: Non rilascia il thread al pool di .NET // Si interrompe subito se ct viene cancellato bool cancelled = ct.WaitHandle.WaitOne(currentDelay); @@ -2932,8 +2833,6 @@ namespace IOB_WIN_FORM lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | delay: {currentDelay}"); } - //lgTrace($"[Thread: {Thread.CurrentThread.ManagedThreadId} ({idxWLS})] WorkerLoopAsync | pre-delay"); - // 4. ATTESA INTERROMPIBILE // Il delay asincrono permette al sistema di riutilizzare il thread se necessario, // ma il 'while' garantisce che non inizieremo il prossimo DoExecTasksAsync prematuramente. diff --git a/IOB-WIN-FORM/Iob/Generic.Protected.cs b/IOB-WIN-FORM/Iob/Generic.Protected.cs index 96912234..d5379f58 100644 --- a/IOB-WIN-FORM/Iob/Generic.Protected.cs +++ b/IOB-WIN-FORM/Iob/Generic.Protected.cs @@ -1217,6 +1217,36 @@ namespace IOB_WIN_FORM.Iob return answ; } + /// + /// Accodamento richieste server + /// + /// + /// + protected void accodaServReq(string codTav, string newReq) + { + JobTaskData jobTask = new JobTaskData(codTav, newReq); + // accodo richiesta serializzata + string serVal = JsonConvert.SerializeObject(jobTask); + QueueSrvReq.Enqueue(serVal); + // loggo! + lgDebug($"[QUEUE] | QueueSrvReq len: {QueueSrvReq.Count}"); + } + + /// + /// Accodamento risposte per il server + /// + /// + /// + protected void accodaServResp(string codTav, string rawData) + { + JobTaskData jobTask = new JobTaskData(codTav, rawData); + // accodo richiesta serializzata + string serVal = JsonConvert.SerializeObject(jobTask); + QueueSrvResp.Enqueue(serVal); + // loggo! + lgDebug($"[QUEUE] | QueueSrvResp len: {QueueSrvResp.Count}"); + } + /// /// Classe di base implementazione traduzione di una LUT da memoria come valore BIT a valore /// esplicito x FLog @@ -1263,8 +1293,6 @@ namespace IOB_WIN_FORM.Iob return answ; } - - /// /// Conversione string row in log generico /// @@ -5573,6 +5601,132 @@ namespace IOB_WIN_FORM.Iob parentForm.dataProcLabel = string.Format("RAW: {0} --> IN: {1} --> OUT: {2}", nReadIN, nReadFilt, nSendOut); } + /// + /// Chiamate ritorno task eseguiti al server + /// + /// + /// + private async Task SendTaskResult(List listaValori) + { + foreach (var rawJob in listaValori) + { + // deserializzo... + JobTaskData jobTask = JsonConvert.DeserializeObject(rawJob); +#if false + // loggo tutti i task done... + foreach (var item in jobTask.TaskList) + { + sendToTaskWatch(item.Key, item.Value, jobTask.CodTav); + } +#endif + // ora chiamo la cancellazione dei task eseguiti... + var taskDict = JobTaskData.TaskDict(jobTask.RawData); + foreach (var item in taskDict) + { + await remTask2exe(item.Key, item.Value, jobTask.CodTav); + } + } + } + + /// + /// Effettua ciclo recupero richieste server + /// + private async Task ServerGetRequestsAsync() + { + // recupero elenco delle cose da fare + string resp = await getTask2exe(""); + // se ho qualcosa --> creo obj e lo accodo... + //if (!string.IsNullOrEmpty(resp)) + if (!string.IsNullOrEmpty(resp) && resp.Length > 2) + { + //await ProcessResp(resp, ""); + accodaServReq("", resp); + if (isMulti) + { + foreach (var item in IOBConfFull.Device.MultiIobList) + { + resp = await getTask2exe(item); + accodaServReq(item, resp); + //await ProcessResp(resp, rawJob); + } + } + } + + // vers originale +#if false + // recupero elenco delle cose da fare + string resp = await getTask2exe(""); + await ProcessResp(resp, ""); + // se fosse multi --> eseguo task per le varie sub (Tavole/Pallet) + if (isMulti) + { + foreach (var item in IOBConfFull.Device.MultiIobList) + { + resp = await getTask2exe(item); + await ProcessResp(resp, item); + } + } +#endif + } + + /// + /// Effettua ciclo invio rispsote (=esito esecuzione richieste) al server + /// + private async Task ServerPutRespAsync() + { + // verifico SE la coda abbia dei valori... + if (QueueSrvResp.Count > 0) + { + // invio pacchetto di dati (max da conf) + for (int i = 0; i < nMaxSend; i++) + { + // SE ho qualcosa in coda... + if (QueueSrvResp.Count > 0) + { + string currVal = ""; + if (MPOnline) + { + if (IobOnline) + { + List listaValori = new List(); + // se ho + di maxJsonData elementi --> invio un set di dati alla volta + if (QueueSrvResp.Count > maxJsonData) + { + // prendoi primi maxJsonDataValori + for (int j = 0; j < maxJsonData; j++) + { + QueueSrvResp.TryDequeue(out currVal); + listaValori.Add(currVal); + } + await SendTaskResult(listaValori); + } + else + { + // invio in blocco + listaValori = QueueSrvResp.ToList(); + await SendTaskResult(listaValori); + // svuoto! + QueueSrvResp = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueServResp", IOBConfFull.General.EnabRedisQue, redisMan); + } + } + else + { + break; + } + } + else + { + break; + } + } + else + { + break; + } + } + } + } + /// /// Imposto alcuni valori di default /// @@ -5598,15 +5752,18 @@ namespace IOB_WIN_FORM.Iob { string codIob = IOBConfFull.General.FilenameIOB; bool useRedis = IOBConfFull.General.EnabRedisQue; - QueueAlarm = new DataQueue(codIob, "QueueAlarm", false, redisMan); QueueIN.Dispose(); QueueIN = new DataQueue(codIob, "QueueIN", useRedis, redisMan); + QueueSrvResp.Dispose(); + QueueSrvResp = new DataQueue(codIob, "QueueServResp", useRedis, redisMan); // no coda redis + QueueAlarm = new DataQueue(codIob, "QueueAlarm", false, redisMan); QueueFLog = new DataQueue(codIob, "QueueFLog", false, redisMan); QueueMessages = new DataQueue(codIob, "QueueMessages", false, redisMan); - QueueRawTransf = new DataQueue(codIob, "QueueRawTransf", false, redisMan); - QueueULog = new DataQueue(codIob, "QueueULog", false, redisMan); QueuePing = new DataQueue(codIob, "QueuePing", false, redisMan); + QueueRawTransf = new DataQueue(codIob, "QueueRawTransf", false, redisMan); + QueueSrvReq = new DataQueue(codIob, "QueueServReq", false, redisMan); + QueueULog = new DataQueue(codIob, "QueueULog", false, redisMan); } // imposto contatori blink a zero... i_counters = new int[32]; diff --git a/IOB-WIN-FORM/Iob/Generic.Public.cs b/IOB-WIN-FORM/Iob/Generic.Public.cs index 67af56d3..499456e9 100644 --- a/IOB-WIN-FORM/Iob/Generic.Public.cs +++ b/IOB-WIN-FORM/Iob/Generic.Public.cs @@ -692,7 +692,6 @@ namespace IOB_WIN_FORM.Iob if (dtVetoPing >= DateTime.Now) return MPOnline; if (DemoOut) return true; - bool isAlive = await ExecuteApiCheckWithRetryAsync(maxRetries: 7); // 3. Gestione Stato (Sincrono) @@ -734,6 +733,422 @@ namespace IOB_WIN_FORM.Iob accodaOtherData(newData); } + /// + /// Effettua i task di comunicazione IN/OUT con la macchina + /// + /// + /// + public void doMachineTask(gatherCycle ciclo) + { + // init obj display + newDisplayData currDispData = new newDisplayData(); + // controllo connessione/connettività + if (connectionOk) + { + // controllo non sia già in esecuzione... + if (!adpCommAct) + { + // provo ad avviare + try + { + // imposto flag adapter running.. + adpCommAct = true; + adpStartRun = DateTime.Now; + } + catch (Exception exc) + { + string errore = $"Adapter NOT STARTED!!!{Environment.NewLine}{exc}"; + adpCommAct = false; + adpStartRun = DateTime.Now; + currDispData.newLiveLogData = errore; + } + if (adpCommAct) + { + // try / catch generale altrimenti segno che è disconnesso... + try + { + if (ciclo == gatherCycle.VHF) + { + //processVHF(); + } + // processing dati memoria (lettura, filtraggio, enqueque) + else if (ciclo == gatherCycle.HF) + { + processWhatchDog(); + Thread.Sleep(5); + processAllMemory(); + } + else if (ciclo == gatherCycle.MF) + { + processMode(); + Thread.Sleep(5); + ExecServerRequests(); +#if false + processServerRequests().GetAwaiter().GetResult(); +#endif + Thread.Sleep(5); + processCustomTaskMF(); + Thread.Sleep(5); + processOverride(); + Thread.Sleep(5); + processContapezzi(); + Thread.Sleep(5); + processCncAlarms(); + Thread.Sleep(5); + processDynData(); + Thread.Sleep(5); + processMem2Write(); + //processAllMemory(); + } + else if (ciclo == gatherCycle.LF) + { + processCustomTaskLF(); + Thread.Sleep(5); + processOtherCounters().GetAwaiter().GetResult(); ; + Thread.Sleep(5); + processProgram(); + //// verifico se devo gestire cambio ODL in modo automatico + //await ProcessAutoOdlAsync(); + //// verifico se devo gestire auto generazione dossier quotidiana + //ProcessAutoDossier(); + //// effettua gestione import file se configurato... + //await ProcessFileImportAsync(); + //// effettua process ritorno ricette + //await ProcessRecipeFileRetAsync(); + } + else if (ciclo == gatherCycle.VLF) + { + //if (utils.CRB("enableContapezzi")) + //{ + // // rilettura contapezzi da server... + // lgTrace("Ciclo MsVLF: pzCntReload(true)"); + // if (!isMulti) + // { + // pzCntReload(true); + // } + // // refresh associazione Macchina - IOB + // await SendM2IobAsync(); + // // invio altri dati accessori... + // await SendMachineConfAsync(); + //} + //// checkLogDir x shrink! + //checkShrinkDir(); + //// eventuale log! + //if (utils.CRB("recTime")) + //{ + // try + // { + // logTimeResults(); + // } + // catch + // { } + //} + //processRecipeSyncArch(); + // recupero dati SETUP (sysinfo) e li invio/mostro se variati... + processSysInfo(); + if (enableSlowData) + { + Thread.Sleep(5); + processSlowDataRead(); + } + } + + //// mostra eventuali altri dati di processo... + //reportDataProc(); + //if (showDebugData) + //{ + // // verifica se debba salvare e mostrare dati + // checkSavePersDataLayer(); + //} + } + catch (Exception exc) + { + // segnalo eccezione e indico disconnesso... + lgError($"Exception doMachineTask | {ciclo} | fermo adapter{Environment.NewLine}{exc}"); + parentForm.fermaAdapter(true, false, true); + } + // tolgo flag running + adpCommAct = false; + } + else + { + if (periodicLog) + { + lgDebug("ADP not running..."); + } + } + } + else + { + // log ADP running + lgError("Non eseguo chiamata: ADP ancora in running"); + // se è bloccato da oltre maxSec lo sblocco... + if (DateTime.Now.Subtract(adpStartRun).TotalSeconds > utils.CRI("maxAdapterLockSec")) + { + // tolgo flag running + adpCommAct = false; + adpStartRun = DateTime.Now; + } + } + } + else + { + //// anche se NON connesso alcuni task di bassa freq li eseguo... + //if (ciclo == gatherCycle.LF) + //{ + // // verifico se devo gestire cambio ODL in modo automatico + // await ProcessAutoOdlAsync(); + // // verifico se devo gestire auto generazione dossier quotidiana + // ProcessAutoDossier(); + // // effettua gestione import file se configurato... + // await ProcessFileImportAsync(); + // // effettua process ritorno ricette + // await ProcessRecipeFileRetAsync(); + //} + //else if (ciclo == gatherCycle.VLF) + //{ + // processRecipeSyncArch(); + //} + + // provo a riconnettere SE abilitato tryRestart... + if (adpTryRestart && !connectionOk) + { + // controllo se sia scaduto periodi di veto al tryConnect... + int waitRecMSec = utils.CRI("waitRecMSec"); + // cerco se ci sia un valore in ovverride x il singolo IOB... + if (IOBConfFull.General.WaitRecMsec > 0) + { + waitRecMSec = IOBConfFull.General.WaitRecMsec; + } + DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); + if (DateTime.Now > dtVeto) + { + lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); + lastConnectTry = DateTime.Now; + tryConnect(); + } + } + currDispData.semIn = Semaforo.SR; + processDisconnectedTask(); + processMemoryDiscon(); + } + raiseRefresh(currDispData); + } + + /// + /// Effettua i task di comunicazione IN/OUT con il server MAPO + /// + /// + /// + public async Task doServerTaskAsync(gatherCycle ciclo) + { + // init obj display + newDisplayData currDispData = new newDisplayData(); + // IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!! + try + { + await TrySendValuesAsync(); + } + catch (Exception exc) + { + lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria"); + currDispData.semOut = Semaforo.SR; + } + // controllo connessione/connettività + if (connectionOk) + { + // try / catch generale altrimenti segno che è disconnesso... + try + { + bool showDebugData = false; + if (ciclo == gatherCycle.VHF) + { + processVHF(); + } + // processing dati memoria (lettura, filtraggio, enqueque) + else if (ciclo == gatherCycle.HF) + { + //processWhatchDog(); + //processAllMemory(); + + // recupera ed invia risposte al server + await ServerPutRespAsync(); + } + else if (ciclo == gatherCycle.MF) + { + //processMode(); + + // recupero elenco richieste + await ServerGetRequestsAsync(); + + //processCustomTaskMF(); + //processOverride(); + //processContapezzi(); + //processCncAlarms(); + //processDynData(); + //processMem2Write(); + //processAllMemory(); + } + else if (ciclo == gatherCycle.LF) + { + //processCustomTaskLF(); + //await processOtherCounters(); + //processProgram(); + + // verifico se devo gestire cambio ODL in modo automatico + await ProcessAutoOdlAsync(); + // verifico se devo gestire auto generazione dossier quotidiana + ProcessAutoDossier(); + // effettua gestione import file se configurato... + await ProcessFileImportAsync(); + // effettua process ritorno ricette + await ProcessRecipeFileRetAsync(); + } + else if (ciclo == gatherCycle.VLF) + { + if (utils.CRB("enableContapezzi")) + { + // rilettura contapezzi da server... + lgTrace("Ciclo MsVLF: pzCntReload(true)"); + if (!isMulti) + { + pzCntReload(true); + } + // refresh associazione Macchina - IOB + await SendM2IobAsync(); + // invio altri dati accessori... + await SendMachineConfAsync(); + } + // checkLogDir x shrink! + checkShrinkDir(); + // eventuale log! + if (utils.CRB("recTime")) + { + try + { + logTimeResults(); + } + catch + { } + } + processRecipeSyncArch(); + + //// recupero dati SETUP (sysinfo) e li invio/mostro se variati... + //processSysInfo(); + //if (enableSlowData) + //{ + // processSlowDataRead(); + //} + } + + // mostra eventuali altri dati di processo... + reportDataProc(); + if (showDebugData) + { + // verifica se debba salvare e mostrare dati + checkSavePersDataLayer(); + } + } + catch (Exception exc) + { + // segnalo eccezione e indico disconnesso... + lgError($"Exception | doServerTaskAsync | {ciclo}{Environment.NewLine}{exc}"); + } + } + else + { + // anche se NON connesso alcuni task di bassa freq li eseguo... + if (ciclo == gatherCycle.LF) + { + // verifico se devo gestire cambio ODL in modo automatico + await ProcessAutoOdlAsync(); + // verifico se devo gestire auto generazione dossier quotidiana + ProcessAutoDossier(); + // effettua gestione import file se configurato... + await ProcessFileImportAsync(); + // effettua process ritorno ricette + await ProcessRecipeFileRetAsync(); + } + else if (ciclo == gatherCycle.VLF) + { + processRecipeSyncArch(); + } + + //// provo a riconnettere SE abilitato tryRestart... + //if (adpTryRestart && !connectionOk) + //{ + // // controllo se sia scaduto periodi di veto al tryConnect... + // int waitRecMSec = utils.CRI("waitRecMSec"); + // // cerco se ci sia un valore in ovverride x il singolo IOB... + // if (IOBConfFull.General.WaitRecMsec > 0) + // { + // waitRecMSec = IOBConfFull.General.WaitRecMsec; + // } + // DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); + // if (DateTime.Now > dtVeto) + // { + // lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); + // lastConnectTry = DateTime.Now; + // tryConnect(); + // } + //} + //currDispData.semIn = Semaforo.SR; + //processDisconnectedTask(); + //processMemoryDiscon(); + } + raiseRefresh(currDispData); + } + + public void ExecServerRequests() + { + // verifica se ci siano richieste da eseguire + if (QueueSrvReq.Count > 0) + { + if (MPOnline) + { + if (IobOnline) + { + // prendo elenco + List listaValori = QueueSrvReq.ToList(); + + foreach (var rawJob in listaValori) + { + // deserializzo... + JobTaskData jobTaskReq = JsonConvert.DeserializeObject(rawJob); + // processo! + var reqDict = JobTaskData.TaskDict(jobTaskReq.RawData); + if (reqDict.Count > 0) + { + var taskDone = ProcessTask(JobTaskData.TaskDict(jobTaskReq.RawData), jobTaskReq.CodTav); + // accodo task eseguiti... + string serVal = JsonConvert.SerializeObject(taskDone); + accodaServResp(jobTaskReq.CodTav, serVal); + } + } + + // svuoto! + QueueSrvReq = new DataQueue(IOBConfFull.General.FilenameIOB, "QueueServResp", IOBConfFull.General.EnabRedisQue, redisMan); + } + } + } + + // versione originale in blocco +#if false + // recupero elenco delle cose da fare + string resp = await getTask2exe(""); + await ProcessResp(resp, ""); + // se fosse multi --> eseguo task per le varie sub (Tavole/Pallet) + if (isMulti) + { + foreach (var item in IOBConfFull.Device.MultiIobList) + { + resp = await getTask2exe(item); + await ProcessResp(resp, item); + } + } +#endif + } + /// /// Esecuzione dei task richiesti e pulizia coda richieste eseguite /// @@ -1241,354 +1656,6 @@ namespace IOB_WIN_FORM.Iob return valTransl; } - /// - /// Effettua i task di comunicazione IN/OUT con la macchina - /// - /// - /// - public async Task doMachineTaskAsync(gatherCycle ciclo) - { - // init obj display - newDisplayData currDispData = new newDisplayData(); - // controllo connessione/connettività - if (connectionOk) - { - // controllo non sia già in esecuzione... - if (!adpCommAct) - { - // provo ad avviare - try - { - // imposto flag adapter running.. - adpCommAct = true; - adpStartRun = DateTime.Now; - } - catch (Exception exc) - { - string errore = $"Adapter NOT STARTED!!!{Environment.NewLine}{exc}"; - adpCommAct = false; - adpStartRun = DateTime.Now; - currDispData.newLiveLogData = errore; - } - if (adpCommAct) - { - // try / catch generale altrimenti segno che è disconnesso... - try - { - if (ciclo == gatherCycle.VHF) - { - //processVHF(); - } - // processing dati memoria (lettura, filtraggio, enqueque) - else if (ciclo == gatherCycle.HF) - { - processWhatchDog(); - processAllMemory(); - } - else if (ciclo == gatherCycle.MF) - { - processMode(); - await processServerRequests(); - processCustomTaskMF(); - processOverride(); - processContapezzi(); - processCncAlarms(); - processDynData(); - processMem2Write(); - //processAllMemory(); - } - else if (ciclo == gatherCycle.LF) - { - processCustomTaskLF(); - await processOtherCounters(); - processProgram(); - //// verifico se devo gestire cambio ODL in modo automatico - //await ProcessAutoOdlAsync(); - //// verifico se devo gestire auto generazione dossier quotidiana - //ProcessAutoDossier(); - //// effettua gestione import file se configurato... - //await ProcessFileImportAsync(); - //// effettua process ritorno ricette - //await ProcessRecipeFileRetAsync(); - } - else if (ciclo == gatherCycle.VLF) - { - //if (utils.CRB("enableContapezzi")) - //{ - // // rilettura contapezzi da server... - // lgTrace("Ciclo MsVLF: pzCntReload(true)"); - // if (!isMulti) - // { - // pzCntReload(true); - // } - // // refresh associazione Macchina - IOB - // await SendM2IobAsync(); - // // invio altri dati accessori... - // await SendMachineConfAsync(); - //} - //// checkLogDir x shrink! - //checkShrinkDir(); - //// eventuale log! - //if (utils.CRB("recTime")) - //{ - // try - // { - // logTimeResults(); - // } - // catch - // { } - //} - //processRecipeSyncArch(); - // recupero dati SETUP (sysinfo) e li invio/mostro se variati... - processSysInfo(); - if (enableSlowData) - { - processSlowDataRead(); - } - } - - //// mostra eventuali altri dati di processo... - //reportDataProc(); - //if (showDebugData) - //{ - // // verifica se debba salvare e mostrare dati - // checkSavePersDataLayer(); - //} - } - catch (Exception exc) - { - // segnalo eccezione e indico disconnesso... - lgError($"Exception doMachineTaskAsync | {ciclo} | fermo adapter{Environment.NewLine}{exc}"); - parentForm.fermaAdapter(true, false, true); - } - // tolgo flag running - adpCommAct = false; - } - else - { - if (periodicLog) - { - lgDebug("ADP not running..."); - } - } - } - else - { - // log ADP running - lgError("Non eseguo chiamata: ADP ancora in running"); - // se è bloccato da oltre maxSec lo sblocco... - if (DateTime.Now.Subtract(adpStartRun).TotalSeconds > utils.CRI("maxAdapterLockSec")) - { - // tolgo flag running - adpCommAct = false; - adpStartRun = DateTime.Now; - } - } - } - else - { - //// anche se NON connesso alcuni task di bassa freq li eseguo... - //if (ciclo == gatherCycle.LF) - //{ - // // verifico se devo gestire cambio ODL in modo automatico - // await ProcessAutoOdlAsync(); - // // verifico se devo gestire auto generazione dossier quotidiana - // ProcessAutoDossier(); - // // effettua gestione import file se configurato... - // await ProcessFileImportAsync(); - // // effettua process ritorno ricette - // await ProcessRecipeFileRetAsync(); - //} - //else if (ciclo == gatherCycle.VLF) - //{ - // processRecipeSyncArch(); - //} - - // provo a riconnettere SE abilitato tryRestart... - if (adpTryRestart && !connectionOk) - { - // controllo se sia scaduto periodi di veto al tryConnect... - int waitRecMSec = utils.CRI("waitRecMSec"); - // cerco se ci sia un valore in ovverride x il singolo IOB... - if (IOBConfFull.General.WaitRecMsec > 0) - { - waitRecMSec = IOBConfFull.General.WaitRecMsec; - } - DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); - if (DateTime.Now > dtVeto) - { - lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); - lastConnectTry = DateTime.Now; - tryConnect(); - } - } - currDispData.semIn = Semaforo.SR; - processDisconnectedTask(); - processMemoryDiscon(); - } - raiseRefresh(currDispData); - } - - - /// - /// Effettua i task di comunicazione IN/OUT con il server MAPO - /// - /// - /// - public async Task doServerTaskAsync(gatherCycle ciclo) - { - // init obj display - newDisplayData currDispData = new newDisplayData(); - // IN OGNI CASO a prima di tutto EFFETTUO GESTIONE INVII dati da code!!! - try - { - await TrySendValuesAsync(); - } - catch (Exception exc) - { - lgError(exc, "Errore in gestione svuotamento/invio preliminare code memoria"); - currDispData.semOut = Semaforo.SR; - } - // controllo connessione/connettività - if (connectionOk) - { - // try / catch generale altrimenti segno che è disconnesso... - try - { - bool showDebugData = false; - if (ciclo == gatherCycle.VHF) - { - - processVHF(); - } - // processing dati memoria (lettura, filtraggio, enqueque) - else if (ciclo == gatherCycle.HF) - { - //processWhatchDog(); - //processAllMemory(); - } - else if (ciclo == gatherCycle.MF) - { - //processMode(); - //await processServerRequests(); - //processCustomTaskMF(); - //processOverride(); - //processContapezzi(); - //processCncAlarms(); - //processDynData(); - //processMem2Write(); - //processAllMemory(); - } - else if (ciclo == gatherCycle.LF) - { - //processCustomTaskLF(); - //await processOtherCounters(); - //processProgram(); - - // verifico se devo gestire cambio ODL in modo automatico - await ProcessAutoOdlAsync(); - // verifico se devo gestire auto generazione dossier quotidiana - ProcessAutoDossier(); - // effettua gestione import file se configurato... - await ProcessFileImportAsync(); - // effettua process ritorno ricette - await ProcessRecipeFileRetAsync(); - } - else if (ciclo == gatherCycle.VLF) - { - if (utils.CRB("enableContapezzi")) - { - // rilettura contapezzi da server... - lgTrace("Ciclo MsVLF: pzCntReload(true)"); - if (!isMulti) - { - pzCntReload(true); - } - // refresh associazione Macchina - IOB - await SendM2IobAsync(); - // invio altri dati accessori... - await SendMachineConfAsync(); - } - // checkLogDir x shrink! - checkShrinkDir(); - // eventuale log! - if (utils.CRB("recTime")) - { - try - { - logTimeResults(); - } - catch - { } - } - processRecipeSyncArch(); - - //// recupero dati SETUP (sysinfo) e li invio/mostro se variati... - //processSysInfo(); - //if (enableSlowData) - //{ - // processSlowDataRead(); - //} - } - - // mostra eventuali altri dati di processo... - reportDataProc(); - if (showDebugData) - { - // verifica se debba salvare e mostrare dati - checkSavePersDataLayer(); - } - } - catch (Exception exc) - { - // segnalo eccezione e indico disconnesso... - lgError($"Exception | doServerTaskAsync | {ciclo}{Environment.NewLine}{exc}"); - } - } - else - { - // anche se NON connesso alcuni task di bassa freq li eseguo... - if (ciclo == gatherCycle.LF) - { - // verifico se devo gestire cambio ODL in modo automatico - await ProcessAutoOdlAsync(); - // verifico se devo gestire auto generazione dossier quotidiana - ProcessAutoDossier(); - // effettua gestione import file se configurato... - await ProcessFileImportAsync(); - // effettua process ritorno ricette - await ProcessRecipeFileRetAsync(); - } - else if (ciclo == gatherCycle.VLF) - { - processRecipeSyncArch(); - } - - //// provo a riconnettere SE abilitato tryRestart... - //if (adpTryRestart && !connectionOk) - //{ - // // controllo se sia scaduto periodi di veto al tryConnect... - // int waitRecMSec = utils.CRI("waitRecMSec"); - // // cerco se ci sia un valore in ovverride x il singolo IOB... - // if (IOBConfFull.General.WaitRecMsec > 0) - // { - // waitRecMSec = IOBConfFull.General.WaitRecMsec; - // } - // DateTime dtVeto = lastConnectTry.AddMilliseconds(waitRecMSec); - // if (DateTime.Now > dtVeto) - // { - // lgInfo($"Veto Time Elapsed | lastConnectTry: {lastConnectTry} | waitRecMSec {waitRecMSec} ms) | NOW tryConnect"); - // lastConnectTry = DateTime.Now; - // tryConnect(); - // } - //} - //currDispData.semIn = Semaforo.SR; - //processDisconnectedTask(); - //processMemoryDiscon(); - } - raiseRefresh(currDispData); - } - #if false /// /// Effettua recupero dati ed invio valori modificati... @@ -1818,7 +1885,7 @@ namespace IOB_WIN_FORM.Iob processMemoryDiscon(); } raiseRefresh(currDispData); - } + } #endif /// @@ -2562,7 +2629,7 @@ namespace IOB_WIN_FORM.Iob if (item.Key == "ND" || string.IsNullOrEmpty(item.Key)) { // log anomalia... - lgTrace($"Errore in predisposizione FL Allarmi CNC: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}"); + lgTrace($"Errore in predisposizione FL Allarmi CNC: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}"); } else { @@ -2584,7 +2651,7 @@ namespace IOB_WIN_FORM.Iob if (item.Key == "ND" || string.IsNullOrEmpty(item.Key)) { // log anomalia... - lgTrace($"Errore in predisposizione FL Allarmi NON CNC: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}"); + lgTrace($"Errore in predisposizione FL Allarmi NON CNC: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}"); } else { @@ -2715,7 +2782,7 @@ namespace IOB_WIN_FORM.Iob if (item.Key == "ND" || string.IsNullOrEmpty(item.Key)) { // log anomalia... - lgTrace($"Errore in processDynData.01: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}"); + lgTrace($"Errore in processDynData.01: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}"); } else { @@ -2746,7 +2813,7 @@ namespace IOB_WIN_FORM.Iob if (item.Key == "ND" || string.IsNullOrEmpty(item.Key)) { // log anomalia... - lgTrace($"Errore in processDynData.02: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}"); + lgTrace($"Errore in processDynData.02: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}"); } else { @@ -2875,7 +2942,7 @@ namespace IOB_WIN_FORM.Iob if (item.Key == "ND" || string.IsNullOrEmpty(item.Key)) { // log anomalia... - lgTrace($"Errore in processOverride: item.key risulta ND! | item.key: {item.Key} | item.Value: {item.Value}"); + lgTrace($"Errore in processOverride: rawJob.key risulta ND! | rawJob.key: {item.Key} | rawJob.Value: {item.Value}"); } else { @@ -2896,24 +2963,65 @@ namespace IOB_WIN_FORM.Iob } /// - /// Effettua ciclo controllo richieste server + /// Processa esecuzione task ricevuti /// - public async Task processServerRequests() + /// + /// + /// Restituisce elenco task svolti --> per accodamento risposta + public Dictionary ProcessTask(Dictionary task2exe, string codTav) { - // recupero elenco delle cose da fare - string resp = await getTask2exe(""); - await ProcessResp(resp, ""); - // se fosse multi --> eseguo task per le varie sub (Tavole/Pallet) - if (isMulti) + Dictionary taskDone = new Dictionary(); + Dictionary task2Add = new Dictionary(); + // eseguo realmente solo se NON disabilitata questa gestione (caso doppio PLC/HMI)... + if (!IOBConfFull.Device.DisabExeTask) { - foreach (var item in IOBConfFull.Device.MultiIobList) + if (task2exe != null && task2exe.Count > 0) { - resp = await getTask2exe(item); - await ProcessResp(resp, item); + string logMsg = $"Task2Exe S01: {task2exe.Count} task ricevuti"; + if (!string.IsNullOrEmpty(codTav)) + { + logMsg += $" | codTav: {codTav}"; + } + lgInfo(logMsg); + int idTask = 0; + foreach (var item in task2exe) + { + idTask++; + lgInfo($"[{idTask:00}] - {item.Key} --> {item.Value}"); + // verifico SE il task abbia un duplo writeLink e nel caso lo aggiungo... + var linkVal = getOptWriteLink(item.Key); + if (!string.IsNullOrEmpty(linkVal)) + { + // aggiungo a task2add SE manca... + if (!task2Add.ContainsKey(linkVal)) + { + task2Add.Add(linkVal, item.Value); + } + lgInfo($"Aggiunta task linked: {linkVal} -> {item.Value}"); + } + } + // se ho task Link da aggiungere li aggiungo! + if (task2Add.Count > 0) + { + foreach (var item in task2Add) + { + task2exe.Add(item.Key, item.Value); + } + } + // chiamo procedura esecutiva (diversa x ogni IOB) + taskDone = executeTasks(task2exe, codTav); + lgInfo($"Task2Exe S02: eseguiti {taskDone.Count} task"); + // loggo tutti i task done... + foreach (var item in taskDone) + { + sendToTaskWatch(item.Key, item.Value, codTav); + } } } + return taskDone; } +#if false /// /// Processa esecuzione task ricevuti /// @@ -2977,6 +3085,7 @@ namespace IOB_WIN_FORM.Iob } return taskDone; } +#endif /// /// Classe fittizia in caso di processing task in MsVHF @@ -4153,6 +4262,7 @@ namespace IOB_WIN_FORM.Iob return false; } +#if false /// /// Processing di una risposta raw di task2exe /// @@ -4175,10 +4285,11 @@ namespace IOB_WIN_FORM.Iob } catch (Exception exc) { - lgError($"Eccezione in processServerRequests.ProcessResp:{Environment.NewLine}{exc}"); + lgError($"Eccezione in ServerGetRequestsAsync.ProcessResp:{Environment.NewLine}{exc}"); } } } +#endif /// /// Update stato server From c2bbf911e3cd72d803b2940f568f96da4881f100 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Wed, 18 Feb 2026 16:32:08 +0100 Subject: [PATCH 7/7] Update x test FLuitek --- IOB-WIN-FANUC/DATA/CONF/MAIN.ini | 4 ++-- IOB-WIN-FANUC/postBuildTgt.bat | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini index 5fefa1bd..6cb43b0d 100644 --- a/IOB-WIN-FANUC/DATA/CONF/MAIN.ini +++ b/IOB-WIN-FANUC/DATA/CONF/MAIN.ini @@ -29,9 +29,9 @@ CLI_INST=SteamWareSim ;STARTLIST=3004,3005 ;STARTLIST=SIMUL_01 ;STARTLIST=FOV106 -;STARTLIST=FOV107 +STARTLIST=FOV107 ;STARTLIST=3028 -STARTLIST=3029 +;STARTLIST=3029 MAXCNC=10 \ No newline at end of file diff --git a/IOB-WIN-FANUC/postBuildTgt.bat b/IOB-WIN-FANUC/postBuildTgt.bat index 6f8f075e..556d3e50 100644 --- a/IOB-WIN-FANUC/postBuildTgt.bat +++ b/IOB-WIN-FANUC/postBuildTgt.bat @@ -49,8 +49,8 @@ REM ROBOCOPY %2 \\10.51.90.10\Steamware\IOB-WIN-FANUC-DEB /MIR REM ROBOCOPY %2 \\10.51.90.14\Steamware\IOB-WIN-FANUC-DEB /MIR REM ROBOCOPY %2 \\10.51.90.13\Steamware\IOB-WIN-FANUC-DEB /MIR -REM ROBOCOPY %2 \\10.51.90.15\Steamware\IOB-WIN-FANUC-DEB /MIR -ROBOCOPY %2 \\10.51.90.9\Steamware\IOB-WIN-FANUC-DEB /MIR +ROBOCOPY %2 \\10.51.90.15\Steamware\IOB-WIN-FANUC-DEB /MIR +REM ROBOCOPY %2 \\10.51.90.9\Steamware\IOB-WIN-FANUC-DEB /MIR goto END