693 lines
25 KiB
C#
693 lines
25 KiB
C#
using Newtonsoft.Json;
|
|
using NLog;
|
|
using S7.Net;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
|
|
namespace IOB.WIN.FileExp
|
|
{
|
|
public partial class IOB_Exporter : Form
|
|
{
|
|
#region oggetti di base
|
|
|
|
/// <summary>
|
|
/// configurazione principale proxy
|
|
/// </summary>
|
|
protected dataProxy currDataProxy;
|
|
/// <summary>
|
|
/// Oggetto PLC da ri-utilizzare...
|
|
/// </summary>
|
|
protected Plc currPLC;
|
|
/// <summary>
|
|
/// parametri di connessione
|
|
/// </summary>
|
|
protected connParam parametri;
|
|
/// <summary>
|
|
/// oggetto logging
|
|
/// </summary>
|
|
public static Logger lg;
|
|
/// <summary>
|
|
/// oggetto uiTimer x gestione refresh UI
|
|
/// </summary>
|
|
protected System.Windows.Forms.Timer uiTimer = new System.Windows.Forms.Timer();
|
|
/// <summary>
|
|
/// oggetto uiTimer x sampling file testuale
|
|
/// </summary>
|
|
protected System.Windows.Forms.Timer sampleTimer = new System.Windows.Forms.Timer();
|
|
/// <summary>
|
|
/// oggetto uiTimer x verifiche
|
|
/// </summary>
|
|
protected System.Windows.Forms.Timer checkTimer = new System.Windows.Forms.Timer();
|
|
/// <summary>
|
|
/// Contatore per decidere quando loggare la lettura del file
|
|
/// </summary>
|
|
protected int loaderLogCounter = 30;
|
|
/// <summary>
|
|
/// Directory base delle risorse (virtuale)
|
|
/// </summary>
|
|
protected const string RESOURCE_DIRECTORY = @"IOB.WIN.FileExp.Config.";
|
|
/// <summary>
|
|
/// file delle conf attivo
|
|
/// </summary>
|
|
protected const string setupFile = RESOURCE_DIRECTORY + @"setup.json";
|
|
/// <summary>
|
|
/// File output x salvataggio
|
|
/// </summary>
|
|
protected string outFilePath = @"C:\TMP\OutFile.csv";
|
|
/// <summary>
|
|
/// Ultimi valori registrati da file
|
|
/// </summary>
|
|
protected Dictionary<string, string> prevValues;
|
|
/// <summary>
|
|
/// valori correntemente letti dal file
|
|
/// </summary>
|
|
protected Dictionary<string, string> currValues;
|
|
/// <summary>
|
|
/// Valore di tentativi di check connessione prima di loggare
|
|
/// </summary>
|
|
protected int numCheckLogTry = 1;
|
|
|
|
#endregion
|
|
|
|
#region inizializazione
|
|
|
|
public IOB_Exporter()
|
|
{
|
|
InitializeComponent();
|
|
myInit();
|
|
}
|
|
|
|
private void myInit()
|
|
{
|
|
lg = LogManager.GetCurrentClassLogger();
|
|
txtLog.Text = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} ----------------------------------------------------";
|
|
tsslApp.Text = $"{utils.CRS("appName")}";
|
|
fixVisibility();
|
|
prevValues = new Dictionary<string, string>();
|
|
currValues = new Dictionary<string, string>();
|
|
loadMemConf();
|
|
loadPlc();
|
|
startUiTimer();
|
|
startCheckTimer();
|
|
startPlcSampleTimer();
|
|
}
|
|
/// <summary>
|
|
/// Fix visibilità parametri conf in debug
|
|
/// </summary>
|
|
private void fixVisibility()
|
|
{
|
|
// bool showParams = false;
|
|
//#if DEBUG
|
|
// showParams = true;
|
|
//#endif
|
|
// lblPlcIp.Visible = showParams;
|
|
// lblPlcCpu.Visible = showParams;
|
|
// lblPlcRack.Visible = showParams;
|
|
// lblPlcSlot.Visible = showParams;
|
|
// txtIP.Visible = showParams;
|
|
// txtCpuType.Visible = showParams;
|
|
// txtRack.Visible = showParams;
|
|
// txtSlot.Visible = showParams;
|
|
}
|
|
/// <summary>
|
|
/// avvio timer update UI
|
|
/// </summary>
|
|
private void startUiTimer()
|
|
{
|
|
// setup progressBar
|
|
toolStripProgressBar1.ProgressBar.Step = 5;
|
|
// UI: ogni 100 ms
|
|
uiTimer.Interval = 100;
|
|
uiTimer.Tick += UiTimer_Tick;
|
|
uiTimer.Start();
|
|
}
|
|
/// <summary>
|
|
/// Avvio timer controlli periodici (PLC; file...)
|
|
/// </summary>
|
|
private void startCheckTimer()
|
|
{
|
|
// check preliminare...
|
|
doPeriodChecks();
|
|
// poi ogni 3 minuti replico (watchdog)
|
|
checkTimer.Interval = 3 * 60 * 1000;
|
|
checkTimer.Tick += CheckTimer_Tick;
|
|
checkTimer.Start();
|
|
}
|
|
/// <summary>
|
|
/// Avvio timer campionamento PLC
|
|
/// </summary>
|
|
private void startPlcSampleTimer()
|
|
{
|
|
int sampleTimerMs = utils.CRI("sampleTimerMs");
|
|
// limite in lettura a 5 hz
|
|
sampleTimerMs = sampleTimerMs < 200 ? 200 : sampleTimerMs;
|
|
sampleTimer.Interval = sampleTimerMs;
|
|
sampleTimer.Tick += plcSampleTimer_Tick;
|
|
sampleTimer.Start();
|
|
}
|
|
|
|
/// <summary>
|
|
/// init PLC
|
|
/// </summary>
|
|
private void loadPlc()
|
|
{
|
|
lgInfo("Refreshing connection...");
|
|
parametri = currDataProxy.confPLC;
|
|
// ora tento avvio PLC... SE PING OK...
|
|
if (testPing() == IPStatus.Success)
|
|
{
|
|
try
|
|
{
|
|
lgInfo($"PLC parameters: CPU {parametri.tipoCpu} | IP: {parametri.ipAdrr} | R/S: {parametri.rack}/{parametri.slot}");
|
|
currPLC = new Plc(parametri.tipoCpu, parametri.ipAdrr, parametri.rack, parametri.slot);
|
|
currPLC.Open();
|
|
if (currPLC.IsConnected)
|
|
{
|
|
lgInfo("PLC Connected!");
|
|
}
|
|
else
|
|
{
|
|
lgInfo("Connection error!");
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError(exc, "Errore in INIT PLC");
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Init conf memoria
|
|
/// </summary>
|
|
protected void loadMemConf()
|
|
{
|
|
lgInfo("Starting loadMemConf");
|
|
// recupero conf da resources
|
|
Assembly myAssembly = Assembly.GetExecutingAssembly();
|
|
//var resNames = myAssembly.GetManifestResourceNames();
|
|
using (Stream confStream = myAssembly.GetManifestResourceStream(setupFile))
|
|
{
|
|
using (StreamReader fileReader = new StreamReader(confStream))
|
|
{
|
|
string jsonData = fileReader.ReadToEnd();
|
|
if (!string.IsNullOrEmpty(jsonData))
|
|
{
|
|
lgInfo("Setup OK!");
|
|
try
|
|
{
|
|
currDataProxy = JsonConvert.DeserializeObject<dataProxy>(jsonData);
|
|
// verifica num max variabili (MAX 8...)
|
|
if (currDataProxy.parametersList.Count > 8)
|
|
{
|
|
lgInfo("Invalid config, reduce params list");
|
|
while (currDataProxy.parametersList.Count > 8)
|
|
{
|
|
currDataProxy.parametersList.Remove(currDataProxy.parametersList.LastOrDefault().Key);
|
|
}
|
|
}
|
|
lgInfo("Completed data deserialization");
|
|
}
|
|
catch
|
|
{
|
|
currDataProxy = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currDataProxy == null)
|
|
{
|
|
lgInfo($"Setup File NOT found: {setupFile} - creating new");
|
|
dataConf newParam = new dataConf()
|
|
{
|
|
Column = "Valore assoluto",
|
|
MemConf = "DB701.DBD142",
|
|
DataType = S7DataType.Byte
|
|
};
|
|
Dictionary<string, dataConf> paramList = new Dictionary<string, dataConf>();
|
|
paramList.Add(newParam.MemConf, newParam);
|
|
// creo nuovo obj...
|
|
currDataProxy = new dataProxy()
|
|
{
|
|
triggerList = null,
|
|
confPLC = new connParam()
|
|
{
|
|
ipAdrr = "192.168.0.102",
|
|
tipoCpu = CpuType.S71500,
|
|
rack = 0,
|
|
slot = 1
|
|
},
|
|
parametersList = paramList
|
|
};
|
|
}
|
|
|
|
// adesso valorizzo tutti i parametri
|
|
outFilePath = utils.CRS("csvFilePath");
|
|
txtCsvPath.Text = outFilePath.Replace("\\\\", "\\");
|
|
txtIP.Text = currDataProxy.confPLC.ipAdrr;
|
|
txtCpuType.Text = $"{currDataProxy.confPLC.tipoCpu}";
|
|
txtRack.Text = $"{currDataProxy.confPLC.rack}";
|
|
txtSlot.Text = $"{currDataProxy.confPLC.slot}";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region metodi ricorrenti
|
|
|
|
/// <summary>
|
|
/// Esecuzione task UI
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void UiTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
toolStripProgressBar1.ProgressBar.Value++;
|
|
if (toolStripProgressBar1.ProgressBar.Value >= toolStripProgressBar1.ProgressBar.Maximum)
|
|
{
|
|
toolStripProgressBar1.ProgressBar.Value = 0;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Esecuzione task di verifica periodica
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void CheckTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
doPeriodChecks();
|
|
}
|
|
/// <summary>
|
|
/// Effettua controlli periodici su PLC, file...
|
|
/// </summary>
|
|
private void doPeriodChecks()
|
|
{
|
|
// loggo!
|
|
lgInfo("Program Alive control...");
|
|
//verifico PLC
|
|
if (currPLC == null)
|
|
{
|
|
lgInfo("PLC NULL!");
|
|
}
|
|
else if (currPLC.IsConnected)
|
|
{
|
|
lgInfo("PLC Connected!");
|
|
}
|
|
else
|
|
{
|
|
lgInfo("Connection error!");
|
|
}
|
|
checkCreateOutFile();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Esecuzione task di campionamento
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void plcSampleTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
// leggo aree di memoria triggers dal PLC
|
|
reloadTriggersFromPLC();
|
|
|
|
// verifico valore confrontando con i precedenti...
|
|
if (dataChanged())
|
|
{
|
|
checkCreateOutFile();
|
|
// invio su file
|
|
saveToFile();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region helper methods
|
|
|
|
/// <summary>
|
|
/// VErifica se esista file OUT altrimenti lo crea
|
|
/// </summary>
|
|
private void checkCreateOutFile()
|
|
{
|
|
// verifico esistenza file sennò creo
|
|
if (!File.Exists(outFilePath))
|
|
{
|
|
lgInfo("Target file not found: creating new!");
|
|
string fileContent = "";
|
|
// se non esiste --> creo con headers se richiesto!
|
|
if (utils.CRB("csvHeader"))
|
|
{
|
|
// hard coded: data-ora
|
|
fileContent = $"Data-Ora{utils.CRS("csvSeparator")}";
|
|
foreach (var item in currDataProxy.parametersList)
|
|
{
|
|
fileContent += $"{item.Value.Column}{utils.CRS("csvSeparator")}";
|
|
}
|
|
// elimino ultimo separatore...
|
|
fileContent = fileContent.Substring(0, fileContent.Length - 1);
|
|
// aggiungo a capo...
|
|
fileContent += Environment.NewLine;
|
|
}
|
|
// scrivo!
|
|
File.WriteAllText(outFilePath, fileContent);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// salva i dati sul FILE in append
|
|
/// </summary>
|
|
private void saveToFile()
|
|
{
|
|
string nextRow = "";
|
|
// leggo TUTTI i dati dal PLC secondo configurazione...
|
|
if (testCncConn())
|
|
{
|
|
// init
|
|
memAddress memoria = null;
|
|
int numByte = 0;
|
|
List<string> newData = new List<string>();
|
|
string outVal = "";
|
|
foreach (var item in currDataProxy.parametersList)
|
|
{
|
|
memoria = new memAddress(item.Value.MemConf);
|
|
numByte = item.Value.memByteSize;
|
|
outVal = "";
|
|
// leggo!
|
|
var currMem = currPLC.ReadBytes(DataType.DataBlock, memoria.DbNum, memoria.indiceMem, numByte);
|
|
// check dati restituiti...
|
|
if (currMem.Length > 0)
|
|
{
|
|
// converto lettura secondo il tipo!
|
|
switch (item.Value.DataType)
|
|
{
|
|
case S7DataType.Bit:
|
|
// uso memSize
|
|
outVal = S7.Net.Types.Bit.FromByte(currMem[0], item.Value.bitAdr).ToString();
|
|
break;
|
|
case S7DataType.Byte:
|
|
outVal = S7.Net.Types.Byte.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.Word:
|
|
outVal = S7.Net.Types.Word.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.DWord:
|
|
outVal = S7.Net.Types.DWord.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.Real:
|
|
outVal = S7.Net.Types.Double.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.String:
|
|
// prendo 2° valore (num max valori)
|
|
int numChar = currMem[1];
|
|
// poi prendo la stringa...
|
|
for (int i = 2; i < numChar + 2; i++)
|
|
{
|
|
outVal += Char.ConvertFromUtf32(currMem[i]);
|
|
}
|
|
break;
|
|
default:
|
|
outVal = BitConverter.ToString(currMem);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgError($"Errore: ricevuto 0 byte per DB{memoria.DbNum}.{memoria.indiceMem:000}");
|
|
}
|
|
newData.Add(outVal);
|
|
}
|
|
// formatto lista come riga CSV
|
|
nextRow = string.Join(utils.CRS("csvSeparator"), newData);
|
|
// aggiungo a capo...
|
|
nextRow += Environment.NewLine;
|
|
}
|
|
// e le schianta nella prox riga del file...
|
|
if (!string.IsNullOrEmpty(nextRow))
|
|
{
|
|
File.AppendAllText(outFilePath, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}{utils.CRS("csvSeparator")}{nextRow}");
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Effettua comparazioen dati vecchi/nuovi
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool dataChanged()
|
|
{
|
|
bool answ = false;
|
|
// se i 2 vettori sono diversi in numero
|
|
if (currValues.Count != prevValues.Count)
|
|
{
|
|
resetPrevVal();
|
|
// loggo!
|
|
lgInfo("Trigger activated on PLC: different count");
|
|
return true;
|
|
}
|
|
try
|
|
{
|
|
// verifico ogni singolo valore...
|
|
foreach (var item in currValues)
|
|
{
|
|
// cerco...
|
|
if (prevValues.ContainsKey(item.Key))
|
|
{
|
|
// verifico se diversi --> trovato cambio, indago!
|
|
if (!currValues[item.Key].Equals(prevValues[item.Key]))
|
|
{
|
|
// loggo!
|
|
lgInfo($"Difference found: {prevValues[item.Key]} --> {currValues[item.Key]}");
|
|
// solo se il NUOVO valore è pari al trigger cercato...
|
|
if (currValues[item.Key] == currDataProxy.triggerList[item.Key].TriggerVal)
|
|
{
|
|
lgInfo($"Valid Trigger found: {currDataProxy.triggerList[item.Key].TriggerVal}");
|
|
answ = true;
|
|
}
|
|
else
|
|
{
|
|
// senza segnalare cambio recepisco nuovo valore...
|
|
resetPrevVal();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lgInfo($"Trigger activated on PLC: new value found --> {item.Key}");
|
|
answ = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
lgError(exc, "Exception on dataChanged");
|
|
}
|
|
// se ho cambiamenti --> esco!
|
|
if (answ)
|
|
{
|
|
// salvo ed esco subito
|
|
resetPrevVal();
|
|
return true;
|
|
}
|
|
return answ;
|
|
}
|
|
/// <summary>
|
|
/// Refresh oggetto valori precedenti
|
|
/// </summary>
|
|
private void resetPrevVal()
|
|
{
|
|
// salvo ed esco subito
|
|
prevValues = new Dictionary<string, string>();
|
|
foreach (var item in currValues)
|
|
{
|
|
prevValues.Add(item.Key, item.Value);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Effettua rilettura da PLC dei triggers data
|
|
/// </summary>
|
|
protected void reloadTriggersFromPLC()
|
|
{
|
|
if (testCncConn())
|
|
{
|
|
// init
|
|
memAddress memoria = null;
|
|
int numByte = 0;
|
|
string outVal = "";
|
|
// leggo triggers dal PLC
|
|
foreach (var item in currDataProxy.triggerList)
|
|
{
|
|
memoria = new memAddress(item.Value.MemConf);
|
|
numByte = item.Value.memByteSize;
|
|
outVal = "";
|
|
|
|
// leggo!
|
|
var currMem = currPLC.ReadBytes(DataType.DataBlock, memoria.DbNum, memoria.indiceMem, numByte);
|
|
// se currMem fosse vuoto --> 1 byte a 0...
|
|
if (currMem.Length == 0)
|
|
currMem = new byte[1];
|
|
|
|
|
|
// converto lettura secondo il tipo!
|
|
switch (item.Value.DataType)
|
|
{
|
|
case S7DataType.Bit:
|
|
outVal = S7.Net.Types.Bit.FromByte(currMem[0], item.Value.bitAdr).ToString();
|
|
break;
|
|
case S7DataType.Byte:
|
|
outVal = S7.Net.Types.Byte.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.Word:
|
|
outVal = S7.Net.Types.Word.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.DWord:
|
|
outVal = S7.Net.Types.DWord.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.Real:
|
|
outVal = S7.Net.Types.Double.FromByteArray(currMem.ToArray()).ToString();
|
|
break;
|
|
case S7DataType.String:
|
|
// prendo 2° valore (num max valori)
|
|
int numChar = currMem[1];
|
|
// poi prendo la stringa...
|
|
for (int i = 2; i < numChar + 2; i++)
|
|
{
|
|
outVal += Char.ConvertFromUtf32(currMem[i]);
|
|
}
|
|
break;
|
|
default:
|
|
outVal = BitConverter.ToString(currMem);
|
|
break;
|
|
}
|
|
// salvo
|
|
upsertValue(item.Value.MemConf, outVal);
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// upsert valori correnti da tracciare
|
|
/// </summary>
|
|
/// <param name="key"></param>
|
|
/// <param name="value"></param>
|
|
protected void upsertValue(string key, string value)
|
|
{
|
|
// cerco se ci sia... aggiorno!
|
|
if (currValues.ContainsKey(key))
|
|
{
|
|
currValues[key] = value;
|
|
}
|
|
// altrimenti aggiungo
|
|
else
|
|
{
|
|
currValues.Add(key, value);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// test ping all'indirizzo impostato nei parametri
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private IPStatus testPing()
|
|
{
|
|
IPStatus answ = IPStatus.Unknown;
|
|
IPAddress address;
|
|
PingReply reply;
|
|
Ping pingSender = new Ping();
|
|
address = IPAddress.Loopback;
|
|
IPAddress.TryParse(parametri.ipAdrr, out address);
|
|
reply = pingSender.Send(address, 100);
|
|
answ = reply.Status;
|
|
return answ;
|
|
}
|
|
/// <summary>
|
|
/// Test connessione CNC
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool testCncConn()
|
|
{
|
|
bool answ = false;
|
|
IPStatus pingStatus = testPing();
|
|
// se passa il ping faccio il resto...
|
|
if (pingStatus != IPStatus.Success)
|
|
{
|
|
numCheckLogTry--;
|
|
if (numCheckLogTry < 0)
|
|
{
|
|
lgError("Errore ping");
|
|
numCheckLogTry = utils.CRI("defNumCheckTry");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!currPLC.IsConnected)
|
|
{
|
|
currPLC.Open();
|
|
}
|
|
|
|
if (!currPLC.IsConnected)
|
|
{
|
|
numCheckLogTry--;
|
|
if (numCheckLogTry < 0)
|
|
{
|
|
lgError("Errore connessione");
|
|
numCheckLogTry = utils.CRI("defNumCheckTry");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
answ = true;
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
/// <summary>
|
|
/// Aggiorno log in area console
|
|
/// </summary>
|
|
/// <param name="contenuto"></param>
|
|
private void appendRTLog(string contenuto)
|
|
{
|
|
// se troppe righe trimmo...
|
|
string fullLog = limitLine2show($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {contenuto}{Environment.NewLine}{txtLog.Text}");
|
|
// update
|
|
txtLog.Text = fullLog;
|
|
}
|
|
/// <summary>
|
|
/// Effettua un trim della stringa al numero max di linee da mostrare a video
|
|
/// </summary>
|
|
/// <param name="newString"></param>
|
|
/// <returns></returns>
|
|
public string limitLine2show(string newString)
|
|
{
|
|
if (!string.IsNullOrEmpty(newString))
|
|
{
|
|
// se num righe superiore a limite trimmo...
|
|
if (newString.Split('\n').Length > 50)
|
|
{
|
|
//int idx = newString.LastIndexOf('\r');
|
|
int idx = newString.LastIndexOf(Environment.NewLine);
|
|
newString = newString.Substring(0, idx);
|
|
}
|
|
}
|
|
return newString;
|
|
}
|
|
protected void lgInfo(string contenuto)
|
|
{
|
|
lg.Info(contenuto);
|
|
appendRTLog($"| INFO | {contenuto}");
|
|
}
|
|
protected void lgError(string contenuto)
|
|
{
|
|
lg.Error(contenuto);
|
|
appendRTLog($"| ERROR | {contenuto}");
|
|
}
|
|
protected void lgError(Exception exc, string contenuto)
|
|
{
|
|
lg.Error(exc, contenuto);
|
|
appendRTLog($"| EXC | {contenuto}");
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|