I. Configuration de l'environnement et des dépendances
Pour ce projet, nous utilisons :
- Visual Studio 2022 ou supérieur
- .NET 6.0 ou version ultérieure
- Packages NuGet :
NModbuspour l'implémentation du protocole,MaterialDesignThemespour l'interface utilisateur
La structure du projet est organisée comme suit :
ModbusTesterApp/
├── Modèles/ # Modèles de données
├── Vues/ # Interface WPF
├── ModèlesVue/ # Logique MVVM
├── Services/ # Services de communication
└── Utilitaires/ # Classes utilitaires (vérification CRC, journalisation)
II. Implémentation du code principal
1. Service de communication Modbus TCP
// Services/ServiceModbusTcp.cs
using Modbus.Device;
using System.Net.Sockets;
public class ServiceModbusTcp : IDisposable
{
private ModbusIpMaster _maitreModbus;
private TcpClient _clientTcp;
public async Task ConnexionAsync(string adresseIp, int port = 502)
{
_clientTcp = new TcpClient();
await _clientTcp.ConnectAsync(adresseIp, port);
_maitreModbus = ModbusIpMaster.CreateIp(_clientTcp);
}
// Lecture des registres de maintien (code fonction 03)
public ushort[] LireRegistresMaintien(ushort adresseDebut, ushort quantite)
{
return _maitreModbus.ReadHoldingRegisters(1, adresseDebut, quantite);
}
// Écriture dans un registre unique (code fonction 06)
public void EcrireRegistreUnique(ushort adresse, ushort valeur)
{
_maitreModbus.WriteSingleRegister(1, adresse, valeur);
}
// Écriture multiple dans des registres (code fonction 16)
public void EcrireRegistresMultiples(ushort adresseDebut, ushort[] valeurs)
{
_maitreModbus.WriteMultipleRegisters(1, adresseDebut, valeurs);
}
public void Dispose()
{
_maitreModbus?.Dispose();
_clientTcp?.Close();
}
}
2. Conception du modèle de données
// Modèles/DonneeRegistre.cs
public class DonneeRegistre : INotifyPropertyChanged
{
private ushort _valeurRegistre;
public ushort ValeurRegistre
{
get => _valeurRegistre;
set
{
_valeurRegistre = value;
OnPropertyChanged(nameof(ValeurRegistre));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string nomPropriete) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nomPropriete));
}
III. Création de l'interface WPF
1. XAML de la fenêtre principale
<window height="450" title="Outil de test Modbus TCP" width="800" x:class="ModbusTesterApp.Vues.FenetrePrincipale" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:materialdesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<grid>
<groupbox header="Paramètres de connexion" margin="10">
<stackpanel orientation="Vertical">
<textbox materialdesign:hintassist.hint="Adresse IP" width="200" x:name="champAdresseIP"></textbox>
<textbox materialdesign:hintassist.hint="Port" width="100" x:name="champPort"></textbox>
<button command="{Binding CommandeConnexion}" content="Se connecter"></button>
</stackpanel>
</groupbox>
<groupbox header="Opérations sur les registres" margin="10,120,10,10">
<stackpanel orientation="Vertical">
<textbox materialdesign:hintassist.hint="Adresse de départ" width="100" x:name="champAdresseDebut"></textbox>
<textbox materialdesign:hintassist.hint="Nombre de registres" width="100" x:name="champQuantite"></textbox>
<button command="{Binding CommandeLectureRegistres}" content="Lire les registres"></button>
<datagrid autogeneratecolumns="False" itemssource="{Binding ListeRegistres}">
<datagrid.columns>
<datagridtextcolumn binding="{Binding Adresse}" header="Adresse"></datagridtextcolumn>
<datagridtextcolumn binding="{Binding Valeur}" header="Valeur"></datagridtextcolumn>
</datagrid.columns>
</datagrid>
</stackpanel>
</groupbox>
</grid>
</window>
2. Logique du ViewModel
// ModelesVue/ModeleVuePrincipal.cs
using MaterialDesignThemes.Wpf;
using System.Windows.Input;
public class ModeleVuePrincipal : ObservableObject
{
private readonly ServiceModbusTcp _serviceModbus = new ServiceModbusTcp();
public ObservableCollection<donneeregistre> ListeRegistres { get; } = new();
public ICommand CommandeConnexion => new RelayCommand(async () =>
{
await _serviceModbus.ConnexionAsync(champAdresseIP.Text, int.Parse(champPort.Text));
EffectuerLectureRegistres();
});
public ICommand CommandeLectureRegistres => new RelayCommand(() =>
{
var valeurs = _serviceModbus.LireRegistresMaintien(0, 10);
ListeRegistres.Clear();
for (int index = 0; index < valeurs.Length; index++)
{
ListeRegistres.Add(new DonneeRegistre {
Adresse = 0 + index,
Valeur = valeurs[index]
});
}
});
private void EffectuerLectureRegistres()
{
// Logique de lecture asynchrone
}
}</donneeregistre>
IV. Débogage et optimisations
Pour la vérification de l'intégrité des données, voici une méthode de calcul de CRC :
// Utilitaires/VerificationIntegrite.cs
public static byte[] CalculerCrc(byte[] donnees)
{
ushort crc = 0xFFFF;
foreach (byte octet in donnees)
{
crc ^= (ushort)octet;
for (int iteration = 0; iteration < 8; iteration++)
{
if ((crc & 0x0001) != 0)
crc >>= 1;
else
crc = (ushort)((crc >> 1) ^ 0xA001);
}
}
return new[] { (byte)crc, (byte)(crc >> 8) };
}
Pour la gestion des erreurs, une approche robuste est essentielle :
try
{
await _serviceModbus.ConnexionAsync(adresseIp, port);
}
catch (SocketException exception)
{
AfficherMessageErreur($"Échec de la connexion : {exception.Message}");
Journal.Erreur(exception, "Problème réseau");
}
Optimisations recommandées :
- Gestion d'un pool de connexions pour réutiliser les instances TcpClient
- Privilégier les opérations en lot comme
EcrireRegistresMultiplespour réduire les appels - Utilisation de
async/awaitpour préserver la réactivité de l'interface
V. Fonctionnalités supplémentaires
Pour la surveillance graphique, l'intégration d'une bibliothèque comme LiveCharts est utile :
// Suivi de courbes en temps réel
public SeriesCollection CollectionSeries { get; } = new();
private void MettreAJourGraphique(double valeur)
{
CollectionSeries.Add(new LineSeries
{
Values = new ChartValues<double> { valeur }
});
}</double>
Pour inspecter les trames brutes, un affichage hexadécimal pratique :
public string AffichageHexadecimal
{
get
{
return BitConverter.ToString(donneesBrutes).Replace("-", "");
}
}
VI. Exemples de cas de test
| Scénario | Paramètres | Résultat attendu |
|---|---|---|
| Lecture de registres de maintien | IP=192.168.1.100, Port=502, Adresse=0, Quantité=10 | 10 valeurs hexadécimales retournées |
| Écriture dans un registre unique | Adresse=40001, Valeur=25 | Registre mis à jour à 0x19 |
| Écriture en lot | Adresse=40002, Tableau de valeurs=[10,20,30] | Trois registres modifiés avec succès |
VII. Conseils pour le déploiement
Pour préparer l'application :
- Utiliser ILMerge pour combiner les DLL et inclure la licence NModbus
- Créer un installeur avec Inno Setup ou un outil similaire
Exemple de configuraton pour Inno Setup :
[Fichiers]
Source: "MonApplicationModbus.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "NModbus.dll"; DestDir: "{app}"; Flags: ignoreversion