Moteur animation transition : update à la seconde - idée?

Bonjour à tous,

Je cherche un moyen -atteignable- pour animer des transitions de couleurs, pouvant être un peu longue, mais toujours douce!

Aujourd’hui, j’ai fait un truc un peu similaire à ce que j’ai dans un scénario, soit avec un sleep sur une boucle d’incrémentation et de mise à jour. Ce sont des transitions de couleur, typiquement chez moi je les lance toutes les 3 à 10 secondes pour un effet « Aube » sans à-coup selon la durée totale.

L’objectif est d’avoir un moteur qui se lance au besoin, dans lequel je référence d’une manière ou d’une autre les équipements/transitions à updater avec leur fréquence d’update, et lancer la mise à jour si nécéssaire - et éviter d’avoir autant de script qui tourne que d’animation à mettre à jour.

J’ai monté en PHP une classe « « moteur » », mais la seule manière que j’ai d’avoir un peu de persistance est de passer par le cache de jeedom… (pour l’exemple ici le moteur tourne «  »«  »« « à la seconde » »«  »" mais j’envisage le paramétrage par la suite)

donc le workflow est :

1.a/ ajout d’une transition au moteur => mise à jour du cache
1.b/ lancement du moteur au besoin

moteur :

2.a / lecture du cache et désérialisation
2.b/ boucle sur les transitions référencées
2.b.i / calcul si la mise à jour est requise
2.b.ii/ récup de l’éqlogic au besoin
2.b.iii/mise à jour de l’équipement
2.c.i/vérif si la transistion est finie
2.c.ii/ suppression de la liste de la transistion au besoin
3/ si toujours des transition en cours : Mise à jour du cache
4/ sleep 1 seconde
5/ relance du cycle moteur

Ce qui est vachement lourd je trouve, surtout vu les écritures de cache incessante, ça sent le sd killer,
et je trouve ça pas vraiment propre…

J’ai tenté de travaillé en static, d’implémenter un singleton, mais je perd toujours la persistance des infos à un moment ou un autre.
Au final le sleep m’ennuie moins que les écriture/lecture du cache, je fais à mon niveau.

J’aurai imaginer pouvoir atteindre les objets en mémoire, tout du moins les faire persister dans le scope !?
J’ai tenter de croiser les référence aux objets dans les classe, mais perte de la persistance également.

Du coup je suis à la recherche de tout autre piste! là c’est fonctionnel mais pas performant.

Pouvez vous m’aiguiller?

la classe du moteur en l'état
<?php
/* * ***************************Includes********************************* */
require_once __DIR__  . '/../../../../core/php/core.inc.php';

class CT_motor  {
  private const CT_CACHE_NAME="COLOR_TRANSITION::";
  private const CT_CACHE_ARRAY="serial_array";

  public static $countLoop=0;
  
  private function __construct() {  
  }
  //remove d'un élément par id
  public static function removeCTA($id){
    log::add('ColorTransition_actuator', 'debug', '║ ║ ╟─── MOTOR REMOVE '.$id);
    $jcacheExist=cache::exist(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY);
   
    
    if(!$jcacheExist)return false;
    $cacheJson=cache::byKey(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY)->getValue();
    $cacheArr=json_decode($cacheJson,true);

    if(!is_null($cacheArr[$id])){
      unset($cacheArr[$id]);
      log::add('ColorTransition_actuator', 'debug', '║ ║ ╟─── new cache '.json_encode($cacheArr));
      cache::set(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY, json_encode($cacheArr));      
      return true;
    }else{
      return false;
    }
  }
  // ajout d'un élément dans le cache
  public static function addCTA($cta_tr){ 
// $cta_tr est un objet qui référence l'équipement, et les données de la transition en durée, steps, ....
    log::add('ColorTransition_actuator', 'debug', '║ ║ ╟─── MOTOR ADD '.self::CT_CACHE_NAME.self::CT_CACHE_ARRAY);
    $jcacheExist=cache::exist(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY);

    if($jcacheExist){
      $cacheJson=cache::byKey(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY)->getValue();
      $cacheArr=json_decode($cacheJson,true);
    }else{
      $cacheArr=Array();
    }
    $arrAdd=$cta_tr->getArray();
    $keyName=strval($arrAdd['id']);
    
    $cacheArr[$keyName]=$arrAdd;
    cache::set(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY, json_encode($cacheArr));

    // mise à l'index initial de l'équipement cible
    $eq= eqLogic::byId($arrAdd['id']);
    if(!is_object($eq)){
      log::add('ColorTransition_actuator_mouv', 'error', '║ ║ ╟─── #############" MOTOR Error id :'.$cta_tr['id']);
    }
    $eq->refreshEquipementColor($arrAdd['move_index']); 

    if(!$jcacheExist || count($cacheArr)==1)self::startTime();
  }


  /// le coeur du moteur
  private static function startTime(){
    self::$countLoop+=1;

    log::add('ColorTransition_actuator_mouv', 'info', '║ ║ ╟─── MOTOR TICK '.self::$countLoop);
    $jcacheExist=cache::exist(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY);
    if(!$jcacheExist)return;
    $cacheArr=json_decode(cache::byKey(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY)->getValue(),true);

    $finalArray=array();
    
   foreach($cacheArr as $id=>$cta_tr){
    log::add('ColorTransition_actuator_mouv', 'info', '║ ║ ╟─── MOTOR CTA : '.$id);
     $cta_tr['curStep']+=1;

      if ($cta_tr['curStep'] >= $cta_tr['dur_interval']){  // si on doit mettre à jour
        $eq= eqLogic::byId($cta_tr['id']);
        if(!is_object($eq)){
          log::add('ColorTransition_actuator_mouv', 'error', '║ ║ ╟─── #############" MOTOR Error id :'.$cta_tr['id']);
        }
        $cta_tr['curStep']=0;
        $cta_tr['move_index']+=$cta_tr['index_step'];
        $eq->refreshEquipementColor($cta_tr['move_index']); 
        
      }
      //log::add('ColorTransition_actuator_mouv', 'info', '║ ║ ╟─ dur : '.$cta_tr['dur']);
      $cta_tr['dur']-=1;
      
      if($cta_tr['dur']>0){
        $finalArray[$id]=$cta_tr;
      }
   }
   log::add('ColorTransition_actuator_mouv', 'info', '║ ║ ╟─ cache array : '.json_encode($finalArray));
     
    if(count($finalArray)>0 && self::$countLoop<100){
      cache::set(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY, json_encode($finalArray));
      sleep(1);
      self::startTime();
    }else{
      cache::delete(self::CT_CACHE_NAME.self::CT_CACHE_ARRAY);
      log::add('ColorTransition_actuator_mouv', 'info', '║ ║ ╟─---------------------------   MOTOR END : ');
      self::$countLoop=0;
    }
    
  }
  
}

Salut,

Je ne comprend pas bien de ce que tu essaies de faire avec le « moteur » etc mais est-ce que tu n’as pas besoin d’un démon (php ici)?
Autrement dit et très concrètement, un démon php sera une méthode (static) de ta class eqlogic qui sera appelée en boucle (avec un délai que tu définis en seconde entre chaque appel);
du coup dans cette méthode tu pourrais vérifier ce que tu dois faire sur chaque eqLogic, calculer ta transition ou je ne sais quoi etc et ensuite tu stops et tu seras réveillé à nouveau quelques secondes plus tard.

Donc ca ressemble fort à un cron en fait sauf que ca tournera dans un process séparé et que cela peut être exécuté chaque seconde si tu en as besoin.

Sinon je ne vois pas trop pourquoi cette histoire de cache, j’imagine que ce que tu y stock provient des données de config des eqLogic donc pourquoi ne pas les prendre à la source directement?

J’espère que ca te donnera des pistes de réflexion :wink:

Salut Mips,

Merci de ta réponse.

Faut que je creuse l’idée du démon, ce qui me manque c’est la notion de persistance des datas, ou l’accès aux objets en mémoire.

L’idée d’utiliser le cache est de faire persister quelques données sur l’éligibilité à la mise à jour pour éviter les appels aux méthode du core/eqLogic en permanence, qui doivent envoyer des appels en base, traiter la data, faire qques test, etc…

bref diminuer la charge, et ne pas avoir un truc qui tourne en permanence pour un service très certainement ponctuel.

Tu es dans un script php, de quelle persistance tu parles? et il n’y a pas d’objet en mémoire…
quand ton script s’arrête, quand la requête http s’arrête, il n’y a plus rien.

une autre option que tu as, c’est que dès que tu as calculé tes transitions (si j’ai bien suivi), si tu sais déjà à quel moment tu vas devoir les exécuter, tu peux te créer un (ou plusieurs) cron « oneshot » qui fer.a.ont le boulot au moment prévu (même concept que le bloc DANS ou A des scénarios) en programmant ce cron pour être executé à ce moment (et tu le flag comme unique pour qu’il soit détruit par le core après)

mauvais biais, j’ai touché la poo avec un langage ou il valait mieux détruire les objets instanciés que de faire confiance au garbage collector!

Le truc, c’est qu’avant de gérer avec le cache, j’ai testé en static et singleton, qui référençaient l’eqLogic de départ.
Une fois la boucle du moteur lancée, je voyais bien les log avec le ‹ heartbeat › s’incrémeter,
Je suppose donc que l’objet était toujours ‹ vivant › en mémoire, et s’exécute.
mais je n’ai pas trouvé le moyen d’avoir accès, de garder une référence pérenne dessus (je n’ai fait que des choses simples en php).

De plus, l’eqlogic qui a lancé le moteur est toujours en mémoire, probablement car exécution (sequentielle?) pas finie car sur le moteur (j’ai des log sur des référence internes qui ne sont pas détruite). Mais je perds l’accès à cette instance également.
J’ai l’impression que quand j’appelle une méthode d’instance de la classe sur cet Id d’eqlogic, je l’appelle sur une nouvelle instance.

Du coup la stratégie, puisque l’objet est instancié et marche quelque part, je lui fait tester un objet commun et pérenne : le cache

Le pb des cron, c’est que ça ne descend qu’à la minute, pour une transition smooth j’ai besoin de descendre à qques secondes au moins.

La solution peut être plus consommatrice de ressource mais moins gourmande en lecture/écriture serait d’avoir un script qui tourne à la X secondes pour chacune des transitions, mais quid de la charge?

Bonjour

J’ai fait un plugin pour cela #plugin-luminotherapie qui peut descende a 10ms mais, on pourrait meme descendre a la µs mais le probleme est plus dans la transmission de l’ordre

Veux tu un code pour l’essayer?

1 « J'aime »

Je pense que tu mélanges tout avec les objets, j’ai l’impression que tu inventes des concepts qui n’existent pas, je ne sais pas trop par quel bout expliquer :sweat_smile:

Non, je parle de faire un cron unique (un job qui ne s’exécutera qu’une seule fois) au moment voulu et ensuite il reprogramme un nouveau pour la prochaine échéance.

et donc soit ca soit un démon qui lui tournera toutes les secondes si tu veux mais qui recalculera chaque fois s’il y a quelque chose à faire ou pas.
si tu sais a quel moment tu devras faire tes changements la première solution semble plus approprié

Merci Mips,il n’y a pas de planification de lancement, c’est à la demande, dans le cas d’usage basique.

de ce que je lit sur l’interpreteur php, il n’y a pas de persistance comme je l’entends.

[note : sur la gestion de la mémoire intéressant pour les néophytes comme moi ]

(pour moi tu instancie un objet, il est réservé en mémoire, ensuite il est ramassé par le gc selon son élligibilité - je dirais à priori tant qu’un pointeur pointe dessus - ici symbole)

Dans mon cas, je vois que des objets sont toujours en mémoire, puisque script toujours en exécution avec des méthodes qui utilisent des variables de son instance qui ont été valorisées, donc réservé en mémoire, que je vois à la fois dans des logs, mais également dans le résultats.

mais je ne sais pas conserver/partager la référence du pointeur dessus.

------------------- Pour le singleton, j'ai testé un template du type :
class SingletonMotor {
private static $_instance = null;

private $elts=null;

private function __construct() {
   $elts=array();
}

public staticfunction getInstance() {
    if(is_null(self::$_instance)) {
     log::add("xxx", "debug", "new singleton instance");
      self::$_instance = new SingletonMotor ();
   }

   return self::$_instance;

}

public function add($cta_rt){
   array[$cta_rt->getId()]=$cta_rt;
    //launch motor
    if(condition motor pas lancé)motorBeat();
}
public function remove($cta_rt){
   if(test si référencé)unset(array[$cta_rt->getId()]);
}

private function motorBeat(){
  foreach($this->elts as $el){
        /// fait deschoses sur $el
  }
 if( test si doit continuer){
      sleep(1);
      motorBeat();
   }else{
      // les trucs de fin
  }
}

}

Mais quand dans un autre eqLogic je fais appel à getInstance (le moteur toujours en train de tourner), je me retrouve avec une nouvelle instance (le log pour témoin). ma (in)compréhension s’arrête là. (hors débat cestbienoupaslesingletons)
et même combat avec tout en variable static et méthode static.

@mika-nt28, Je te remercie de l’offre, mais je décline. Il est pas impossible que j’en fasse l’acquisition, il a l’air top!
(Note : je ne l’ai pas trouvé avec les mots clé couleur ou lumière dans ma recherche initiale sur le market!)

mais je ne voudrait surtout pas par mégarde emprunter de ton code sur un plugin valorisé financièrement!

En revanche si tu peux m’aiguiller sur la techno que tu as implémenté, je suis preneur.

Ca aurrai ete avec plaisir surtout si cela peut permet aussi d’avoir une autre vision pour l’amélioré

C’est un demon php.

  • une ambiance et decrite par step
  • un step est cadencé avec un usleep
1 « J'aime »

Merci Mika,
Je vais voir du coté du démon php.

Tu as noté un gros intérêt à descendre sous la seconde?
J’ai l’impression que le temps entre l’envoi de la commande, l’envoi par le pont (zigbee pour moi) de la commande, la réception et la mise à jour du module distant (une ampoule par ex) je dois pas être trop loin de la demi seconde.

Si enplus j’ajoute par exemple sur mes wled une demi seconde de transition (paramétrable logiciel), mais c’est un cas particuler.

Un script piloté par cron ne descend pas en dessous de la minute, même sous jeedom (si?) Et d’ailleurs, d’une minute à l’autre ça relance le script de zero, donc pas possible d’utiliser un cache en mémoire… Le cache jeedom il est dans un fichier peut être?

Par contre un démon, ça tourne en permanence. En principe c’est un processus qui « écoute », par exemple un serveur qui attend des requêtes. Lui, pour le coup, il a un cache pérenne, puisqu’il n’est jamais arrêté. Mais, du coup j’ai du mal à voir comment un démon va répondre à ton besoin, il tourne H24, il écoute, mais comment tu l’interroge ?

Il faudrait planifier un cron qui déclenche le démon ?

Salut Pifou,

Oui, c’est tout le pb, j’imagine que je ne l’atteint pas directement, mais que je le fait écouter quelque chose que je suis capable de mettre à jour.
et sans autre idée, soit j’interroge tout les équipements, soit je vais retomber sur le cache de jeedom. Mon moteur avec le cache fonctionne très bien, tourne uniquement au besoin, mais j’ai des écriture incessante du cache ‹ jeedom ›, je dois donc lancer des écriture à tout va!

D’un autre coté, j’ai un pb de compréhension conceptuel du php ds un framwork complexe comme jeedom (je ne connais pas bien php!) : c’est pourquoi le singleton ne fonctionne pas…
une fois instancié, et qui plus est en train de travailler, je devrais pouvoir l’atteindre… le pointeur static devrait être valorisé?
Mais à chaque fois je créé une nouvelle instance, ce qui est un comble pour un singleton :crazy_face: :laughing:!!

[edit] réponse trouvé sur l’ancien forum : Ajout d'attributs dans la class d'un plugin - Forum Communauté Jeedom

oui, le singleton ne s’applique pas à un programme qui se termine, donc un script, un cron, une requête HTTP, c’est foutu. Par contre le démon lui il garde l’instance de l’objet en mémoire tant que tu ne le redémarre pas, donc c’est lui seul qui peut conserver le singleton.

Et du coup, tu l’interroge comment ton démon ? tu utilise un listener, une socket, ou bien c’est lui même une API en mode HTTP ?

Moi non mais c’est une demande qui m’a ete faite pour justement avoir de la fluidité et pour passé la persistance retiniaine.
Apres il faut un echange d’information rapide c’est pour cela que je me suis arreter a 10ms

Salut @pifou,
Aucune idée, je ne suis pas sur même que ce soit pertinent pour ce que je cherche à faire. L’idée de mobiliser de la ressource 24/7 pour un truc qui sert qques minutes par jours, à voir.

Merci pour ta réponse @mika-nt28, faudrait que je test avec mon set up de prod si ça passe chez moi.

Un demon jeedom c’est un cron avec l’option setDeamon(1).
Dans le code du cron tu as un boucle infini que tu doit gere

C’est justement la gestion du demon qui fait ca soit par un temps de boucle.
Par exemple lorsque ta simu tourne tu as un temps de boucle faible et en attente de simu un temps d’attente long.
Tu peux aussi cree/ supprimer le demon que lorsque tu en as besoin

Je ne pense pas qu’il est inutil de cree plusieur plugin qui font la meme chose autant apporter des amelioration a ce qui existe deja

Je ne suis pas d’accord, un démon n’est pas un cron, il n’est pas planifié et ne doit pas s’arrêter non plus; Si on le voit comme un cron, il va démarrer 1 fois toutes les x minutes / heures / jours et puis on aura autant d’instances du démon qui s’accumule ?
Sur mon plugin (LGThinq le seul plugin que j’ai fait du reste) le démon est un script python, une API HTTP, on doit le lancer manuellement (ou automatique si géré par Jeedom) mais 1 seule fois, et n’est pas planifié.

Il parlait des démon php et j’en parlais déjà dans le 2eme message:

et l’autre option reste des crons séparés si les transition ne sont pas si fréquente et qu’il ne faut faire une action qu’une fois toutes les lunes; autant de crons que de transitions donc:

faut pas aller faire un démon en python pour lancer des commandes jeedom non plus…
j’ai l’impression que vous cherchez à faire plus compliqué que besoin et que ce sujet tourne en rond :upside_down_face:

non c’est clair, loin de moi cette idée de shadock :smiley: mais j’imagine qu’on peut aussi faire un « vrai » démon en php même si ce n’est peut être pas nécessaire ici… Je suppose que la méthode static dont tu parle c’est demon_start / demon_stop et demon_info ?
Et le délai dont tu parles c’est le « heartbeat » (en minutes, sur l’interface, est-ce possible de descendre à la seconde?)

En fait, toute la question repose sur le cache: si c’est un vrai démon il garde le cache en mémoire… Mais si c’est juste un cron, ou assimilé, pas de cache en mémoire, il faut donc passer par un fichier ou un cache en bdd…

Non, c’est une méthode de ton choix.

Celles que tu cites sont les méthodes que le plug-in doit implémenter par convention pour que le core puisse gérer ton démon (quelque soit le langage utilisé pour celui-ci).

Concernant le cache je pense qu’il faut d’abord s’interroger sur l’utilité d’un cache ici: il est inutile de cacher une information qui est volatile. Si une donnée présente dans un cache n’est utilisée qu’une seule fois, comme cette transition alors elle n’a rien à y faire à mon avis.