From e44fcbd5a4557e531ddfd83255a2d72fb3130d92 Mon Sep 17 00:00:00 2001 From: Richard Perez <4702185+ripleyXLR8@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:00:42 +0200 Subject: [PATCH 1/2] Create ash_GarageDoorController.class.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implémentation de la classe ash_GarageDoorController pour permettre le contrôle vocal (ouverture et fermeture) des portes de garage (GARAGE_DOOR) via Alexa. Historiquement, la catégorie GARAGE_DOOR ne remontait qu'une interface ContactSensor (état ouvert/fermé), empêchant toute action vocale sans contournement (comme l'utilisation du type Volet). Cette nouvelle classe respecte les spécifications de l'API Amazon Alexa pour les portes de garage en implémentant l'interface Alexa.ModeController (instance GarageDoor.Position) avec les sémantiques d'action Position.Up (Ouverture) et Position.Down (Fermeture). Détails techniques : Types génériques d'ouverture supportés : GB_OPEN, GARAGE_OPEN Types génériques de fermeture supportés : GB_CLOSE, GARAGE_CLOSE Types génériques d'état supportés : GARAGE_STATE, BARRIER_STATE L'interface de contrôle n'est ajoutée que si les deux commandes d'action (ouverture/fermeture) sont détectées. Prise en charge native de l'inversion binaire (invertBinary) sur la commande d'état. --- core/class/ash_GarageDoorController.class.php | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 core/class/ash_GarageDoorController.class.php diff --git a/core/class/ash_GarageDoorController.class.php b/core/class/ash_GarageDoorController.class.php new file mode 100644 index 0000000..1dbadf3 --- /dev/null +++ b/core/class/ash_GarageDoorController.class.php @@ -0,0 +1,129 @@ + 'AlexaInterface', + 'interface' => 'Alexa.ModeController', + 'instance' => 'GarageDoor.Position', + 'version' => '3', + 'properties' => array ( + 'supported' => array(array('name' => 'mode')), + 'proactivelyReported' => false, + 'retrievable' => false, + ), + 'capabilityResources' => array ( + 'friendlyNames' => array( + array('@type' => 'asset', 'value' => array ('assetId' => 'Alexa.Setting.Opening')) + ), + ), + 'configuration' => array ( + 'ordered' => false, + 'supportedModes' => array ( + array( + 'value' => 'Position.Up', + 'modeResources' => array('friendlyNames' => array(array('@type' => 'asset', 'value' => array('assetId' => 'Alexa.Value.Open')))) + ), + array( + 'value' => 'Position.Down', + 'modeResources' => array('friendlyNames' => array(array('@type' => 'asset', 'value' => array('assetId' => 'Alexa.Value.Close')))) + ) + ) + ), + 'semantics' => array ( + 'actionMappings' => array ( + array ( + '@type' => 'ActionsToDirective', + 'actions' => array ('Alexa.Actions.Open', 'Alexa.Actions.Raise'), + 'directive' => array ('name' => 'SetMode', 'payload' => array ('mode' => 'Position.Up')) + ), + array ( + '@type' => 'ActionsToDirective', + 'actions' => array ('Alexa.Actions.Close', 'Alexa.Actions.Lower'), + 'directive' => array ('name' => 'SetMode', 'payload' => array ('mode' => 'Position.Down')) + ), + ), + 'stateMappings' => array ( + array ('@type' => 'StatesToValue', 'states' => array ('Alexa.States.Open'), 'value' => 'Position.Up'), + array ('@type' => 'StatesToValue', 'states' => array ('Alexa.States.Closed'), 'value' => 'Position.Down'), + ), + ), + ); + + foreach ($_eqLogic->getCmd() as $cmd) { + if (in_array($cmd->getGeneric_type(), self::$_OPEN)) { + $return['cookie']['GarageDoorController_setOpen'] = $cmd->getId(); + } + if (in_array($cmd->getGeneric_type(), self::$_CLOSE)) { + $return['cookie']['GarageDoorController_setClose'] = $cmd->getId(); + } + if (in_array($cmd->getGeneric_type(), self::$_STATE)) { + $return['capabilities']['Alexa.GarageDoorController']['properties']['retrievable'] = true; + $return['cookie']['GarageDoorController_getState'] = $cmd->getId(); + } + } + + if (!isset($return['cookie']['GarageDoorController_setOpen']) || !isset($return['cookie']['GarageDoorController_setClose'])) { + return array(); + } + + return $return; + } + + public static function needGenericType(){ + return array( + __('Ouvrir',__FILE__) => self::$_OPEN, + __('Fermer',__FILE__) => self::$_CLOSE, + __('Etat',__FILE__) => self::$_STATE + ); + } + + public static function exec($_device, $_directive) { + if ($_directive['header']['name'] == 'SetMode') { + $mode = $_directive['payload']['mode']; + if ($mode == 'Position.Up' && isset($_directive['endpoint']['cookie']['GarageDoorController_setOpen'])) { + $cmd = cmd::byId($_directive['endpoint']['cookie']['GarageDoorController_setOpen']); + if (is_object($cmd)) $cmd->execCmd(); + } else if ($mode == 'Position.Down' && isset($_directive['endpoint']['cookie']['GarageDoorController_setClose'])) { + $cmd = cmd::byId($_directive['endpoint']['cookie']['GarageDoorController_setClose']); + if (is_object($cmd)) $cmd->execCmd(); + } + } + return self::getState($_device, $_directive); + } + + public static function getState($_device, $_directive) { + $return = array(); + $cmd = null; + if (isset($_directive['endpoint']['cookie']['GarageDoorController_getState'])) { + $cmd = cmd::byId($_directive['endpoint']['cookie']['GarageDoorController_getState']); + } + if (!is_object($cmd)) return $return; + + $value = $cmd->execCmd(); + if ($cmd->getSubtype() == 'binary' && $cmd->getDisplay('invertBinary') == 1) { + $value = ($value) ? 0 : 1; + } + + $modeValue = ($value) ? 'Position.Up' : 'Position.Down'; + + $return[] = array( + 'namespace' => 'Alexa.ModeController', + 'instance' => 'GarageDoor.Position', + 'name' => 'mode', + 'value' => $modeValue, + 'timeOfSample' => date('Y-m-d\TH:i:s\Z', strtotime($cmd->getValueDate())), + 'uncertaintyInMilliseconds' => 0, + ); + return array('properties' => $return); + } +} From 51b38d70f1dc95bf1b3f5ff7b180ec48c90193bf Mon Sep 17 00:00:00 2001 From: Richard Perez <4702185+ripleyXLR8@users.noreply.github.com> Date: Wed, 15 Apr 2026 11:05:53 +0200 Subject: [PATCH 2/2] =?UTF-8?q?Feat:=20D=C3=A9claration=20de=20la=20comp?= =?UTF-8?q?=C3=A9tence=20GarageDoorController=20pour=20les=20portes=20de?= =?UTF-8?q?=20garage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mise à jour de la classe principale ash pour intégrer la nouvelle gestion des portes de garage. Modifications apportées : Inclusion du nouveau fichier de classe ash_GarageDoorController.class.php. Mise à jour du tableau getSupportedType() : la catégorie GARAGE_DOOR implémente désormais la compétence GarageDoorController en complément du ContactSensor préexistant. Cette modification fait le lien indispensable pour que le plugin génère l'interface Alexa.ModeController (Position.Up/Down) lors de la transmission d'un équipement de type porte de garage à Amazon Alexa. --- core/class/ash.class.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/class/ash.class.php b/core/class/ash.class.php index 7749eaa..6fac14b 100644 --- a/core/class/ash.class.php +++ b/core/class/ash.class.php @@ -36,6 +36,7 @@ include_file('core', 'ash_PowerLevelController', 'class', 'ash'); include_file('core', 'ash_ToggleController', 'class', 'ash'); include_file('core', 'ash_LockController', 'class', 'ash'); +include_file('core', 'ash_GarageDoorController', 'class', 'ash'); class ash extends eqLogic { @@ -68,7 +69,7 @@ public static function getSupportedType() { 'DOOR' => array('name' => __('Porte', __FILE__), 'skills' => array('ContactSensor', 'LockController')), 'EXTERIOR_BLIND' => array('name' => __('Volet', __FILE__), 'skills' => array('RangeController')), 'FAN' => array('name' => __('Ventilateur', __FILE__), 'skills' => array('PowerController', 'RangeController')), - 'GARAGE_DOOR' => array('name' => __('Porte de garage', __FILE__), 'skills' => array('ContactSensor')), + 'GARAGE_DOOR' => array('name' => __('Porte de garage', __FILE__), 'skills' => array('ContactSensor', 'GarageDoorController')), 'MICROWAVE' => array('name' => __('Micro-onde', __FILE__), 'skills' => array('PowerController')), 'NETWORK_HARDWARE' => array('name' => __('Equipement réseaux', __FILE__), 'skills' => array('PowerController')), 'PRINTER' => array('name' => __('Imprimante', __FILE__), 'skills' => array('PowerController')),