Files
2020-07-11 09:58:10 +02:00

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
}
}