Files
Mapo-IOB-WIN/IOB-WIN-NEXT/IobRest/Base.cs
T
2024-11-12 10:57:44 +01:00

765 lines
29 KiB
C#

using RestSharp;
using IOB_UT_NEXT;
using MapoSDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.NetworkInformation;
using System.IO;
using System.Windows.Forms;
using System.Threading;
using S7.Net.Types;
using System.Text.RegularExpressions;
namespace IOB_WIN_NEXT.IobRest
{
/// <summary>
/// Adapter base per sviluppo chiamate con servizi REST
/// </summary>
public class Base : Iob.Generic
{
#region Public Constructors
/// <summary>
/// Costruttore dell'IOB Rest generico
/// </summary>
/// <param name="caller">AdapterForm chiamante</param>
/// <param name="IOBConf">Configurazione IOB per avvio</param>
public Base(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf)
{
lgInfo($"Richiesto Adapter IobRest.Base con i parametri seguenti | ADDR: {IOBConf.cncIpAddr} | PORT: {IOBConf.cncPort}");
lastPING = DateTime.Now.AddHours(-1);
// predispongo configurazione specifica Rest...
if (!string.IsNullOrEmpty(getOptPar("REST_CONF")))
{
setupRestConf(getOptPar("REST_CONF"));
}
}
#endregion Public Constructors
#region Public Methods
/// <summary>
/// Implementazione custom esecuzione task specifici
/// </summary>
/// <param name="task2exe"></param>
/// <returns></returns>
public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe)
{
/*---------------------------------------
* gestione execute task SPECIFICI x pesa:
* - salva i parametri richiesta (RM, cod1..cod6)
* - esegue metodo richiesta (IN/OUT)
*---------------------------------------*/
// Verificare il protocollo: dovrebbe togliere SOLO i task eseguiti...
Dictionary<string, string> taskDone = new Dictionary<string, string>();
#if false
if (task2exe != null)
{
lgTrace($"executeTasks: richiesta esecuzione {task2exe.Count} task");
// controllo se memMap != null...
if (memMap != null)
{
bool taskOk = false;
string taskVal = "";
// cerco task specifici: qui sono NON standard...
foreach (var item in task2exe)
{
lgInfo($"TASK | {item.Key} --> {item.Value}");
taskOk = false;
taskVal = "";
// converto richiesta in enum...
taskType tName = taskType.nihil;
Enum.TryParse(item.Key, out tName);
// controllo sulla KEY...
switch (tName)
{
case taskType.setParameter:
// richiedo da URL i parametri WRITE da popolare
lgInfo("Chiamata setParameter --> 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;
default:
taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
break;
}
// aggiungo task!
taskDone.Add(item.Key, taskVal);
}
}
else
{
lgError($"Attenzione! memMap è nullo, non posso eseguire task2exe!");
}
}
#endif
return taskDone;
}
/// <summary>
/// Recupero dati dinamici...
/// </summary>
public override Dictionary<string, string> getDynData()
{
// valore non presente in vers default... se gestito fare override
Dictionary<string, string> outVal = new Dictionary<string, string>();
#if false
// controllo se ho indicato ancora che ci siano pesate già lette da inviare...
if (num2send() > 0)
{
// preparo oggetti x confronto ...
List<WeightRec> listaArch = listPesateArchivio;
// se non ho nulla in archivio prendo ultima pesata (essendo DESC è la prima cronologicamente)
if (listaArch.Count == 0)
{
WeightRec lastRec = listPesateCurr.LastOrDefault();
// aggiungo...
SavePesata(ref outVal, lastRec);
}
// ora il confronto è cronologico sulle pesate + recenti...
else
{
// prendo prima della lista pesate archiviate = più recente come dt da cui partire...
DateTime dtRif = listaArch.FirstOrDefault().DtEvent;
var firstRec = listPesateCurr
.Where(x => x.DtEvent > dtRif)
.OrderBy(x => x.DtEvent)
.FirstOrDefault();
if (firstRec != null)
{
SavePesata(ref outVal, firstRec);
}
}
// processo comunque le aree memoria READ...
if (memMap.mMapRead.Count > 0)
{
foreach (var item in memMap.mMapRead)
{
var currVal = getCurrProdData(item.Key, "");
if (!outVal.ContainsKey(item.Key))
{
outVal.Add(item.Key, $"{currVal}");
}
// se fosse logReq --> resetto...
if (item.Key == "logReq" && !string.IsNullOrEmpty(currVal))
{
upsertKey(item.Key, "");
}
}
}
}
// altrimenti rileggo e cerco se ci siano
else
{
Stopwatch sw = new Stopwatch();
sw.Start();
try
{
// test lettura elenco pesate... se NON nullo --> OK!
var weightArray = RestConn.reqWeightList("ALL", dataFrom, dataTo);
// riordino DESC
listPesateCurr = WeightRec.ConvertPesate(weightArray.OrderByDescending(x => x.dateIn).ToList());
}
catch (Exception exc)
{
lgError($"Eccezione in RestConn.reqWeightList{Environment.NewLine}{exc}");
}
sw.Stop();
lgInfo($"getDynData | SOAP: effettuata chiamata reqWeightList in {sw.Elapsed.TotalMilliseconds}ms | {dataFrom} --> {dataTo} | {listPesateCurr.Count} rec");
}
#endif
// indico esecuzione e proseguo
lastReadPLC = DateTime.Now;
return outVal;
}
/// <summary>
/// Effettua lettura semafori principale
/// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
/// </summary>
public override void readSemafori(ref newDisplayData currDispData)
{
DateTime adesso = DateTime.Now;
lastReadPLC = adesso;
// verifico non sia in veto invio iniziale...
if (queueInEnabCurr)
{
try
{
if (verboseLog)
{
lgInfo("inizio read semafori");
}
currDispData.semIn = Semaforo.SV;
// effettua refresh dati da leggere SPECIFICi x citizen...
refreshData();
// decodifica e gestione
decodeToBaseBitmap(ref currDispData);
// display
reportRawInput(ref currDispData);
}
catch (Exception exc)
{
currDispData.semIn = Semaforo.SR;
lgError($"Eccezione in readSemafori:{Environment.NewLine}{exc}");
}
}
else
{
lgDebug($"[VETO readSemafori] | veto attivo alle {adesso:yyyy.MM.dd HH:mm:ss}");
checkVetoQueueIn();
}
}
/// <summary>
/// Override connessione
/// </summary>
public override void tryConnect()
{
if (!connectionOk)
{
// controllo che il ping sia stato tentato almeno pingTestSec fa...
if (DateTime.Now.Subtract(lastPING).TotalSeconds > utils.CRI("pingTestSec"))
{
if (verboseLog || periodicLog)
{
lgInfo("Rest: ConnKO - tryConnect");
}
// in primis salvo data ping...
lastPING = DateTime.Now;
// se passa il ping faccio il resto...
if (testPingMachine == IPStatus.Success)
{
string szStatusConnection = "";
try
{
// ora provo connessione...
parentForm.commPlcActive = true;
// chiamo metodo connect
var checkResp = ExecuteCallGet(GetUrlResource("GetConnection"));
// forse va eliminato...
lgInfo($"GetConnection | {checkResp}");
if (checkResp != null && checkResp.ToLower().Contains("true"))
{
connectionOk = true;
}
else
{
lgError($"Errore check connessione | checkResp: {checkResp}");
}
// refresh stato connessione!!!
if (connectionOk)
{
queueInEnabCurr = true;
if (adpRunning)
{
lgInfo("Connessione OK");
}
}
else
{
lgError("Impossibile procedere, connessione mancante...");
}
}
catch (Exception exc)
{
lgFatal($"Errore nella connessione all'Adapter IobRest.Base: {szStatusConnection}{Environment.NewLine}{exc}");
connectionOk = false;
lgInfo($"Eccezione in TryConnect, Adapter IobRest.Base NON running, pausa di {utils.CRI("waitRecMSec")} msec prima di ulteriori tentativi di riconnessione");
}
}
else
{
// loggo no risposta ping ...
connectionOk = false;
if (verboseLog || periodicLog)
{
lgInfo($"Attenzione: Rest controllo PING fallito per IP {cIobConf.cncPingAddr}");
}
}
}
}
else
{
needRefresh = true;
}
}
public override void tryDisconnect()
{
// registro solo che è disconnesso
connectionOk = false;
queueInEnabCurr = false;
}
#endregion Public Methods
#region Protected Fields
/// <summary>
/// Api Url di base x chiamate REST
/// </summary>
protected string apiUrl = "http://localhost:8733";
/// <summary>
/// Timeout chiamate REST
/// </summary>
protected int tOutSec = 60;
#endregion Protected Fields
#region Protected Properties
/// <summary>
/// Parametri specifici Client Rest
/// </summary>
protected RestParamConf restParams { get; set; } = new RestParamConf();
#endregion Protected Properties
#region Protected Methods
/// <summary>
/// verifica stato ok ovvero connected oppure open
/// </summary>
/// <param name="currState"></param>
/// <returns></returns>
protected bool checkStateOk(System.ServiceModel.CommunicationState currState)
{
bool answ = false;
#if false
answ == System.ServiceModel.CommunicationState.Opened || currState == System.ServiceModel.CommunicationState.Created;
#endif
return answ;
}
/// <summary>
/// Esecuzione chiamata Rest tipo GET
/// </summary>
/// <param name="resource"></param>
/// <returns></returns>
protected string ExecuteCallGet(string resource)
{
string answ = "";
if (!string.IsNullOrEmpty(resource))
{
try
{
// client chiamate rest
using (var client = new RestClient(restOptStd))
{
var currReq = new RestRequest(resource, Method.Get);
currReq.AddHeader("Content-type", "application/json");
// effettuo vera chiamata
var currResp = client.Get(currReq);
if (currResp.StatusCode == System.Net.HttpStatusCode.OK && currResp.Content != null)
{
answ = currResp.Content ?? "";
}
}
}
catch(Exception exc)
{
lgError($"Eccezione in ExecuteCallGet{Environment.NewLine}{exc}");
}
}
return answ;
}
/// <summary>
/// restituisce URL della risorsa eventualmente completato con token o altro
/// </summary>
/// <param name="resName"></param>
/// <returns></returns>
protected string GetUrlResource(string resName)
{
string answ = "";
if (restParams != null && restParams.CallList != null && restParams.CallList.Count > 0)
{
if (restParams.CallList.ContainsKey(resName))
{
answ = restParams.CallList[resName].Url;
// eseguo eventuale sostituzione (se trovo "[[")
if (answ.Contains("[["))
{
string pattern = @"\[\[.*\]\]";
Regex regex = new Regex(pattern);
Match match = regex.Match(resName);
if (match.Success)
{
string key = match.Value.Replace("[[", "").Replace("]]", "");
// cerco nella LUT...
if (RestDataLUT.ContainsKey(key))
{
// sostituisco!
answ = answ.Replace($"[[{key}]]", RestDataLUT[key]);
}
}
}
}
}
return answ;
}
/// <summary>
/// Upsert in cache LUT del parametro
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
protected void RestLutUpsert(string key, string value)
{
if (RestDataLUT.ContainsKey(key))
{
RestDataLUT[key] = value;
}
else
{
RestDataLUT.Add(key, value);
}
}
/// <summary>
/// Recupero valore da cache LUT
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
protected string RestLutGet(string key)
{
string value = "";
if (RestDataLUT.ContainsKey(key))
{
value = RestDataLUT[key];
}
return value;
}
/// <summary>
/// Metodo da overridare x scrivere DAVVERO i parametri sul PLC
/// </summary>
/// <param name="updatedPar"></param>
protected override void plcWriteParams(ref List<objItem> updatedPar)
{
lgTrace($"plcWriteParams: richiesta per {updatedPar.Count} params");
foreach (var item in updatedPar)
{
lgInfo($"ITEM | {item.uid} | {item.value}");
// salvo i valori di setup x prox pesata...
upsertKey(item.uid, item.value);
#if false
bool fatto = false;
bool isPesata = false;
bool isIN = false;
gestWeightOut answ = new gestWeightOut();
// se è richiesta pesata IN/OUT --> mando chiamata
if (item.uid == "reqPesata")
{
isPesata = true;
isIN = item.reqValue.ToUpper() == "IN";
//isIN = item.value.ToUpper() == "IN";
try
{
answ = reqWeight(isIN);
}
catch (Exception exc)
{
lgError($"Eccezione in plcWriteParams.reqWeight | isIn: {isIN}{Environment.NewLine}{exc}");
}
}
else if (item.uid == "RM" || item.uid.StartsWith("Cod"))
{
// comunque segno fatto x altri casi
fatto = true;
}
// se è pesata...
if (isPesata)
{
// se è OK
if (answ.feedback == "C")
{
lgInfo($"reqWeight | Effettuato richiesta | {answ.feedback} | {answ.notes}");
// resetto pesata
upsertKey(item.uid, "");
}
else
{
lgError($"reqWeight | Errore in richiesta peso Rest | {answ.feedback} | {answ.notes}");
}
item.value = "";
item.reqValue = "";
item.lastRead = DateTime.Now;
item.UM = "";
// salvo esito richiesta comunque
upsertKey("logReq", $"{answ.feedback} | {answ.notes}");
// faccio in modo di eseguire subito getDynData
demFactDynData = 1;
processDynData();
}
else
{
// se fatto --> aggiorno!
if (fatto)
{
//item.value = item.reqValue;
item.reqValue = "";
item.lastRead = DateTime.Now;
item.UM = "";
}
}
#endif
}
}
protected void setupRestConf(string restConfFile)
{
// se ho file specifico
if (!string.IsNullOrEmpty(getOptPar("REST_CONF")))
{
string rawData = "";
// leggo file e decodifico...
string confPath = $"{Application.StartupPath}/DATA/CONF/{restConfFile}";
lgInfo($"Apertura file {confPath}");
using (StreamReader sr = new StreamReader(confPath))
{
rawData = sr.ReadToEnd().Replace("\n", "").Replace("\r", "");
}
// continuo decodifica
if (!string.IsNullOrEmpty(rawData))
{
restParams = JsonConvert.DeserializeObject<RestParamConf>(rawData);
if (restParams != null)
{
// inizializzo dal file di conf le variabili necessarie...
tOutSec = restParams.timeOutSec;
apiUrl = restParams.apiUrl ?? "http://localhost:8733";
}
}
}
// sistemo conf standard x call REST
restOptStd = new RestClientOptions
{
Timeout = TimeSpan.FromSeconds(tOutSec),
BaseUrl = new Uri(apiUrl)
};
}
#endregion Protected Methods
#region Private Fields
/// <summary>
/// LookUpTable informazioni raccolte
/// </summary>
protected Dictionary<string, string> RestDataLUT = new Dictionary<string, string>();
/// <summary>
/// Conf client RestSharp standard:
/// - timeout 1 min
/// </summary>
private RestClientOptions restOptStd = new RestClientOptions { Timeout = TimeSpan.FromSeconds(60) };
#endregion Private Fields
#region Private Methods
/// <summary>
/// Effettua decodifica aree memoria alla bitmap usata x MAPO
/// </summary>
protected virtual void decodeToBaseBitmap(ref newDisplayData currDispData)
{
// init a zero...
B_input = 0;
if (queueInEnabCurr)
{
/* -----------------------------------------------------
* bitmap MAPO STD 60
* B0: POWER_ON
* B1: RUN
* B2: pzCount
* B3: allarme
* B4: manuale
* B5: allarme TCiclo (SLOW)
* B6: WarmUp_CoolDown
* B7: EmergArmed (1 = NON emergenza, 0 = emergenza)
----------------------------------------------------- */
// per prima cosa controllo ping e se sia connesso...
#if false
if (connectionOk)
{
B_input = 1;
currDispData.semIn = Semaforo.SV;
// se ho pesate in memoria nel periodo richiesto --> RUN
if (listPesateCurr != null && listPesateCurr.Count > 0)
{
B_input += (1 << 1);
}
// metto manuale
else
{
B_input += (1 << 4);
}
// accodo NON emergenza
B_input += (1 << 7);
}
else
{
B_input = 0;
currDispData.semIn = Semaforo.SR;
}
#endif
#if false
// Controllo booleano PING e POWERON...
string currPowerOn = getDataItemValue(mtcParams.condPowerOn.keyName);
// se valido il check ping lo eseguo... altrimenti lo do x buono
bool isPingOk = mtcParams.pingAsPowerOn && (testPingMachine == IPStatus.Success);
// verifico da target value richiesto...
bool checkPowerOn = (currPowerOn == mtcParams.condPowerOn.targetValue);
// bit 0 (poweron) imposto a 1 SE pingo o PowerOn=="ON"...
B_input = (isPingOk || checkPowerOn) ? 1 : 0;
// variabili RUN...
string currRun = getDataItemValue(mtcParams.keyRunMode);
// controllo RUN MODE preliminare... CABLATO - è GENERALE x MTC
if (currRun == "AUTOMATIC" || currRun == "SEMI_AUTO" || currRun == "SEMI_AUTOMATIC")
{
int numCond = mtcParams.condWork.Count;
int numCondOk = 0;
// cerco nell'elenco delle condizioni che indicano lavora se sono ok faccio +1 conteggio......
foreach (var item in mtcParams.condWork)
{
if (getDataItemValue(item.keyName) == item.targetValue)
{
numCondOk++;
}
}
// se tutte condizioni rispettate --> lavora!
if (numCond == numCondOk)
{
// RUN = LAVORA!
B_input += (1 << 1);
}
}
// se ho almeno 1 allarme E NON SONO IN AUTO --> ALARM!
else if (hasError)
{
B_input += (1 << 3);
}
// 2024.01.90: gestione dati UNAVAILABLE che indicano poweroff...
else if (hasUnavailableData && unavailPoweroff)
{
B_input = 0;
}
else
{
// se ho run mode != auto --> manual
B_input += (1 << 4);
}
// emergenza armata da riportare con bit True/ 1
if (mtcParams.emergencyArmedTrue)
{
//se NON premuta lazo il bit
if (!hasEStopTriggered)
{
B_input += (1 << 5);
}
}
// emergenza armata da riportare come False/0 (!mtcParams.emergencyArmedTrue)
else
{
// se premuta alzo il bit...
if (hasEStopTriggered)
{
B_input += (1 << 5);
}
}
DateTime adesso = DateTime.Now;
int vFactor = 1;
// controllo SE HO dati per fare verifiche...
if (string.IsNullOrEmpty(currRun))
{
// se ho parametro x gestione reset...
if (enableMtcRestart)
{
// controllo se ho ricevuto il current da OLTRE 1 minuto...
if (lastCurrent.AddMinutes(3) < adesso)
{
lastCurrent = adesso;
// stop...
lgInfo("Fermato MTC_ref per mancanza dati current");
MTC_ref.Stop();
Thread.Sleep(1000);
// restart
lgInfo("Riavviato MTC_ref per mancanza dati current");
MTC_ref.Start();
}
}
}
else
{
vFactor = 6;
}
// solo se non ho veto check
if (vetoCheckStatus < adesso)
{
lgInfo($"Stato variabili: currRun: {currRun}");
// imposto veto per vetoSeconds...
vetoCheckStatus = adesso.AddSeconds(vetoSeconds * vFactor);
}
// log opzionale!
if (verboseLog)
{
lgInfo($"Trasformazione B_input: {B_input} | currRun = {currRun}");
}
#endif
}
else
{
lgDebug($"[VETO getDataItemValue] | veto attivo alle {DateTime.Now:yyyy.MM.dd HH:mm:ss}");
}
}
/// <summary>
/// Esegue lettura dati + salvataggio in LUT
/// </summary>
protected virtual void refreshData()
{ }
#endregion Private Methods
}
}