[C#] Evènement mise à jour SO

Bonjour,

J’aurais voulu savoir s’il existe un évènement permettant de connaître dès qu’un SO a été modifié ( en particulier sa valeur ) sans pour autant passer par un StateObjectLink étant donné que les paramètres ( sentinel, package, nom du state object) sont obtenus dynamiquement alors que pour un StateObjectLink ceux-ci doivent être fournis " en dur" si j’ai bien compris.
J’ai déjà essayé à l’aide du SubscribeStateObjects auquel je fournis les paramètres adéquats; cependant je ne reçois jamais l’évènement StateObjectUpdated m’indiquant la modification de mon SO. Voici la manière dont j’ai procédé actuellement :
public StateObjectValueProvider(string sentinelName, string packageName, string stateObjectName, string xPath)
{
SentinelName = sentinelName;
PackageName = packageName;
StateObjectName = stateObjectName;
XPath = xPath;
PackageHost.StateObjectUpdated += (s, e) =>
{
PackageHost.WriteInfo($“Received notif {e.StateObject.Name}”);
};
PackageHost.RequestStateObjects(sentinel:SentinelName, package : packageName, name : stateObjectName);
PackageHost.SubscribeStateObjects(sentinel: SentinelName, package: packageName, name: stateObjectName);
}
De plus, je souhaiterais également savoir s’il existe une fonction / évènement permettant de détecter l’expiration d’un SO ?

Merci par avance.

Bonjour,

travaillant avec Dypz, je peux déjà dire que nous avons réussi à résoudre le premier problème:
Nous fait une erreur dans le nom du state object que nous voulions écouter.

Il nous reste tout de même la deuxième question :

De plus, je souhaiterais également savoir s’il existe une fonction / évènement permettant de détecter l’expiration d’un SO ?

Merci !

Hello,

Oui à lire votre code ce WE j’étais étonné de voir que ça ne marche pas ! En effet avec le bon nom de SO ça marche mieux :wink:

Pour ce qui est de l’expiration, non pas d’événement, il y a juste une propriété IsExpired sur l’objet StateObject qui indique si le SO a expiré ou non en comparant la différence entre la date courante et propriété « LastUpdate » avec le « Lifetime » défini sur le SO (exprimé en seconde).

Vous pouvez vous même ajouter une classe qui s’occuperait de surveiller l’expiration de vos SO et de lever un event. « surveiller » veut dire, créer un thread en background qui contrôle régulièrement si la propriété « IsExpired » change.

Imaginez la classe suivante :

public class StateObjectCollectionWatcher : StateObjectCollectionNotifier
{
    public event EventHandler<StateObjectUpdatedEventArgs> StateObjectExpired;

    public StateObjectCollectionWatcher() : base()
    {
        Task.Factory.StartNew(async () =>
        {
            while (PackageHost.IsRunning)
            {
                try
                {
                    foreach (StateObjectNotifier so in this)
                    {
                        if (so.Value.IsExpired && !so.Value.Metadatas.ContainsKey("__Watcher_SeenExpired"))
                        {
                            so.Value.Metadatas.Add("__Watcher_SeenExpired", null);
                            this.StateObjectExpired?.Invoke(this, new StateObjectUpdatedEventArgs() { StateObject = so.Value });
                        }
                    }
                }
                catch { }
                await Task.Delay(1000);
            }
        });
    }
}

Ici on crée une classe StateObjectCollectionWatcher qui hérite de la classe StateObjectCollectionNotifier que nous avons vu rapidement en cours (pour plus d’info voir ici), c’est à dire que notre classe « StateObjectCollectionWatcher » est une collection de StateObjectNotifier.

Sur cette classe nous rajoutons un événement « StateObjectExpired » qui sera levé quand un SO de la collection expirera.

Pour le savoir, nous créons une tache en background via le Task.Factory dans le constructeur de notre classe. Cette tache tourne tant que le package est démarré (PackageHost.IsRunning) en marquant une pause d’une seconde à chaque itération. Cette boucle parcourt tous les SO qu’elle contient pour savoir qui a expiré. Quand elle en trouve, elle vient lever l’événement « StateObjectExpired » en passant dans les EventArgs l’instance du SO qui a expiré.

Pour finir, on ajoute dans les metadatas du SO, une clé que j’ai nommé ici « __Watcher_SeenExpired » de façon à éviter que l’événement « StateObjectExpired » soit levé en boucle. En gros, on tag le SO pour dire qu’on « sait qu’il a expiré » :wink:

De ce fait, dans votre package, vous pouvez tout simplement créer une instance de notre « StateObjectCollectionWatcher » et vous abonnez à l’événement "StateObjectExpired " dans lequel vous récupérez dans les EventArgs (la variable ‹ e ›), le SO venant d’expirer :

StateObjectCollectionWatcher watcher = new StateObjectCollectionWatcher();
watcher.StateObjectExpired += (s, e) =>
{
    PackageHost.WriteWarn($"Le StateObject '{e.StateObject.Name}' produit par {e.StateObject.SentinelName}/{e.StateObject.PackageName} vient juste d'expirer !!!");
};

Dernière étape, c’est bien sur d’alimenter notre « watcher ». Pour cela, il suffit juste de s’abonnez au « StateObjectUpdated » du PackageHost pour ajouter les SO réceptionnés dans notre collection « watcher » :

PackageHost.StateObjectUpdated += (s, e) =>
{
    watcher.AddOrUpdate(e.StateObject);
};

Ainsi à chaque SO reçu, on l’ajoute (ou met à jour) dans notre collection « watcher » qui surveille l’expiration du SO et si expiré on lève l’event !
Notez bien que si le SO expire, on ajoute un tag dans les métadatas ! Ce tag est présent seulement dans les metadatas de l’objet StateObject côté .NET ! Cette métadata n’est pas remontée dans la Constellation (les SO sont en read-only à moins de faire un PushStateObject). Si le SO expiré est à nouveau mis à jour dans votre Constellation par le package d’origine, il sera donc reçu dans le PackageHost.StateObjectUpdated qui le mettra à jour dans la collection « watcher ». Donc la valeur du SO et ses métadatas seront bien écrasés par le nouveau SO, ainsi si il est amené à expiré de nouveau, l’événement sera bien levé :slight_smile:

Petite précision et pas des moindres car vous risquez d’être déçu :wink: La méthode « AddOrUpdate » de la classe « StateObjectCollectionNotifier » est une méthode « internal » donc elle vous est inaccessible actuellement ! Dans la version de la libraire 16166, elle est publique !
La mise à jour sera disponbile sur Nuget dès demain. Il faudra juste, dans le gestionnaire Nuget de votre package, cliquez sur le bouton « Update » ! Je posterai un message ici dès que l’update sera dispo :slight_smile:

Bonne soirée,

Pour info, le package “Constellation” en version 1.8.0.16166 est disponible.

Pour le mettre à jour :

    1- Clic droit sur votre projet, "Manage Nuget packages"
    2 - Dans la liste "package source" en haut à droite, sélectionnez "Constellation"
    3 - Cliquez sur l'onglet "Updates"
    4 - Cliquez sur le bouton "Update" sur le package "Constellation"

Nuget se chargera alors de télécharger et mettre à jour la nouvelle librairie Constellation pour votre package.

Pour info, dans cette nouvelle version 16166 :

  • gestion des MessageCallbacks avec des parametres optionnels et WriteError en cas d'erreur de dispatch
  • ajout de la propriété "UniqueId" sur l'object StateObject (= Sentinel/Package/Name)
  • StateObjectCollectionNotifier : l'indexeur est maintenant le UniqueId, idem pour la méthode ContainsStateObject
  • StateObjectCollectionNotifier : ajout d'un indexeur avec parametres optionnels (sentinel/package/name/type) et qui retourne un nouveau StateObjectCollectionNotifier (+ méthode ContainsStateObject associée)
  • StateObjectCollectionNotifier : la méthode "AddOrUpdate" est maintenant publique

De ce fait, le code proposé ci-dessus pour la notification d’expiration de SO peut maintenant compiler.

Sébastien,
tout d’abord, chapeau bas pour le travail réalisé !
Cela fait un petit moment que je fais le tour de la ‹ constellation › :wink: et suis assez bluffé.

Je ne suis encore qu’en classe primaire, j’ai néanmoins fabriqué quelques capteurs et relais sur base d’ESP8266 en phase pilote (tests terminés !) sur un serveur dédié (Cerebro… et oui, quelle créativité, mais en même temps, le pendant s’en trouve simplifié).

Bref, un magistral « merci ».

Je me retrouve néanmoins et assez souvent confronté a des lacunes techniques; j’ai passé 30+ années dans l’informatique, mais moins de 5 à coder; ça rouille et coince un peu et me prend donc pas mal de temps…

J’ai essayé d’ajouter une temporisation sur mes relais, et n’ai eu aucun soucis avec le fonctionnement de base. Je voudrais néanmoins évité de géré le timeout sur chaque relais en utilisant plutôt l’expiration des SO. J’ai donc créé un package VS comme documenté ici, mais mon code ne rentre jamais dans le « foreach (StateObjectNotifier so in this) {ici-donc} ». Je ne veux pas surchargé inutilement le post; la classe StateObjectCollectionWatcher est déclarée tout au début du namespace et le code dans le OnStart().

J’ai évidemment modifié un SO qui a également expiré pendant que le package tournait, semble-t’il correctement.

Il y a forcément une finesse qui m’échappe ;-(( Une idée d’emblée ?

Le code et la sortie console sont publiés dans mon GDrive (fichier TXT) sur https://drive.google.com/file/d/1Og8G8VCVTr9hdR44tugEgNA4hTTtKxRi/view?usp=sharing

MERCI encore !-!

Bien cordialement,
Dominique

Bonjour Dominique et merci pour votre message :slight_smile:

J’ai regardé votre code sur GDrive mais je ne vois pas le « StateObjectLink » mappé sur le StateObjectCollectionWatcher.

De plus il ne faut pas instancier le StateObjectCollectionWatcher vous même, c’est Constellation qui s’en chargera via le « StateObjectLink ». Autrement dit, dans l’état actuel, il n’est pas utilisé par Constellation, car non connu.

Voir : https://developer.myconstellation.io/client-api/net-package-api/consommer-des-stateobjects/#StateObjectCollectionNotifier_collection_de_StateObjectNotifier

Il faudrait donc ajouter dans votre classe « Program » un StateObjectLink vers le ou les SO que vous voulez suivre en utilisant la classe StateObjectCollectionWatcher pour ajouter la notion de « suivi de l’expiration »

Exemple, dans votre classe ajouter :

[StateObjectLink(Package = "MonPackage")]
public StateObjectCollectionWatcher MyWatcher { get; set; }

Et dans votre programme, supprimez l’instanciation (et l’event sur le StateObjectUpdated) et ajoutez directement un handler sur l’event « StateObjectExpired » sur « MyWatcher » :

MyWatcher.StateObjectExpired += (s, e) =>
{
    PackageHost.WriteInfo("Le StateObject '{e.StateObject.Name}' produit par {e.StateObject.SentinelName}/{e.StateObject.PackageName} vient juste d'expirer !!!");
};

Ainsi tous les StateObjects produits par le package « MonPackage » seront « linkés » sur la propriété nommée « MyWatcher » de type « StateObjectCollectionWatcher ».

Constellation se chargera d’instancier StateObjectCollectionWatcher et créera les abonnements pour recevoir toutes les mises à jour des SO produit par ce package afin de les envoyer dans le StateObjectCollectionWatcher « MyWatcher », qui lui, surveillera les dates d’expiration des SO pour lever l’event « StateObjectExpired » si nécéssaire.

Bien à vous


Pour référence, votre code sur GDrive corrigé deviendra :

public class Program : PackageBase
{
    [StateObjectLink(Package = "MonPackage")]
    public StateObjectCollectionWatcher MyWatcher { get; set; }

    static void Main(string[] args)
    {
        PackageHost.Start<Program>(args);
    }

    public override void OnStart()
    {
        PackageHost.WriteInfo("Package v1.03c starting - IsRunning: {0} - IsConnected: {1}", PackageHost.IsRunning, PackageHost.IsConnected);

        MyWatcher.StateObjectExpired += (s, e) =>
        {
            PackageHost.WriteInfo("Le StateObject '{e.StateObject.Name}' produit par {e.StateObject.SentinelName}/{e.StateObject.PackageName} vient juste d'expirer !!!");
        };
    }
}

Bonjour Sébastien,
Je me doutais bien qu’il y avait un petit soucis dans « ma logique », mais sans avoir vraiment le « câblage » de constellation en tête… c’est un peu plus dur.
Merci en tout cas pour votre travail et surtout votre réactivité, impressionnants !-!
J’ai réfléchi à un scénario alternatif, avec l’ESP qui « surveille » lui-même l’expiration et la remise à jour de ses SO, mais cela sollicitera d’avantage l’ESP et sera plus compliqué à maintenir.
Je regarde et reviendrai vers vous.
Merci encore et Joyeuses Pâques à vous ainsi qu’à tous ceux qui me liront sur le forum.
Bien cordialement,
Dominique