Merge branch 'release/TestSimUfficio_01'

This commit is contained in:
Samuele Locatelli
2026-05-20 19:31:01 +02:00
21 changed files with 4253 additions and 4295 deletions
+13 -7
View File
@@ -10,16 +10,22 @@ A collection of .NET (C#/VB) projects for industrial communication with NC contr
```powershell
& "$env:MSBUILD_PATH" "ProjectName\ProjectName.csproj" -target:Build /p:Configuration=Release /p:Platform="x86" /p:OutputPath=bin/ /nodeReuse:false /verbosity:minimal /m
```
- **NuGet**: Requires Steamware Nexus Proxy sources (defined in `.gitlab-ci.yml`).
- **Versioning**: Automated during CI via `VersGen\VersGen.cs` and `.nuspec` updates.
- **Release Artifacts**: Zipped using `7z.exe`; MD5/SHA1 hashes are generated; uploaded to Nexus.
- **NuGet**: Requires Steamware Nexus Proxy sources. Use `dotnet nuget restore` on the specific `.sln` before building.
- **Versioning**: Automated via `VersGen\VersGen.cs` and `.nuspec` updates during CI. Manual updates to `VersGen.cs` might be needed if mimicking CI.
- **Release Artifacts**: Zipped using `7z.exe`; MD5/SHA1 hashes are generated.
## Key Directories & Files
- **`IOB-WIN-*`**: Protocol-specific implementations (e.g., `FANUC`, `SIEMENS`, `SHELLY`).
- **`VersGen\`**: Handles automated assembly versioning during build.
- **`UtilityScripts\`**: Contains `alarmFormatter.py` for converting alarm CSV/Excel to JSON. Requires `python3` and `pip install inquirer`.
- **`VersGen\`**: Handles automated assembly versioning.
- **`UtilityScripts\`**: Contains `alarmFormatter.py`. Requires `python3` and `pip install inquirer`.
- **`EgwCApp\`**: Core application logic and testing projects.
## Environment & Setup
- **Siemens PLC**: Must enable "PUT/GET" permission in TIA Portal.
- **Dependencies**: Uses `saltminion` (via Chocolatey) and specific Windows accounts (`steamware`/`IOB`).
- **Docs**: Documentation for several modules is generated via `docfx`.
- **Dependencies**: Uses `saltminion` and specific Windows accounts (`steamware`/`IOB`).
- **Documentation**: Generated via `docfx`.
## Important Workflow Notes
- **CI Logic**: The `.gitlab-ci.yml` contains critical automation logic for NuGet sources, versioning, and deployment. Refer to it for exact operational steps.
- **NuGet Fixes**: The CI uses a `.nuget-fix` helper to manage Steamware Nexus Proxy sources. If encountering NuGet errors, check source configuration.
- **Versioning Flow**: Versioning involves updating `VersGen.cs` (replacing `0.0.0.0`) and updating `.nuspec` files.
+1 -1
View File
@@ -217,7 +217,7 @@
<Compile Include="Logging.cs" />
<Compile Include="Objects.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RedisMan.cs" />
<Compile Include="RedisIobCache.cs" />
<Compile Include="utils.cs" />
</ItemGroup>
<ItemGroup>
+73 -87
View File
@@ -49,13 +49,6 @@ namespace IOB_UT_NEXT.Iob
/// </summary>
public AlarmBlockType alarmType = AlarmBlockType.Bitmap;
#if false
/// <summary>
/// Conf adapter corrente
/// </summary>
public IobConfiguration cIobConf;
#endif
/// <summary>
/// Conteggio ATTUALE ore macchina IN LAVORO
/// </summary>
@@ -146,6 +139,11 @@ namespace IOB_UT_NEXT.Iob
/// </summary>
public DateTime lastIobOnline = DateTime.Now.AddHours(-1);
/// <summary>
/// Ultima verifica status IOB x forzare display status SRV
/// </summary>
public DateTime lastIobStatusDisplUpdate = DateTime.Now;
/// <summary>
/// dataOra ultimo log periodico...
/// </summary>
@@ -171,11 +169,6 @@ namespace IOB_UT_NEXT.Iob
/// </summary>
public DateTime lastSim;
/// <summary>
/// Ultima verifica status IOB x forzare display status SRV
/// </summary>
public DateTime lastIobStatusDisplUpdate = DateTime.Now;
/// <summary>
/// dataOra ultimo segnale inviato al SERVER...
/// </summary>
@@ -206,11 +199,6 @@ namespace IOB_UT_NEXT.Iob
/// </summary>
public bool needRefreshPzCount = true;
/// <summary>
/// Coda degli esiti di ping x calcolo stato macchina
/// </summary>
public DataQueue QueuePing;
/// <summary>
/// Determina se utilizzare blocchi di memoria IOT contigui (e quindi processing
/// "monoblocco" semplificato"=
@@ -220,22 +208,27 @@ namespace IOB_UT_NEXT.Iob
/// <summary>
/// Coda valori ALLARMI ove gestiti...
/// </summary>
public DataQueue QueueAlarm;// = new DataQueue("000", "QueueAlarm", false);
public DataQueue QueueAlarm;
/// <summary>
/// Oggetto della coda degli elementi letti di tipo FluxLog (e non ancora trasmessi)
/// </summary>
public DataQueue QueueFLog;// = new DataQueue("000", "QueueFLog", false);
public DataQueue QueueFLog;
/// <summary>
/// Oggetto della coda degli elementi letti (e non ancora trasmessi)
/// </summary>
public DataQueue QueueIN;// = new DataQueue("000", "QueueIN", false);
public DataQueue QueueIN;
/// <summary>
/// Coda valori MESSAGGI/EVENTI (da non sottocampionare come samples)...
/// </summary>
public DataQueue QueueMessages;// = new DataQueue("000", "QueueMessages", false);
public DataQueue QueueMessages;
/// <summary>
/// Coda degli esiti di ping x calcolo stato macchina
/// </summary>
public DataQueue QueuePing;
/// <summary>
/// Oggetto della coda degli elementi di tipo RawTransf (e non ancora trasmessi)
@@ -326,38 +319,20 @@ namespace IOB_UT_NEXT.Iob
/// <summary>
/// Verifica se sia in modalità DEMO avanzata (campionamento da set di valori ammessi...)
/// </summary>
public static bool DemoInSample
{
get
{
return baseUtils.CRB("DemoInSample");
}
}
public static bool DemoInSample => baseUtils.CRB("DemoInSample");
/// <summary>
/// Verifica se sia in modalità DEMO x dati OUTPUT
/// </summary>
public static bool DemoOut
{
get
{
return utils.CRB("DemoOut");
}
}
public static bool DemoOut => utils.CRB("DemoOut");
/// <summary>
/// Indicazione VETO PING a server sino alla data-ora indicata
/// </summary>
public static DateTime dtVetoPing
{
get
{
return utils.dtVetoPing;
}
set
{
utils.dtVetoPing = value;
}
get => utils.dtVetoPing;
set => utils.dtVetoPing = value;
}
/// <summary>
@@ -365,14 +340,8 @@ namespace IOB_UT_NEXT.Iob
/// </summary>
public static DateTime dtVetoQueueIN
{
get
{
return utils.dtVetoQueueIN;
}
set
{
utils.dtVetoQueueIN = value;
}
get => utils.dtVetoQueueIN;
set => utils.dtVetoQueueIN = value;
}
/// <summary>
@@ -380,46 +349,29 @@ namespace IOB_UT_NEXT.Iob
/// </summary>
public static DateTime dtVetoSend
{
get
{
return utils.dtVetoSend;
}
set
{
utils.dtVetoSend = value;
}
get => utils.dtVetoSend;
set => utils.dtVetoSend = value;
}
/// <summary>
/// Verifica se sia abilitato test lettura blocchi memoria all'avvio
/// </summary>
public static bool EnableTest
{
get
{
return baseUtils.CRB("enableTest");
}
}
public static bool EnableTest => baseUtils.CRB("enableTest");
/// <summary>
/// stato Online/Offline del server MP IO (su REDIS)
/// </summary>
public static bool MPOnline
{
get
{
return utils.MPIO_Online;
}
set
{
utils.MPIO_Online = value;
}
get => utils.MPIO_Online;
set => utils.MPIO_Online = value;
}
#endregion Public Properties
#region Public Methods
#if false
/// <summary>
/// Effettua chiamata URL e restituisce risultato
/// </summary>
@@ -452,7 +404,7 @@ namespace IOB_UT_NEXT.Iob
}
}
return answ;
}
}
/// <summary>
/// Effettua chiamata URL e restituisce risultato
@@ -480,6 +432,7 @@ namespace IOB_UT_NEXT.Iob
}
return answ;
}
#endif
/// <summary>
/// processa dataLayer e se necessario salva/mostra
@@ -504,22 +457,12 @@ namespace IOB_UT_NEXT.Iob
return sMacAddress;
}
public static void resetDebugConsole()
{
}
/// <summary>
/// Reset dei webclients
/// </summary>
public static void resetWebClients()
{
utils.resetWebClients();
}
#endregion Public Methods
#region Protected Fields
/// <summary>
/// Valore di attesa (random) dopo ogni invio x evitare congestione send...
/// </summary>
@@ -642,6 +585,49 @@ namespace IOB_UT_NEXT.Iob
return doVeto;
}
/// <summary>
/// Recupera la chiave per le statistiche delle chiamate.
/// </summary>
protected string GetCallStatsKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CallStats");
/// <summary>
/// Recupera la chiave per i dati di produzione correnti.
/// </summary>
protected string GetCurrProdDataKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:CurrProdData");
/// <summary>
/// Recupera la chiave per il flusso di memoria.
/// </summary>
protected string GetFluxMemKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:FluxMem");
/// <summary>
/// Recupera la chiave per l'invio dei PODL.
/// </summary>
protected string GetPOdlSentKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:POdlSent");
/// <summary>
/// Recupera un valore specifico dal hash dello stato dell'IOB.
/// </summary>
protected string GetStatusField(string field)
{
string baseKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}");
return redisMan.redGetHashField(baseKey, field);
}
/// <summary>
/// Recupera la chiave per le statistiche settimanali.
/// </summary>
protected string GetWeekStatsKey() => redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}:WeekStats");
/// <summary>
/// Imposta un valore nel hash dello stato dell'IOB.
/// </summary>
protected void SetStatusField(string field, string value)
{
string baseKey = redisMan.redHash($"IOB:Status:{IOBConfFull.General.FilenameIOB}");
redisMan.redSetHashField(baseKey, field, value);
}
/// <summary>
/// Setup di tutti gli oggetti Queue, ma solo alcuni hanno coda REDIS (quelli senza sono "sacrificabili"
/// </summary>
@@ -1171,6 +1171,28 @@ namespace IOB_UT_NEXT
return answ;
}
/// <summary>
/// Salva un SINGOLO valore nella hash dati key e field
/// </summary>
/// <param name="hashKey"></param>
/// <param name="hashField"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool redSetHashField(string hashKey, string hashField, string value)
{
bool answ = false;
// cerco se ci sia valore in redis...
try
{
answ = currDb.HashSet((RedisKey)hashKey, (RedisValue)hashField, (RedisValue)value);
}
catch (Exception exc)
{
Logging.Instance.Error($"redSetHashField {exc}");
}
return answ;
}
/// <summary>
/// Conteggio elementi in QUEUE (LIFO)
/// </summary>
-15
View File
@@ -600,12 +600,6 @@ namespace IOB_UT_NEXT
if (num >= 100) num /= 100;
if (num >= 10) num /= 10;
#if false
// formulazione alternativa con ciclo...
while (num >= 10)
num /= 10;
#endif
return num;
}
@@ -812,15 +806,6 @@ namespace IOB_UT_NEXT
return result;
}
public static void resetWebClients()
{
#if false
// resetto i webclients...
client = new WebClientWT();
clientPayload = new WebClientWT();
#endif
}
/// <summary>
/// Effettua reverse della stringa
/// </summary>
+1
View File
@@ -34,3 +34,4 @@ CLI_INST=SteamWareSim
STARTLIST=SIMUL_01
MAXCNC=10
+1 -1
View File
@@ -1466,7 +1466,7 @@ namespace IOB_WIN_FORM
// salvo nuovo valore invio
iobObj.LastSendSet(sendKey, DateTime.Now);
// segnalo reboot (programma - url file)...
await Iob.Generic.callUrl(iobObj.urlReboot, true);
await utils.callUrlAsync(iobObj.urlReboot);
}
}
else
+3 -2
View File
@@ -131,9 +131,10 @@
</Compile>
<Compile Include="ControlExtensions.cs" />
<Compile Include="Iob\BaseObj.cs" />
<Compile Include="Iob\Generic.Protected.cs" />
<Compile Include="Iob\Generic.Public.cs" />
<Compile Include="Iob\Generic.cs" />
<Compile Include="Iob\PingWatchDog.cs" />
<Compile Include="Iob\Services\DataSerializer.cs" />
<Compile Include="Iob\Services\XmlDataSerializer.cs" />
<Compile Include="Iob\Simula.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
namespace IOB_WIN_FORM.Iob.Services
{
/// <summary>
/// Gestisce tutte le operazioni di serializzazione e deserializzazione dei dati.
/// </summary>
public static class DataSerializer
{
/// <summary>
/// Serializza un oggetto in una stringa JSON utilizzando la cultura invariante.
/// </summary>
public static string Serialize<T>(T obj)
{
if (obj == null) return null;
// Utilizzo di CultureInfo.InvariantCulture per garantire la coerenza dei formati (es. decimali)
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
Culture = CultureInfo.InvariantCulture
});
}
/// <summary>
/// Deserializza una stringa JSON in un oggetto del tipo specificato.
/// </summary>
public static T Deserialize<T>(string json)
{
if (string.IsNullOrWhiteSpace(json)) return default;
return JsonConvert.DeserializeObject<T>(json, new JsonSerializerSettings
{
Culture = CultureInfo.InvariantCulture
});
}
/// <summary>
/// Helper per convertire un dizionario di oggetti in un dizionario di stringhe.
/// </summary>
public static Dictionary<string, string> ToDictionary(Dictionary<string, object> input)
{
if (input == null) return new Dictionary<string, string>();
var dict = new Dictionary<string, string>();
foreach (var pair in input)
{
dict[pair.Key] = pair.Value?.ToString();
}
return dict;
}
}
}
@@ -0,0 +1,63 @@
using System;
using System.IO;
using System.Xml.Serialization;
namespace IOB_WIN_FORM.Iob.Services
{
/// <summary>
/// Gestisce le operazioni di serializzazione e deserializzazione in formato XML.
/// </summary>
public static class XmlDataSerializer
{
/// <summary>
/// Serializza un oggetto in una stringa XML.
/// </summary>
/// <typeparam name="T">Tipo dell'oggetto.</typeparam>
/// <param name="obj">L'oggetto da serializzare.</param>
/// <returns>Stringa XML o null se l'oggetto è null.</returns>
public static string Serialize<T>(T obj)
{
if (obj == null) return null;
try
{
using (var stringWriter = new StringWriter())
{
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(stringWriter, obj);
return stringWriter.ToString();
}
}
catch (Exception ex)
{
// Log l'errore se necessario. In un contesto di refactoring,
// potremmo voler passare l'eccezione verso l'alto.
throw new InvalidOperationException($"Errore durante la serializzazione XML per il tipo {typeof(T).Name}", ex);
}
}
/// <summary>
/// Deserializza una stringa XML in un oggetto del tipo specificato.
/// </summary>
/// <typeparam name="T">Tipo dell'oggetto.</typeparam>
/// <param name="xmlString">La stringa XML.</param>
/// <returns>L'oggetto deserializzato o default se la stringa è nulla/vuota.</returns>
public static T Deserialize<T>(string xmlString)
{
if (string.IsNullOrWhiteSpace(xmlString)) return default;
try
{
using (var stringReader = new StringReader(xmlString))
{
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stringReader);
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"Errore durante la deserializzazione XML per il tipo {typeof(T).Name}", ex);
}
}
}
}
+7 -7
View File
@@ -1603,10 +1603,10 @@ namespace IOB_WIN_FORM.Iob
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
var rawListArt = await callUrl(urlGetCurrArt, false);
var rawListDOSS = await callUrl(urlGetCurrDOSS, false);
var rawListPODL = await callUrl(urlGetNextPODL, false);
var rawLVFasi = await callUrl(urlGetListValFasiPodl, false);
var rawListArt = await utils.callUrlAsync(urlGetCurrArt);
var rawListDOSS = await utils.callUrlAsync(urlGetCurrDOSS);
var rawListPODL = await utils.callUrlAsync(urlGetNextPODL);
var rawLVFasi = await utils.callUrlAsync(urlGetListValFasiPodl);
if (!string.IsNullOrEmpty(rawListArt))
{
@@ -1754,9 +1754,9 @@ namespace IOB_WIN_FORM.Iob
// PONTE SYNC/ASYNC: Ora sw.Stop() aspetterà la fine reale dell'operazione
Task.Run(async () =>
{
var rawListArt = await callUrl(urlGetCurrArt, false);
var rawListDOSS = await callUrl(urlGetCurrDOSS, false);
var rawListPODL = await callUrl(urlGetNextPODL, false);
var rawListArt = await utils.callUrlAsync(urlGetCurrArt);
var rawListDOSS = await utils.callUrlAsync(urlGetCurrDOSS);
var rawListPODL = await utils.callUrlAsync(urlGetNextPODL);
if (!string.IsNullOrEmpty(rawListArt))
{
try
+1
View File
@@ -20,3 +20,4 @@ CLI_INST=SteamWareSim
STARTLIST=FTP_SONATEST
MAXCNC=10
+1
View File
@@ -27,3 +27,4 @@ STARTLIST=3024
;STARTLIST=LVF652
MAXCNC=10
+1
View File
@@ -34,3 +34,4 @@ STARTLIST=3026
;STARTLIST=SIMUL_01
MAXCNC=10
+1
View File
@@ -20,3 +20,4 @@ STARTLIST=SIMUL_01
;STARTLIST=3023-PING
MAXCNC=10
+2 -1
View File
@@ -27,4 +27,5 @@ STARTLIST=SIMUL_03_SHELLY
STARTLIST=SIMUL_03_SHELLY
STARTLIST=SIMUL_03_SHELLY
MAXCNC=10
MAXCNC=10
+1
View File
@@ -42,3 +42,4 @@ CLI_INST=SteamWareSim
STARTLIST=3010
MAXCNC=10
+1
View File
@@ -24,3 +24,4 @@ STARTLIST=3018
;STARTLIST=SIMUL_06
MAXCNC=10
+36
View File
@@ -0,0 +1,36 @@
# WIP: Refactoring Phase 1 - Infrastructure Extraction
## Status: IN_PROGRESS
## Objective
Extract infrastructure and helper components from `Generic.cs` to improve modularity and reduce the monolithic footprint.
## Tasks
### 1. Communication Service Extraction
- [ ] Extract REST/HTTP logic (currently using `utils.callUrl`, `callUrlWithPayloadAsync`, etc.).
- [ ] Implement a singleton/service-based `CommunicationService` using `HttpClient`.
- [ ] **Italian Commenting Requirement**: All new/modified code comments must be in Italian.
### 2. Redis Service Extraction (COMPLETED)
- [x] Encapsulate all `redisMan` calls into a `RedisService`.
- [x] Define a clean interface for key/value and hash operations.
- [x] **Refactored**: Moved semantic key construction from `RedisService` to `BaseObj` to eliminate redundant service layer.
### 3. Data Serializer Extraction
- [ ] Move all `JsonConvert` and custom string formatting (e.g., `qEncodeFLog`, `qEncodeIN`) to a `DataSerializer` service.
- [ ] Centralize `CultureInfo.InvariantCulture` usage.
### 4. BaseObj Simplification (NEW)
- [ ] Analyze `BaseObj` responsibilities (State, Config, Messaging, Diagnostics).
- [ ] Identify candidates for extraction into specialized services (e.g., `QueueManager`, `ConfigService`, `DiagnosticService`).
- [ ] Implement extraction of identified components.
## Progress Log
- [x] Created WIP document.
- [x] Analyzed `Generic.cs` for Phase 1 candidates.
- [x] Completed Redis Semantic Refactoring:
- Moved key construction logic from `RedisService` to `BaseObj`.
- Refactored `Generic.cs` to use `BaseObj` semantic methods.
- Eliminated redundant `RedisService.cs`.
- [ ] Started Analysis of `BaseObj` to identify extraction candidates.