Skip to content

Profils Modbus & objets métier ​

Les profils standard (INIS-E16, AVISS) et les clés métier (incidents, commandes, sons, états système) sont définis en code. Les incidents, commandes et sons portent une clé d'enum ; les états système portent une constante de chaîne (SystemFlagKeys), une clé étant partagée par des centrales qui la servent à des bits différents. Les profils personnalisés créés depuis l'application sont stockés en base. Au démarrage, chaque profil standard est validé puis semé en base ; sur les incidents et les commandes, une incohérence — clé inconnue, doublon, son inexistant — fait échouer le démarrage avec un message clair. Les définitions d'états système ne passent pas par ce validateur : leur cohérence tient au typage et aux tests.

Où vivent les adresses Modbus ​

Les adresses de registres et les codes de commande des centrales standard sont déclarés dans une seule source :

dotnet/shared/Profiles/StandardPanelProfiles.cs

Ce fichier expose une cartographie PanelRegisterMap par type de centrale (StandardPanelProfiles.InisE16, StandardPanelProfiles.Aviss) : adresses fil JBUS brutes (PointFireAddress, ZoneFireAddress, EvacuationAddress…), décalage d'adressage (AddressOffset), nombre de zones et codes de fonction des commandes (CmdReset, CmdSilence, CmdDisableZone…).

Cette cartographie est la source de vérité partagée par les deux côtés qui parlent Modbus :

ConsommateurPoint d'entréeRôle
BackendModbusProfile.CreateInisE16() / CreateAviss() (helper FromRegisterMap)Construit le profil complet (adresses + incidents / commandes / états système) servi à l'application
Cœur du simulateurProfileLoader.GetProfile() dans dotnet/simulator.core/ProfileConfig.cs (remplit ProfileConfig) ; HandleCommand compare aux codes du profilÉmule une centrale aux mêmes adresses

Le projet shared est une bibliothèque légère (aucune dépendance backend), référencée par le backend et par simulator.core. Les trois consommateurs du simulateur — l'interface graphique (dotnet/simulator), l'hôte sans interface (dotnet/simulator.headless) et les tests (dotnet/tests/simulator) — passent tous par simulator.core, donc par la même cartographie. Les adresses ne peuvent pas diverger.

Modifier une adresse ou un code de commande ​

Éditer la valeur dans StandardPanelProfiles (InisE16 ou Aviss) — un seul endroit. Le backend et le simulateur reflètent le changement au prochain build.

Le test ModbusProfileTests (dotnet/tests/backend/Domain/ValueObjects/) asservit les valeurs littérales des profils standard : le mettre à jour en même temps que la cartographie.

Identité de drapeau : incidents à comportement partagé ​

Le vocabulaire des comportements (ZoneStatus) est fermé — il commande la mécanique : priorité par défaut, danger appliqué sans délai à l'apparition, synthèses, couleur de repli. Mais l'identité d'un incident dans tout ce qui circule — drapeaux des instantanés, acquittements, journaux, affichage — est son FlagId (IncidentDefinition.FlagId) :

  • un incident à clé livrée (fire, fault, dm… — vocabulaire IncidentKey) s'identifie par le nom de son comportement (« Fire », « Fault »), son identité historique de bout en bout ;
  • un incident à clé libre s'identifie par sa clé.

Un profil peut donc déclarer plusieurs incidents partageant un comportement de base — « Défaut pompe » et « Défaut ventilation » sont deux dérangements — chacun sous sa clé : ils s'empilent dans les drapeaux (ActiveFlags, plan, flux, tooltips), s'acquittent séparément (FlagAckStatuses indexé par identité) et se journalisent sous leur nom déclaré. Le validateur refuse deux incidents de même identité, plus deux incidents de même comportement.

Côté frontend, le store des profils résout libellé/icône/son par identité (byFlag), et enregistre les identités déclarées auprès de useIncidentStyling (registerDeclaredStatuses) : famille de couleur — choisie dans l'échelle de sévérité partagée, jamais une couleur libre — icône et criticité. Un drapeau qui ne se résout plus (profil réédité pendant qu'un incident était actif) est traité en dérangement : jamais ignoré.

Portée réelle de trois réglages ​

Trois champs de l'éditeur n'agissent que sur une partie des profils ou dans une seule circonstance ; les livrer sans le dire fait croire à un réglage sans effet.

RéglageCe qu'il commandeOù il agit
ReadFunction, Encoding, RegisterLength, ActiveLowFonction Modbus de lecture, disposition des états, étendue lue, sens de lectureGenericModbusAdapter seul, donc les profils déclarés (IsBuiltIn = false). PanelAdapterFactory aiguille sur ce seul critère : un profil livré — même modifié sur site, même rattaché à une centrale d'un autre type — garde l'adaptateur de son firmware, qui lit des bobines en dur
IncidentDefinition.AcknowledgeableSort du drapeau à sa disparition : verrouillé en HardwareCleared jusqu'à effacement, ou retiré sans traceZone/PointManagementService.ProcessDisappearedFlagsAsync, via IncidentResolver.IsAcknowledgeable ; côté poste, incidentProfileStore.isAcknowledgeable masque l'action
CommandDefinition.ConfirmRequiredFenêtre de confirmation avant l'envoiuseSidebarCommands.confirmCommand. Une commande de portée Zone ou Point passe toujours par la fenêtre : sa cible s'y désigne

Un drapeau qu'aucun profil ne déclare est réputé acquittable, serveur et poste alignés — mieux vaut une trace de trop qu'un incident qui s'évapore.

Sens de lecture ​

IncidentDefinition.ActiveLow retourne la lecture brute d'une table, dans ReadTableAsync, avant que la position de tête et les entités n'en soient extraites : un bit comme un registre par entité s'y plient, et la tête subit l'inversion comme le reste. C'est la convention des contacts à sécurité positive, où c'est l'ouverture de la boucle qui signale l'état.

Unpack ne rend que les positions réellement reçues, et non la fenêtre entière : compléter par des positions au repos les ferait passer pour lues, et une table à logique inverse les retournerait toutes en incidents fantômes. Une position non lue reste donc au repos, quel que soit le sens de lecture.

Éprouver une centrale déclarée sur une installation réelle ​

Deux outils lisent la centrale à partir du profil, et non de valeurs codées :

  • Scan brut — GET /api/modbus/scan-plans sert, par profil, une table par incident déclaré : adresse, décalage de tête, étendue, fonction de lecture. Le scan couvre donc tout ce que la centrale expose, et chaque bit lu annonce l'entité qu'il décrit (entité système, zone n, point n) — c'est ce rattachement qui rend un décalage faux visible sur place.
  • Campagne de validation JBUS — GET /api/panels/{id}/jbus-validation dérive du profil les plages à lire sur une centrale INIS-E16 connectée et confronte le modèle « zone IHM / DI IHM » aux bits réellement remontés. Strictement passive : aucune écriture n'en part.

Où est vérifié un profil déclaré ​

Trois couches, chacune couvrant ce que les autres ne peuvent pas atteindre :

CoucheFichierCe qu'elle fixe
Tramedotnet/tests/backend/Infrastructure/Adapters/GenericModbusAdapterParameterMatrixTests.csValeur par valeur : adresse interrogée, fonction Modbus employée, nombre d'unités demandé, zone décrite par chaque bit — fonctions de lecture, dispositions, étendue explicite, décalages de table, décalage d'adressage, adresse laissée vide, comportement partagé
Cycle de viedotnet/tests/backend/Integration/CustomProfileLifecycleTests.csApparition, étiquette du journal, sévérité déclarée, acquittement d'un drapeau sans solder l'autre, cascade zone → points, verrouillage à la disparition, effacement, incident non acquittable, réapparition, statut principal par priorité, drapeau retiré du profil
Exploitationvue/e2e/specs/custom-profile.spec.tsCentrale simulée rattachée à un profil déclaré : étiquette du flux en direct, deux dérangements côte à côte, acquittement séparé, « à effacer » puis disparition, incident qui se solde seul, couleur de la famille déclarée sur le plan, trace à l'historique

La campagne d'exploitation rattache la centrale INIS simulée au profil e2e-atelier puis la rend à inis-e16 : aucun de ses incidents ne porte de clé livrée, si bien qu'un libellé livré à l'écran signale une résolution retombée sur le code. Elle se lance avec le reste de la suite (cd vue && npm run test:e2e) et exige PostgreSQL et le jeu de démonstration.

Ajouter un type de centrale ​

Pour une nouvelle centrale compatible Modbus :

  1. Type : ajouter une valeur à PanelType (dotnet/backend/Domain/Enums/PanelType.cs).
  2. Adresses : ajouter une cartographie PanelRegisterMap dans StandardPanelProfiles (adresses de registres, AddressOffset, ZoneCount, codes de commande).
  3. Profil : ajouter une fabrique ModbusProfile.CreateXxx() dérivant de la cartographie via FromRegisterMap(StandardPanelProfiles.Xxx, "NOM", PanelType.Xxx).
  4. Enregistrement : ajouter la fabrique au tableau de PanelProfileRegistry — le profil est alors validé et semé au démarrage.
  5. Comportement (si le mapping registres → état ou le timing diffèrent) : déclarer une PanelTypePolicy dans PanelAdapterFactory et un adaptateur héritant de ModbusFirePanelAdapterBase. Un type sans policy utilise l'adaptateur générique config-driven.
  6. Front : PanelType étant exposé à l'interface, régénérer les types (voir plus bas) et committer enums.ts.
  7. Simulateur (facultatif, pour émuler la centrale) : dans dotnet/simulator.core/, ajouter une valeur à SimulatorPanelType (SimulatorTypes.cs) et la brancher dans ProfileLoader.GetProfile (ProfileConfig.cs) sur la cartographie partagée. L'interface graphique et l'hôte sans interface en héritent sans modification.

Ajouter / gérer un objet métier ​

Chaque objet a un seul endroit où l'ajouter. La validation et le typage garantissent le reste.

Ajouter…Où
Un incident, une commande ou un sonla valeur d'enum (IncidentKey / CommandKey / SoundKey) + l'entrée dans le BuildStandard* correspondant (ModbusProfile.cs) + une migration de reseed : le profil entier est semé par HasData, incidents et commandes compris
Un état systèmela clé dans SystemFlagKeys (dotnet/shared/Profiles/), le bit dans la carte GlobalBits de chaque centrale qui le sert, la description dans ModbusProfile.BuildStandardSystemFlags, puis une migration de reseed — procédure détaillée dans États système et états d'exploitation
Un type de centralevoir la procédure ci-dessus
Un protocole / transportune implémentation d'IModbusClient
Un rôleUserRole
Exposer un enum au frontendla liste exported de dotnet/tools/TsEnumGenerator

Fichiers : enums dans dotnet/backend/Domain/Enums/ ; ModbusProfile et ses définitions (IncidentDefinition, CommandDefinition, SystemFlagDefinition) dans dotnet/backend/Domain/ValueObjects/ ; validateur et registre des profils standard dans dotnet/backend/Domain/Profiles/ ; cartographies d'adresses dans dotnet/shared/Profiles/. L'écran d'édition des profils vit dans vue/src/features/settings/views/admin/panel-profiles/.

À fournir en plus (ressources référencées) ​

  • LabelKey → traduction dans vue/src/i18n/locales/fr.json et en.json. Pour un état système, le nœud pointé porte label et details.
  • ShortLabelKey (états système) → libellé court des indicateurs de santé, où la place manque.
  • Icon → nom reconnu par le frontend (sinon icône par défaut).
  • AudioKey → une valeur de SoundKey et un son réellement configuré.
  • FunctionCode inédit → un cas dans le simulateur (HandleCommand) pour le tester ; la vraie centrale réagit d'elle-même. Les codes déjà présents dans la cartographie sont reconnus automatiquement.

Types partagés front ↔ back ​

Les enums exposés à l'interface (ZoneStatus, PanelType, IncidentScope, CommandScope…) sont générés vers vue/src/config/generated/enums.ts, dont constants.ts fait dériver ZONE_STATUSES et PANEL_TYPES : leurs valeurs restent alignées sur le backend. Après modification d'un enum, régénérer :

powershell
./scripts/generate-ts-enums.ps1

La CI régénère ce fichier et échoue si la version versionnée n'est pas à jour : committer enums.ts après régénération.

Exemple — ajouter une commande « Évacuation générale » ​

  1. CommandKey : ajouter EvacuateAll.
  2. ModbusProfile.BuildStandardCommands : ajouter l'entrée (FunctionCode, Scope, LabelKey, Icon, Variant…).
  3. Migration de reseed : dotnet ef migrations add AddEvacuateAllCommand --project dotnet/backend/backend.csproj. Les profils standard étant semés par HasData, le modèle EF diverge du snapshot tant qu'elle manque.
  4. i18n : commands.evacuateAll dans fr.json et en.json.
  5. Simulateur (si testé ainsi) : gérer le nouveau FunctionCode dans HandleCommand.
  6. Redémarrer le backend — le profil est validé, la commande apparaît.

Documentation Technique