fa976dd2bb
- aggiunto InitializeAsync - gestione override x ogni IOB
385 lines
14 KiB
C#
385 lines
14 KiB
C#
using EgwProxy.Shelly;
|
||
using EgwProxy.Shelly.Clients;
|
||
using EgwProxy.Shelly.Options;
|
||
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.Http;
|
||
using System.Net.Http.Headers;
|
||
using System.Net.NetworkInformation;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace IOB_WIN_SHELLY.Iob
|
||
{
|
||
/// <summary>
|
||
/// Classe gestione oggetti Shelly tipo Gen2 (Pro3Em)
|
||
/// </summary>
|
||
public class ShellyClientGen2 : GenericNext
|
||
{
|
||
#region Public Constructors
|
||
|
||
/// <summary>
|
||
/// Estende l'init della classe base, impiegando il pacchetto EgwProxy.Shelly
|
||
/// - gestione dei task da svolgere da configurazione json specifica
|
||
/// - specializzazione da conf e non da codice
|
||
/// </summary>
|
||
/// <param name="caller">Form chiamante</param>
|
||
/// <param name="IobConfFull">Configurazione (v 4.x)</param>
|
||
public ShellyClientGen2(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
|
||
{
|
||
lgInfo("Init Iob.ShellyClientGen2");
|
||
// imposto ping a 3
|
||
maxQueuePing = 3;
|
||
// imposto
|
||
B_input = 0;
|
||
// init datetime counters
|
||
DateTime adesso = DateTime.Now;
|
||
lastPzCountSend = adesso;
|
||
lastWarnODL = adesso;
|
||
vetoCheckStatus = adesso;
|
||
// 2023.09.05 imposto anche primo ping e check disconnected...
|
||
lastPING = adesso;
|
||
lastDisconnCheck = adesso;
|
||
var VETO_PING_SEC = getOptPar("VETO_PING_SEC");
|
||
if (!string.IsNullOrEmpty(VETO_PING_SEC))
|
||
{
|
||
int.TryParse(VETO_PING_SEC, out vetoPingSec);
|
||
}
|
||
var POWEROFF_TIMEOUT_SEC = getOptPar("POWEROFF_TIMEOUT_SEC");
|
||
if (!string.IsNullOrEmpty(POWEROFF_TIMEOUT_SEC))
|
||
{
|
||
int.TryParse(POWEROFF_TIMEOUT_SEC, out PoweroffTimeoutSec);
|
||
}
|
||
|
||
var devData = IobConfFull.Device.Connect;
|
||
ShellyOptions options = new ShellyOptions()
|
||
{
|
||
DefaultTimeout = TimeSpan.FromMilliseconds(devData.PingMsTimeout * 10),
|
||
ServerUri = new Uri($"http://{devData.IpAddr}/rpc")
|
||
};
|
||
// init secondo il tipo di Shelly...
|
||
|
||
shellyCli = new ShellyPro3EmClient(new HttpClient(), options);
|
||
|
||
}
|
||
|
||
public override async Task InitializeAsync()
|
||
{
|
||
// invio conf macchina all'inizio
|
||
await SendMachineConfAsync();
|
||
}
|
||
|
||
#endregion Public Constructors
|
||
|
||
#region Public Methods
|
||
|
||
public override Dictionary<string, string> getDynData()
|
||
{
|
||
Dictionary<string, string> outData = new Dictionary<string, string>();
|
||
// verifico che non sia una chiamata troppo frequente, ogni 10 sec minimo...
|
||
if (DateTime.Now > lastReadPLC.AddSeconds(shellyCallDelay))
|
||
{
|
||
// chiama lettura status valor RT (Potenza, Corrente...)
|
||
lastShellyRespRT = Task.Run(() => ((ShellyPro3EmClient)shellyCli).GetEmStatus(CancellationToken.None, 0)).Result;
|
||
lastShellyRespEnergy = Task.Run(() => ((ShellyPro3EmClient)shellyCli).GetEmDataStatus(CancellationToken.None, 0)).Result;
|
||
|
||
// Salvo negli oggetti DynData x EnergyTotal
|
||
if (lastShellyRespEnergy.IsSuccess)
|
||
{
|
||
lastReadPLC = DateTime.Now;
|
||
// recupero da conf memMap e ciclo
|
||
foreach (var item in IOBConfFull.Memory.mMapRead)
|
||
{
|
||
//string outVal = "";
|
||
double outValDbl = 0;
|
||
// se trovo includo in dynData...
|
||
switch (item.Value.memAddr)
|
||
{
|
||
case "ActEnergy":
|
||
outValDbl = lastShellyRespEnergy.Value.TotalAll.ActEnergy;
|
||
break;
|
||
|
||
case "ActEnergy_PhaseA":
|
||
outValDbl = lastShellyRespEnergy.Value.TotalPhaseA.ActEnergy;
|
||
break;
|
||
|
||
case "ActEnergy_PhaseB":
|
||
outValDbl = lastShellyRespEnergy.Value.TotalPhaseB.ActEnergy;
|
||
break;
|
||
|
||
case "ActEnergy_PhaseC":
|
||
outValDbl = lastShellyRespEnergy.Value.TotalPhaseC.ActEnergy;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
// se ho fattore --> gestisco!
|
||
if (item.Value.factor != 1)
|
||
{
|
||
outValDbl = outValDbl * item.Value.factor;
|
||
}
|
||
// modalità saveValue x reduce fLog
|
||
saveValue(ref outData, item.Key, outValDbl);
|
||
}
|
||
}
|
||
|
||
// Salvo negli oggetti DynData x RT
|
||
if (lastShellyRespRT.IsSuccess)
|
||
{
|
||
lastReadPLC = DateTime.Now;
|
||
// recupero da conf memMap e ciclo
|
||
foreach (var item in IOBConfFull.Memory.mMapRead)
|
||
{
|
||
//string outVal = "";
|
||
double outValDbl = 0;
|
||
// se trovo includo in dynData...
|
||
switch (item.Value.memAddr)
|
||
{
|
||
|
||
case "Current":
|
||
outValDbl = lastShellyRespRT.Value.TotalCurrent;
|
||
break;
|
||
case "Current_Neutral":
|
||
outValDbl = lastShellyRespRT.Value.NeutralCurrent;
|
||
break;
|
||
case "Current_PhaseA":
|
||
outValDbl = lastShellyRespRT.Value.PhaseA.Current;
|
||
break;
|
||
case "Current_PhaseB":
|
||
outValDbl = lastShellyRespRT.Value.PhaseB.Current;
|
||
break;
|
||
case "Current_PhaseC":
|
||
outValDbl = lastShellyRespRT.Value.PhaseC.Current;
|
||
break;
|
||
|
||
case "Freq_PhaseA":
|
||
outValDbl = lastShellyRespRT.Value.PhaseA.Frequency;
|
||
break;
|
||
case "Freq_PhaseB":
|
||
outValDbl = lastShellyRespRT.Value.PhaseB.Frequency;
|
||
break;
|
||
case "Freq_PhaseC":
|
||
outValDbl = lastShellyRespRT.Value.PhaseC.Frequency;
|
||
break;
|
||
|
||
case "Power":
|
||
outValDbl = lastShellyRespRT.Value.TotalActivePower;
|
||
break;
|
||
case "Power_PhaseA":
|
||
outValDbl = lastShellyRespRT.Value.PhaseA.ActivePower;
|
||
break;
|
||
case "Power_PhaseB":
|
||
outValDbl = lastShellyRespRT.Value.PhaseB.ActivePower;
|
||
break;
|
||
case "Power_PhaseC":
|
||
outValDbl = lastShellyRespRT.Value.PhaseC.ActivePower;
|
||
break;
|
||
|
||
case "Voltage_PhaseA":
|
||
outValDbl = lastShellyRespRT.Value.PhaseA.Voltage;
|
||
break;
|
||
case "Voltage_PhaseB":
|
||
outValDbl = lastShellyRespRT.Value.PhaseB.Voltage;
|
||
break;
|
||
case "Voltage_PhaseC":
|
||
outValDbl = lastShellyRespRT.Value.PhaseC.Voltage;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
// se ho fattore --> gestisco!
|
||
if (item.Value.factor != 1)
|
||
{
|
||
outValDbl = outValDbl * item.Value.factor;
|
||
}
|
||
// modalità saveValue x reduce fLog
|
||
saveValue(ref outData, item.Key, outValDbl);
|
||
}
|
||
}
|
||
// traccio dati ricevuti
|
||
trackExchDataRaw(lastShellyRespRT, 512);
|
||
// traccio dati ricevuti
|
||
trackExchDataRaw(lastShellyRespEnergy, 256);
|
||
}
|
||
return outData;
|
||
}
|
||
|
||
/// <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;
|
||
// se ha risposto ad ultima chiamata --> ok
|
||
if (connectionOk && adesso.Subtract(lastReadPLC).TotalMinutes < 1)
|
||
{
|
||
B_input = 3;
|
||
// aggiungo NON emergenza...
|
||
B_input += (1 << 7);
|
||
}
|
||
else
|
||
{
|
||
B_input = 0;
|
||
}
|
||
lastReadPLC = DateTime.Now;
|
||
lastWatchDog = adesso;
|
||
}
|
||
|
||
public override void startAdapter(bool resetQueue)
|
||
{
|
||
base.startAdapter(resetQueue);
|
||
// 2023.09.05 imposto anche primo ping e check disconnected...
|
||
DateTime adesso = DateTime.Now;
|
||
lastWatchDog = adesso;
|
||
//lastPING = adesso;
|
||
lastReadPLC = adesso;
|
||
lastDisconnCheck = adesso;
|
||
// faccio un primo check POST ritardo
|
||
tryConnect();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Override connessione
|
||
/// </summary>
|
||
public override void tryConnect()
|
||
{
|
||
bool doLog = (verboseLog || periodicLog);
|
||
lgDebug($"Shelly: tryConnect step 01 | connectionOk: {connectionOk}");
|
||
if (!connectionOk)
|
||
{
|
||
//// resetto coda...
|
||
//QueuePing = new DataQueue("000", "QueuePing", false);
|
||
// controllo che il ping sia stato tentato almeno pingTestSec fa...
|
||
if (DateTime.Now.Subtract(lastPING).TotalSeconds > vetoPingSec)
|
||
{
|
||
if (doLog)
|
||
{
|
||
lgInfo("Shelly: ConnKO - tryConnect");
|
||
}
|
||
lgDebug("Shelly: tryConnect step 04");
|
||
|
||
lgDebug("Shelly: Reset QueuePing");
|
||
|
||
bool pingOK = testPingMachine == IPStatus.Success;
|
||
addTest(pingOK);
|
||
|
||
// se passa il ping faccio il resto...
|
||
if (pingStatusOk() || pingOK)
|
||
{
|
||
// in primis salvo data ping...
|
||
lastPING = DateTime.Now;
|
||
connectionOk = true;
|
||
queueInEnabCurr = true;
|
||
lgInfo("Shelly - ping OK");
|
||
}
|
||
else
|
||
{
|
||
// loggo no risposta ping ...
|
||
lgError("Shelly - ping KO");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Override disconnessione
|
||
/// </summary>
|
||
public override void tryDisconnect()
|
||
{
|
||
lgInfo("Richiesta disconnessione adapter Shelly!");
|
||
connectionOk = false;
|
||
queueInEnabCurr = false;
|
||
}
|
||
|
||
#endregion Public Methods
|
||
|
||
#region Private Fields
|
||
|
||
/// <summary>
|
||
/// Oggetto dove viene salvata ultima risposta Shelly x dati RT (potenza, correnti, ...)
|
||
/// </summary>
|
||
private ShellyResult<EgwProxy.Shelly.DTO.Gen2.EMDto> lastShellyRespRT;
|
||
|
||
/// <summary>
|
||
/// Oggetto dove viene salvata ultima risposta Shelly x dati Energia (Total per fase e globle)
|
||
/// </summary>
|
||
private ShellyResult<EgwProxy.Shelly.DTO.Gen2.EMDataDto> lastShellyRespEnergy;
|
||
|
||
private int PoweroffTimeoutSec = 100;
|
||
|
||
/// <summary>
|
||
/// Delay tra chiamate in sec
|
||
/// </summary>
|
||
private int shellyCallDelay = 1;
|
||
|
||
/// <summary>
|
||
/// Client interno x comunicazione con Shelly devices
|
||
/// </summary>
|
||
private ShellyClientBase shellyCli;
|
||
|
||
/// <summary>
|
||
/// Veto controllo status x log...
|
||
/// </summary>
|
||
private DateTime vetoCheckStatus = DateTime.Now;
|
||
|
||
#endregion Private Fields
|
||
|
||
#region Private Methods
|
||
|
||
private void addTest(bool pingOk)
|
||
{
|
||
int score = pingOk ? 1 : 0;
|
||
// controllo: se era spenta e risulta ping ok --> reset coda!
|
||
if (B_input == 0 && pingOk)
|
||
{
|
||
B_input = 1;
|
||
QueuePing = new DataQueue(IOBConfFull.General.FilenameIOB, "QueuePing", false, redisMan);
|
||
lgTrace($"QueuePing resetted on addTest");
|
||
}
|
||
QueuePing.Enqueue($"{score}");
|
||
while (QueuePing.Count > maxQueuePing)
|
||
{
|
||
string res = "";
|
||
QueuePing.TryDequeue(out res);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Calcola status ping:
|
||
/// - se ha ‹ 50% coda richiesta --› true
|
||
/// - se ha › 50% coda richiesta --› true se è maggior parte a 1 (true)
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private bool pingStatusOk()
|
||
{
|
||
bool answ = false;
|
||
long numVal = QueuePing.Count;
|
||
if (numVal > maxQueuePing / 2)
|
||
{
|
||
var listaValori = QueuePing.ToList();
|
||
long numOk = listaValori.Where(x => x == "1").Count();
|
||
long numKo = numVal - numOk;
|
||
answ = numOk >= numKo;
|
||
lgTrace($"PING ok per: {numOk} > {numKo}");
|
||
}
|
||
else
|
||
{
|
||
lgTrace($"PING check: {answ} per mancanza dati minimi test");
|
||
}
|
||
return answ;
|
||
}
|
||
|
||
#endregion Private Methods
|
||
}
|
||
} |