Files
Mapo-IOB-WIN/IOB-WIN-MBUS/IobModbustTCP/ModbusTCP.cs
T

2367 lines
101 KiB
C#

using EasyModbus;
using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading;
using System.Windows.Forms;
namespace IOB_WIN_MBUS.IobModbusTCP
{
/* --------------------------------------------------------------------------------
* Controlli ModBusTCP (primo: COMECA)
* - protocollo ModBus TCP
*
* -------------------------------------------------------------------------------- */
public class ModbusTCP : Iob.GenericNext
{
#region Public Constructors
/// <summary>
/// Classe base con i metodi x ModBusTCP
/// </summary>
/// <param name="caller">Form chiamante</param>
/// <param name="IOBConf">Configurazione (legacy)</param>
/// <param name="IobConfFull">Configurazione (v 4.x)</param>
public ModbusTCP(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
{
lgInfo("NEW IOB ModBus TCP");
DateTime adesso = DateTime.Now;
memMap = new plcMemMapExt();
if (IOBConfFull != null)
{
// gestione invio ritardato contapezzi
lastPzCountSend = adesso;
lastWarnODL = adesso;
// inizializzo parametri...
parametri = new ModBusTcpParamConf();
#if false
{
ipAdrr = "127.0.0.1",
port = 502,
pingMsTimeout = IOBConfFull.Device.Connect.PingMsTimeout,
holdRegBaseAddr = 40001,
memAddrRead = "40001",
memAddrWrite = "41001",
memSizeRead = 0,
memSizeWrite = 0
};
#endif
setParamPlc();
//
string sMinWait = getOptPar("minWait");
if (!string.IsNullOrEmpty(sMinWait))
{
int.TryParse(sMinWait, out minWait);
}
string sMaxWait = getOptPar("maxWait");
if (!string.IsNullOrEmpty(sMaxWait))
{
int.TryParse(sMaxWait, out maxWait);
}
// salvo info su conf IOB...
string iobConfSer = "";
try
{
iobConfSer = JsonConvert.SerializeObject(IOBConfFull, Formatting.Indented);
}
catch
{ }
// finito!
lgInfoStartup($"Init IOB, con {iobConfSer}");
}
else
{
lgError("Impossibile avviare, IOBConf nullo/non valido!");
}
}
/// <summary>
/// Min attesa chiamate consecutive ModBus
/// </summary>
private int minWait = 40;
/// <summary>
/// Max attesa chiamate consecutive ModBus
/// </summary>
private int maxWait = 60;
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Metodo dispose x il currPLC contenuto
/// </summary>
public void Dispose()
{
currPLC.Disconnect();
}
/// <summary>
/// Processo i task richiesti e li elimino dalla coda 1:1
/// </summary>
/// <param name="task2exe"></param>
public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
{
lgInfo($"Chiamata executeTasks specifica ModBus TCP: {task2exe.Count} task ricevuti");
// Verificare il protocollo: dovrebeb togliere SOLO i task eseguiti...
Dictionary<string, string> taskDone = new Dictionary<string, string>();
string taskVal = "";
if (task2exe != null)
{
// cerco task specifici
foreach (var item in task2exe)
{
taskVal = "";
// converto richiesta in enum...
taskType tName = taskType.nihil;
Enum.TryParse(item.Key, out tName);
// controllo sulla KEY
switch (tName)
{
case taskType.nihil:
case taskType.fixStopSetup:
case taskType.forceResetPzCount:
case taskType.forceSetPzCount:
case taskType.sendWatchDogMes2Plc:
case taskType.startSetup:
case taskType.stopSetup:
taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
break;
case taskType.setProg:
case taskType.setPzComm:
case taskType.setArt:
case taskType.setComm:
// cerco se ho configurato parametri x invio...
if (memMap.mMapWrite.ContainsKey(item.Key))
{
memMap.mMapWrite[item.Key].value = item.Value;
// preparo obj oparametro da scrivere...
List<objItem> updatedPar = new List<objItem>();
objItem newParam = new objItem()
{
uid = item.Key,
name = item.Key,
value = item.Value,
reqValue = "",
writable = true,
lastRequest = DateTime.Now,
displOrdinal = memMap.mMapWrite[item.Key].displOrdinal
};
updatedPar.Add(newParam);
// richiamo scrittura parametri su PLC
plcWriteParams(ref updatedPar);
taskVal = $"REQUEST SET val: {item.Key} --> {item.Value}";
}
else
{
taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | NOT FOUND IN mMapWrite List | NO EXEC";
}
break;
case taskType.setParameter:
// richiedo da URL i parametri WRITE da popolare
lgDebug("Chiamata processMemWriteRequests");
taskVal = processMemWriteRequests();
// se restituiscce "" faccio altra prova...
if (string.IsNullOrEmpty(taskVal))
{
// i parametri me li aspetto come stringa composta paramName|paramvalue
if (item.Value.Contains("|"))
{
string[] paramsJob = item.Value.Split('|');
taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}";
}
else
{
taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value";
}
}
break;
case taskType.processOtherInfo:
bool okProc = ProcessOtherInfo(item.Key, item.Value);
taskVal = okProc ? $"OK ProcessOtherInfoAsync | {item.Key} | {item.Value}" : $"ERROR ProcessOtherInfoAsync | {item.Key} | {item.Value}";
break;
case taskType.syncDbData:
ProcessDataSync();
break;
default:
taskVal = "SKIPPED | NO EXEC";
break;
}
// aggiungo task!
taskDone.Add(item.Key, taskVal);
}
}
return taskDone;
}
/// <summary>
/// Decodifica tipo indirizzo dal codice vedere ad esempio https://www.fernhillsoftware.com/help/drivers/modbus/data-address-format.html
/// </summary>
/// <param name="memAddr"></param>
/// <returns></returns>
public modBusAddrType getAddrType(string memAddr)
{
modBusAddrType answ = modBusAddrType.Coil;
// leggo prima cifra...
answ = (modBusAddrType)Enum.Parse(typeof(modBusAddrType), memAddr.Substring(0, 1));
return answ;
}
/// <summary>
/// Recupero dati dinamici... ATTENZIONE factor usato come FONDOSCALA
///
/// Esempio:
/// - RealVal : 0 - 28000
/// - ReadVal : 0 - 100
/// - Factor : 28000 / 100 = 280
/// </summary>
public override Dictionary<string, string> getDynData()
{
Random rnd = new Random();
// valore non presente in vers default... se gestito fare override
Dictionary<string, string> outVal = new Dictionary<string, string>();
if (utils.CRB("enableTSVC"))
{
// processing SOLO SE ho in memoria abbastanza dati...
if (RawInput.Length < parametri.memSizeRead)
{
lgError($"Impossibile processare getDynData x ModBus TCP PLC, vettore memoria troppo piccolo: {RawInput.Length} byte / {parametri.memSizeRead} byte presenti/richiesti)");
}
else
{
Dictionary<string, dataConfTSVC> readErrorList = new Dictionary<string, dataConfTSVC>();
Dictionary<string, dataConfTSVC> readErrorListRepeat = new Dictionary<string, dataConfTSVC>();
bool useLUT = memSetR != null && memSetR.Count > 0;
// se configurato leggo IN BLOCCHI da memoria...
if (useLUT)
{
foreach (var item in memSetR)
{
// verifico il tipo di dati da leggere e salvare... FIXME TODO FARE !!!
readMemoryBlock(item.Key, item.Value);
int waitTime = rnd.Next(minWait, maxWait);
Thread.Sleep(waitTime);
}
}
// procedo ...
try
{
// processo x ogni valore configurato...
if (memMap.mMapRead.Count > 0)
{
var memItemList = memMap.mMapRead;
readErrorList = getDataDictionary(memItemList, useLUT, ref outVal);
// se qualcosa è andato storto riprovo a caricare SOLO gli errori... 1
// sola volta
if (readErrorList.Count > 0)
{
lgDebug($"Attesa prima di rilettura | LUT: {useLUT}");
// attendo 3 sec
Thread.Sleep(3000);
lgDebug($"Effettuo rilettura per {readErrorList.Count} variabili | LUT: {useLUT}");
readErrorListRepeat = getDataDictionary(readErrorList, useLUT, ref outVal);
}
// se avessi ancora errori --> disconnetto
if (readErrorListRepeat.Count > maxErrorRead)
{
lgError($"Trovati valori non validi al secondo tentativo | LUT: {useLUT} --> invalido valori letti e resetto adapter con tryDisconnect!");
tryDisconnect();
// invalido output
outVal = new Dictionary<string, string>();
tryConnect();
}
else
{
lastReadPLC = DateTime.Now;
}
}
else
{
lgDebug($"getDynData: {memMap.mMapRead.Count} record in mMapRead | LUT: {useLUT}");
}
}
catch (Exception exc)
{
lgError(exc, $"Errore in getDynData x ModBus TCP PLC --> ciclo disconnect/reconnect | LUT: {useLUT}{Environment.NewLine}{exc}");
tryDisconnect();
outVal = new Dictionary<string, string>();
tryConnect();
}
}
}
else
{
lgDebug($"Non processo getDynData: enableTSVC = false");
}
if (periodicLog || outVal.Count > 0)
{
lgDebug($"Esito getDynData: {outVal.Count} valori VALIDI in outVal");
}
return outVal;
}
/// <summary>
/// Gestione contapezzi
/// </summary>
public override void processContapezzi()
{
if (enablePzCountByApp)
{
if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrWhiteSpace(IOBConfFull.Device.PzCountMode))
{
// cerco in lastDynData...
string pzCountKey = IOBConfFull.Device.PzCountMode.Replace("STD.", "");
if (lastDynData.ContainsKey(pzCountKey))
{
string rawVal = lastDynData[pzCountKey];
double cntDouble = -1;
if (!string.IsNullOrEmpty(rawVal))
{
//double.TryParse(rawVal, out cntDouble);
bool success = double.TryParse(rawVal, NumberStyles.Any, CultureInfo.InvariantCulture, out cntDouble);
if (success)
{
contapezziPLC = (int)cntDouble;
}
}
}
}
}
else
{
lgTrace($"processContapezzi escluso: enablePzCountByApp = {enablePzCountByApp} | disablePzCountByIob: {disablePzCountByIob}");
}
}
/// <summary>
/// Lettura valori Coils (1...)
/// </summary>
/// <param name="startAddr"></param>
/// <param name="qty"></param>
/// <returns></returns>
public bool[] readCoil(int startAddr, int qty)
{
bool[] answ = new bool[1];
try
{
if (currPLC.Connected)
{
answ = currPLC.ReadCoils(startAddr, qty);
trackReadData(qty / 8, sw.Elapsed.TotalMilliseconds / 1000);
}
}
catch (Exception exc)
{
lgError($"Errore in readCoil{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Lettura valori DiscreteInputs (2...)
/// </summary>
/// <param name="startAddr"></param>
/// <param name="qty"></param>
/// <returns></returns>
public bool[] readDiscrInputs(int startAddr, int qty)
{
bool[] answ = new bool[1];
try
{
if (currPLC.Connected)
{
answ = currPLC.ReadDiscreteInputs(startAddr, qty);
trackReadData(qty / 8, sw.Elapsed.TotalMilliseconds / 1000);
}
}
catch (Exception exc)
{
lgError($"Errore in readDiscrInputs{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Lettura valori Holding Register (3...)
/// </summary>
/// <param name="startAddr"></param>
/// <param name="qty"></param>
/// <returns></returns>
public int[] readHoldReg(int startAddr, int qty)
{
int[] answ = new int[2];
try
{
if (currPLC.Connected)
{
answ = currPLC.ReadHoldingRegisters(startAddr, qty);
trackReadData(qty * 2, sw.Elapsed.TotalMilliseconds / 1000);
}
}
catch (Exception exc)
{
lgError($"Errore in readHoldReg{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Lettura valori Input Register (4...)
/// </summary>
/// <param name="startAddr"></param>
/// <param name="qty"></param>
/// <returns></returns>
public int[] readInputReg(int startAddr, int qty)
{
int[] answ = new int[2];
try
{
if (currPLC.Connected)
{
answ = currPLC.ReadInputRegisters(startAddr, qty);
trackReadData(qty * 2, sw.Elapsed.TotalMilliseconds / 1000);
}
}
catch (Exception exc)
{
lgError($"Errore in readInputReg{Environment.NewLine}{exc}");
}
return answ;
}
/// <summary>
/// Effettua lettura semafori principale <paramref name="currDispData">Parametri da
/// aggiornare x display in form</paramref>
/// </summary>
public override void readSemafori(ref newDisplayData currDispData)
{
if (!IOBConfFull.Device.DisabStateCh)
{
// NON eseguo x NON indicare data ora ultima lettura se NON fatta davvero
//base.readSemafori(ref currDispData);
byte[] MemBlock = new byte[2];
try
{
currDispData.semIn = Semaforo.SV;
if (verboseLog)
{
lgDebug("inizio read semafori");
}
if (verboseLog)
{
lgTrace(string.Format("RawInput[0]: {0}", utils.binaryForm(RawInput[0])));
}
// salvo il solo BYTE dell'input decifrando il semaforo...
decodeToBaseBitmap();
decodeOtherData();
// riporto bitmap...
reportRawInput(ref currDispData);
}
catch (Exception exc)
{
currDispData.semIn = Semaforo.SR;
lgError($"Eccezione in readSemafori{Environment.NewLine}{exc}");
}
}
}
/// <summary>
/// Override connessione
/// </summary>
public override void tryConnect()
{
bool doLog = (verboseLog || periodicLog);
lgInfo("ModBus TCP: tryConnect step 01");
if (!connectionOk)
{
// SE è necessario refresh...
if (needRefresh)
{
lgInfo("ModBus TCP: tryConnect step 02");
// reimposto parametri PLC se necessario...
setParamPlc();
}
lgInfo("ModBus TCP: tryConnect step 03");
// controllo che il ping sia stato tentato almeno pingTestSec fa...
if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
{
if (doLog)
{
lgInfo("ModBus TCP: ConnKO - tryConnect");
}
lgInfo("ModBus TCP: tryConnect step 04");
// in primis salvo data ping...
lastPING = DateTime.Now;
// se passa il ping faccio il resto...
if (testPingMachine == IPStatus.Success)
{
string szStatusConnection = "ND";
try
{
// ora provo connessione...
parentForm.commPlcActive = true;
currPLC.Connect();
szStatusConnection = "OPEN";
parentForm.commPlcActive = false;
connectionOk = currPLC.Connected;
lgInfo($"StatusConnection: {szStatusConnection}");
// refresh stato allarmi!!!
if (connectionOk)
{
//queueInEnabCurr = true;
lgInfo($"Connessione OK: {connectionOk}");
if (adpRunning)
{
lgInfo($"AdpRunning: {adpRunning}");
}
}
else
{
lgError("Impossibile procedere, connessione mancante...");
}
}
catch (Exception exc)
{
lgFatal($"Errore in TryConnect adapter ModBusTCP | szStatusConnection {szStatusConnection}{Environment.NewLine}{exc}");
connectionOk = false;
needRefresh = true;
}
}
else
{
// loggo no risposta ping ...
connectionOk = false;
if (doLog)
{
lgError($"Attenzione: ModBusTCP controllo PING fallito per IP {IOBConfFull.Device.Connect.PingIpAddr}");
}
}
}
}
// se non è ancora connesso faccio procesisng memoria caso disconnesso...
if (!connectionOk)
{
// processo semafori ed invio...
processMemoryDiscon();
}
}
/// <summary>
/// Override disconnessione
/// </summary>
public override void tryDisconnect()
{
if (currPLC != null && currPLC.Connected)
{
lgInfo("Richiesta disconnessione adapter ModBusTCP!");
try
{
if (currPLC.Connected)
{
currPLC.Disconnect();
}
lgInfo("Effettuata disconnessione adapter ModBusTCP!");
}
catch (Exception exc)
{
lgFatal($"Errore nella disconnessione dall'adapter ModBusTCP{Environment.NewLine}{exc}");
}
}
connectionOk = false;
queueInEnabCurr = false;
// resetto last ping...
lastPING = DateTime.Now.AddMinutes(-1);
}
/// <summary>
/// Scrittura di un singolo valore COIL (1...)
/// </summary>
/// <param name="startAddr"></param>
/// <param name="currValue"></param>
/// <returns></returns>
public bool writeCoil(int startAddr, bool currValue)
{
bool answ = false;
if (currPLC.Connected)
{
try
{
currPLC.WriteSingleCoil(startAddr, currValue);
answ = true;
}
catch
{ }
}
return answ;
}
/// <summary>
/// Scrittura di un valore Holding Register (4...)
/// </summary>
/// <param name="startAddr"></param>
/// <param name="currRegVal">Valore in formato INT da registri</param>
/// <returns></returns>
public bool writeHoldingReg(int startAddr, int[] currRegVal)
{
bool answ = false;
if (currPLC.Connected)
{
try
{
currPLC.WriteMultipleRegisters(startAddr + deltaBase + indexLutCorr, currRegVal);
answ = true;
}
catch
{ }
}
return answ;
}
#endregion Public Methods
#region Protected Fields
/// <summary>
/// Copia locale dei valori in COIL (OUT) | 0.000/09.999, oppure 0.000/65.536 = 0xFFFF, come
/// array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo
/// </summary>
protected Dictionary<int, bool> CoilLUT = new Dictionary<int, bool>();
protected ModbusClient currPLC;
/// <summary>
/// Copia locale dei valori in Discrete Inputs (IN) | 10.000/19.999, oppure 100.000/165.536
/// = 1xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire
/// secondo tipo
/// </summary>
protected Dictionary<int, bool> DiscreteInputLUT = new Dictionary<int, bool>();
/// <summary>
/// Copia locale dei valori in Holding Registers | 40.000/49.999, oppure 400.000/465.536 =
/// 4xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo
/// </summary>
protected Dictionary<int, int[]> HoldingRegisterLUT = new Dictionary<int, int[]>();
/// <summary>
/// Copia locale dei valori in Input Registers | 30.000/39.999, oppure 300.000/365.536 =
/// 3xFFFF, come array chiave (int) valori int[] letti tramite ModBus, da convertire secondo tipo
/// </summary>
protected Dictionary<int, int[]> InputRegisterLUT = new Dictionary<int, int[]>();
/// <summary>
/// Ultimo controllo ping x evitare ping flood...
/// </summary>
protected DateTime lastPingConn = DateTime.Now.AddMinutes(-10);
/// <summary>
/// Esito ultimo ping
/// </summary>
protected bool lastPingOk = false;
/// <summary>
/// num max di errori lettura permessi prima di disconnettere
/// </summary>
protected int maxErrorRead = 10;
/// <summary>
/// Setup blocchi memorie read (indirizzo inizio, size)
/// </summary>
protected Dictionary<int, int> memSetR = new Dictionary<int, int>();
/// <summary>
/// parametri di connessione
/// </summary>
protected ModBusTcpParamConf parametri;
#endregion Protected Fields
#region Protected Properties
/// <summary>
/// Dizionario delle ultime operazioni di scrittura per OGNI memoria (in modo che fa log
/// ogni x sec...)
/// </summary>
protected Dictionary<string, DateTime> lastMemWrite { get; set; } = new Dictionary<string, DateTime>();
#endregion Protected Properties
#region Protected Methods
/// <summary>
/// Classe di base implementazione traduzione di una LUT da memoria come valore BIT
/// (multipli) a valore esplicito x FLog
/// </summary>
protected override void checkTranslateBit()
{
// verifico se devo processare decodifica di qualche valore...
if (OptVar2TranslBit != null && OptVar2TranslBit.Count > 0)
{
Dictionary<string, int> valUpdated = new Dictionary<string, int>();
// ciclo x ogni valore da tradurre
foreach (var item in OptVar2TranslBit)
{
int reqAddr = 0;
// traduco in int...
int.TryParse(item.Key, out reqAddr);
int lutAddr = reqAddr + indexLutCorr;
int valInt = 0;
int[] listInt = new int[2];
// cerco valore nella LUT... se è pari cerco "direttamente" altrimenti dal pari
// inferiore e 2° INT[]
if (lutAddr % 2 == 0)
{
listInt = HoldingRegisterLUT[lutAddr];
valInt = listInt[0];
}
else
{
lutAddr--;
listInt = HoldingRegisterLUT[lutAddr];
valInt = listInt[1];
}
// gestisco a bitmap --> salvo valore INT x check..
string sVal = $"{valInt}";
// se è variato rispetto precedente --> calcolo valori string, accodo in FLog e salvo...
if (item.Value != sVal)
{
valUpdated.Add(item.Value, valInt);
}
}
// se ho valori aggiornati --> aggiorno dictionary e invio
if (valUpdated.Count > 0)
{
DateTime locTStamp = DateTime.Now;
foreach (var item in valUpdated)
{
// salvo il valore modificato
OptVar2TranslInt[item.Key] = $"{item.Value}";
List<string> listVal = new List<string>();
// si tratta di valori bit --> devo cercare OGNI bit attivo (0..8 --> 0.255)
for (int i = 0; i < 8; i++)
{
if (BitUtils.isActive(item.Value, i))
{
// traduco ed accodo...
listVal.Add(itemTranslation($"{item.Key}.{i}"));
}
}
// costruisco stringa finale...
string trad = string.Join(",", listVal);
string sVal = $"Change {item.Key}: {locTStamp.ToString()} | Val: {item.Value} | trad: {trad}";
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, trad));
if (sent)
{
trackDynData(item.Key, trad);
}
}
}
}
}
/// <summary>
/// Classe di base implementazione traduzione di una LUT da memoria come valore INT a valore
/// esplicito x FLog
/// </summary>
protected override void checkTranslateInt()
{
if (OptVar2TranslInt != null && OptVar2TranslInt.Count > 0)
{
Dictionary<string, string> valUpdated = new Dictionary<string, string>();
// ciclo x ogni valore da tradurre
foreach (var item in OptVar2TranslInt)
{
int reqAddr = 0;
// traduco in int...
int.TryParse(item.Key, out reqAddr);
int lutAddr = reqAddr + indexLutCorr;
int valInt = 0;
int[] listInt = new int[2];
// cerco valore nella LUT... se è pari cerco "direttamente" altrimenti dal pari
// inferiore e 2° INT[]
if (lutAddr % 2 == 0)
{
listInt = HoldingRegisterLUT[lutAddr];
valInt = listInt[0];
}
else
{
lutAddr--;
listInt = HoldingRegisterLUT[lutAddr];
valInt = listInt[1];
}
// cerco traduzione...
string trad = itemTranslation(item.Key, $"{valInt}");
// se è variato rispetto precedente --> accodo in FLog e salvo...
if (item.Value != trad)
{
valUpdated.Add(item.Value, trad);
}
}
// se ho valori aggiornati --> aggiorno dictionary e invio
if (valUpdated.Count > 0)
{
string sVal = "";
DateTime locTStamp = DateTime.Now;
foreach (var item in valUpdated)
{
OptVar2TranslInt[item.Key] = item.Value;
sVal = $"Change {item.Key}: {locTStamp.ToString()} | Val: {item.Value}";
bool sent = accodaFLog(item.Key, sVal, qEncodeFLog(item.Key, item.Value));
if (sent)
{
trackDynData(item.Key, item.Value);
}
}
}
}
}
/// <summary>
/// Decodifica il resto dell'area x i dati accessori (allarmi, ...)
/// </summary>
protected virtual void decodeOtherData()
{
}
/// <summary>
/// Effettua decodifica aree memoria alla bitmap usata x MAPO/GWMS
/// - per macchine base (GWMS) IN REALTA' non gestito lo stato macchina.... previsto comunque
/// </summary>
protected virtual void decodeToBaseBitmap()
{
// init a zero...
B_input = 0;
}
/// <summary>
/// Recupero allarmi specifico x ModbusTCP (16 e 32 bit...)
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
protected override int getAlarmStatus(BaseAlarmConf item)
{
int answ = 0;
int[] listInt = new int[2];
// verifico se usare LUT
bool useLUT = memSetR != null && memSetR.Count > 0;
if (useLUT)
{
int lutAddress = parametri.holdRegBaseAddr + item.index + deltaBase + indexLutCorr;
if (HoldingRegisterLUT.ContainsKey(lutAddress))
{
try
{
listInt = HoldingRegisterLUT[lutAddress];
}
catch (Exception exc)
{
lgError($"Errore in lettura da HoldingRegisterLUT per indirizzo {lutAddress} | item {item.memAddr}{Environment.NewLine}{exc}");
}
}
}
else
{
listInt = readInputReg(item.index, item.size);
}
answ = ModbusClient.ConvertRegistersToInt(listInt);
return answ;
}
/// <summary>
/// Override metodo x scrittura parametri su PLC
/// </summary>
/// <param name="updatedPar"></param>
protected override void plcWriteParams(ref List<objItem> updatedPar)
{
dataConf currMem = null;
int byteSize = 0;
int[] CurrVal = new int[1];
string memAddrWrite = "";
bool fatto = false;
string serObj = "";
if (updatedPar != null)
{
// controllo i parametri... ne gestisco 4...
foreach (var item in updatedPar)
{
try
{
memAddrWrite = "";
int valInt = 0;
uint valUInt = 0;
double valDouble = 0;
// cerco in area memMapWrite...
if (memMap.mMapWrite.ContainsKey(item.uid))
{
// recupero!
currMem = memMap.mMapWrite[item.uid];
byteSize = currMem.size;
memAddrWrite = currMem.memAddr;
CurrVal = new int[byteSize];
// faccio preliminarmente upsertKey...
upsertKey(currMem.name, currMem.value);
serObj = JsonConvert.SerializeObject(item, Formatting.Indented);
lgInfo($"Inizio processing plcWriteParams per {currMem.name} | valore richiesto {currMem.value}{Environment.NewLine}---------------UPDATED PARAM---------------{Environment.NewLine}{serObj}{Environment.NewLine}---------------");
serObj = JsonConvert.SerializeObject(currMem, Formatting.Indented);
lgInfo($"---------------MEMORY CONTENT---------------{Environment.NewLine}{serObj}{Environment.NewLine}---------------");
switch (currMem.tipoMem)
{
case plcDataType.Boolean:
break;
case plcDataType.Int:
valInt = getScaledInt(currMem, true);
if (byteSize == 1)
{
CurrVal[0] = valInt;
}
else
{
CurrVal = ModbusClient.ConvertIntToRegisters(valInt);
}
fatto = writeHoldingReg(currMem.index, CurrVal);
break;
case plcDataType.IntLH:
valInt = getScaledInt(currMem, true);
if (byteSize == 1)
{
CurrVal[1] = valInt;
}
else
{
CurrVal = ModbusClient.ConvertIntToRegisters(valInt);
Array.Reverse(CurrVal);
}
fatto = writeHoldingReg(currMem.index, CurrVal);
break;
case plcDataType.DInt:
valUInt = getScaledUInt(currMem, true);
CurrVal = ModbusClient.ConvertLongToRegisters(valInt);
fatto = writeHoldingReg(currMem.index, CurrVal);
break;
case plcDataType.DIntLH:
valUInt = getScaledUInt(currMem, true);
CurrVal = ModbusClient.ConvertLongToRegisters(valInt);
Array.Reverse(CurrVal);
fatto = writeHoldingReg(currMem.index, CurrVal);
break;
//case plcDataType.Word:
// valUInt = getScaledUInt(currMem);
// saveWordOnMemBlock(ref MemBlock, 0, valInt.ToString());
// break;
//case plcDataType.DWord:
// valUInt = getScaledUInt(currMem);
// saveDWordOnMemBlock(ref MemBlock, 0, valInt.ToString());
// break;
case plcDataType.Real:
case plcDataType.RealLH:
valDouble = getScaledDouble(currMem);
CurrVal = ModbusClient.ConvertFloatToRegisters((float)valDouble, ModbusClient.RegisterOrder.LowHigh);
fatto = writeHoldingReg(currMem.index, CurrVal);
break;
case plcDataType.FloatBADC:
case plcDataType.RealHL:
valDouble = getScaledDouble(currMem);
CurrVal = ModbusClient.ConvertFloatToRegisters((float)valDouble, ModbusClient.RegisterOrder.HighLow);
fatto = writeHoldingReg(currMem.index, CurrVal);
break;
case plcDataType.String:
CurrVal = ModbusClient.ConvertStringToRegisters(currMem.value.PadRight(currMem.size * 2));
var regLen = ModbusClient.ConvertIntToRegisters(currMem.size * 2 - 2);
Array.Copy(regLen, 0, CurrVal, CurrVal.Length - regLen.Length, regLen.Length);
//CurrVal[CurrVal.Length - 2] = regLen[1];
//CurrVal[CurrVal.Length - 1] = regLen[0];
fatto = writeHoldingReg(currMem.index, CurrVal);
break;
default:
break;
}
lgInfo($"---------------MemBlock data---------------{Environment.NewLine}CurrVal: {CurrVal}{Environment.NewLine}--------------- END data ---------------");
if (!string.IsNullOrEmpty(memAddrWrite))
{
// se fatto --> aggiorno!
if (fatto)
{
item.value = item.reqValue;
item.reqValue = "";
item.lastRead = DateTime.Now;
item.UM = currMem.unit;
}
}
else
{
lgError($"Errore: memAddrWrite vuoto!");
}
}
else
{
lgError($"Errore uid non trovato in area write memory: {item.uid}, ci sono {memMap.mMapWrite.Count} in area write");
}
}
catch (Exception exc)
{
lgError($"Eccezione in fase di plcWriteParams per item {item.uid} con valore {item.value}{Environment.NewLine}{exc}");
}
}
}
}
/// <summary>
/// Imposto parametri PLC
/// </summary>
protected override void setParamPlc()
{
DateTime adesso = DateTime.Now;
// Creo oggetto connessione NC
parentForm.commPlcActive = true;
lgInfo($"Start init Adapter ModBus TCP all'IP {IOBConfFull.Device.Connect.IpAddr} | port: {IOBConfFull.Device.Connect.Port} | --> IOB {IOBConfFull.General.CodIOB}");
// SE è necessario refresh...
if (needRefresh)
{
lgInfo("Refreshing connection...");
if (parametri != null)
{
try
{
lgInfoStartup("Init parametri speciali da IobConfTree...");
parametri.ipAdrr = IOBConfFull.Device.Connect.IpAddr;
parametri.port = int.Parse(IOBConfFull.Device.Connect.Port);
if (IOBConfFull.Special.ModbusConf != null)
{
// ora leggo valori speciali
parametri.memAddrRead = IOBConfFull.Special.ModbusConf.memAddrRead;
parametri.memAddrWrite = IOBConfFull.Special.ModbusConf.memAddrWrite;
parametri.memSizeRead = IOBConfFull.Special.ModbusConf.memSizeRead;
parametri.memSizeWrite = IOBConfFull.Special.ModbusConf.memSizeWrite;
parametri.holdRegBaseAddr = IOBConfFull.Special.ModbusConf.holdRegBaseAddr;
parametri.useCalcBaseAddr = IOBConfFull.Special.ModbusConf.useCalcBaseAddr;
// valore delta base e shift
deltaBase = IOBConfFull.Special.ModbusConf.deltaBase;
indexLutCorr = IOBConfFull.Special.ModbusConf.indexLutCorr;
// verifico eventuale parametro memoria estesa (non 0...10'000 ma 0...xFFFF=65536)
modbusExtReg = IOBConfFull.Special.ModbusConf.modbusExtReg;
}
#if false
// leggo file init...
lgInfoStartup("Reading ini file...");
IniFile fIni = new IniFile(cIobConf.iniFileName);
// ora leggo valori speciali
parametri.memAddrRead = fIni.ReadString("MEMORY", "ADDR_READ", "");
parametri.memAddrWrite = fIni.ReadString("MEMORY", "ADDR_WRITE", "");
parametri.memSizeRead = fIni.ReadInteger("MEMORY", "SIZE_READ", 0);
parametri.memSizeWrite = fIni.ReadInteger("MEMORY", "SIZE_WRITE", 0);
parametri.holdRegBaseAddr = fIni.ReadInteger("MEMORY", "HR_BASE_ADDR", 40001);
parametri.useCalcBaseAddr = fIni.ReadBoolean("MEMORY", "CALC_BASE_ADDR", true);
// valore delta base e shift
deltaBase = fIni.ReadInteger("MEMORY", "DELTA_BASE", 0);
indexLutCorr = fIni.ReadInteger("MEMORY", "INDEX_LUT_CORR", 0);
// verifico eventuale parametro memoria estesa (non 0...10'000 ma 0...xFFFF=65536)
modbusExtReg = fIni.ReadBoolean("MEMORY", "MODBUS_EXT_REG");
#endif
// salvo vettori memoria...
lgInfoStartup("Set RawInput dimensions...");
RawInput = new byte[parametri.memSizeRead];
RawOutput = new byte[parametri.memSizeWrite];
// salvo parametri conn!
lgInfoStartup($"Parametri memoria: memAddrRead: {parametri.memAddrRead} | memAddrWrite: {parametri.memAddrWrite} | memSizeRead: {parametri.memSizeRead} | memSizeWrite: {parametri.memSizeWrite} | deltaBase: {deltaBase}");
}
catch (Exception exc)
{
lgError(exc, "Errore in parse parametri da IOBConf");
}
// carico conf vettore memoria...
loadMemConf();
fixDefaultPar();
// avvio conf blocchi memoria
setupMemBlocks();
// aggiungo DELTA x calcolo min/MAX...
string deltaValStr = getOptPar("DELTA_VAL");
if (!string.IsNullOrEmpty(deltaValStr))
{
double.TryParse(deltaValStr.Replace(".", ","), out deltaVal);
}
// 2025.05.28: evita contapezzi dove NON configurato esplicitamente (versione "Imperativa" da nuova conf)
bool enabPzCnt = IOBConfFull.Device.EnabPzCount;
#if false
bool enableByApp = utils.CRB("enableContapezzi");
bool enableByIob = (getOptPar("ENABLE_PZCOUNT") == "TRUE");
bool disableByIob = (getOptPar("DISABLE_PZCOUNT") == "TRUE");
#endif
var strMaxError = getOptPar("MAX_ERROR_READ");
if (!string.IsNullOrEmpty(strMaxError))
{
int.TryParse(strMaxError, out maxErrorRead);
}
//if ((enableByApp || enableByIob) && !(disableByIob))
if (enabPzCnt)
{
lgInfoStartup("ModBus TCP: inizio gestione contapezzi");
try
{
// verifico quale modalità sia richiesta: STD (6711) oppure BIT (Custom,
// con indicazione area)
if (IOBConfFull.OptPar.Count > 0 && !string.IsNullOrWhiteSpace(IOBConfFull.Device.PzCountMode))
{
if (IOBConfFull.Device.PzCountMode.StartsWith("STD"))
{
lgInfoStartup("Init contapezzi ModBusTCP: pzCntReload(true)");
pzCntReload(true);
// refresh associazione Macchina - IOB
SendM2IOB();
// invio altri dati accessori...
SendMachineConf();
// gestione allarmi: controllo se deve rileggere da REDIS
if (resetAlarmOnStart)
{
SendAlarmReset();
}
// per adesso imposto lettura PLC == contapezzi (poi farà vera lettura...)
contapezziPLC = contapezziIOB;
}
else
{
contapezziIOB = 0;
lgDebug("Contapezzi STD disabilitato: modalità {0}", IOBConfFull.Device.PzCountMode);
}
}
else
{
contapezziIOB = 0;
lgError("Parametro mancante PZCOUNT_MODE");
}
}
catch (Exception exc)
{
lgError(exc, "Errore in contapezzi ModBusTCP");
}
}
// verifico se sia connessa...
if (!connectionOk)
{
// ora tento avvio PLC... SE PING OK...
lastPING = adesso;
IPStatus esitoPing = testPingMachine;
if (esitoPing == IPStatus.Success)
{
needRefresh = false;
try
{
//Ip-Address and Port of Modbus-TCP-Server
currPLC = new ModbusClient(parametri.ipAdrr, parametri.port);
currPLC.ConnectionTimeout = 5000;
currPLC.ConnectedChanged += CurrPLC_ConnectedChanged;
// disconnetto e connetto...
if (isVerboseLog)
{
lgInfoStartup("ModBus TCP: tryDisconnect");
}
tryDisconnect();
// tolgo needRefresh x evitare un loop... (tryConnect richiama setParamPlc)
needRefresh = false;
lgInfoStartup("ModBus TCP: tryConnect");
tryConnect();
lgInfoStartup("End init Adapter ModBusTCP");
if (isVerboseLog)
{
lgInfo("ModBus TCP CONNESSIONE AVVENUTA");
}
}
catch (Exception exc)
{
lgError(exc, "Errore in INIT ModBusTCP");
}
}
else
{
lgError($"ModBusTCP IOB | Errore in ping: esito {esitoPing}");
}
}
parentForm.commPlcActive = false;
}
else
{
lgError("Parametri null!");
}
}
}
/// <summary>
/// effettua il setup dei memblock da gestire (NON leggo intera memoria ma tanti blocchi...)
/// </summary>
protected void setupMemBlocks()
{
// se configurato -_> deserializzo
string confFile = getOptPar("MEM_BLOCK");
if (!string.IsNullOrEmpty(confFile))
{
string jsonFileName = $"{Application.StartupPath}/DATA/CONF/{confFile}";
lgInfo($"Apertura file {jsonFileName}");
using (StreamReader reader = new StreamReader(jsonFileName))
{
string jsonData = reader.ReadToEnd();
if (!string.IsNullOrEmpty(jsonData))
{
lgInfo($"File json MemBlock composto da {jsonData.Length} caratteri");
try
{
var currMem = JsonConvert.DeserializeObject<MemBlockConf>(jsonData);
memSetR = currMem.ReadBlocks;
}
catch (Exception exc)
{
lgError($"Errore in setupMemBlock{Environment.NewLine}{exc}");
}
}
}
}
}
/// <summary>
/// Testa la condition modbus da LUT + configurazione, valori tipo BIT
/// </summary>
/// <param name="cKey"></param>
/// <returns></returns>
protected bool testBitCondition(string cKey)
{
bool answ = false;
if (OptCheckCondBit.ContainsKey(cKey))
{
int currStatus = 0;
int[] listInt = new int[2];
listInt = HoldingRegisterLUT[OptCheckCondBit[cKey].BaseAddr];
currStatus = ModbusClient.ConvertRegistersToInt(listInt);
// controllo valore del bit richiesto
answ = ((currStatus & (1 << OptCheckCondBit[cKey].BitNum)) == OptCheckCondBit[cKey].ValOk);
lgTrace($"testBitCondition for {cKey} | BaseAddr: {OptCheckCondBit[cKey].BaseAddr} | BitNum: {OptCheckCondBit[cKey].BitNum} | ValOk: {OptCheckCondBit[cKey].ValOk}");
}
else
{
lgTrace($"testBitCondition error: {cKey} not found");
}
return answ;
}
/// <summary>
/// Effettua ciclo letture di ogni area configurata
/// </summary>
/// <param name="baseAddr">indirizzo di partenza</param>
/// <param name="numVals">num valori da leggere</param>
protected void testBlockReadExt(int baseAddr, int numVals)
{
int baseHold = deltaBase;
int[] readTestInt = new int[2];
bool[] readTestBool = new bool[2];
modbusMemType currMemType = modbusMemType.NotDefined;
// valori setup secondo conf memoria: memoria estesa... 0 ... xFFFF per ogni set da 100'000...
int maxVal = modbusExtReg ? 65536 : 10000;
int baseMult = modbusExtReg ? 100000 : 10000;
lgTrace($"-------------------- Inizio test lettura EXT {baseAddr} --------------------");
try
{
sw.Restart();
// calcolo tipo memoria
currMemType = getMemType(baseAddr, maxVal, baseMult);
switch (currMemType)
{
case modbusMemType.Coil:
readTestBool = currPLC.ReadCoils(baseAddr - baseHold, numVals);
trackReadData(numVals / 8, sw.Elapsed.TotalMilliseconds / 1000);
break;
case modbusMemType.DiscreteInput:
readTestBool = currPLC.ReadDiscreteInputs(baseAddr - 1 * baseMult + baseHold, numVals);
trackReadData(numVals / 8, sw.Elapsed.TotalMilliseconds / 1000);
break;
case modbusMemType.InputRegister:
readTestInt = currPLC.ReadInputRegisters(baseAddr - 3 * baseMult + baseHold, numVals);
trackReadData(numVals * 2, sw.Elapsed.TotalMilliseconds / 1000);
break;
case modbusMemType.HoldingRegister:
readTestInt = currPLC.ReadHoldingRegisters(baseAddr - 4 * baseMult + baseHold, numVals);
trackReadData(numVals * 2, sw.Elapsed.TotalMilliseconds / 1000);
break;
case modbusMemType.NotDefined:
default:
break;
}
sw.Stop();
lgTrace($"Stats lettura | {readTestInt.Length} val | {sw.ElapsedMilliseconds}ms");
}
catch
{ }
switch (currMemType)
{
case modbusMemType.Coil:
case modbusMemType.DiscreteInput:
for (int i = 0; i < readTestBool.Length; i++)
{
lgTrace($"{currMemType} | {baseAddr + i:000} | Val: {readTestBool[i]}");
}
break;
case modbusMemType.InputRegister:
case modbusMemType.HoldingRegister:
for (int i = 0; i < readTestInt.Length / 2; i++)
{
int[] thisSet = new int[2];
Array.Copy(readTestInt, i * 2, thisSet, 0, 2);
lgTrace($"{currMemType} | {baseAddr + i * 2:000} | Valori: {thisSet[0]} / {thisSet[1]} | Val Int: {ModbusClient.ConvertRegistersToInt(thisSet)} | Val Real: {ModbusClient.ConvertRegistersToFloat(thisSet):N6}");
}
// provo anche come stringa intera
lgTrace($"{currMemType} | {baseAddr:000} | Val string: {ModbusClient.ConvertRegistersToString(readTestInt, 0, readTestInt.Length)}");
break;
case modbusMemType.NotDefined:
default:
lgTrace($"Errore: tipo memoria non definitito per {baseAddr} / {numVals}");
break;
}
lgTrace($"-------------------- Completato test lettura {baseAddr} --------------------");
}
/// <summary>
/// Test connessione CNC
/// </summary>
/// <returns></returns>
protected bool testCncConn()
{
bool answ = currPLC.Connected;
if (!answ)
{
// riduco i controlli ping.. li faccio solo ogni 5 ping period se precedente positivo...
DateTime adesso = DateTime.Now;
if (lastPingOk && adesso.Subtract(lastPingConn).TotalMilliseconds < 5 * parametri.pingMsTimeout)
{
answ = lastPingOk;
}
else
{
IPStatus pingStatus = testPingMachine;
// se non ok riprovo 1 volta dopo attesa
if (pingStatus != IPStatus.Success)
{
Thread.Sleep(2 * IOBConfFull.Device.Connect.PingMsTimeout);
pingStatus = testPingMachine;
}
// se non passa ancora errore!
if (pingStatus != IPStatus.Success)
{
lgError($"Errore in testCncConn | reply Status {pingStatus} | IP: {parametri.ipAdrr} | T.Out: {parametri.pingMsTimeout}ms");
}
// se passa il ping faccio il resto...
else
{
if (!currPLC.Connected)
{
currPLC.Connect();
}
if (!currPLC.Available(500))
{
lgError($"PLC ModBus NON disponibile: {currPLC.IPAddress} | {currPLC.Port}");
}
else
{
if (!currPLC.Connected)
{
lgError($"PLC ModBus NON connesso:{currPLC.IPAddress} | {currPLC.Port}");
}
else
{
// tutto ok
parentForm.updateComStats("Connection OK");
answ = true;
}
}
}
// salvo stato ping
lastPingConn = adesso;
}
lastPingOk = answ;
}
return answ;
}
/// <summary>
/// Testa la condition modbus da LUT + configurazione, valori tipo BIT tramite DiscreteInput
/// (0x02 - 10000+)
/// </summary>
/// <param name="cKey"></param>
/// <returns></returns>
protected bool testDisInBitCondition(string cKey)
{
bool answ = false;
if (OptCheckCondBit.ContainsKey(cKey))
{
if (DiscreteInputLUT.Count > 0)
{
int idxMem = OptCheckCondBit[cKey].BaseAddr + indexLutCorr;
if (DiscreteInputLUT.ContainsKey(idxMem))
{
answ = DiscreteInputLUT[idxMem] == (OptCheckCondBit[cKey].ValOk != 0);
lgTrace($"testDisInBitCondition for {cKey} | BaseAddr: {OptCheckCondBit[cKey].BaseAddr} | BitNum: {OptCheckCondBit[cKey].BitNum} | ValOk: {OptCheckCondBit[cKey].ValOk} | actual: {answ}");
}
}
}
else
{
lgTrace($"testDiscrInputBitCondition error: {cKey} not found");
}
return answ;
}
/// <summary>
/// Testa la condition modbus da LUT + configurazione, valori tipo INT
/// </summary>
/// <param name="cKey"></param>
/// <returns></returns>
protected bool testIntCondition(string cKey)
{
bool answ = false;
if (OptCheckCondInt.ContainsKey(cKey))
{
int currStatus = 0;
int[] listInt = new int[2];
listInt = HoldingRegisterLUT[OptCheckCondInt[cKey].BaseAddr];
// leggo da conf OptCheckCondInt[cKey].IntIndex
if (OptCheckCondInt[cKey].IntIndex < 2)
{
currStatus = listInt[OptCheckCondInt[cKey].IntIndex];
}
else
{
currStatus = ModbusClient.ConvertRegistersToInt(listInt);
}
//answ = (currStatus == OptCheckCondInt[cKey].ValOk);
// controllo se il valore sia in elenco di quelli validi...
answ = OptCheckCondInt[cKey].ValOk.Contains(currStatus);
lgTrace($"testIntCondition for {cKey} | BaseAddr: {OptCheckCondInt[cKey].BaseAddr} | currStatus: {currStatus} | ValOk: {string.Join(",", OptCheckCondInt[cKey].ValOk)}");
}
else
{
lgTrace($"testIntCondition error: {cKey} not found");
}
return answ;
}
/// <summary>
/// Test di lettura dei blocchi di memoria ESTESO (considerato memorie xFFFF e tutti e 4 i
/// tipi di dati modbus)
/// </summary>
protected void testReadExt()
{
foreach (var item in memSetR)
{
testBlockReadExt(item.Key, item.Value);
}
}
#endregion Protected Methods
#region Private Fields
/// <summary>
/// Valore delta x gestione min/MAX e valore rilevato
/// </summary>
private double deltaVal = 0;
#endregion Private Fields
#region Private Properties
/// <summary>
/// Valore da gestire come delta degli indirizzi configurati
/// </summary>
private int deltaBase { get; set; } = 0;
/// <summary>
/// Valore da gestire come delta degli index x lettura LUT
/// </summary>
private int indexLutCorr { get; set; } = 0;
/// <summary>
/// Indica se usare gli indirizzi estesi per ogni blocco: false = 0...10'000 true =
/// 0...65'536 (= xFFFF)
/// </summary>
private bool modbusExtReg { get; set; } = false;
#endregion Private Properties
#region Private Methods
/// <summary>
/// converte valore in tipo desiderato
/// </summary>
/// <param name="listInt"></param>
/// <param name="size"></param>
/// <param name="tipoMem"></param>
/// <returns></returns>
private static double convertFromReg(int[] listInt, int size, plcDataType tipoMem)
{
double valore = 0;
//verifico se sia INT o real x convertire...
switch (tipoMem)
{
case plcDataType.Real:
case plcDataType.RealLH:
case plcDataType.FloatABCD:
if (size == 4)
{
valore = ModbusClient.ConvertRegistersToDouble(listInt);
}
else if (size == 2)
{
valore = ModbusClient.ConvertRegistersToFloat(listInt, ModbusClient.RegisterOrder.LowHigh);
}
break;
case plcDataType.RealHL:
case plcDataType.FloatCDAB:
if (size == 4)
{
//// inverto array...
//Array.Reverse(listInt);
//valore = ModbusClient.ConvertRegistersToDouble(listInt);
valore = ModbusClient.ConvertRegistersToDouble(listInt, ModbusClient.RegisterOrder.HighLow);
}
else if (size == 2)
{
//// inverto array...
//Array.Reverse(listInt);
//valore = ModbusClient.ConvertRegistersToFloat(listInt);
valore = ModbusClient.ConvertRegistersToFloat(listInt, ModbusClient.RegisterOrder.HighLow);
}
break;
// caso speciale x Cedax, che usa HighVal moltiplicato x 32768...
case plcDataType.HLPInt:
if (size == 4)
{
valore = ModbusClient.ConvertRegistersToLong(listInt);
}
else if (size == 2)
{
int fact = Int16.MaxValue + 1;
valore = listInt[1] * fact + listInt[0];
}
break;
case plcDataType.Int:
default:
if (size == 4)
{
valore = ModbusClient.ConvertRegistersToLong(listInt);
}
else if (size == 2)
{
valore = ModbusClient.ConvertRegistersToInt(listInt);
}
else if (size == 1)
{
valore = listInt[0];
}
break;
case plcDataType.IntLH:
case plcDataType.DIntLH:
if (size == 4)
{
Array.Reverse(listInt);
valore = ModbusClient.ConvertRegistersToLong(listInt);
}
else if (size == 2)
{
Array.Reverse(listInt);
valore = ModbusClient.ConvertRegistersToInt(listInt);
}
break;
}
return valore;
}
private static double getScaledDouble(dataConf currMem)
{
double valDouble;
// prima faccio eventuale fattore di scala...
double.TryParse(currMem.value, out valDouble);
if (currMem.factor != 1)
{
valDouble = valDouble * (double)currMem.factor;
}
return valDouble;
}
/// <summary>
/// Effettua scalatura valore secondo fattore Read --&gt; * (moltiplico) Write --&gt; / (divido)
/// </summary>
/// <param name="currMem"></param>
/// <param name="isWrite"></param>
/// <returns></returns>
private static int getScaledInt(dataConf currMem, bool isWrite = false)
{
int valInt = 0;
double temp;
// prima faccio eventuale fattore di scala...
double.TryParse(currMem.value.Replace(".", ","), out temp);
//int.TryParse(currMem.value, out valInt);
if (currMem.factor != 1)
{
if (isWrite)
{
valInt = (int)(temp * 10 / currMem.factor) / 10;
}
else
{
valInt = (int)(temp * 10 * currMem.factor) / 10;
}
}
else
{
valInt = (int)temp;
}
return valInt;
}
/// <summary>
/// Effettua scalatura valore secondo fattore Read --&gt; * (moltiplico) Write --&gt; / (divido)
/// </summary>
/// <param name="currMem"></param>
/// <param name="isWrite"></param>
private static uint getScaledUInt(dataConf currMem, bool isWrite = false)
{
uint valUInt;
// prima faccio eventuale fattore di scala...
uint.TryParse(currMem.value, out valUInt);
if (currMem.factor != 1)
{
if (isWrite)
{
valUInt = valUInt / (uint)currMem.factor;
}
else
{
valUInt = valUInt * (uint)currMem.factor;
}
}
return valUInt;
}
/// <summary>
/// se si disconnette --&gt; processo tentativo riconnessione..
/// </summary>
/// <param name="sender"></param>
private void CurrPLC_ConnectedChanged(object sender)
{
if (!currPLC.Connected)
{
tryDisconnect();
}
}
/// <summary>
/// Recupero dati da singole letture
/// </summary>
/// <param name="memItemList">Valori da processare</param>
/// <param name="useLUT">Impiego della LookUpTable x diminuire accessi</param>
/// <param name="outVal">Dizionario valori in uscita</param>
/// <returns>Errori di lettura</returns>
private Dictionary<string, dataConfTSVC> getDataDictionary(Dictionary<string, dataConfTSVC> memItemList, bool useLUT, ref Dictionary<string, string> outVal)
{
Dictionary<string, dataConfTSVC> readErrorList = new Dictionary<string, dataConfTSVC>();
// inizializzo i valori
bool[] listBool = new bool[1];
int[] listInt = new int[2];
// procedo x ogni valore configurato...
foreach (var item in memItemList)
{
try
{
listInt = new int[item.Value.size];
// attesa 50 ms prima di procedere x evitare burst dati
if (!useLUT)
{
Thread.Sleep(50);
}
string valString = "";
double valore = 0;
double valoreScal = 0;
bool dataOk = false;
// in primis DEVO determinare di quale TIPO di valore ho bisogno... dalla PRIMA
// cifra di memAddr...
modBusAddrType memAddrType = getAddrType(item.Value.memAddr);
// in base al tipo leggo array...
switch (memAddrType)
{
case modBusAddrType.Coil:
listBool = readCoil(item.Value.index, item.Value.size);
valore = listBool[0] ? 1 : 0;
break;
case modBusAddrType.DiscreteInput:
listBool = readDiscrInputs(item.Value.index, item.Value.size);
valore = listBool[0] ? 1 : 0;
break;
case modBusAddrType.InputRegister:
if (useLUT)
{
int lutAddress = (parametri.holdRegBaseAddr / 4 * 3) + item.Value.index + deltaBase + indexLutCorr;
// se size = 1 --> devo cambiare modo recupero
if (item.Value.size == 1)
{
//if (item.Value.index % 2 == 0)
if (lutAddress % 2 == 0)
{
//Array.Copy(HoldingRegisterLUT[lutAddress], listInt, 1);
Array.Copy(InputRegisterLUT[lutAddress], 0, listInt, 0, 1);
}
else
{
lutAddress--;
Array.Copy(InputRegisterLUT[lutAddress], 1, listInt, 0, 1);
}
}
else
{
if (InputRegisterLUT.ContainsKey(lutAddress))
{
try
{
if (listInt.Length == 2)
{
listInt = InputRegisterLUT[lutAddress];
}
else
{
// faccio ciclo x copiare 2 alla volta...
for (int i = 0; i < listInt.Length; i += 2)
{
Array.Copy(InputRegisterLUT[lutAddress + i], 0, listInt, i, 2);
}
}
}
catch (Exception exc)
{
lgError($"Errore in lettura da InputRegisterLUT per indirizzo {lutAddress} | item {item.Key}{Environment.NewLine}{exc}");
}
}
}
}
else
{
listInt = readInputReg(item.Value.index, item.Value.size);
}
if (item.Value.tipoMem == plcDataType.String)
{
// valore string...
valString = ModbusClient.ConvertRegistersToString(listInt, 0, item.Value.size);
}
else
{
// valori numerici...
valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem);
}
break;
case modBusAddrType.HoldingRegister:
if (useLUT)
{
int lutAddress = parametri.holdRegBaseAddr + item.Value.index + deltaBase + indexLutCorr;
// se size = 1 --> devo cambiare modo recupero
if (item.Value.size == 1)
{
//if (item.Value.index % 2 == 0)
if (lutAddress % 2 == 0)
{
Array.Copy(HoldingRegisterLUT[lutAddress], 0, listInt, 0, 1);
}
else
{
lutAddress--;
Array.Copy(HoldingRegisterLUT[lutAddress], 1, listInt, 0, 1);
}
}
else
{
if (HoldingRegisterLUT.ContainsKey(lutAddress))
{
try
{
if (listInt.Length == 2)
{
listInt = HoldingRegisterLUT[lutAddress];
}
else
{
// faccio ciclo x copiare 2 alla volta...
for (int i = 0; i < listInt.Length; i += 2)
{
Array.Copy(HoldingRegisterLUT[lutAddress + i], 0, listInt, i, 2);
}
}
}
catch (Exception exc)
{
lgError($"Errore in lettura da HoldingRegisterLUT per indirizzo {lutAddress} | item {item.Key}{Environment.NewLine}{exc}");
}
}
}
}
else
{
listInt = readHoldReg(item.Value.index - 1, item.Value.size);
}
if (item.Value.tipoMem == plcDataType.String)
{
// valore string...
valString = ModbusClient.ConvertRegistersToString(listInt, 0, item.Value.size);
valString = valString.Replace("\n", "").Replace("\r", "").Replace("\0", string.Empty).Trim();
}
else
{
valore = convertFromReg(listInt, item.Value.size, item.Value.tipoMem);
}
break;
default:
break;
}
if (item.Value.tipoMem == plcDataType.String)
{
saveValueString(ref outVal, item.Key, valString);
lgDebug($"getDataDictionary: valore ricevuto | {item.Key} | val: {valString}");
}
else
{
// verifica limite... con delta da impianto
dataOk = (valore >= (item.Value.minVal + deltaVal) && valore <= (item.Value.maxVal - deltaVal));
if (dataOk || disDynDataRangeCheck)
{
// verifica valori EPOCH x convertirli in datetime...
if (item.Value.unit == "EPOCH")
{
valString = TimeUtils.epochConvert(valore).ToString("yyyy-MM-dd HH:mm:ss");
saveValueString(ref outVal, item.Key, valString);
lgDebug($"getDataDictionary: valore EPOCH ricevuto | {item.Key} | val: {valore} | valString: {valString}");
}
else
{
// moltiplico x fattore conversione...
valoreScal = valore * item.Value.factor;
saveValue(ref outVal, item.Key, valoreScal);
lgDebug($"getDataDictionary: valore ricevuto | {item.Key} | val: {valore} | valoreScal: {valoreScal}");
}
}
else
{
lgError($"getDataDictionary: valore scartato x limiti min/max | {item.Key} | val: {valore} | valoreScal: {valoreScal} | min-max: {item.Value.minVal}-{item.Value.maxVal} | deltaVal: {deltaVal}");
readErrorList.Add(item.Key, item.Value);
lgDebug($"--> rimesso in coda lettura | parametro: {item.Key} | index: {item.Value.index} | size: {item.Value.size}");
}
}
}
catch (Exception exc)
{
lgError($"Eccezione in lettura variabile | item: {item.Key} | mem: {item.Value.memAddr}{Environment.NewLine}{exc}");
}
}
return readErrorList;
}
/// <summary>
/// Calcola tipo memoria modbus dato indirizzo, dati parametri maxVal (slot) + base moltiplica
/// </summary>
/// <param name="baseAddr">Indirizzo memoria</param>
/// <param name="maxVal">Valore massimo ammesso</param>
/// <param name="baseMult">Base moltiplica slot memoria</param>
/// <returns></returns>
private modbusMemType getMemType(int baseAddr, int maxVal, int baseMult)
{
modbusMemType currMemType = modbusMemType.NotDefined;
// determino tipo di memoria
if (baseAddr < maxVal)
{
currMemType = modbusMemType.Coil;
}
else if (baseAddr < baseMult + maxVal)
{
currMemType = modbusMemType.DiscreteInput;
}
else if (baseAddr < 3 * baseMult + maxVal)
{
currMemType = modbusMemType.InputRegister;
}
else
{
currMemType = modbusMemType.HoldingRegister;
}
return currMemType;
}
/// <summary>
/// Verifica SE sia il caso di fare il log della memoria indicata
/// </summary>
/// <param name="memAddrWrite"></param>
/// <param name="logValue"></param>
private void maybeLogWrite(string memAddrWrite, string logValue)
{
bool doWrite = true;
DateTime adesso = DateTime.Now;
if (!lastMemWrite.ContainsKey(memAddrWrite))
{
lastMemWrite.Add(memAddrWrite, adesso.AddMinutes(-1));
}
// ora mi leggo valore ultimas crittura e confronto con adesso
try
{
doWrite = (lastMemWrite[memAddrWrite].AddSeconds(vetoSeconds) < adesso);
}
catch (Exception exc)
{
lgError($"Eccezione in maybeLogWrite{Environment.NewLine}{exc}");
}
// se encessario --> LOG!
if (doWrite)
{
lgInfo(logValue);
lastMemWrite[memAddrWrite] = adesso;
}
}
/// <summary>
/// Effettua lettura blocco memoria indicato e lo salva in copia locale in CoilLUT
/// </summary>
/// <param name="startAddr">Indirizzo di partenza</param>
/// <param name="numReg">Num registri da leggere (max 120)</param>
private bool readMemBlockCoil(int startAddr, int numReg, int baseAddr)
{
bool allOk = false;
if (currPLC.Connected)
{
allOk = true;
// fix massima lunghezza pacchetto
if (numReg > 120)
{
lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
numReg = 120;
}
bool[] rawData = new bool[2];
try
{
sw.Restart();
rawData = currPLC.ReadCoils(startAddr - baseAddr, numReg);
sw.Stop();
lgTrace($"Lettura in blocco Colis | startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms");
// salvo statistica...
trackReadData(rawData.Length / 8, sw.Elapsed.TotalMilliseconds / 1000);
lastReadPLC = DateTime.Now;
// salvo in LUT i dati...
if (rawData.Length > 0)
{
for (int i = 0; i < rawData.Length; i++)
{
if (rawData.Length >= i)
{
// salvo nel registro...
int currAddr = startAddr + i;
if (CoilLUT.ContainsKey(currAddr))
{
CoilLUT[currAddr] = rawData[i];
}
else
{
CoilLUT.Add(currAddr, rawData[i]);
}
}
else
{
currReadErrors++;
allOk = false;
lgError($"CoilLUT | Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i: {i} | errori currReadErrors: {currReadErrors}");
}
}
}
}
catch (Exception exc)
{
currReadErrors++;
allOk = false;
lgError($"Eccezione in readMemBlockCoil | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
}
}
else
{
lgError($"readMemBlockCoil | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
tryDisconnect();
}
return allOk;
}
/// <summary>
/// Effettua lettura blocco memoria indicato e lo salva in copia locale in DiscreteInputLUT
/// </summary>
/// <param name="startAddr">Indirizzo di partenza</param>
/// <param name="numReg">Num registri da leggere (max 120)</param>
private bool readMemBlockDiscreteIn(int startAddr, int numReg, int baseAddr)
{
bool allOk = false;
if (currPLC.Connected)
{
allOk = true;
// fix massima lunghezza pacchetto
if (numReg > 120)
{
lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
numReg = 120;
}
bool[] rawData = new bool[2];
try
{
sw.Restart();
int memAddr = startAddr - baseAddr;
rawData = currPLC.ReadDiscreteInputs(memAddr, numReg);
sw.Stop();
lgTrace($"Lettura in blocco DiscreteInput | startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms");
// salvo statistica...
trackReadData(rawData.Length / 8, sw.Elapsed.TotalMilliseconds / 1000);
lastReadPLC = DateTime.Now;
// salvo in LUT i dati...
if (rawData.Length > 0)
{
for (int i = 0; i < rawData.Length; i++)
{
if (rawData.Length >= i)
{
// salvo nel registro...
int currAddr = startAddr + i;
if (DiscreteInputLUT.ContainsKey(currAddr))
{
DiscreteInputLUT[currAddr] = rawData[i];
}
else
{
DiscreteInputLUT.Add(currAddr, rawData[i]);
}
}
else
{
currReadErrors++;
allOk = false;
lgError($"DiscreteInputLUT | Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i: {i} | errori currReadErrors: {currReadErrors}");
}
}
}
}
catch (Exception exc)
{
currReadErrors++;
allOk = false;
lgError($"Eccezione in readMemBlockDiscreteIn | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
}
}
else
{
lgError($"readMemBlockDiscreteIn | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
tryDisconnect();
}
return allOk;
}
/// <summary>
/// Effettua lettura blocco memoria indicato e lo salva in copia locale in HoldingRegisterLUT
/// </summary>
/// <param name="startAddr">Indirizzo di partenza</param>
/// <param name="numReg">Num registri da leggere (max 120)</param>
/// <param name="baseAddr">Indirizzo di base (da sottrarre all'indirizzo richiesto)</param>
private bool readMemBlockHoldingReg(int startAddr, int numReg, int baseAddr)
{
bool allOk = false;
if (currPLC.Connected)
{
allOk = true;
// fix massima lunghezza pacchetto
if (numReg > 120)
{
lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
numReg = 120;
}
// si lavora rispetto all'indirizzo base 40001
int[] rawData = new int[2];
try
{
sw.Restart();
int memAddr = startAddr - baseAddr;
rawData = currPLC.ReadHoldingRegisters(memAddr, numReg);
sw.Stop();
lgTrace($"Lettura in blocco HoldingRegisters| startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.Elapsed.TotalMilliseconds}ms");
// salvo statistica...
trackReadData(rawData.Length * 2, sw.Elapsed.TotalMilliseconds / 1000);
lastReadPLC = DateTime.Now;
// se risposta troppo rapida (< minRespTimeMs, tipicamente 5 msse non impostato in OptPar) indico NON ok e errori...
if (sw.Elapsed.TotalMilliseconds < minRespTimeMs)
{
allOk = false;
currReadErrors += 10;
}
// se leggo tutti zero --> indico +5 read error!
bool allZero = (rawData.Where(x => x > 0).Count() == 0);
if (allZero)
{
currReadErrors += 5;
}
// salvo in LUT la versione ESPLOSA 2 byte alla volta...
if (rawData.Length > 0)
{
for (int i = 0; i < rawData.Length / 2; i++)
{
int[] thisSet = new int[2];
if (rawData.Length >= (i + 1) * 2)
{
Array.Copy(rawData, i * 2, thisSet, 0, 2);
// salvo nel registro...
int currAddr = startAddr + i * 2;
if (HoldingRegisterLUT.ContainsKey(currAddr))
{
HoldingRegisterLUT[currAddr] = thisSet;
}
else
{
HoldingRegisterLUT.Add(currAddr, thisSet);
}
}
else
{
currReadErrors++;
allOk = false;
lgError($"Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i*2: {i * 2} | errori currReadErrors: {currReadErrors}");
}
}
}
}
catch (Exception exc)
{
// eccezione aggiunge 10 read errors!
currReadErrors += 10;
allOk = false;
lgError($"Eccezione in readMemBlockHoldingReg | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
}
}
else
{
lgError($"readMemBlockHoldingReg | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
tryDisconnect();
}
// all ok: se non scattato verifico se errori < 50...
if (allOk)
{
allOk = currReadErrors < 10;
}
return allOk;
}
/// <summary>
/// Effettua lettura blocco memoria indicato e lo salva in copia locale in InputRegisterLUT
/// </summary>
/// <param name="startAddr">Indirizzo di partenza</param>
/// <param name="numReg">Num registri da leggere (max 120)</param>
private bool readMemBlockInputReg(int startAddr, int numReg, int baseAddr)
{
bool allOk = false;
if (currPLC.Connected)
{
allOk = true;
// fix massima lunghezza pacchetto
if (numReg > 120)
{
lgError($"Attenzione richiesta lettura blocco troppo grande: numReg {numReg} --> 120");
numReg = 120;
}
// si lavora rispetto all'indirizzo base 40001
int[] rawData = new int[2];
try
{
sw.Restart();
int memAddr = startAddr - baseAddr;
rawData = currPLC.ReadInputRegisters(memAddr, numReg);
sw.Stop();
lgTrace($"Lettura in blocco InputRegisters| startAddr: {startAddr} | numReg {numReg} | rawData.lenght: {rawData.Length} | {sw.ElapsedMilliseconds}ms");
// salvo statistica...
trackReadData(rawData.Length * 2, sw.Elapsed.TotalMilliseconds / 1000);
lastReadPLC = DateTime.Now;
// salvo in LUT la versione ESPLOSA 2 byte alla volta...
if (rawData.Length > 0)
{
for (int i = 0; i < rawData.Length / 2; i++)
{
int[] thisSet = new int[2];
if (rawData.Length >= (i + 1) * 2)
{
Array.Copy(rawData, i * 2, thisSet, 0, 2);
// salvo nel registro...
int currAddr = startAddr + i * 2;
if (InputRegisterLUT.ContainsKey(currAddr))
{
InputRegisterLUT[currAddr] = thisSet;
}
else
{
InputRegisterLUT.Add(currAddr, thisSet);
}
}
else
{
currReadErrors++;
allOk = false;
lgError($"Impossibile copiare dati, array di origine troppo corto: rawData.lenght: {rawData.Length} | base addr richiesto i*2: {i * 2} | errori currReadErrors: {currReadErrors}");
}
}
}
}
catch (Exception exc)
{
currReadErrors++;
allOk = false;
lgError($"Eccezione in readMemBlockInputReg | startAddr: {startAddr} | baseAddr: {baseAddr} | numReg: {numReg} | currReadErrors: {currReadErrors} |{Environment.NewLine}{exc}");
}
}
else
{
lgError($"readMemBlockInputReg | Attenzione, PLC disconnesso... currReadErrors: {currReadErrors}");
tryDisconnect();
}
return allOk;
}
/// <summary>
/// Verifica il tipo di memoria e di conseguenza effettua chiama la procedura di lettura
/// </summary>
/// <param name="startAddr">Indirizzo di partenza</param>
/// <param name="numReg">Num registri da leggere (max 120)</param>
private void readMemoryBlock(int startAddr, int numReg)
{
bool allOk = false;
modbusMemType currMemType = modbusMemType.NotDefined;
// valori setup secondo conf memoria: memoria estesa... 0 ... xFFFF per ogni set da 100'000...
int maxVal = modbusExtReg ? 65536 : 10000;
int baseMult = modbusExtReg ? 100000 : 10000;
int baseAddr = parametri.holdRegBaseAddr;
// calcolo tipo memoria
currMemType = getMemType(startAddr, maxVal, baseMult);
// in base al tipo di memoria procedo
switch (currMemType)
{
case modbusMemType.Coil:
if (parametri.useCalcBaseAddr)
{
baseAddr = 0 * baseMult;
}
allOk = readMemBlockCoil(startAddr, numReg, baseAddr);
break;
case modbusMemType.DiscreteInput:
if (parametri.useCalcBaseAddr)
{
baseAddr = 1 * baseMult;
}
allOk = readMemBlockDiscreteIn(startAddr, numReg, baseAddr);
break;
case modbusMemType.InputRegister:
if (parametri.useCalcBaseAddr)
{
baseAddr = 3 * baseMult;
}
allOk = readMemBlockInputReg(startAddr, numReg, baseAddr);
break;
case modbusMemType.HoldingRegister:
if (parametri.useCalcBaseAddr)
{
baseAddr = 4 * baseMult;
}
allOk = readMemBlockHoldingReg(startAddr, numReg, baseAddr);
break;
case modbusMemType.NotDefined:
default:
break;
}
// se tutto ok --> riduco contatore errori di 2...
if (allOk)
{
currReadErrors = currReadErrors >= 2 ? currReadErrors - 2 : 0;
}
else
{
// se > max errori --> disconnetto
if (currReadErrors > maxReadErrors)
{
lgError($"Read Errors: Superato limite --> tryDisconnect | currReadErrors: {currReadErrors} | maxReadErrors: {maxReadErrors}");
currReadErrors = 0;
tryDisconnect();
}
else
{
// altrimenti pausa forzata
Thread.Sleep(1000);
}
}
}
#endregion Private Methods
}
}