Gestion des connexions S7 et prévention des problèmes
Le protocole S7 utilise une structure PDU (Protocol Data Unit) complexe, distincte des protocoles génériques comme Modbus TCP. Une configuration incorrecte des paramètres peut entraîner des échecs de connexion, notamment en raison de différences entre les gammes de CPU.
Voici un exemple de configuration de base avec la bibliothèque S7.NetPlus :
// Initialisation d'une connexion S7
var automate = new Plc(CpuType.S71500, "192.168.1.100", 0, 1);
// CpuType : modèle du CPU ; IP : adresse réseau ; rack : 0 pour S7-1200/1500 ; slot : 1 pour S7-1200/1500
Des erreurs fréquentes incluent des conflits de port (le protocole S7 utilise le port 102), des configurations TSAP (Transport Service Access Point) inadaptées, ou des délais d'attente mal réglés. Pour les environnements de production, un délai de 3 à 5 secondes est recommandé afin d'éviter le blocage de l'interface utilisateur.
Afin de stabiliser les échanges, l'utilisation d'un pool de connexions est conseillée. Voici une implémentation alternative qui gère plusieurs instances de connexion de manière asynchrone :
public class PoolConnexionsS7 : IDisposable
{
private readonly BlockingCollection<Plc> _poolConnexions;
private readonly string _adresseIP;
private readonly int _taillePool;
public PoolConnexionsS7(CpuType typeCPU, string adresseIP, int taillePool = 5)
{
_adresseIP = adresseIP;
_taillePool = taillePool;
_poolConnexions = new BlockingCollection<Plc>(taillePool);
for (int i = 0; i < taillePool; i++)
{
_poolConnexions.Add(new Plc(typeCPU, _adresseIP, 0, 1));
}
}
public Plc ObtenirConnexion(int delaiExpirationMs = 3000)
{
if (_poolConnexions.TryTake(out Plc connexion, delaiExpirationMs))
return connexion;
throw new InvalidOperationException("Aucune connexion disponible dans le pool.");
}
public void LibererConnexion(Plc connexion)
{
if (connexion != null && !connexion.IsConnected)
{
connexion.Open();
}
_poolConnexions.Add(connexion);
}
public void Dispose()
{
foreach (Plc connexion in _poolConnexions.GetConsumingEnumerable())
{
connexion.Close();
}
_poolConnexions.Dispose();
}
}
Stratégies d'optimisation des lectures et écritures de données
Les opérations de lecture/écriture individuelles sur des variables isolées sont souvent inefficaces. Les performences peuvent être considérablement améliorées en regroupant les accès aux blocs de données (DB).
Comparaison des performances pour 100 opérations
| Méode d'accès | Temps (ms) | Charge réseau (Ko) |
|---|---|---|
| Lecture/écriture variable par variable | ~1200 | ~48 |
| Lecture/écriture par lots | ~180 | ~12 |
| Lecture/écriture complète d'un DB | ~85 | ~8 |
Exemple de lecture groupée d'entires à partir d'un bloc de données, avec une méthode transformée :
public int[] LireEntiersDepuisDB(Plc automate, int numeroDB, int offsetDepart, int nombreEntiers)
{
int octetsParEntier = 2;
int tailleTotale = nombreEntiers * octetsParEntier;
byte[] donneesBrutes = automate.ReadBytes(DataType.DataBlock, numeroDB, offsetDepart, tailleTotale);
int[] resultat = new int[nombreEntiers];
for (int index = 0; index < nombreEntiers; index++)
{
int decalage = index * octetsParEntier;
resultat[index] = S7.Net.Types.Int.FromByteArray(new[] { donneesBrutes[decalage], donneesBrutes[decalage + 1] });
}
return resultat;
}
Pour éviter de ralentir l'interface graphique, les opérations de communication doivent être exécutées de manière asynchrone. Une approche basée sur des tâches (Task) est préconisée :
public async Task<int[]> LireEntiersDBAsync(Plc automate, int numeroDB, int depart, int compteur)
{
return await Task.Run(() =>
{
byte[] octetsLus = automate.ReadBytes(DataType.DataBlock, numeroDB, depart * 2, compteur * 2);
int[] valeurs = new int[compteur];
for (int i = 0; i < compteur; i++)
{
valeurs[i] = S7.Net.Types.Int.FromByteArray(octetsLus.Skip(i * 2).Take(2).ToArray());
}
return valeurs;
});
}