using IOB_UT_NEXT; using IOB_UT_NEXT.Config; using MapoSDK; using Newtonsoft.Json; using NLog; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace IOB_WIN_FORM { public partial class AdapterForm : Form { #region Public Fields /// /// oggetto logging /// public static Logger lg; /// /// Configurazione gerarchica completa (v 4.x.x.x) /// public IobConfTree IOBConfFull; /// /// Oggetto x gestione dell'adapter GENERICO (x poter usare metodi di ognuno...) /// public Iob.Generic iobObj; #endregion Public Fields #region Public Constructors /// /// Avvio MainForm /// /// public AdapterForm(string codIOB) { CurrIOB = codIOB; // continuo avvio... InitializeComponent(); myGraphInitForm(); checkEditMes2Plc(); // inizializzo orologi firstStart = DateTime.Now; lastStartTry = DateTime.Now.AddHours(-1); initDatamonitor(); waitRecMSec = utils.CRI("waitRecMSec"); LogManager.ReconfigExistingLoggers(); lg = LogManager.GetCurrentClassLogger(); displayTaskAndLog("AdapterForm Starting"); } #endregion Public Constructors #region Public Properties public long alQueueLen { set { qAlLen = value; lblQueueAlarmLen.Text = qAlLen.ToString(); showQueueData(); // se supero max precedente, ed è > 10... loggo! if (qAlLen > maxAlQueue && qAlLen > 10) { maxAlQueue = qAlLen; lgInfo($"[WARN] Coda FLog di {value} record"); } else { maxAlQueue--; maxAlQueue = maxAlQueue < qAlLen ? qAlLen : maxAlQueue; } } get { return qAlLen; } } /// /// Button restart encapsulato x gestione da classi derivate /// public Button btnRestart { get => restart; } /// /// Button start encapsulato x gestione da classi derivate /// public Button btnStart { get => start; } /// /// Button stop encapsulato x gestione da classi derivate /// public Button btnStop { get => stop; } /// /// Visualizzazione stato di comunicazione attiva con PLC /// public bool commPlcActive { get { return _commPlcActive; } set { if (_commPlcActive != value) { _commPlcActive = value; // Se l'applicazione è minimizzata, NON invocare la UI: risparmio il Context Switch del thread grafico. if (!ShouldUpdateUI()) return; // se true --> comunica/verde, altrimenti grigio this.UIThread(delegate { lblCNC.ForeColor = value ? Color.SeaGreen : Color.Black; //statusStrip1.Refresh(); statusStrip1.Invalidate(); }); } } } /// /// Visualizzazione stato di comunicazione attiva con PLC: 0 = NO PING 1 = PING OK 2 = IOB enabled /// public int commSrvActive { get { return _commSrvActive; } set { _commSrvActive = value; if (!ShouldUpdateUI()) return; this.UIThread(delegate { switch (value) { case 0: lblSrvUrl.ForeColor = Color.Red; break; case 1: lblSrvUrl.ForeColor = Color.OrangeRed; break; case 2: lblSrvUrl.ForeColor = Color.SeaGreen; break; default: break; } statusStrip1.Refresh(); }); } } public string counterIob { get { return lblPzCountIob.Text; } set { lblPzCountIob.Text = value; } } public string counterMac { get { return lblPzCountMac.Text; } set { lblPzCountMac.Text = value; } } /// /// Modello macchina /// public string curModel { get => IOBConfFull != null ? IOBConfFull.Device.Model : ""; } /// /// Vendor macchina /// public string curVendor { get => IOBConfFull != null ? IOBConfFull.Device.Vendor : ""; } /// /// Stringa dati monitoraggio mostrata (1 SX)... /// public string dataMonitor_0 { set { DateTime adesso = DateTime.Now; // salvo in cima alla lista... dMonValues[0].Insert(0, value); // se supero limite --> trim! if (dMonValues[0].Count > nLine2show) { dMonValues[0] = dMonValues[0].Take(nLine2show).ToList(); } if (dMonDisplVetoVeto[0] < adesso) { // update veto! dMonDisplVetoVeto[0] = adesso.AddMilliseconds(delayShowLogMs); if (!ShouldUpdateUI()) return; // se scaduto veto --> display! lblRawData.Text = String.Join(Environment.NewLine, dMonValues[0]); } } } /// /// Stringa dati monitoraggio mostrata (1 SX)... /// public string dataMonitor_1 { set { DateTime adesso = DateTime.Now; // salvo nell'array... dMonValues[1].Insert(0, value); // se supero limite --> trim! if (dMonValues[1].Count > nLine2show) { dMonValues[1] = dMonValues[1].Take(nLine2show).ToList(); } if (dMonDisplVetoVeto[1] < adesso) { // update veto! dMonDisplVetoVeto[1] = adesso.AddMilliseconds(delayShowLogMs); if (!ShouldUpdateUI()) return; // se scaduto veto --> display! lblOutMessage.Text = String.Join(Environment.NewLine, dMonValues[1]); } } } /// /// Stringa dati monitoraggio mostrata (2 centro)... /// public string dataMonitor_2 { set { DateTime adesso = DateTime.Now; // salvo nell'array... dMonValues[2].Insert(0, value); // se supero limite --> trim! if (dMonValues[2].Count > nLine2show) { dMonValues[2] = dMonValues[2].Take(nLine2show).ToList(); } if (dMonDisplVetoVeto[2] < adesso) { // update veto! dMonDisplVetoVeto[2] = adesso.AddMilliseconds(delayShowLogMs); if (!ShouldUpdateUI()) return; // se scaduto veto --> display! lblOutMessage2.Text = String.Join(Environment.NewLine, dMonValues[2]); } } } /// /// Stringa dati monitoraggio mostrata (3 dx)... /// public string dataMonitor_3 { set { DateTime adesso = DateTime.Now; // salvo nell'array... dMonValues[3].Insert(0, value); // se supero limite --> trim! if (dMonValues[3].Count > nLine2show) { dMonValues[3] = dMonValues[3].Take(nLine2show).ToList(); } if (dMonDisplVetoVeto[3] < adesso) { // update veto! dMonDisplVetoVeto[3] = adesso.AddMilliseconds(delayShowLogMs); if (!ShouldUpdateUI()) return; // se scaduto veto --> display! lblOutMessage3.Text = String.Join(Environment.NewLine, dMonValues[3]); } } } /// /// label del numero di record processati (libera) /// public string dataProcLabel { get { return lblDataProc.Text; } set { lblDataProc.Text = value; } } /// /// File configurazione default /// public string defConfFilePath { get { return Path.Combine(utils.confDir, $"{CurrIOB}.ini"); } } /// /// File configurazione json (completo) se presente /// public string defConfFilePathJson { get { return Path.Combine(utils.confDir, $"{CurrIOB}.json"); } } /// /// File configurazione yaml (completo) se presente /// public string defConfFilePathYaml { get { return Path.Combine(utils.confDir, $"{CurrIOB}.yaml"); } } public bool enableEditMes2Plc { get; set; } public long evQueueLen { set { qEvLen = value; lblQueueLen.Text = qEvLen.ToString(); showQueueData(); // se supero max precedente, ed è > 10... loggo! if (qEvLen > maxEvQueue && qEvLen > 10) { maxEvQueue = qEvLen; lgInfo($"[WARN] Coda EV di {value} record"); } else { maxEvQueue--; maxEvQueue = maxEvQueue < qEvLen ? qEvLen : maxEvQueue; } } get { return qEvLen; } } public long flQueueLen { set { qFlLen = value; lblQueueFLogLen.Text = qFlLen.ToString(); showQueueData(); // se supero max precedente, ed è > 10... loggo! if (qFlLen > maxFlQueue && qFlLen > 10) { maxFlQueue = qFlLen; lgInfo($"[WARN] Coda FLog di {value} record"); } else { maxFlQueue--; maxFlQueue = maxFlQueue < qFlLen ? qFlLen : maxFlQueue; } } get { return qFlLen; } } /// /// Log verboso da configurazione (SOLO CHIAVE "verbose"... /// public bool isVerboseLog { get; set; } = utils.CRB("verbose"); public string lblCncText { get => lblCNC.Text; set => lblCNC.Text = value; } public string lblSrvUrlText { get => lblSrvUrl.Text; set => lblSrvUrl.Text = value; } /// /// Logwatcher (in modalità "accodamento in testa" ultimi messaggi...) /// public string logWatcher { get { return lblLogfile.Text; } set { try { // aggiungo in testa la NUOVA stringa (eventualmente multiline) logWatchString.Enqueue(value); // se supero limite --> trim! string sTemp = ""; while (logWatchString.Count > nLine2show) { logWatchString.TryDequeue(out sTemp); //logWatchString = logWatchString.Take(nLine2show).ToList(); } DateTime adesso = DateTime.Now; if (logWatchWriteVeto < adesso) { if (!ShouldUpdateUI()) return; this.UIThread(delegate { lblLogfile.Text = string.Join(Environment.NewLine, logWatchString); lblLogfile.Refresh(); }); logWatchWriteVeto = adesso.AddMilliseconds(delayShowLogMs); } } catch (Exception exc) { lgError($"Errore in esecuzione logWatcher{Environment.NewLine}--> {value}"); if (isVerboseLog) { lgError($"{exc}"); } } } } public long msQueueLen { set { qMsLen = value; lblQueueMessLen.Text = qMsLen.ToString(); showQueueData(); // se supero max precedente, ed è > 10... loggo! if (qMsLen > maxMsQueue && qMsLen > 10) { maxMsQueue = qMsLen; lgInfo($"[WARN] Coda Ms di {value} record"); } else { maxMsQueue--; maxMsQueue = maxMsQueue < qMsLen ? qMsLen : maxMsQueue; } } get { return qMsLen; } } /// /// Numero max linee da mostrare (da controllo)... /// public int nLine2show { get { int answ = 5; try { Int32.TryParse(nLines.Text, out answ); } catch { } return answ; } set { nLines.Text = value.ToString(); } } public long rtrQueueLen { set { qRTrLen = value; lblQueueRwTrLog.Text = qRTrLen.ToString(); showQueueData(); // se supero max precedente, ed è > 10... loggo! if (qRTrLen > maxRwTrQueue && qRTrLen > 10) { maxRwTrQueue = qRTrLen; lgInfo($"[WARN] Coda RawTransf di {value} record"); } else { maxRwTrQueue--; maxRwTrQueue = maxRwTrQueue < qRTrLen ? qRTrLen : maxRwTrQueue; } } get { return qRTrLen; } } /// /// Imposta COLORE SFONDO x Semaforo IN /// public Semaforo sIN { get { return _sIN; } set { _sIN = value; var newColor = decSemaforo(value); if (newColor != bIN.BackColor) { if (!ShouldUpdateUI()) return; this.UIThread(delegate { bIN.BackColor = newColor; bIN.Refresh(); }); } } } /// /// Imposta COLORE SFONDO x Semaforo OUT /// public Semaforo sOUT { get { return _sOUT; } set { _sOUT = value; var newColor = decSemaforo(value); if (newColor != bOUT.BackColor) { if (!ShouldUpdateUI()) return; this.UIThread(delegate { bOUT.BackColor = newColor; bOUT.Refresh(); }); } } } /// /// Task watcher (in modalità "accodamento in testa" ultimi messaggi...) /// public string taskWatcher { get { return lblTaskLog.Text; } set { try { logTaskString.Insert(0, value); // se supero limite --> trim! if (logTaskString.Count > nLine2show) { logTaskString = logTaskString.Take(nLine2show).ToList(); } if (!ShouldUpdateUI()) return; this.UIThread(delegate { lblTaskLog.Text = string.Join(Environment.NewLine, logTaskString); lblTaskLog.Refresh(); }); } catch (Exception exc) { lgError($"Errore in esecuzione taskWatcher{Environment.NewLine}--> {value}"); if (isVerboseLog) { lgError($"{exc}"); } } } } /// /// tipo di adapter prescelto... /// public tipoAdapter tipoScelto { get => IOBConfFull != null ? IOBConfFull.General.IobType : tipoAdapter.ND; } public long ulQueueLen { set { qUlLen = value; lblQueueULog.Text = qUlLen.ToString(); showQueueData(); // se supero max precedente, ed è > 10... loggo! if (qUlLen > maxUlQueue && qUlLen > 10) { maxUlQueue = qUlLen; lgInfo($"[WARN] Coda UL di {value} record"); } else { maxUlQueue--; maxUlQueue = maxUlQueue < qUlLen ? qUlLen : maxUlQueue; } } get { return qUlLen; } } #endregion Public Properties #region Public Methods /// /// Decodifica colore da valore semaforico /// /// /// public static Color decSemaforo(Semaforo valore) { Color colore = Color.LightGray; switch (valore) { case Semaforo.SV: colore = Color.LightGreen; break; case Semaforo.SG: colore = Color.LightGoldenrodYellow; break; case Semaforo.SR: colore = Color.Red; break; case Semaforo.SS: colore = Color.DarkGray; break; default: colore = Color.LightGray; break; } return colore; } /// /// Avvio l'adapter /// /// indica se sia richiesto di SVUOTARE le code delle info public void AvviaAdapter(bool resetQueue) { displayTaskAndLog("Adapter starting", true); // se NON sta girando... if (!iobObj.adpRunning) { iobObj.startAdapter(resetQueue); displayTaskAndLog("Adapter started!"); // fix buttons start/stop/dump start.Enabled = false; stop.Enabled = true; restart.Enabled = true; // 2026.01.02 fix timers al restart gather.Enabled = true; // gestione worker StartWorker(); displayTaskAndLog("Start Timers", true); // inizializzo le scadenze dei timers... TimersVeto = new Dictionary(); TimersCycleCount = new Dictionary(); TimersCycleElaps = new Dictionary(); DateTime adesso = DateTime.Now; foreach (gatherCycle item in Enum.GetValues(typeof(gatherCycle))) { // setup iniziale deterministico TimersVeto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); TimersCycleCount.Add(item, 0); TimersCycleElaps.Add(item, 0); } sendStartFLog = utils.CRB("sendStartFLog"); displayTaskAndLog("Adapter Running...", true); // init max queue maxEvQueue = 1; maxFlQueue = 1; try { if (sendStartFLog) { // segnalo reboot (programma)... iobObj.QueueFLog.Enqueue(iobObj.qEncodeFLog("IOB-STATUS", "IOB Adapter Started")); } } catch (Exception exc) { lgError(string.Format("EXCEPTION in fase di chiamata URL di segnalazione AVVIO IOB:{0}{1}", Environment.NewLine, exc)); } } else { displayTaskAndLog("Adapter STILL Running...", true); } } /// /// mostra un testo sulla status bar + LOG /// /// /// public void displayTaskAndLog(string txt2show, bool forceDebug = false) { if (forceDebug) { lgDebug(txt2show); } else { lgInfo(txt2show); } if (!ShouldUpdateUI()) return; lblStatus.Text = txt2show; lblStatus.Invalidate(); } /// /// Ferma l'adapter /// /// /// determina se si debba tentare riavvio automatico (per caduta connessione) /// /// indica se sia richiesto di SVUOTARE le code delle info /// indica se aggiornare la form prima di fermare public void fermaAdapter(bool tryRestart, bool forceDequeue, bool updateForm) { lgInfo($"fermaAdapter | tryRestart: {tryRestart} | forceDequeue: {forceDequeue} | updateForm: {updateForm}"); //fermaTutto(false, tryRestart, forceDequeue, updateForm); fermaTutto(false, tryRestart, forceDequeue, updateForm); } /// /// Mostra update delle statistiche di comunicazione (numero chiamate, tempo medio...) /// /// public void updateComStats(string txt2show) { if (!ShouldUpdateUI()) return; lblComStats.Text = string.Format("{0} | ", txt2show); } /// /// Effettua update form una volta ricevuto OBJ che contiene le varie innovazioni... /// /// public void updateFormDisplay(newDisplayData currDispData) { if (!ShouldUpdateUI()) return; this.UIThread(delegate { // ciclo x ogni oggetto x caricare le innovazioni... if (currDispData != null && currDispData.hasData) { DateTime adesso = DateTime.Now; // RealTime display if (!string.IsNullOrWhiteSpace(currDispData.newInData)) { dataMonitor_0 = currDispData.newInData; } if (!string.IsNullOrWhiteSpace(currDispData.newSignalData)) { dataMonitor_1 = currDispData.newSignalData; } if (!string.IsNullOrWhiteSpace(currDispData.newFLogData)) { dataMonitor_3 = currDispData.newFLogData; } if (!string.IsNullOrWhiteSpace(currDispData.newUrlCallData)) { dataMonitor_2 = currDispData.newUrlCallData; } // Bitmap lettura attuale if (currDispData.counter >= 0) { if (logCounterVeto < adesso || lblCounter.Text != $"{currDispData.counter}") { lblCounter.Text = $"{currDispData.counter}"; lblCounter.Refresh(); logCounterVeto = adesso.AddMilliseconds(delayShowLogMs); } } // Bitmap lettura attuale if (!string.IsNullOrWhiteSpace(currDispData.currBitmap)) { lblBitmap.Text = currDispData.currBitmap; lblBitmap.Refresh(); } // LiveLog if (!string.IsNullOrWhiteSpace(currDispData.newLiveLogData)) { logWatcher = currDispData.newLiveLogData; } // semafori if (currDispData.semOut != Semaforo.ND) { // aggiorno SE diverso if (sOUT != currDispData.semOut) { sOUT = currDispData.semOut; } } if (currDispData.semIn != Semaforo.ND) { //aggiorno SE diverso if (sIN != currDispData.semIn) { sIN = currDispData.semIn; } } } }); } public void WriteTextSafe(string text) { if (!ShouldUpdateUI()) return; this.UIThread(delegate { // accoda messaggio in gestione dataMonitor_3 = text; }); } #endregion Public Methods #region Protected Fields protected static string baseUrl = @"https://liman.egalware.com/MP/IO/"; /// /// Data-Ora prima apertura FORM... /// protected DateTime firstStart; /// /// Oggetto ultimo inviato stato IOB x REDIS /// protected IobWinStatus lastIobStatus = new IobWinStatus() { lastUpdate = DateTime.Now.AddHours(-1), lastDataOut = DateTime.Now.AddHours(-1) }; /// /// Oggetto ultimo inviato stato MP-IO x REDIS /// protected ServerMpStatus lastSrvStatus = new ServerMpStatus(); /// /// ultimo tentativo riavvio... /// protected DateTime lastStartTry; /// /// Generatore numeri random /// protected Random rndGen = new Random(); /// /// Contatore campionamento memoria /// protected int sampleMemCount; /// /// Indica se inviare avvio adapter con FluxLog /// protected bool sendStartFLog = false; /// /// Temnpo attesa std in MS /// protected int waitRecMSec = 30000; #endregion Protected Fields #region Protected Properties /// /// Valore protected comunicazione PLC /// protected bool _commPlcActive { get; set; } = false; /// /// Valore protetto stato comunicazione /// protected int _commSrvActive { get; set; } = 0; /// /// Valore protected semaforo IN /// protected Semaforo _sIN { get; set; } = Semaforo.ND; /// /// Valore protected semaforo OUT /// protected Semaforo _sOUT { get; set; } = Semaforo.ND; /// /// Codice IOB della macchina cui connettersi (x scegliere corretto file di conf...) /// protected string CurrIOB { get; set; } protected int delayShowLogMs { get; set; } = utils.CRI("delayShowLogMs"); /// /// Dictionary dei divieti del datamonitor /// protected Dictionary dMonDisplVetoVeto { get; set; } = new Dictionary(); /// /// array degli oggetti datamonitor da mostrare /// protected Dictionary> dMonValues { get; set; } = new Dictionary>(); /// /// Veto a NUOVE scritture in COUNTER... /// protected DateTime logCounterVeto { get; set; } = DateTime.Now; /// /// Lista String da mostrare quale TaskLog corrente... /// protected List logTaskString { get; set; } = new List(); /// /// Lista String da mostrare quale WatchLog corrente... /// protected ConcurrentQueue logWatchString { get; set; } = new ConcurrentQueue(); /// /// Veto a NUOVE scritture in logWatch... /// protected DateTime logWatchWriteVeto { get; set; } = DateTime.Now; protected long maxAlQueue { get; set; } protected long maxEvQueue { get; set; } protected long maxFlQueue { get; set; } protected long maxMsQueue { get; set; } protected long maxRwTrQueue { get; set; } protected long maxUlQueue { get; set; } protected long qAlLen { get; set; } protected long qEvLen { get; set; } protected long qFlLen { get; set; } protected long qMsLen { get; set; } protected long qRTrLen { get; set; } protected long qUlLen { get; set; } protected long totQueue { get { return qEvLen + qFlLen + qAlLen + qMsLen + qUlLen; } } /// /// URL per salvare i file dell'IOB (SENZA IOB) /// protected string urlUploadFile { get => $"http://{IOBConfFull.MapoMes.IpAddr}{IOBConfFull.MapoMes.BaseAppUrl}IOB/uploadFile/"; } /// /// URL per salvare i file dell'IOB su CLOUD (SENZA IOB) /// protected string urlUploadFileCloud { get => $"{baseUrl}IOB/uploadFile/"; } #endregion Protected Properties #region Protected Methods protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } /// /// GEstione evento refresh /// /// /// protected void IobObj_eh_refreshed(object sender, iobRefreshedEventArgs e) { if (!ShouldUpdateUI()) return; // aggiorno! this.UIThread(delegate { updateFormDisplay(e.DisplayDataObject); }); } /// /// Effettua logging DEBUG corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgDebug(string txt2log) { lg.Factory.Configuration.Variables["codIOB"] = this.CurrIOB;//IOBConfFull.General.FilenameIOB ??this.CurrIOB; lg.Debug(txt2log); // salvo anche in logwatcher... newDisplayData currDispData = new newDisplayData(); currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | INFO | {txt2log}"; updateFormDisplay(currDispData); } /// /// Effettua logging ERROR corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgError(string txt2log) { if (!string.IsNullOrEmpty(txt2log)) { lg.Factory.Configuration.Variables["codIOB"] = this.CurrIOB;//IOBConfFull.General.FilenameIOB ??this.CurrIOB; lg.Error(txt2log); // salvo anche in logwatcher... SE non si dimostra ricorsivo... if (!txt2log.Contains("logWatcher")) { newDisplayData currDispData = new newDisplayData(); currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | ERROR | {txt2log}"; updateFormDisplay(currDispData); } } } /// /// Effettua logging INFO corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgInfo(string txt2log) { lg.Factory.Configuration.Variables["codIOB"] = this.CurrIOB;//IOBConfFull.General.FilenameIOB ??this.CurrIOB; lg.Info(txt2log); // salvo anche in logwatcher... newDisplayData currDispData = new newDisplayData(); currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | INFO | {txt2log}"; updateFormDisplay(currDispData); } /// /// Effettua logging TRACE corretto impostanto anche la variabile IOB prima di scrivere... /// /// protected void lgTrace(string txt2log) { lg.Factory.Configuration.Variables["codIOB"] = this.CurrIOB;//IOBConfFull.General.FilenameIOB ??this.CurrIOB; lg.Trace(txt2log); // salvo anche in logwatcher... newDisplayData currDispData = new newDisplayData(); currDispData.newLiveLogData = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} | TRACE | {txt2log}"; updateFormDisplay(currDispData); } /// /// carica IOB richiesto /// protected virtual async Task loadIobType() { if (IOBConfFull != null) { switch (tipoScelto) { case tipoAdapter.ND: default: iobObj = new Iob.Simula(this, IOBConfFull); start.Enabled = false; break; } if (!await iobInitAsync()) { return; } UpdateDisplTypeIobSel(); } } /// /// 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 /// protected void myGraphInitForm() { lblStatus.Text = "Loading"; // aggiunta tooltip x send forzato ToolTip ttForceSend = new ToolTip(); // imposto delay. ttForceSend.AutoPopDelay = 3000; ttForceSend.InitialDelay = 500; ttForceSend.ReshowDelay = 500; // sempre visibile (che sia o meno attiva la form). ttForceSend.ShowAlways = true; // imposto tooltip ttForceSend.SetToolTip(this.chkForceDequeue, "Forza invio eventi allo stop"); } protected override void OnFormClosing(FormClosingEventArgs e) { CloseTimers(); base.OnFormClosing(e); } /// /// impostazione valori defaults /// protected void setDefaults() { stop.Enabled = false; alQueueLen = 0; flQueueLen = 0; msQueueLen = 0; rtrQueueLen = 0; ulQueueLen = 0; 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 /// protected void showQueueData() { lblQueueLenTop.Text = totQueue == 0 ? "realtime" : $"ev: {qEvLen} | flog: {qFlLen} | tot: {totQueue}"; } /// /// Update display info tipo IOB selezionato /// protected void UpdateDisplTypeIobSel() { lblCNC.Text = $"CNC: {IOBConfFull.General.IobType} [{IOBConfFull.Device.Connect.IpAddr}:{IOBConfFull.Device.Connect.Port}]"; lblSrvUrl.Text = $"SRV: {IOBConfFull.MapoMes.IpAddr} | BaseURL: {IOBConfFull.MapoMes.ApiUrl("")}"; // aggancio evento refresh iobObj.eh_refreshed += IobObj_eh_refreshed; // carico i default values su interfaccia setDefaults(); displayTaskAndLog($"Caricata conf per adapter {tipoScelto}"); } #endregion Protected Methods #region Private Fields private readonly object _lock = new object(); private CancellationTokenSource _cts; // Per thread-safety private bool _isSuspended = false; private Task _workerTask; /// /// Contatore chiamate timers x debug timing /// private Dictionary TimersCycleCount = new Dictionary(); /// /// Contatore durata exec x debug timing /// private Dictionary TimersCycleElaps = new Dictionary(); /// /// Dizionario scadenza timers interni x esecuzione dei vari task a differente frequenza di chiamata /// private Dictionary TimersVeto = new Dictionary(); /// /// Dizionario dei valori bloccati x evitare log eccessivo /// private Dictionary vetoLogError = new Dictionary(); /// /// Periodo di veto log in minuti /// private int vetoPeriodMin = 15; #endregion Private Fields #region Private Delegates private delegate void SafeCallDelegate(string text); #endregion Private Delegates #region Private Methods /// /// Esecuzione dei metodi in modalità async dopo il load della Form /// /// /// private async void AdapterForm_Load(object sender, EventArgs e) { // se abilitato autoload conf leggo file corretto... if (utils.CRB("autoLoadConf")) { try { if (File.Exists(defConfFilePathYaml)) { loadYamlFile(defConfFilePathYaml); await sendConfFile(defConfFilePathYaml); } else { loadIniFile(defConfFilePath); await sendConfFile(defConfFilePath); } lgInfo("INI LOADED"); } catch (Exception exc) { displayTaskAndLog(string.Format("Eccezione in autoLoadConf: {0}", exc)); } } if (IOBConfFull == null || !utils.CRB("autoLoadConf")) { IOBConfFull = new IobConfTree(); loadIobType(); displayTaskAndLog("Waiting for config file selection"); } // Start timer periodico comunicazione gather.Interval = IOBConfFull.General.Timers.MsUI; gather.Enabled = true; displayTaskAndLog($"Main UI timer set: {gather.Interval}ms", true); // Start timer periodico interfaccia displTimer.Interval = utils.CRI("timerIntMs"); displTimer.Enabled = true; displayTaskAndLog("UI Program Running", true); // check oggetto not null if (iobObj != null) { //verifico sia da inviare, ovvero non ci sia veto redis (da 12h..) bool needSend = false; string redKey = iobObj.redisMan.redHash($"IOB:SendReboot:{CurrIOB}"); var rawVal = iobObj.redisMan.getRSV(redKey); // verifico uguaglianza MD5... if (string.IsNullOrEmpty(rawVal)) { needSend = true; // attendo meno di 1 min x il prox... int ttlSec = rndGen.Next(20, 30); iobObj.redisMan.setRSV(redKey, $"{DateTime.Now}", ttlSec); } try { // verifico server online... bool srvAlive = iobObj.CheckServerAlive(); if (srvAlive) { if (needSend) { // segnalo reboot (programma - url file)... await Iob.Generic.callUrl(iobObj.urlReboot, true); } } else { displayTaskAndLog("AdapterForm: Server OFFLINE"); } } catch (Exception exc) { lgError($"AdapterForm: EXCEPTION in fase di chiamata URL di reboot:{iobObj.urlReboot}{Environment.NewLine}{exc}"); } // avvio timer secondario x esecuzione (periodo di base: VHF!!!) StartWorker(); displayTaskAndLog($"Main workerTimer set: {IOBConfFull.General.Timers.MsVHF}ms", true); } displayTaskAndLog("Main Form OK", true); } private void btnForceAutoOdl_Click(object sender, EventArgs e) { iobObj.forceResetOdl(); } private void BtnOpenLog_Click(object sender, EventArgs e) { try { string logPath = $"{System.AppDomain.CurrentDomain.BaseDirectory}logs\\{CurrIOB}\\{DateTime.Today:yyyy-MM-dd}.log"; Process.Start(logPath); } catch (Exception exc) { lgError($"Eccezione in show log (MAIN):{Environment.NewLine}{exc}"); } } private void BtnSendPLC_Click(object sender, EventArgs e) { // invia a MES il parametro selezionato (es ART / ODL / PRG NAME, come task2exe..) Dictionary forcedTask = new Dictionary(); // guardo i parametri... if (!string.IsNullOrEmpty(txtValue.Text)) { forcedTask.Add(cmbParamValues.SelectedValue.ToString(), txtValue.Text); } // chiedo esecuzione task! forzato SENZA tavola _ = iobObj.ProcessTask(forcedTask, ""); chkEdit.Checked = false; toggleEditMes2Plc(); } private void checkAssignSize() { int valSize = 0; int.TryParse(txtMReadSize.Text, out valSize); // verifico sia valida con lenght array... int maxSize = iobObj.RawInput.Length - iobObj.RawDataInputStart; valSize = valSize > maxSize ? maxSize : valSize; // riporto iobObj.RawDataInputSize = valSize; } private void checkAssignStart() { int valStart = 0; int.TryParse(txtMReadStart.Text, out valStart); // verifico sia valida con start/lenght array... int maxStart = iobObj.RawInput.Length - 1; valStart = valStart > maxStart ? maxStart : valStart; // riporto iobObj.RawDataInputStart = valStart; } private void checkEditMes2Plc() { cmbParamValues.Enabled = enableEditMes2Plc; txtValue.Enabled = enableEditMes2Plc; if (enableEditMes2Plc) { // aggiorno (se possibile) i parametri selezionabili... fixComboParameters(); } } private void checkSampleMem() { // decremento contatore... sampleMemCount--; if (sampleMemCount <= 0) { sampleMemCount = utils.CRI("sampleMemCount"); // avvio fase raccolta dati e invio con adapter iobObj.saveMemDump(dumpType.SAMPLE); } } /// /// Verifica scadenza task /// /// private async Task checkScad() { 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 (TimersVeto.ContainsKey(item)) { if (TimersVeto[item] <= adesso) { // se non ho già processato a questo giro... if (!sendDone) { TimersCycleCount[item]++; Stopwatch sw = Stopwatch.StartNew(); await iobObj.getAndSendAsync(item); sw.Stop(); sendDone = true; TimersCycleElaps[item] += sw.Elapsed.TotalMilliseconds; // metto nuova scadenza perturbata 10% TimersVeto[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 = TimersCycleElaps.ContainsKey(item) ? TimersCycleElaps[item] : 0; if (TimersCycleCount[item] >= 100 || totalMs > limMs) { int nCount = Math.Max(TimersCycleCount[item], 1); lgDebug($"checkScad | {item} | {totalMs / nCount:F1}ms x {nCount}"); TimersCycleCount[item] = 0; TimersCycleElaps[item] = 0; } } } else { TimersVeto.Add(item, adesso.AddMilliseconds(iobObj.IOBConfFull.TimerMs(item, 0))); TimersCycleCount.Add(item, 0); if (!TimersCycleElaps.ContainsKey(item)) TimersCycleElaps.Add(item, 0); } } } private void ChkEdit_CheckedChanged(object sender, EventArgs e) { toggleEditMes2Plc(); } /// /// Chiusura adapter /// private void closeAdapter() { lgDebug("closeAdapter | true | false | true | false"); fermaTutto(true, false, true, false); } private void CloseTimers() { displTimer?.Stop(); displTimer?.Dispose(); gather?.Stop(); gather?.Dispose(); _isSuspended = true; _ = StopWorker(); } private void displTimer_Tick(object sender, EventArgs e) { } /// /// Metodo principale esecuzione task in thread background (no interferenza con UI) x processi IO bound /// private async Task DoExecTasks() { bool doLog = false; // procedo! if (iobObj.periodicLog) { doLog = true; } try { // check esecuzione SendTask (MsVHF) COMUNQUE... await iobObj.getAndSendAsync(gatherCycle.VHF); // eseguo cicli attivi SOLO se adapter è in EFFETTIVO running... if (iobObj.adpRunning) { if (iobObj.connectionOk) { // 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(); } // controllo TUTTE le scadenze... checkScad(); if (utils.CRI("waitEndCycle") > 0) { await Task.Delay(utils.CRI("waitEndCycle")); } } 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(); } } } 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(string.Format("Eccezione in fase di gatherTick: {0}{1}", Environment.NewLine, exc)); } } /// /// Ferma tutti i componenti adapter + update buttons /// /// /// indica se fermare il timer (gather) principale (solo se non si chiude) /// /// indica se tentare di riconnettersi /// indica se sia richiesto di SVUOTARE le code delle info /// indica se si debba aggiornare la form (no se si sta chiudendo...) private void fermaTutto(bool stopTimer, bool tryRestart, bool forceDequeue, bool updateForm) { if (iobObj != null) { try { try { // PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione Task.Run(async () => { await iobObj.stopAdapter(tryRestart, forceDequeue); stop.Enabled = false; start.Enabled = true; restart.Enabled = false; _isSuspended = true; if (stopTimer) { gather.Enabled = false; await StopWorker(); } }) .GetAwaiter() .GetResult(); } catch (Exception ex) { 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; if (updateForm) { updateFormDisplay(currDispData); } } catch (Exception exc) { lgError($"Errore in chiusura:{Environment.NewLine}{exc}"); } } } private void fixComboParameters() { if (iobObj != null) { if (iobObj.memMap != null) { if (iobObj.memMap.mMapWrite != null) { if (iobObj.memMap.mMapWrite.Count > 0) { var oldParams = cmbParamValues.DataSource; List parametri = new List(); foreach (var item in iobObj.memMap.mMapWrite) { parametri.Add(item.Key); } // salvo selezione int oldIdx = cmbParamValues.SelectedIndex; // riassegno e ri-seleziono cmbParamValues.DataSource = parametri; cmbParamValues.SelectedIndex = oldIdx >= 0 ? oldIdx : 0; } } } } } /// /// Evento principale scadenza del timer interno MsUI da cui scaturire le varie esecuzioni /// /// /// private void gather_Tick(object sender, EventArgs e) { if (iobObj != null) { // fermo il timer... gather.Stop(); try { refreshFormData(); } catch (Exception exc) { lgError(string.Format("Eccezione in fase di gatherTick: {0}{1}", Environment.NewLine, exc)); } // riavvio il timer... gather.Start(); } } /// /// Init oggetti datamonitor /// private void initDatamonitor() { for (int i = 0; i < 4; i++) { dMonDisplVetoVeto.Add(i, DateTime.Now); dMonValues.Add(i, new List()); } } /// /// Carica file ini della configurazione richiesta /// /// private void loadIniFile(string iniConfFile) { // out di cosa faccio... displayTaskAndLog($"[STARTUP] Loading ConfFile: {iniConfFile}"); var appVers = Assembly.GetExecutingAssembly().GetName().Version; string path = fileMover.GetExecutingDirectoryName(); // Directory.GetCurrentDirectory(); string fileName = Path.GetFileName(iniConfFile).Replace(".ini", ".iob"); string dirPath = Path.Combine(path, "DATA", "CONF_RUN"); // verifica directory baseUtils.checkDir(dirPath); string fullPath = Path.Combine(dirPath, fileName); // preparazione file conf con nuovo formato e salvataggio... IOBConfFull = IobConfTree.LoadFromINI(iniConfFile); if (IOBConfFull.General.FileName.EndsWith("ini")) { IOBConfFull.General.FileName = IOBConfFull.General.FileName.Replace("ini", "yaml"); } // salvo anche yaml... IOBConfFull.SaveYaml(fullPath.Replace("iob", "yaml")); // carico IOB loadIobType(); // invio file di conf! IOBConfFull.SendConfYaml(iobObj.urlSaveConfYaml); // avvio macchina con adapter specificato... if (utils.CRB("autoStartOnLoad")) { displayTaskAndLog("Auto Starting...", true); // avvio! AvviaAdapter(chkForceDequeue.Checked); displayTaskAndLog("Auto Started!", true); } } /// /// Carica file yaml della configurazione richiesta /// /// private void loadYamlFile(string yamlConfFile) { // out di cosa faccio... displayTaskAndLog($"[STARTUP] Loading yamlConfFile: {yamlConfFile}"); // leggo file IniFile fIni = new IniFile(yamlConfFile); // carivo vettore parametri opzionai Dictionary optParRead = new Dictionary(); string[] optParRows = fIni.ReadSection("OPTPAR"); if (optParRows.Length > 0) { try { string[] kvp; foreach (var item in optParRows) { kvp = item.Split('='); optParRead.Add(kvp[0], kvp[1]); } lgDebug($"Caricati {optParRead.Count} parametri opzionali da OPTPAR"); } catch (Exception exc) { lgError(string.Format("EXCEPTION in fase di lettura OPTPAR: {0}{1}", Environment.NewLine, exc)); } } var appVers = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; loadIobType(); // avvio macchina con adapter specificato... if (utils.CRB("autoStartOnLoad")) { displayTaskAndLog("Auto Starting...", true); // avvio! AvviaAdapter(chkForceDequeue.Checked); displayTaskAndLog("Auto Started!", true); } } /// /// Verifica se il log di un dato errore sia permesso /// /// ID del valore log da loggare/verificare /// private bool logValuePermit(string logKey) { bool doLog = false; if (vetoLogError.ContainsKey(logKey)) { // verifico se veto scaduto... if (DateTime.Now > vetoLogError[logKey]) { doLog = true; vetoLogError[logKey] = DateTime.Now.AddMinutes(vetoPeriodMin); } } else { doLog = true; vetoLogError.Add(logKey, DateTime.Now.AddMinutes(vetoPeriodMin)); } return doLog; } /// /// Fase chiusura Form /// /// /// private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { lgTrace("MainForm_FormClosing"); closeAdapter(); } /// /// Completato resize form /// /// /// private void MainForm_Resize(object sender, EventArgs e) { // Verifichiamo se la finestra è appena stata ripristinata if (this.WindowState != FormWindowState.Minimized && _lastState == FormWindowState.Minimized) { displayTaskAndLog("Adapter Form Restored from Minimized"); } _lastState = this.WindowState; } private FormWindowState _lastState = FormWindowState.Minimized; /// /// Mostrata form /// /// /// private void MainForm_Shown(object sender, EventArgs e) { displayTaskAndLog("Adapter Form SHOWN (Adapter)", true); } private void mLoadConf_Click(object sender, EventArgs e) { // mostro selettore file x leggere adapter.. OpenFileDialog openFileDial = new OpenFileDialog(); // directory iniziale openFileDial.InitialDirectory = utils.confDir; // string.Format(@"{0}\{1}", Application.StartupPath, utils.CRS("dataConfPath")); // Set filter options and filter index. openFileDial.Filter = "INI Files (.ini)|*.ini|All Files (*.*)|*.*"; openFileDial.FilterIndex = 1; // altre opzioni openFileDial.Multiselect = false; // Call the ShowDialog method to show the dialog box. DialogResult userClickedOK = openFileDial.ShowDialog(); // Process input if the user clicked OK. if (userClickedOK == DialogResult.OK) { string iniConfFile = openFileDial.FileName; loadIniFile(iniConfFile); lgInfo("INI LOADED"); } } private void refreshFormData() { // aggiorno visualizzazioni varie in form... alQueueLen = iobObj.QueueAlarm.Count; evQueueLen = iobObj.QueueIN.Count; flQueueLen = iobObj.QueueFLog.Count; msQueueLen = iobObj.QueueMessages.Count; rtrQueueLen = iobObj.QueueRawTransf.Count; ulQueueLen = iobObj.QueueULog.Count; // aggiorno labels counters... counterIob = $"pz MES {iobObj.contapezziIOB}"; counterMac = $"pz PLC {iobObj.contapezziPLC}"; Dictionary setPar = new Dictionary(); setPar.Add("IP", iobObj.IOBConfFull.Device.Connect.IpAddr); setPar.Add("PORT", iobObj.IOBConfFull.Device.Connect.Port); string note = $"{iobObj.IOBConfFull.General.FilenameIOB} | DT ultimo avvio: {iobObj.dtAvvioAdp}"; // verifico IOB status IobWinStatus currIobStatus = new IobWinStatus() { CodIob = iobObj.IOBConfFull.General.FilenameIOB, IobType = $"{iobObj.IOBConfFull.General.IobType}",//iobObj.cIobConf.tipoIob.ToString(), queueAlLen = alQueueLen, queueEvLen = evQueueLen, queueFlLen = flQueueLen, queueMsLen = msQueueLen, queueRawTransfLen = rtrQueueLen, queueUlLen = ulQueueLen, counterIOB = iobObj.contapezziIOB, counterMAC = iobObj.contapezziPLC, lastUpdate = lastIobStatus.lastUpdate > iobObj.lastWatchDog ? lastIobStatus.lastUpdate : iobObj.lastWatchDog, online = utils.IOB_Online, lastDataIn = iobObj.lastReadPLC, lastDataOut = iobObj.lastIobOnline, setupParams = setPar, freeNotes = note }; // se diverso SALVO! if (!currIobStatus.Equals(lastIobStatus)) { // aggiorno data currIobStatus.lastUpdate = DateTime.Now; // salvo su redis e in obj corrente iobObj.redisMan.iobStatus = currIobStatus; lastIobStatus = currIobStatus; } // se diverso SALVO MP IO status if (lastSrvStatus.online != utils.MPIO_Online) { // aggiorno ServerMpStatus currSrvStatus = iobObj.redisMan.servStatus; currSrvStatus.online = utils.MPIO_Online; currSrvStatus.lastUpdate = DateTime.Now; // salvo su redis e in obj corrente iobObj.redisMan.servStatus = currSrvStatus; lastSrvStatus = currSrvStatus; } } /// /// Button restart /// /// /// private void restart_Click(object sender, EventArgs e) { string message = "Attenzione: con l'operazione di reset veranno persi (e non inviati) i dati eventualmente accumulati (indipendentemente dalla selezione del checkbox 'svuota coda')."; string caption = "Conferma Reset Dati IOB"; MessageBoxButtons buttons = MessageBoxButtons.YesNo; DialogResult result; // verifica con messagebox result = MessageBox.Show(message, caption, buttons); // solo se ho conferma da utente if (result == DialogResult.Yes) { // faccio stop... SENZA inviare fermaAdapter(false, false, true); displayTaskAndLog("RESTARTING: Adapter Stopped", true); // rileggo INI loadIniFile(defConfFilePath); displayTaskAndLog("RESTARTING: Ini File Reloaded", true); // faccio start... CON RESET delle code AvviaAdapter(true); displayTaskAndLog("RESTARTING: Adapter Started", true); // resetto i data monitor... dataMonitor_0 = ""; dataMonitor_1 = ""; dataMonitor_2 = ""; dataMonitor_3 = ""; } } /// /// Invio per backup contenuto file conf /// /// private async Task sendConfFile(string confFilePath) { string answ = ""; fileEmbed objFiles = new fileEmbed(); try { if (File.Exists(confFilePath)) { string fileContent = System.IO.File.ReadAllText($"{confFilePath}"); smallFile currFile = new smallFile() { fileName = confFilePath, content = fileContent.Replace("\r\n", Environment.NewLine) }; objFiles.fileList.Add(currFile); // serializzo string rawData = JsonConvert.SerializeObject(objFiles); // mod 2022.12.30: verifico se diverso da copia redis ultimo MD5 del set // salvato, nel caso NON invio... bool needSend = false; using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create()) { var hash = md5.ComputeHash(Encoding.ASCII.GetBytes(rawData)); string newMD5 = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); string redKey = iobObj.redisMan.redHash($"IOB:ConfFileMD5:{CurrIOB}"); var rawVal = iobObj.redisMan.getRSV(redKey); // verifico uguaglianza MD5... if (string.IsNullOrEmpty(rawVal) || !newMD5.Equals(rawVal)) { needSend = true; int CacheConfToCloudDuratHour = utils.CRI("CacheConfToCloudDuratHour"); // calcolo attesa con errore random -/+ 10% int ttlSec = 60 * 60 * CacheConfToCloudDuratHour * rndGen.Next(900, 1100) / 1000; iobObj.redisMan.setRSV(redKey, newMD5, ttlSec); } } // invio solo se necessario if (needSend) { if (utils.CRB("ConfToCloud")) { // invio su cloud... answ = await utils.callUrlAsync($"{urlUploadFileCloud}{CurrIOB}", rawData); } else { // provo invio locale answ = utils.callUrl($"{urlUploadFile}{CurrIOB}", rawData); // se va male invio cloud... if (answ.ToUpper() != "OK") { answ = await utils.callUrlAsync($"{urlUploadFileCloud}{CurrIOB}", rawData); } } } } } catch (Exception exc) { lgError($"Eccezione in upload IOB conf files{Environment.NewLine}{exc}"); } } private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e) { } /// /// Avvio dell'adapter /// /// /// private void start_Click(object sender, EventArgs e) { stopForced = false; AvviaAdapter(chkForceDequeue.Checked); // salvo che ho avviato adapter lgInfo("Completato LOAD Adapter"); } /// /// Metodo per avviare il worker (es. nel Form_Load) /// private void StartWorker() { // 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 _workerTask = Task.Run(() => WorkerLoopAsync(_cts.Token)); } } /// /// fermata dell'adapter /// /// /// private void stop_Click(object sender, EventArgs e) { stopForced = true; fermaAdapter(false, chkForceDequeue.Checked, true); // salvo che ho fermato adapter lgInfo("UNLOAD Adapter"); } private bool stopForced = false; // Metodo per fermare tutto (es. nel Form_Closing o Dispose) private async Task StopWorker() { if (_cts == null) return; _cts.Cancel(); try { // Opzionale: attendi che il task finisca davvero prima di fare il dispose if (_workerTask != null) { await _workerTask; } } catch (OperationCanceledException) { /* Normale amministrazione */ } finally { if (_cts != null) { _cts.Dispose(); _cts = null; } if (_workerTask != null) { _workerTask = null; } } } private void TabData_Selected(object sender, TabControlEventArgs e) { } private void toggleEditMes2Plc() { // abilita i campi --> PLC per editing enableEditMes2Plc = !enableEditMes2Plc; checkEditMes2Plc(); } private void txtMReadSize_TextChanged(object sender, EventArgs e) { checkAssignSize(); } private void txtMReadStart_TextChanged(object sender, EventArgs e) { checkAssignStart(); checkAssignSize(); } private async Task WorkerLoopAsync(CancellationToken ct) { try { while (!ct.IsCancellationRequested) { if (!_isSuspended) { try { await DoExecTasks(); } catch (Exception ex) { lgError("Errore durante i 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 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. await Task.Delay(currentDelay, ct); } lgInfo("Worker interrotto per cancellation token."); } catch (OperationCanceledException) { // Eccezione normale quando si preme Stop/Cancel lgInfo("Worker interrotto correttamente."); } } #endregion Private Methods } }