Architecture du système
Module de communication série
using System.IO.Ports;
public class GestionnairePortSerie : IDisposable
{
private SerialPort _portConnexion;
private const int VitesseTransmission = 115200;
private const Parity ModeParite = Parity.None;
private const StopBits BitsArret = StopBits.One;
private const int BitsDonnees = 8;
public event Action<string> DonnéesReçues;
public void EtablirConnexion(string nomPort)
{
_portConnexion = new SerialPort(nomPort, VitesseTransmission, ModeParite, BitsDonnees, BitsArret);
_portConnexion.DataReceived += (sender, args) =>
{
string contenu = _portConnexion.ReadExisting();
DonnéesReçues?.Invoke(contenu);
};
_portConnexion.Open();
}
public void FermerConnexion() => _portConnexion?.Close();
public void Dispose() => _portConnexion?.Dispose();
}
Traiteur de données
public class TraiteurDonnées
{
public (double Temperature, double Humidite)? AnalyserMessage(string trame)
{
if (string.IsNullOrEmpty(trame) || trame.Length < 8)
return null;
try
{
var parties = trame.Split(':');
double temperature = double.Parse(parties[0]);
double humidite = double.Parse(parties[1]);
return (temperature, humidite);
}
catch
{
return null;
}
}
}
Graphiqeu dynamique
using OxyPlot;
using OxyPlot.Series;
public class GraphiqueTempsReel
{
private PlotModel _modeleGraphique;
private LineSeries _serieTemperature;
private LineSeries _serieHumidite;
private int _compteurPoints = 0;
private const int CapaciteTampon = 200;
public GraphiqueTempsReel()
{
_modeleGraphique = new PlotModel { Title = "Données environnementales" };
_serieTemperature = new LineSeries
{
Title = "Température",
Color = OxyColors.Crimson,
StrokeThickness = 2
};
_serieHumidite = new LineSeries
{
Title = "Humidité",
Color = OxyColors.SteelBlue,
StrokeThickness = 2
};
_modeleGraphique.Series.Add(_serieTemperature);
_modeleGraphique.Series.Add(_serieHumidite);
}
public void AjouterMesure(double temp, double humi)
{
_serieTemperature.Points.Add(new DataPoint(_compteurPoints, temp));
_serieHumidite.Points.Add(new DataPoint(_compteurPoints, humi));
if (_serieTemperature.Points.Count > CapaciteTampon)
{
_serieTemperature.Points.RemoveAt(0);
_serieHumidite.Points.RemoveAt(0);
}
_compteurPoints++;
_modeleGraphique.InvalidatePlot(true);
}
public PlotModel ObtenirModele() => _modeleGraphique;
}
Fonctionnalités avancées
Système d'alertes
public class GestionnaireAlarmes
{
private readonly double[] _seuilsTemp = { 5.0, 35.0 };
private readonly double[] _seuilsHumi = { 20.0, 80.0 };
public EvenementAlarme VerifierSeuils(double temp, double humi)
{
if (temp > _seuilsTemp[1] || temp < _seuilsTemp[0])
return new EvenementAlarme("Temperature", DateTime.Now);
if (humi > _seuilsHumi[1] || humi < _seuilsHumi[0])
return new EvenementAlarme("Humidite", DateTime.Now);
return null;
}
public void DeclencherAlerte(EvenementAlarme evenement)
{
// Logique d'alerte sonore et visuelle
}
}
Persistance des données
using LiteDB;
public class DepotDonnées
{
private readonly LiteDatabase _baseDonnées;
private const string CheminFichier = "mesures.db";
public DepotDonnées()
{
_baseDonnées = new LiteDatabase(CheminFichier);
InitialiserCollection();
}
private void InitialiserCollection()
{
var collection = _baseDonnées.GetCollection<Mesure>("historique");
collection.EnsureIndex(x => x.Horodatage);
}
public void EnregistrerMesure(double temperature, double humidite)
{
var nouvelleMesure = new Mesure
{
Horodatage = DateTime.UtcNow,
ValeurTemperature = temperature,
ValeurHumidite = humidite
};
_baseDonnées.GetCollection<Mesure>("historique").Insert(nouvelleMesure);
}
public List<Mesure> ConsulterHistorique(DateTime debut, DateTime fin)
{
return _baseDonnées.GetCollection<Mesure>("historique")
.Find(x => x.Horodatage >= debut && x.Horodatage <= fin)
.ToList();
}
}
Optimisations techniques
Traitement asynchrone
public class CollecteurAsynchrone
{
private CancellationTokenSource _annulationSource;
private Task _tachePrincipale;
public async Task DemarrerAcquisitionAsync(Func<Task> traitementDonnées)
{
_annulationSource = new CancellationTokenSource();
_tachePrincipale = Task.Run(async () =>
{
while (!_annulationSource.Token.IsCancellationRequested)
{
await traitementDonnées();
await Task.Delay(500, _annulationSource.Token);
}
}, _annulationSource.Token);
}
public void ArreterAcquisition()
{
_annulationSource?.Cancel();
_tachePrincipale?.Wait();
}
}
Gestion de la mémoire
public class TamponCirculaire<T>
{
private readonly T[] _elements;
private int _positionEcriture;
private int _compteur;
public TamponCirculaire(int capacite)
{
_elements = new T[capacite];
}
public void Ajouter(T element)
{
_elements[_positionEcriture] = element;
_positionEcriture = (_positionEcriture + 1) % _elements.Length;
if (_compteur < _elements.Length)
_compteur++;
}
public T[] ExtraireDonnées()
{
var resultat = new T[_compteur];
int indexDepart = (_positionEcriture - _compteur + _elements.Length) % _elements.Length;
for (int i = 0; i < _compteur; i++)
{
resultat[i] = _elements[(indexDepart + i) % _elements.Length];
}
return resultat;
}
}
Procédures de test
Simulation de capteur
public class SimulateurCapteur
{
private Random _generateur = new Random();
public (double temp, double humi) GenererMesure()
{
double baseTemp = 22.0 + _generateur.NextDouble() * 6;
double baseHumi = 45.0 + _generateur.NextDouble() * 15;
// Ajout de variations temporelles
double variation = Math.Sin(DateTime.Now.Second * 0.1) * 2;
return (baseTemp + variation, baseHumi - variation);
}
public string CreerTrame(double temp, double humi)
{
return $"{temp:F1}:{humi:F1}";
}
}
Déploiement
Configuration système
<?xml version="1.0" encoding="utf-8"?>
<Configuration>
<ParametresSerie>
<Port>COM3</Port>
<Vitesse>115200</Vitesse>
</ParametresSerie>
<ParametresAlarmes>
<SeuilTempHaute>35.0</SeuilTempHaute>
<SeuilTempBasse>5.0</SeuilTempBasse>
<SeuilHumiHaute>80.0</SeuilHumiHaute>
<SeuilHumiBasse>20.0</SeuilHumiBasse>
</ParametresAlarmes>
<Stockage>
<CheminBase>C:\\DonneesCapteurs\\mesures.db</CheminBase>
<IntervalleSauvegarde>60</IntervalleSauvegarde>
</Stockage>
</Configuration>