Avoir la possibilité de déclencher une action lors du déclenchement d'une condition

Page : index.php?v=d&m=sunshutter&p=sunshutter&id=311&saveSuccessFull=1#commandtab
Jeedom_version : 3.3.52
Uname : Linux jeedom 3.14.79-94 #1 SMP PREEMPT Mon Nov 21 17:13:27 BRST 2016 aarch64 GNU/Linux


Message :
Ce serait un gros plus de pouvoir déclencher une action lors du déclenchement d’une condition.

Voici mon cas d’usage :
Mes volets s’ouvrent le matin quand la luminosité est suffisante mais pas avant une certaine heure (et d’autre conditions). Ça fonctionne bien, mais quand ils s’ouvre, je voudrais faire deux choses :
- mettre à jour une variable pour que cette condition (et pas tout le moteur de gestion) ne se déclenche plus (cette variable sera remise à jour lors de la fermeture du volet sur condition de luminosité).
- Envoyer un message pour me prévenir de l’ouverture.

Bonjour,

Pourquoi ne pas le faire directement par scénario ?

1 « J'aime »

Il n’y a justement pas la possibilité de déclencher un scénario lors du déclenchement d’une condition du plugin gestion des volets

Comment déclenches-tu l’ouverture des volets quand la luminosité est suffisante ?

Je n’utilise pas SunShutter mais tu ne pourrais pas déclencher un scénario sur l’ouverture des volets en fonction de l’état du volet ?

Bonjour @scanab

Je vois deux plugin pour la gestion des volets et tu écris:

Capture

Donc cela veux dire qu’il s’agit du plugin de mika-nt28 et non de celui de Jeedom SAS?

Peux tu confirmer le plugin utilisé?

C’est le officiel SunShutter :

1 « J'aime »

C’est le plugin officiel, et non, je ne veux pas déclencher de scénario sur l’ouverture des volets si ce n’est pas le plugin qui a déclenché cette ouverture. Et pour etre encore plus précis, même si c’est le plugin qui déclenche l’ouverture, ce n’est pas le même scenario que je veux appeler en fonction de la condition qui a fait ouvrir le volet…

Je comprends bien.

Ça m’intrigue je vais tester le plugin du coup :grin:

Le plugin est vraiment bien, très puissant une fois qu’on a compris le principe. Mais il manque juste cette possibilité de déclencher des actions.

Tu n’a plus qua prendre ton clavier et écrire puis proposer un PR …

J’ai une autre idée pour cette évolution : avoir un label pour chaque condition (et ligne de positionnement) et une commande info qui prendrait la valeur de ce label quand elle serait exécuté. Avec en plus une valeur « aucun » quand aucune condition n’est remplie, et il ne resterai qu’à faire un scénario avec un déclencheur sur cette nouvelle commande !
Quelqu’un de l’équipe jeedom pourrait donner la faisabilité de cette évolution ?

@Loic : si je veux proposer le code pour ma modif, ça marche comment ? Il n’y a pas de git il me semble, puisque c’est un plugin officiel payant…

Salut
Tu peux mettre les fonctions modifié la mais je te promet pas de l’intégrer avec les derniers événements on est frilleux la dessus

Les derniers événements ? Qu’est ce qui s’est passé ?

On a pas mal de personne proposant (ou même imposant) des modifications qui ont enormement de conséquence et quand il y a des soucis on a plus personne… Comme lors du passage en verification valide de tous les certificats, malgrès mes avertissements les personnes ont insisté je l’ai fait ben la je continue toujours a passer plusieurs heures par semaine dessus pour aider les gens chez qui ca bloque… Ce genre de truc nous entraine une derive enorme de notre roadmap ce qui est vraiment vraiment genant.

1 « J'aime »

Je comprends bien le soucis.
Bon écoute, je vais faire la modif en faisant le max pour que ça n’ai aucun impact sur l’existant.
Et je te poste les modifs ici après.
Et tu en fera ce que tu veux.

@Loic il y a 3 fichiers de modifiés :

sunshutter.php (lignes 268 / 333)

<?php
if (!isConnect('admin')) {
	throw new Exception('{{401 - Accès non autorisé}}');
}
$plugin = plugin::byId('sunshutter');
sendVarToJS('eqType', $plugin->getId());
$eqLogics = eqLogic::byType($plugin->getId());
?>

<div class="row row-overflow">
	<div class="col-xs-12 eqLogicThumbnailDisplay">
		<legend><i class="fas fa-cog"></i>  {{Gestion}}</legend>
		<div class="eqLogicThumbnailContainer">
			<div class="cursor eqLogicAction logoPrimary" data-action="add">
				<i class="fas fa-plus-circle"></i>
				<br>
				<span>{{Ajouter}}</span>
			</div>
			<div class="cursor eqLogicAction logoSecondary" data-action="gotoPluginConf">
				<i class="fas fa-wrench"></i>
				<br>
				<span>{{Configuration}}</span>
			</div>
			<div class="cursor logoSecondary" id="bt_healthsunshutter">
				<i class="fas fa-medkit"></i>
				<br/>
				<span>{{Santé}}</span>
			</div>
		</div>
		<legend><i class="fas fa-table"></i> {{Mes volets}}</legend>
		<input class="form-control" placeholder="{{Rechercher}}" id="in_searchEqlogic" />
		<div class="eqLogicThumbnailContainer">
			<?php
			foreach ($eqLogics as $eqLogic) {
				$opacity = ($eqLogic->getIsEnable()) ? '' : 'disableCard';
				echo '<div class="eqLogicDisplayCard cursor '.$opacity.'" data-eqLogic_id="' . $eqLogic->getId() . '">';
				echo '<img src="' . $plugin->getPathImgIcon() . '"/>';
				echo '<br>';
				echo '<span class="name">' . $eqLogic->getHumanName(true, true) . '</span>';
				echo '</div>';
			}
			?>
		</div>
	</div>
	
	<div class="col-xs-12 eqLogic" style="display: none;">
		<div class="input-group pull-right" style="display:inline-flex">
			<span class="input-group-btn">
				<a class="btn btn-default btn-sm eqLogicAction roundedLeft" data-action="configure"><i class="fa fa-cogs"></i> {{Configuration avancée}}</a><a class="btn btn-default btn-sm eqLogicAction" data-action="copy"><i class="fas fa-copy"></i> {{Dupliquer}}</a><a class="btn btn-sm btn-success eqLogicAction" data-action="save"><i class="fas fa-check-circle"></i> {{Sauvegarder}}</a><a class="btn btn-danger btn-sm eqLogicAction roundedRight" data-action="remove"><i class="fas fa-minus-circle"></i> {{Supprimer}}</a>
			</span>
		</div>
		<ul class="nav nav-tabs" role="tablist">
			<li role="presentation"><a href="#" class="eqLogicAction" aria-controls="home" role="tab" data-toggle="tab" data-action="returnToThumbnailDisplay"><i class="fa fa-arrow-circle-left"></i></a></li>
			<li role="presentation" class="active"><a href="#eqlogictab" aria-controls="home" role="tab" data-toggle="tab"><i class="fas fa-tachometer-alt"></i> {{Equipement}}</a></li>
			<li role="presentation"><a href="#configtab" aria-controls="home" role="tab" data-toggle="tab"><i class="fas fa-wrench"></i> {{Configuration}}</a></li>
			<li role="presentation"><a href="#conditiontab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fas fa-vial"></i> {{Condition}}</a></li>
			<li role="presentation"><a href="#positiontab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fas fa-drafting-compass"></i> {{Positionnement}}</a></li>
			<li role="presentation"><a href="#scheduletab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fas fa-calendar"></i> {{Planning}}</a></li>
			<li role="presentation"><a href="#commandtab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-list-alt"></i> {{Commandes}}</a></li>
		</ul>
		<div class="tab-content" style="height:calc(100% - 50px);overflow:auto;overflow-x: hidden;">
			<div role="tabpanel" class="tab-pane active" id="eqlogictab">
				<br/>
				<form class="form-horizontal">
					<fieldset>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Nom de l'équipement volet}}</label>
							<div class="col-sm-3">
								<input type="text" class="eqLogicAttr form-control" data-l1key="id" style="display : none;" />
								<input type="text" class="eqLogicAttr form-control" data-l1key="name" placeholder="{{Nom de l'équipement template}}"/>
							</div>
						</div>
						<div class="form-group">
							<label class="col-sm-3 control-label" >{{Objet parent}}</label>
							<div class="col-sm-3">
								<select id="sel_object" class="eqLogicAttr form-control" data-l1key="object_id">
									<option value="">{{Aucun}}</option>
									<?php
									foreach (jeeObject::all() as $object) {
										echo '<option value="' . $object->getId() . '">' . $object->getName() . '</option>';
									}
									?>
								</select>
							</div>
						</div>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Catégorie}}</label>
							<div class="col-sm-9">
								<?php
								foreach (jeedom::getConfiguration('eqLogic:category') as $key => $value) {
									echo '<label class="checkbox-inline">';
									echo '<input type="checkbox" class="eqLogicAttr" data-l1key="category" data-l2key="' . $key . '" />' . $value['name'];
									echo '</label>';
								}
								?>
							</div>
						</div>
						<div class="form-group">
							<label class="col-sm-3 control-label"></label>
							<div class="col-sm-9">
								<label class="checkbox-inline"><input type="checkbox" class="eqLogicAttr" data-l1key="isEnable" checked/>{{Activer}}</label>
								<label class="checkbox-inline"><input type="checkbox" class="eqLogicAttr" data-l1key="isVisible" checked/>{{Visible}}</label>
							</div>
						</div>
					</fieldset>
				</form>
			</div>
			<div role="tabpanel" class="tab-pane" id="commandtab">
				<a class="btn btn-success btn-sm cmdAction pull-right" data-action="add" style="margin-top:5px;"><i class="fa fa-plus-circle"></i> {{Commandes}}</a><br/><br/>
				<table id="table_cmd" class="table table-bordered table-condensed">
					<thead>
						<tr>
							<th>{{Nom}}</th><th>{{Options}}</th><th>{{Action}}</th>
						</tr>
					</thead>
					<tbody>
					</tbody>
				</table>
			</div>
			<div role="tabpanel" class="tab-pane" id="configtab">
				<br/>
				<fieldset>
					<form class="form-horizontal">
						<legend><i class="fas fa-cog"></i> {{Général}}</legend>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Vérification}}</label>
							<div class="col-sm-2">
								<select class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="cron::executeAction">
									<option value="*/5 * * * *">{{Toutes les 5 minutes}}</option>
									<option value="*/10 * * * *">{{Toutes les 10 minutes}}</option>
									<option value="*/15 * * * *">{{Toutes les 15 minutes}}</option>
									<option value="*/30 * * * *">{{Toutes les 30 minutes}}</option>
									<option value="*/45 * * * *">{{Toutes les 45 minutes}}</option>
									<option value="custom">{{Cron Personnalisé}}</option>
								</select>
							</div>
							<label class="col-sm-1 control-label customcron" style="display : none;">{{Cron Personnalisé}}</label>
							<div class="col-sm-2 customcron" style="display : none;">
								<input type="text" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="cron::custom"/>
							</div>
						</div>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Reprendre la main}}</label>
							<div class="col-sm-2">
								<select class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::nobackhand">
									<option value="0">{{Oui}}</option>
									<option value="1">{{Non}}</option>
									<option value="2">{{Oui avec délai}}</option>
								</select>
							</div>
							<label class="col-sm-1 control-label customDelay" style="display : none;">{{Au delà de (min)}}</label>
							<div class="col-sm-2 customDelay" style="display : none;">
								<input type="number" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::customDelay"/>
							</div>
						</div>
						<legend><i class="fas fa-globe"></i> {{Coordonnées}}</legend>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Utilisé les coordonnées jeedom}}</label>
							<div class="col-sm-2">
								<input type="checkbox" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="useJeedomLocalisation"/>
							</div>
						</div>
						<div class="form-group customLocalisation">
							<label class="col-sm-3 control-label">{{Latitude}}</label>
							<div class="col-sm-2">
								<input type="text" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="lat"/>
							</div>
						</div>
						<div class="form-group customLocalisation">
							<label class="col-sm-3 control-label">{{Longitude}}</label>
							<div class="col-sm-2">
								<input type="text" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="long"/>
							</div>
						</div>
						<div class="form-group customLocalisation">
							<label class="col-sm-3 control-label">{{Altitude}}</label>
							<div class="col-sm-2">
								<input type="text" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="alt"/>
							</div>
						</div>
						<legend><i class="icon jeedom-volet-ferme"></i> {{Volet}}</legend>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Etat volet}}</label>
							<div class="col-sm-3">
								<div class="input-group">
									<input type="text" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::state"/>
									<span class="input-group-btn">
										<a class="btn btn-default listCmdInfo roundedRight"><i class="fas fa-list-alt"></i></a>
									</span>
								</div>
							</div>
						</div>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Position volet}}</label>
							<div class="col-sm-3">
								<div class="input-group">
									<input type="text" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::position"/>
									<span class="input-group-btn">
										<a class="btn btn-default listCmdAction roundedRight"><i class="fas fa-list-alt"></i></a>
									</span>
								</div>
							</div>
						</div>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Rafraîchir position volet}}</label>
							<div class="col-sm-3">
								<div class="input-group">
									<input type="text" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::refreshPosition"/>
									<span class="input-group-btn">
										<a class="btn btn-default listCmdAction roundedRight"><i class="fas fa-list-alt"></i></a>
									</span>
								</div>
							</div>
						</div>
						<div class="form-group">
							<label class="col-sm-3 control-label">{{Temps maximum pour un déplacement}} <sub>s</sub></label>
							<div class="col-sm-3">
								<input type="number" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::moveDuration"/>
							</div>
						</div>
					</fieldset>
				</form>
				<br/>
				<div class="alert alert-info">{{Dans cet onglet, vous allez définir la configuration générale de la gestion :<br>
					- Vérification : tous les combiens le plugin vérifiera la position du soleil pour éventuellement changer la position du volet (un cron personnalisé est possible)<br>
					- Reprendre la main : si la position du volet se retrouve dans une position différente de celle voulue (changement manuel ou par exception), le système doit il reprendre la main automatiquement,
					ne pas la reprendre, ou après un délai (délai après detection de l'écart et mise en suspens).<br>
					- Coordonnées : la latitude, la longitude et l'altitude de votre volet (suncalc.org est très bien pour cela)
					-Volet : ici vous allez choisi la commande état et la commande positionnement de votre volet}}</div>
				</div>
				<div role="tabpanel" class="tab-pane" id="conditiontab">
					<br/>
					<fieldset>
						<form class="form-horizontal">
							<legend><i class="fas fa-cog"></i> {{Général}}</legend>
							<div class="form-group">
								<label class="col-sm-3 control-label">{{Condition pour action}}</label>
								<div class="col-sm-9">
									<div class="input-group">
										<textarea class="eqLogicAttr form-control" data-concat="1" data-l1key="configuration" data-l2key="condition::allowmove" style="height:75px"></textarea>
										<span class="input-group-addon hasBtn roundedRight">
											<a class="btn btn-default listCmdInfo roundedRight" ><i class="fas fa-list-alt"></i></a>
										</span>
									</div>
								</div>
							</div>
							<div class="form-group">
								<label class="col-sm-3 control-label">{{Un changement de mode annule les suspensions en cours}}</label>
								<div class="col-sm-9">
									<input type="checkbox" class="eqLogicAttr" data-l1key="configuration" data-l2key="condition::allowIgnoreSuspend"/>
								</div>
							</div>
							<div class="form-group">
								<label class="col-sm-3 control-label">{{Les actions immédiate sont systématique et prioritaire}}</label>
								<div class="col-sm-9">
									<input type="checkbox" class="eqLogicAttr" data-l1key="configuration" data-l2key="condition::systematic"/>
								</div>
							</div>
							<legend><i class="icon jeedom-volet-ferme"></i> {{Conditions}}
								<a class="btn btn-default btn-xs pull-right" style="margin-right:15px;" id="bt_addConditions"><i class="fas fa-plus"></i> {{Ajouter}}</a>
							</legend>
							<table class="table table-condensed" id="table_sunShutterConditions">
								<thead>
									<tr>
										<th>{{Position}}</th>
										<th>{{Mode}}</th>
										<th>{{Action Immédiate}}</th>
										<th>{{Suspendre}}</th>
                                      	<th>{{Label}}</th>
										<th style="width:50%">{{Condition}}</th>
										<th style="width:15%">{{Commentaire}}</th>
									</tr>
								</thead>
								<tbody>
									
								</tbody>
							</table>
						</fieldset>
					</form>
					<br/>
					<div class="alert alert-info">{{Dans cet onglet vous allez définir les conditions pour le moteur de gestion : <br>
						- Condition pour action : Condition nécessaire pour que le moteur fonctionne, si elle est remplie le moteur ne fera rien à part les conditions systématiques. Par défaut le champ est vide ce qui veut dire que le moteur est toujours actif.
						(exemples : fenêtre fermée, température > 25° etc...<br>
						- Conditions : cette partie permet de rajouter des règles d'exception <br>
						. Position : la position désirée (vide = ne rien faire)<br>
						. Action systématique et immédiate : si coché cette action sera systématique si la condition est remplie et immédiatement exécutée. Sinon elle est executée que si la condition globale est vérifiée et au moment de la vérification<br>
						. Suspendre : pour les conditions systematique (si cochée elle suspendra la gestion immédiatement et dans le cas d'une reprise sur délai repoussera le délai à chaque fois qu'elle est triggée) pour une condition normale elle suspendra lors de la vérification<br>
						. Condition : la condition en tant que tel (exemple : si j'ouvre la fenêtre, si j'active la climatisation etc...<br>
						. Commentaire : si vous voulez garder une trace de pourquoi vous avez mis cette rêgle c'est ici que ça se passe<br>
						Exemple de systématique : j'ouvre la fenêtre je veux que immédiatement le volet s'ouvre peut importe la condition sur action<br>
						Exemple de non systématique : si la luminosité est inférieur à 50lux alors je veux que le volet lors de la prochaine vérification s'ouvre à 80% indépendamment de la position du soleil que si le moteur à le droit de tourner (condition pour action)}}<br>
						NB : les lignes peuvent être déplacées en drag and drop (il est bon à noter que c'est la première condition valide qui sera prise en compte)</div>
					</div>
					<div role="tabpanel" class="tab-pane" id="positiontab">
						<br/>
						<fieldset>
							<form class="form-horizontal">
								<legend><i class="fas fa-cog"></i> {{Général}}</legend>
								<div class="form-group">
									<label class="col-sm-3 control-label">{{% ouverture}}</label>
									<div class="col-sm-1">
										<input type="number" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::openPosition"/>
									</div>
									<label class="col-sm-1 control-label">{{% fermeture}}</label>
									<div class="col-sm-1">
										<input type="number" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::closePosition"/>
									</div>
									<label class="col-sm-1 control-label">{{Action par défaut}}</label>
									<div class="col-sm-2">
										<select class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::defaultAction">
											<option value="none">{{Ne rien faire}}</option>
											<option value="open">{{Ouvrir}}</option>
											<option value="close">{{Fermer}}</option>
											<option value="custom">{{Position Personnalisée}}</option>
										</select>
									</div>
									<label class="col-sm-1 control-label customPosition" style="display : none;">{{Position personnalisée}}</label>
									<div class="col-sm-1 customPosition" style="display : none;">
										<input type="number" class="eqLogicAttr form-control" data-l1key="configuration" data-l2key="shutter::customPosition"/>
									</div>
								</div>
							</fieldset>
						</form>
						<legend><i class="icon jeedom-volet-ferme"></i> {{Positionnement}}
							<a class="btn btn-default btn-xs pull-right" style="margin-right:15px;" id="bt_addPosition"><i class="fas fa-plus"></i> {{Ajouter}}</a>
						</legend>
						<table class="table table-condensed" id="table_sunShutterPosition">
							<thead>
								<tr>
									<th style="width:150px">{{Azimuth}}</th>
									<th style="width:150px">{{Elevation}}</th>
									<th style="width:75px">{{Position}}</th>
                                    <th style="width:75px">{{Label}}</th>
									<th>{{Condition}}</th>
									<th style="width:15%">{{Commentaire}}</th>
								</tr>
							</thead>
							<tbody>
								
							</tbody>
						</table>
						<br/>
						<div class="alert alert-info">{{Dans cet onglet vous allez définir le coeur même du moteur de gestion en définissant les règles de base de postionnement en fonction de la position du soleil : <br>
							-%ouverture : % maximum d'ouverture du volet (généralement 99 ou 100) <br>
							-% fermeture : % minimum de fermeture du volet (généralement 0) <br>
							- action par défaut : que faire lorsqu'aucune règle du tableau positionnement ne correspond (ouvrir/fermer/position personnalisée) <br>
							- Tableau de positionnement : ici vous allez définir les règles de positionnement (si aucune exception n'est en cours) <br>
							. Azimuth : borne d'azimuth du soleil (suncalc.org permet de faire bouger le soleil et voir son azimuth par rapport à votre volet) <br>
							. Elevation : borne d'élévation du soleil (suncalc.org permet de faire bouger le soleil et voir son élévation par rapport à votre volet) <br>
							. Position : valeur de position à appliquer au volet si les conditions d'azimuth et d'élévation sont réunies <br>
							. Condition : permet de définir une condition supplémentaire (cela permet par exemple pour une même position du soleil d'avoir un comportement différent si température au dessus ou en dessous d'une valeur, ou toute autres conditions) <br>
							. Commentaire : si vous voulez garder une trace de pourquoi vouz mis cette rêgle c'est ici que ça se passe<br>
							NB : si pour une position du soleil donnée il y a plusieurs positionnement possible (toutes les conditions réunies) le système prendra la première (vous pouvez réorganiser les règles en drag and drop<br>
						}}</div>
					</div>
					<div class="tab-pane" id="scheduletab">
						<form class="form-horizontal">
							<fieldset>
								<br/>
								<div id="div_schedule"></div>
							</fieldset>
						</form>
						<br/>
						<div class="alert alert-info">{{Dans cet onglet vous pouvez voir s'il y a un planning dans le plugin agenda agissant sur votre gestion de volet.<br>
							Exemple : planifier une suspension et une reprise manuelle pendant les heures de sieste d'un enfant etc....}}</div>
						</div>
					</div>
				</div>
			</div>
			
			<?php include_file('desktop', 'sunshutter', 'js', 'sunshutter');?>
			<?php include_file('core', 'plugin.template', 'js');?>
			

sunshutter.class.php (lignes 260-269 / 459-460 / 480-481 / 535-538 / 557 / 572 / 577 / 594 / 607)

<?php

/* This file is part of Jeedom.
*
* Jeedom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jeedom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
*/

/* * ***************************Includes********************************* */
require_once __DIR__  . '/../../../../core/php/core.inc.php';
require_once dirname(__FILE__) . '/../../vendor/autoload.php';

class sunshutter extends eqLogic {
  /*     * *************************Attributs****************************** */
  
  
  
  /*     * ***********************Methode static*************************** */
  
  
  public static function cron5() {
    foreach (eqLogic::byType('sunshutter') as $sunshutter) {
      if(date('Gi') == 0){
        $sunshutter->save();
      }else{
        $sunshutter->updateData();
      }
    }
  }
  
  public static function cron() {
    foreach (eqLogic::byType('sunshutter', true) as $sunshutter) {
      if($sunshutter->getIsEnable() == 0){
        continue;
      }
      $forcedByDelay = 0;
      $stateHandlingCmd = $sunshutter->getCmd(null,'stateHandling');
      if($sunshutter->getConfiguration('shutter::nobackhand',0) == 2){
        if ($stateHandlingCmd->execCmd() == false) {
          if (!$sunshutter->getCache('manualSuspend')){
            $delay = $sunshutter->getConfiguration('shutter::customDelay',0);
            $since = $sunshutter->getCache('beginSuspend');
            $deltadelay = abs($since - time())/60;
            log::add('sunshutter','debug',$sunshutter->getHumanName().' - CRON CHECK DELAY : delay is ' . $delay . ' min - delta is ' . round($deltadelay,2)) . ' minutes';
            if ($deltadelay>=$delay){
              log::add('sunshutter','debug',$sunshutter->getHumanName().' - CRON CHECK DELAY Going back to normal delay is passed recalculating...');
              $sunshutter->checkAndUpdateCmd('stateHandling', true);
              $sunshutter->checkAndUpdateCmd('stateHandlingLabel', 'Aucun');
              $sunshutter->setCache('beginSuspend',0);
              $sunshutter->executeAction(true);
              $forcedByDelay = 1;
            }
          }
        } else {
          $lastPositionOrder = $sunshutter->getCache('lastPositionOrder',null);
          $currentPosition = $sunshutter->getCurrentPosition();
          if($currentPosition !== null  && $lastPositionOrder !== null){
            $amplitude = abs($sunshutter->getConfiguration('shutter::closePosition',0)-$sunshutter->getConfiguration('shutter::openPosition',100));
            $delta = abs($currentPosition-$lastPositionOrder);
            $ecart = ($delta/$amplitude)*100;
            log::add('sunshutter','debug',$sunshutter->getHumanName().' - [cron] - Gap since last order : ' . $ecart);
            if ($ecart>4 && ($sunshutter->getConfiguration('shutter::moveDuration',0) == 0 || (strtotime('now') - $sunshutter->getCache('lastPositionOrderTime',0)) > $sunshutter->getConfiguration('shutter::moveDuration'))){
              $sunshutter->checkAndUpdateCmd('stateHandling', false);
              $sunshutter->checkAndUpdateCmd('stateHandlingLabel', 'Auto');
              $sunshutter->setCache('beginSuspend',time());
              $sunshutter->setCache('manualSuspend',false);
              log::add('sunshutter','debug',$sunshutter->getHumanName().'- [cron] - Position != last order by far 4% i suspend');
            }
          }
        }
      }
      $cron = $sunshutter->getConfiguration('cron::executeAction');
      if ($cron == 'custom'){
        $cron = $sunshutter->getConfiguration('cron::custom');
      }
      if ($cron != '') {
        try {
          $c = new Cron\CronExpression(checkAndFixCron($cron), new Cron\FieldFactory);
          if ($c->isDue() && $forcedByDelay == 0) {
            $sunshutter->executeAction();
          }
        } catch (Exception $exc) {
          log::add('sunshutter', 'error', __('Expression cron non valide pour ', __FILE__) . $sunshutter->getHumanName() . ' : ' . $cron);
        }
      }
    }
  }
  
  public static function immediateAction($_options){
    $sunshutter = eqLogic::byId($_options['sunshutter_id']);
    if (!is_object($sunshutter)) {
      return;
    }
    if($sunshutter->getIsEnable() == 0){
      return;
    }
    if ($sunshutter->getCache('manualSuspend')){
      return;
    }
    log::add('sunshutter', 'debug', $sunshutter->getHumanName().' - Immediate Trigger from ' . print_r($_options,true));
    if ($sunshutter->getConfiguration('condition::systematic',0) == 1) {
      log::add('sunshutter', 'debug', $sunshutter->getHumanName().' - Immediate must be systematic');
      $sunshutter->systematicAction($_options['event_id']);
    } else {
      $sunshutter->executeAction();
    }
  }
  
  public static function getPanel($_type){
    $return = array('shutters'=>array());
    $numberShutters = 0;
    $sumposition =0;
    $numbersupendedAuto =0;
    $numbersupendedManual =0;
    foreach (eqLogic::byType('sunshutter', true) as $sunshutter) {
      $numberShutters += 1;
      $name = $sunshutter->getHumanName(true);
      $cmdHandling = $sunshutter->getCmd(null, 'stateHandling');
      $cmdHandlingLabel = $sunshutter->getCmd(null, 'stateHandlingLabel');
      $cmdAzimuth = $sunshutter->getCmd(null, 'sun_azimuth');
      $cmdElevation = $sunshutter->getCmd(null, 'sun_elevation');
      $cmdpause = $sunshutter->getCmd(null, 'suspendHandling');
      $cmdresume = $sunshutter->getCmd(null, 'resumeHandling');
      $cmdExecute = $sunshutter->getCmd(null, 'executeAction');
      $openvalue = $sunshutter->getConfiguration('shutter::openPosition',0);
      $closevalue = $sunshutter->getConfiguration('shutter::closePosition',0);
      $refreshId = str_replace('#','',$sunshutter->getConfiguration('shutter::refreshPosition',0));
      $currentPosition = null;
      $cmdstatehtml = '';
      $cmdhtml = '';
      $currentMode = 'Aucun';
      $modeCmd = $sunshutter->getCmd(null, 'mode');
      if (is_object($modeCmd)) {
        $currentMode = $modeCmd->execCmd();
      }
      if ( $currentMode == ''){
        $currentMode = 'Aucun';
      }
      $cmd = cmd::byId(str_replace('#','',$sunshutter->getConfiguration('shutter::state')));
      if (is_object($cmd)) {
        $currentPosition = $cmd->execCmd();
        $sumposition += $currentPosition;
        $cmdstatehtml = $cmd->toHtml($_type);
      }
      $cmdPosition = str_replace('#','',$sunshutter->getConfiguration('shutter::position'));
      $cmd = cmd::byId($cmdPosition);
      if (is_object($cmd)) {
        $cmdhtml = $cmd->toHtml($_type);
      }
      $handling =  $cmdHandling->execCmd();
      $handlingLabel = $cmdHandlingLabel->execCmd();
      if ($handling == false){
        if ($handlingLabel == 'Auto'){
          $numbersupendedAuto +=1;
        } else {
          $numbersupendedManual += 1;
        }
      }
      $cmdMode = '';
      foreach ($sunshutter->getCmd('action', 'mode',null,true) as $cmd) {
        $cmdMode .= $cmd->toHtml($_type);
      }
      $datas = array('name' => $name,
      'position' => $sunshutter->getCache('lastPositionOrder',null),
      'handling' => $handling,
      'pauseId' => $cmdpause->getId(),
      'resumeId' => $cmdresume->getId(),
      'executeId' => $cmdExecute->getId(),
      'state' => $currentPosition,
      'openvalue' => $openvalue,
      'closevalue' => $closevalue,
      'refreshId' => $refreshId,
      'positionId' => $cmdPosition,
      'cmdhtml' => $cmdhtml,
      'HandlingLabel' => $handlingLabel,
      'cmdstatehtml' => $cmdstatehtml,
      'elevation' => $cmdElevation->execCmd(),
      'azimuth' => $cmdAzimuth->execCmd(),
      'link' => $sunshutter->getLinkToConfiguration(),
      'mode' => $currentMode,
      'cmdmode' => $cmdMode,
      'suspendTime' => date('d-m H:i:s',$sunshutter->getCache('beginSuspend',time())),
    );
    $return['shutters'][]=$datas;
    $return['global']=array('moyPos' => ($numberShutters == 0) ? 'N/A' : round($sumposition/$numberShutters),'auto' => $numbersupendedAuto,'manual' => $numbersupendedManual,);
  }
  return $return;
}

/*     * *********************Méthodes d'instance************************* */

public function postSave() {
  $cmd = $this->getCmd(null, 'sun_elevation');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('sun_elevation');
    $cmd->setName(__('Elévation soleil', __FILE__));
  }
  $cmd->setType('info');
  $cmd->setSubType('numeric');
  $cmd->setUnite('°');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'sun_azimuth');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('sun_azimuth');
    $cmd->setName(__('Azimuth soleil', __FILE__));
  }
  $cmd->setType('info');
  $cmd->setSubType('numeric');
  $cmd->setUnite('°');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'stateHandling');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('stateHandling');
    $cmd->setName(__('Etat gestion', __FILE__));
  }
  $cmd->setType('info');
  $cmd->setSubType('binary');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'stateHandlingLabel');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('stateHandlingLabel');
    $cmd->setName(__('Suspension (Label)', __FILE__));
  }
  $cmd->setType('info');
  $cmd->setSubType('string');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'mode');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('mode');
    $cmd->setName(__('Mode', __FILE__));
  }
  $cmd->setType('info');
  $cmd->setSubType('string');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'label');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('label');
    $cmd->setName(__('Label', __FILE__));
  }
  $cmd->setType('info');
  $cmd->setSubType('string');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'lastposition');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('lastposition');
    $cmd->setName(__('Dernière position', __FILE__));
  }
  $cmd->setType('info');
  $cmd->setSubType('numeric');
  $cmd->setUnite('%');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'executeAction');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('executeAction');
    $cmd->setName(__('Forcer action', __FILE__));
  }
  $cmd->setType('action');
  $cmd->setSubType('other');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'suspendHandling');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('suspendHandling');
    $cmd->setName(__('Suspendre', __FILE__));
  }
  $cmd->setType('action');
  $cmd->setSubType('other');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'resumeHandling');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('resumeHandling');
    $cmd->setName(__('Reprendre', __FILE__));
  }
  $cmd->setType('action');
  $cmd->setSubType('other');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $cmd = $this->getCmd(null, 'refresh');
  if (!is_object($cmd)) {
    $cmd = new sunshutterCmd();
    $cmd->setLogicalId('refresh');
    $cmd->setName(__('Rafraichir', __FILE__));
  }
  $cmd->setType('action');
  $cmd->setSubType('other');
  $cmd->setEqLogic_id($this->getId());
  $cmd->save();
  
  $conditions = $this->getConfiguration('conditions','');
  if($conditions != '' ){
    $listener = listener::byClassAndFunction('sunshutter', 'immediateAction', array('sunshutter_id' => intval($this->getId())));
    if (!is_object($listener)) {
      $listener = new listener();
    }
    $listener->setClass('sunshutter');
    $listener->setFunction('immediateAction');
    $listener->setOption(array('sunshutter_id' => intval($this->getId())));
    $listener->emptyEvent();
    $nblistener = 0;
    foreach ($conditions as $condition) {
      if ($condition['conditions::immediate']) {
        preg_match_all("/#([0-9]*)#/", $condition['conditions::condition'], $matches);
        foreach ($matches[1] as $cmd_id) {
          $nblistener += 1;
          $listener->addEvent($cmd_id);
        }
      }
    }
    if ($nblistener > 0) {
      $listener->save();
    }
  } else {
    $listener = listener::byClassAndFunction('sunshutter', 'immediateAction', array('sunshutter_id' => intval($this->getId())));
    if (is_object($listener)) {
      $listener->remove();
    }
  }
  $this->updateData();
}

public function updateData(){
  $SD = new SolarData\SolarData();
  if($this->getConfiguration('useJeedomLocalisation') == 1){
    $SD->setObserverPosition(config::byKey('info::latitude'),config::byKey('info::longitude'),config::byKey('info::altitude'));
  }else{
    $SD->setObserverPosition($this->getConfiguration('lat'),$this->getConfiguration('long'),$this->getConfiguration('alt'));
  }
  $SD->setObserverDate(date('Y'), date('n'), date('j'));
  $SD->setObserverTime(date('G'), date('i'),date('s'));
  $SD->setDeltaTime(67);
  $SD->setObserverTimezone(date('Z') / 3600);
  $SunPosition = $SD->calculate();
  $this->checkAndUpdateCmd('sun_elevation', round($SunPosition->e0°,2));
  $this->checkAndUpdateCmd('sun_azimuth', round($SunPosition->Φ°,2));
  $handlingCmd = $this->getCmd(null, 'stateHandling');
  if ($handlingCmd->execCmd() === '') {
    $handlingCmd->event(true);
    $this->checkAndUpdateCmd('stateHandlingLabel', 'Aucun');
  }
}

public function getCurrentPosition(){
  if($this->getConfiguration('shutter::refreshPosition') != ''){
    $cmd = cmd::byId(str_replace('#','',$this->getConfiguration('shutter::refreshPosition')));
    if(is_object($cmd)){
      $cmd->execCmd();
    }
  }
  $currentPosition = null;
  $cmd = cmd::byId(str_replace('#','',$this->getConfiguration('shutter::state')));
  if(is_object($cmd)){
    $currentPosition = $cmd->execCmd();
  }
  return $currentPosition;
}

public function systematicAction($_cmdId){
  $mode = '';
  if(is_object($this->getCmd(null,'mode'))){
    $mode = strtolower($this->getCmd(null,'mode')->execCmd());
  }
  $conditions = $this->getConfiguration('conditions','');
  if($conditions != '' ){
    foreach ($conditions as $condition) {
      if ($condition['conditions::immediate'] && strpos($condition['conditions::condition'],'#'.$_cmdId.'#') !== false) {
        if(isset($condition['conditions::mode']) && $condition['conditions::mode'] != ''){
          if(!in_array($mode, explode(',',strtolower($condition['conditions::mode'])))){
            log::add('sunshutter','debug',$this->getHumanName().' - Mode not ok : ' . ' (' . $mode . ')');
            continue;
          }
        }
        if($condition['conditions::condition'] != '' && jeedom::evaluateExpression($condition['conditions::condition'])){
          if ($condition['conditions::position'] != '') {
            log::add('sunshutter','debug',$this->getHumanName().' - Immediate Condition Met : ' . $condition['conditions::condition'] . ' (' . $condition['conditions::position'] . '%)');
            $cmd = cmd::byId(str_replace('#','',$this->getConfiguration('shutter::position')));
            if(is_object($cmd)){
              $position = $condition['conditions::position'];
              if ($condition['conditions::suspend'] == 1) {
                log::add('sunshutter','debug',$this->getHumanName().' - Immediate Condition is a suspendable condition : suspend');
                $this->setCache('beginSuspend',time());
                $this->checkAndUpdateCmd('stateHandling', false);
                $cmdStateLabel = $this->getCmd(null, 'stateHandlingLabel');
                $stateLabel = $cmdStateLabel->execCmd();
                if ($stateLabel != 'Manuel'){
                  $this->checkAndUpdateCmd('stateHandlingLabel', 'Auto');
                }
              }
              $currentPosition = null;
              $currentPosition = $this->getCurrentPosition();
              $amplitude = abs($this->getConfiguration('shutter::closePosition',0)-$this->getConfiguration('shutter::openPosition',100));
              $delta = abs($position-$currentPosition);
              $ecart = ($delta/$amplitude)*100;
              log::add('sunshutter','debug',$this->getHumanName().' - Ecart avec la cible : ' . $ecart);
              if ($ecart<=4){
                log::add('sunshutter','debug',$this->getHumanName().' - Do nothing, position != new position by less than 4%');
              } else {
                log::add('sunshutter','debug',$this->getHumanName().' - Do action ' . $position);
                $cmd->execCmd(array('slider' => $position));
                $this->setCache('lastPositionOrder',$position);
                $this->setCache('lastPositionOrderTime',strtotime('now'));
                $this->checkAndUpdateCmd('lastposition', $position);
              }
            }
            break;
          }
        }
      }
    }
  }
}

public function calculPosition(){
  $sun_elevation = $this->getCmd(null, 'sun_elevation')->execCmd();
  $sun_azimuth = $this->getCmd(null, 'sun_azimuth')->execCmd();
  $positions = $this->getConfiguration('positions');
  foreach ($positions as $position) {
    if($sun_elevation > $position['sun::elevation::from'] && $sun_elevation <= $position['sun::elevation::to']){
      if($sun_azimuth > $position['sun::azimuth::from'] && $sun_azimuth <= $position['sun::azimuth::to']){
        if($position['position::allowmove'] == '' || jeedom::evaluateExpression($position['position::allowmove']) == true){
          log::add('sunshutter','debug',$this->getHumanName().' - Valid condition : ' . $position['position::allowmove'] . ' Elevation : ' . $position['sun::elevation::from'] . '°-' . $position['sun::elevation::to'] . '° Azimuth : ' . $position['sun::azimuth::from'] . '°-' . $position['sun::azimuth::to'] . '° ('  . $position['shutter::position'] . '%)');
          //return $position['shutter::position'];
          return array('position'=>$position['shutter::position'], 'label' => $position['position::label']);
        }
        log::add('sunshutter','debug',$this->getHumanName().' - Invalid condition : ' . $position['position::allowmove'] . ' Elevation : ' . $position['sun::elevation::from'] . '°-' . $position['sun::elevation::to'] . '° Azimuth : ' . $position['sun::azimuth::from'] . '°-' . $position['sun::azimuth::to'] . '° ('  . $position['shutter::position'] . '%)');
      }
    }
  }
  log::add('sunshutter','debug',$this->getHumanName().' - Do default action');
  $default = $this->getConfiguration('shutter::openPosition',0);
  if ($this->getConfiguration('shutter::defaultAction','open') == 'close'){
    log::add('sunshutter','debug',$this->getHumanName().' - Do default close action');
    $default = $this->getConfiguration('shutter::closePosition',0);
  }
  if ($this->getConfiguration('shutter::defaultAction','open') == 'custom'){
    log::add('sunshutter','debug',$this->getHumanName().' - Do default custom action');
    $default = $this->getConfiguration('shutter::customPosition',0);
  }
  if ($this->getConfiguration('shutter::defaultAction','close') == 'none'){
    log::add('sunshutter','debug',$this->getHumanName().' - Do default none');
    $default = $this->getCurrentPosition();
  }
  //return $default;
  return array('position'=>$default, 'label' => '');
}

public function executeAction($_force = false){
  $stateHandlingCmd = $this->getCmd(null,'stateHandling');
  if (!$_force && $stateHandlingCmd->execCmd() == false) {
    if ($this->getConfiguration('shutter::nobackhand',0) == 2){
      $delay = $this->getConfiguration('shutter::customDelay',0);
      $since = $this->getCache('beginSuspend');
      $deltadelay = abs($since - time())/60;
      log::add('sunshutter','debug',$this->getHumanName().' - Handling desactivated : delay is ' . $delay . ' min - delta is ' . round($deltadelay,2)) . ' minutes';
      if ($this->getCache('manualSuspend')){
        log::add('sunshutter','debug',$this->getHumanName().' - Do nothing, Handling desactivated manually');
        return;
      }
      if ($deltadelay>=$delay){
        log::add('sunshutter','debug',$this->getHumanName().' - Going back to normal delay is passed ');
        $this->checkAndUpdateCmd('stateHandling', true);
        $this->checkAndUpdateCmd('stateHandlingLabel', 'Aucun');
        $_force = true;
      } else {
        log::add('sunshutter','debug',$this->getHumanName().' - Do nothing, handling desactivated');
        return;
      }
    } else{
      log::add('sunshutter','debug',$this->getHumanName().' - Do nothing, handling desactivated');
      return;
    }
  }
  log::add('sunshutter','debug',$this->getHumanName().' - Start executeAction mode : '.$_force);
  $this->updateData();
  if($this->getConfiguration('condition::allowmove') != '' && jeedom::evaluateExpression($this->getConfiguration('condition::allowmove')) == false){
    log::add('sunshutter','debug',$this->getHumanName().' - Do nothing, false condition');
    return;
  }
  $currentPosition = $this->getCurrentPosition();
  if(!$_force && $this->getConfiguration('shutter::nobackhand',0) != 0){
    $lastPositionOrder = $this->getCache('lastPositionOrder',null);
    if($currentPosition !== null  && $lastPositionOrder !== null){
      $amplitude = abs($this->getConfiguration('shutter::closePosition',0)-$this->getConfiguration('shutter::openPosition',100));
      $delta = abs($currentPosition-$lastPositionOrder);
      $ecart = ($delta/$amplitude)*100;
      log::add('sunshutter','debug',$this->getHumanName().' - Gap since last order : ' . $ecart);
      if ($ecart > 4 && ($this->getConfiguration('shutter::moveDuration',0) == 0 || (strtotime('now') - $this->getCache('lastPositionOrderTime',0)) > $this->getConfiguration('shutter::moveDuration'))){
        $this->checkAndUpdateCmd('stateHandling', false);
        $this->checkAndUpdateCmd('stateHandlingLabel', 'Auto');
        $this->setCache('beginSuspend',time());
        $this->setCache('manualSuspend',false);
        log::add('sunshutter','debug',$this->getHumanName().' - Do nothing, position != last order by far 4% i suspend');
        return;
      }
    }
  }
  $position = null;
  //$position = $this->calculPosition();
  $positionArray = $this->calculPosition();
  $position = $positionArray['position'];
  $label = $positionArray['label'];
  $conditions = $this->getConfiguration('conditions','');
  $mode = '';
  if(is_object($this->getCmd(null,'mode'))){
    $mode = strtolower($this->getCmd(null,'mode')->execCmd());
  }
  if(is_array($conditions) && count($conditions) > 0){
    foreach ($conditions as $condition) {
      if ($condition['conditions::immediate'] && $this->getConfiguration('condition::systematic',0) == 1) {
        continue;
      }
      if(isset($condition['conditions::mode']) && $condition['conditions::mode'] != ''){
        if(!in_array($mode, explode(',',strtolower($condition['conditions::mode'])))){
          log::add('sunshutter','debug',$this->getHumanName().' - Mode not ok : ' . ' (' . $mode . ')');
          continue;
        }
        if ($condition['conditions::condition'] == '') {
          log::add('sunshutter','debug',$this->getHumanName().' - No Condition defined but valid mode ['.$mode.'] : ' . ' (' . $condition['conditions::position'] . ')');
          $position = $condition['conditions::position'];
          $label = $condition['conditions::label'];
          break;
        }
      }
      if($condition['conditions::condition'] != '' && jeedom::evaluateExpression($condition['conditions::condition'])){
        if (isset($condition['conditions::suspend'])  && $condition['conditions::suspend'] == 1) {
          log::add('sunshutter','debug',$this->getHumanName().' - Condition wants to suspend or extend suspension : ' . $condition['conditions::condition']);
          $this->checkAndUpdateCmd('stateHandling', false);
          $this->checkAndUpdateCmd('stateHandlingLabel', 'Auto');
          $this->setCache('beginSuspend',time());
          $this->setCache('manualSuspend',false);
        }
        if ($condition['conditions::position'] != '') {
          log::add('sunshutter','debug',$this->getHumanName().' - Condition Met : ' . $condition['conditions::condition'] . ' (' . $condition['conditions::position'] . '%)');
          $position = $condition['conditions::position'];
          $label = $condition['conditions::label'];
          break;
        } else {
          log::add('sunshutter','debug',$this->getHumanName().' - Condition Met : ' . $condition['conditions::condition'] . ' (Empty position do nothing)');
          $position = $currentPosition;
          $label = $condition['conditions::label'];
          break;
        }
      }
    }
  }
  log::add('sunshutter','debug',$this->getHumanName().' - Calcul position '.$position);
  log::add('sunshutter','debug',$this->getHumanName().' - Current position '.$currentPosition);
  if(($position !== null && $currentPosition !== null)){
    $amplitude = abs($this->getConfiguration('shutter::closePosition',0)-$this->getConfiguration('shutter::openPosition',100));
    $delta = abs($position-$currentPosition);
    $ecart = ($delta/$amplitude)*100;
    log::add('sunshutter','debug',$this->getHumanName().' - Gap with target : ' . $ecart);
    if ($ecart<=4){
      log::add('sunshutter','debug',$this->getHumanName().' - Do nothing, position != new position by less than 4%');
      $this->setCache('lastPositionOrder',$position);
      $this->checkAndUpdateCmd('lastposition', $position);
      $this->checkAndUpdateCmd('label', $label);
      return;
    }
  }
  if ($position !== null || $_force){
    log::add('sunshutter','debug',$this->getHumanName().' - Do action ' . $position);
    $cmd = cmd::byId(str_replace('#','',$this->getConfiguration('shutter::position')));
    if(is_object($cmd)){
      $cmd->execCmd(array('slider' => $position));
    }
    $this->setCache('lastPositionOrder',$position);
    $this->setCache('lastPositionOrderTime',strtotime('now'));
    $this->checkAndUpdateCmd('lastposition', $position);
    $this->checkAndUpdateCmd('label', $label);
  }
}

/*     * **********************Getteur Setteur*************************** */
}

class sunshutterCmd extends cmd {
  /*     * *************************Attributs****************************** */
  
  
  /*     * ***********************Methode static*************************** */
  
  
  /*     * *********************Methode d'instance************************* */
  
  
  public function execute($_options = array()) {
    $sunshutter = $this->getEqLogic();
    if($this->getLogicalId() == 'refresh'){
      $sunshutter->updateData();
    }
    
    if($this->getLogicalId() == 'executeAction'){
      $sunshutter->executeAction(true);
    }
    if($this->getLogicalId() == 'suspendHandling'){
      log::add('sunshutter','debug',$sunshutter->getHumanName().' - Suspend Handling');
      $sunshutter->checkAndUpdateCmd('stateHandling', false);
      $sunshutter->checkAndUpdateCmd('stateHandlingLabel', 'Manuel');
      $sunshutter->setCache('beginSuspend',time());
      $sunshutter->setCache('manualSuspend',true);
    }
    if($this->getLogicalId() == 'resumeHandling'){
      log::add('sunshutter','debug',$sunshutter->getHumanName().' - Resume Handling');
      $sunshutter->checkAndUpdateCmd('stateHandling', true);
      $sunshutter->checkAndUpdateCmd('stateHandlingLabel', 'Aucun');
      $sunshutter->setCache('beginSuspend',0);
      $sunshutter->setCache('manualSuspend',false);
      $sunshutter->executeAction(true);
    }
    if($this->getLogicalId() == 'mode'){
      log::add('sunshutter','debug',$sunshutter->getHumanName().' - Change shutter to mode : ' . $this->getName());
      $sunshutter->checkAndUpdateCmd('mode', $this->getName());
      if ($sunshutter->getConfiguration('condition::allowIgnoreSuspend',0) == 1) {
        $sunshutter->checkAndUpdateCmd('stateHandling', true);
        $sunshutter->checkAndUpdateCmd('stateHandlingLabel', 'Aucun');
        $sunshutter->setCache('beginSuspend',0);
        $sunshutter->setCache('manualSuspend',false);
        $sunshutter->executeAction(true);
      }
    }
  }
  
  /*     * **********************Getteur Setteur*************************** */
}

sunshutter.js (lignes 56-58 / 87-89)


/* This file is part of Jeedom.
*
* Jeedom is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jeedom is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
*/

$('.eqLogicAttr[data-l1key=configuration][data-l2key=useJeedomLocalisation]').off('change').on('change',function(){
  if($(this).value() == 1){
    $('.customLocalisation').hide()
  }else{
    $('.customLocalisation').show()
  }
})

$('#bt_healthsunshutter').on('click', function () {
  $('#md_modal').dialog({title: "{{Santé Gestion Volet}}"});
  $('#md_modal').load('index.php?v=d&plugin=sunshutter&modal=health').dialog('open');
});

$('#bt_addPosition').off('click').on('click',function(){
  addPosition({});
});

$('#bt_addConditions').off('click').on('click',function(){
  addConditions({});
});

function addPosition(_position){
  if(!_position['sun::elevation::from']){
    _position['sun::elevation::from'] = 0;
  }
  if(!_position['sun::elevation::to']){
    _position['sun::elevation::to'] = 90;
  }
  var tr = '<tr class="position">';
  tr += '<td>';
  tr += '<input class="form-control positionAttr" data-l1key="sun::azimuth::from" style="width:calc( 50% - 10px);display:inline-block;" /> {{à}} <input class="form-control positionAttr" data-l1key="sun::azimuth::to"  style="width:calc( 50% - 10px);display:inline-block;"/>';
  tr += '</td>';
  tr += '<td>';
  tr += '<input class="form-control positionAttr" data-l1key="sun::elevation::from" style="width:calc( 50% - 10px);display:inline-block;" /> {{à}} <input class="form-control positionAttr" data-l1key="sun::elevation::to" style="width:calc( 50% - 10px);display:inline-block;"/>';
  tr += '</td>';
  tr += '<td>';
  tr += '<input class="form-control positionAttr" data-l1key="shutter::position" style="width:calc( 100% - 20px);display:inline-block;" /> %';
  tr += '</td>';
  tr += '<td>';
  tr += '<input class="form-control positionAttr" data-l1key="position::label" />';
  tr += '</td>';
  tr += '<td>';
  tr += '<div class="input-group"><textarea class="positionAttr form-control" data-concat="1" data-l1key="position::allowmove" style="height:75px"></textarea><span class="input-group-addon hasBtn roundedRight"><a class="btn btn-default listCmdInfoPos roundedRight" ><i class="fas fa-list-alt"></i></a></span></div>';
  tr += '</td>';
  tr += '<td>';
  tr += '<textarea class="positionAttr form-control" data-concat="1" data-l1key="position::comment" style="width:100%;height:75px"></textarea>';
  tr += '</td>';
  tr += '<td>';
  tr += '<i class="fas fa-minus-circle cursor bt_removePosition"></i>';
  tr += '</td>';
  tr += '</tr>';
  $('#table_sunShutterPosition').find('tbody').append(tr);
  $('#table_sunShutterPosition').find('tbody tr').last().setValues(_position, '.positionAttr');
}

function addConditions(_condition){
  var tr = '<tr class="conditions">';
  tr += '<td>';
  tr += '<input class="form-control conditionsAttr" data-l1key="conditions::position" style="width:calc( 50% - 10px);display:inline-block;" /> %';
  tr += '</td>';
  tr += '<td>';
  tr += '<input class="form-control conditionsAttr" data-l1key="conditions::mode" />';
  tr += '</td>';
  tr += '<td>';
  tr += '<input type="checkbox" class="conditionsAttr" data-l1key="conditions::immediate" />';
  tr += '</td>';
  tr += '<td>';
  tr += '<input type="checkbox" class="conditionsAttr" data-l1key="conditions::suspend" />';
  tr += '</td>';
  tr += '<td>';
  tr += '<input class="form-control conditionsAttr" data-l1key="conditions::label" />';
  tr += '</td>';
  tr += '<td>';
  tr += '<div class="input-group"><textarea class="conditionsAttr form-control" data-concat="1" data-l1key="conditions::condition" style="height:75px"></textarea><span class="input-group-addon hasBtn roundedRight"><a class="btn btn-default listCmdInfoConditions roundedRight" ><i class="fas fa-list-alt"></i></a></span></div>';
  tr += '</td>';
  tr += '<td>';
  tr += '<textarea class="conditionsAttr form-control" data-concat="1" data-l1key="position::comment" style="width:100%;height:75px"></textarea>';
  tr += '</td>';
  tr += '<td>';
  tr += '<i class="fas fa-minus-circle cursor bt_removeCondition"></i>';
  tr += '</td>';
  tr += '</tr>';
  $('#table_sunShutterConditions').find('tbody').append(tr);
  $('#table_sunShutterConditions').find('tbody tr').last().setValues(_condition, '.conditionsAttr');
}

$('#table_sunShutterPosition').off('click','.bt_removePosition').on('click','.bt_removePosition',function(){
  $(this).closest('tr').remove();
});

$('#table_sunShutterConditions').off('click','.bt_removeCondition').on('click','.bt_removeCondition',function(){
  $(this).closest('tr').remove();
});

$(".eqLogic").on('click',".listCmdInfo",  function () {
  var el = $(this).closest('.form-group').find('.eqLogicAttr');
  jeedom.cmd.getSelectModal({cmd: {type: 'info'}}, function (result) {
    if (el.attr('data-concat') == 1) {
      el.atCaret('insert', result.human);
    } else {
      el.value(result.human);
    }
  });
});

$(".eqLogic").on('click',".listCmdInfoPos",  function () {
  var el = $(this).closest('.input-group').find('.positionAttr');
  jeedom.cmd.getSelectModal({cmd: {type: 'info'}}, function (result) {
    if (el.attr('data-concat') == 1) {
      el.atCaret('insert', result.human);
    } else {
      el.value(result.human);
    }
  });
});

$(".eqLogic").on('click',".listCmdInfoConditions",  function () {
  var el = $(this).closest('.input-group').find('.conditionsAttr');
  jeedom.cmd.getSelectModal({cmd: {type: 'info'}}, function (result) {
    if (el.attr('data-concat') == 1) {
      el.atCaret('insert', result.human);
    } else {
      el.value(result.human);
    }
  });
});


$('.eqLogicAttr[data-l1key=configuration][data-l2key="cron::executeAction"]').on('change', function () {
  if($(this).value() == 'custom'){
    $('.customcron').show();
  }else{
    $('.customcron').hide();
  }
});

$('.eqLogicAttr[data-l1key=configuration][data-l2key="shutter::defaultAction"]').on('change', function () {
  if($(this).value() == 'custom'){
    $('.customPosition').show();
  }else{
    $('.customPosition').hide();
  }
});

$('.eqLogicAttr[data-l1key=configuration][data-l2key="shutter::nobackhand"]').on('change', function () {
  if($(this).value() == '2'){
    $('.customDelay').show();
  }else{
    $('.customDelay').hide();
  }
});

$("body").on('click',".listCmdAction", function () {
  var el = $(this).closest('.form-group').find('.eqLogicAttr');
  jeedom.cmd.getSelectModal({cmd: {type: 'action'}}, function (result) {
    el.value(result.human);
  });
});


function saveEqLogic(_eqLogic) {
  if (!isset(_eqLogic.configuration)) {
    _eqLogic.configuration = {};
  }
  _eqLogic.configuration.positions = $('#table_sunShutterPosition').find('tbody tr').getValues('.positionAttr');
  _eqLogic.configuration.conditions = $('#table_sunShutterConditions').find('tbody tr').getValues('.conditionsAttr');
  return _eqLogic;
}

function printEqLogic(_eqLogic) {
  $('#table_sunShutterPosition').find('tbody').empty();
  if (isset(_eqLogic.configuration)) {
    if (isset(_eqLogic.configuration.positions)) {
      for (var i in _eqLogic.configuration.positions) {
        addPosition(_eqLogic.configuration.positions[i]);
      }
    }
  }
  $('#table_sunShutterConditions').find('tbody').empty();
  if (isset(_eqLogic.configuration)) {
    if (isset(_eqLogic.configuration.conditions)) {
      for (var i in _eqLogic.configuration.conditions) {
        addConditions(_eqLogic.configuration.conditions[i]);
      }
    }
  }
  printScheduling(_eqLogic);
}

function printScheduling(_eqLogic){
  $.ajax({
    type: 'POST',
    url: 'plugins/sunshutter/core/ajax/sunshutter.ajax.php',
    data: {
      action: 'getLinkCalendar',
      id: _eqLogic.id,
    },
    dataType: 'json',
    error: function (request, status, error) {
      handleAjaxError(request, status, error);
    },
    success: function (data) {
      if (data.state != 'ok') {
        $('#div_alert').showAlert({message: data.result, level: 'danger'});
        return;
      }
      $('#div_schedule').empty();
      console.log(data);
      if(data.result.length == 0){
        $('#div_schedule').append("<center><span style='color:#767676;font-size:1.2em;font-weight: bold;'>{{Vous n'avez encore aucune programmation. Veuillez cliquer <a href='index.php?v=d&m=calendar&p=calendar'>ici</a> pour programmer votre volet à l'aide du plugin agenda}}</span></center>");
      }else{
        var html = '<legend>{{Liste des programmations du plugin Agenda liées au Volet}}</legend>';
        for (var i in data.result) {
          var color = init(data.result[i].cmd_param.color, '#2980b9');
          if(data.result[i].cmd_param.transparent == 1){
            color = 'transparent';
          }
          html += '<span class="label label-info cursor" style="font-size:1.2em;background-color : ' + color + ';color : ' + init(data.result[i].cmd_param.text_color, 'black') + '">';
          html += '<a href="index.php?v=d&m=calendar&p=calendar&id='+data.result[i].eqLogic_id+'&event_id='+data.result[i].id+'" style="color : ' + init(data.result[i].cmd_param.text_color, 'black') + '">'
          if (data.result[i].cmd_param.eventName != '') {
            html += data.result[i].cmd_param.icon + ' ' + data.result[i].cmd_param.eventName;
          } else {
            html += data.result[i].cmd_param.icon + ' ' + data.result[i].cmd_param.name;
          }
          html += '</a></span>';
          html += ' ' + data.result[i].startDate.substr(11,5) + ' à ' + data.result[i].endDate.substr(11,5)+'<br\><br\>';
        }
        $('#div_schedule').empty().append(html);
      }
    }
  });
  
}


$("#table_cmd").sortable({axis: "y", cursor: "move", items: ".cmd", placeholder: "ui-state-highlight", tolerance: "intersect", forcePlaceholderSize: true});
$("#table_sunShutterPosition").sortable({axis: "y", cursor: "move", items: ".position", placeholder: "ui-state-highlight", tolerance: "intersect", forcePlaceholderSize: true});
$("#table_sunShutterConditions").sortable({axis: "y", cursor: "move", items: ".conditions", placeholder: "ui-state-highlight", tolerance: "intersect", forcePlaceholderSize: true});
/*
* Fonction pour l'ajout de commande, appellé automatiquement par plugin.template
*/
function addCmdToTable(_cmd) {
  if (!isset(_cmd)) {
    var _cmd = {configuration: {}};
  }
  if (!isset(_cmd.configuration)) {
    _cmd.configuration = {};
  }
  if(!_cmd.logicalId){
    _cmd.logicalId = 'mode';
    _cmd.type = 'action';
    _cmd.subType = 'other';
  }
  var tr = '<tr class="cmd" data-cmd_id="' + init(_cmd.id) + '">';
  tr += '<td>';
  tr += '<span class="cmdAttr" data-l1key="id" style="display:none;"></span>';
  tr += '<span class="cmdAttr" data-l1key="logicalId" style="display:none;"></span>';
  tr += '<input class="cmdAttr form-control input-sm" data-l1key="name" style="width : 140px;" placeholder="{{Nom}}">';
  tr += '</td>';
  tr += '<td>';
  tr += '<span class="type" type="' + init(_cmd.type) + '" style="display:none;">' + jeedom.cmd.availableType() + '</span>';
  tr += '<span class="subType" subType="' + init(_cmd.subType) + '" style="display:none;"></span>';
  tr += '<span><label class="checkbox-inline"><input type="checkbox" class="cmdAttr checkbox-inline" data-l1key="isVisible" checked/>{{Afficher}}</label></span> ';
  tr += '<span><label class="checkbox-inline"><input type="checkbox" class="cmdAttr checkbox-inline" data-l1key="isHistorized" checked/>{{Historiser}}</label></span> ';
  tr += '</td>';
  tr += '<td>';
  if (is_numeric(_cmd.id)) {
    tr += '<a class="btn btn-default btn-xs cmdAction" data-action="configure"><i class="fa fa-cogs"></i></a> ';
    tr += '<a class="btn btn-default btn-xs cmdAction" data-action="test"><i class="fa fa-rss"></i> {{Tester}}</a>';
  }
  tr += '<i class="fa fa-minus-circle pull-right cmdAction cursor" data-action="remove"></i>';
  tr += '</td>';
  tr += '</tr>';
  $('#table_cmd tbody').append(tr);
  $('#table_cmd tbody tr:last').setValues(_cmd, '.cmdAttr');
  if (isset(_cmd.type)) {
    $('#table_cmd tbody tr:last .cmdAttr[data-l1key=type]').value(init(_cmd.type));
  }
  jeedom.cmd.changeType($('#table_cmd tbody tr:last'), init(_cmd.subType));
}

Ok j’ai poussé en beta on verra bien les retours.