Files
Mapo-IOB-WIN/IOB-WIN-SHELLY/Iob/ShellyClientGen2.cs
T
Samuele Locatelli fa976dd2bb Update IOB BASE:
- aggiunto InitializeAsync
- gestione override x ogni IOB
2026-01-23 10:22:00 +01:00

385 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
}