diff --git a/3rdparty/JsonPath/JsonPath.php b/3rdparty/JsonPath/JsonPath.php new file mode 100644 index 0000000..9512fcf --- /dev/null +++ b/3rdparty/JsonPath/JsonPath.php @@ -0,0 +1,283 @@ +'); + + public function __construct() {} + + public function jsonPath($obj, $expr, $args = null) + { + if (is_object($obj)) { + throw new \Exception('You sent an object, not an array.'); + } + + $this->resultType = ($args ? $args['resultType'] : "VALUE"); + $x = $this->normalize($expr); + + $this->obj = $obj; + if ($expr && $obj && ($this->resultType == "VALUE" || $this->resultType == "PATH")) { + $this->trace(preg_replace("/^\\$;/", "", $x), $obj, "$"); + if (count($this->result)) { + return $this->result; + } + + return false; + } + } + + // normalize path expression + private function normalize($expression) + { + // Replaces filters by #0 #1... + $expression = preg_replace_callback( + array("/[\['](\??\(.*?\))[\]']/", "/\['(.*?)'\]/"), + array(&$this, "tempFilters"), + $expression + ); + + // ; separator between each elements + $expression = preg_replace( + array("/'?\.'?|\['?/", "/;;;|;;/", "/;$|'?\]|'$/"), + array(";", ";..;", ""), + $expression + ); + + // Restore filters + $expression = preg_replace_callback("/#([0-9]+)/", array(&$this, "restoreFilters"), $expression); + $this->result = array(); // result array was temporarily used as a buffer .. + return $expression; + } + + /** + * Pushs the filter into the list + * @param string $filter + * @return string + */ + private function tempFilters($filter) + { + $f = $filter[1]; + $elements = explode('\'', $f); + + // Hack to make "dot" works on filters + for ($i=0, $m=0; $i 0 && substr($elements[$i-1], 0, 1) == '\\') { + continue; + } + + $e = explode('.', $elements[$i]); + $str = ''; $first = true; + foreach ($e as $substr) { + if ($first) { + $str = $substr; + $first = false; + continue; + } + + $end = null; + if (false !== $pos = $this->strpos_array($substr, $this->keywords)) { + list($substr, $end) = array(substr($substr, 0, $pos), substr($substr, $pos, strlen($substr))); + } + + $str .= '[' . $substr . ']'; + if (null !== $end) { + $str .= $end; + } + } + $elements[$i] = $str; + } + + $m++; + } + + return "[#" . (array_push($this->result, implode('\'', $elements)) - 1) . "]"; + } + + /** + * Get a filter back + * @param string $filter + * @return mixed + */ + private function restoreFilters($filter) + { + return $this->result[$filter[1]]; + } + + /** + * Builds json path expression + * @param string $path + * @return string + */ + private function asPath($path) + { + $expr = explode(";", $path); + $fullPath = "$"; + for ($i = 1, $n = count($expr); $i < $n; $i++) { + $fullPath .= preg_match("/^[0-9*]+$/", $expr[$i]) ? ("[" . $expr[$i] . "]") : ("['" . $expr[$i] . "']"); + } + + return $fullPath; + } + + private function store($p, $v) + { + if ($p) { + array_push($this->result, ($this->resultType == "PATH" ? $this->asPath($p) : $v)); + } + + return !!$p; + } + + private function trace($expr, $val, $path) + { + if ($expr !== "") { + $x = explode(";", $expr); + $loc = array_shift($x); + $x = implode(";", $x); + + if (is_array($val) && array_key_exists($loc, $val)) { + $this->trace($x, $val[$loc], $path . ";" . $loc); + } + else if ($loc == "*") { + $this->walk($loc, $x, $val, $path, array(&$this, "_callback_03")); + } + else if ($loc === "..") { + $this->trace($x, $val, $path); + $this->walk($loc, $x, $val, $path, array(&$this, "_callback_04")); + } + else if (preg_match("/^\(.*?\)$/", $loc)) { // [(expr)] + $this->trace($this->evalx($loc, $val, substr($path, strrpos($path, ";") + 1)) . ";" . $x, $val, $path); + } + else if (preg_match("/^\?\(.*?\)$/", $loc)) { // [?(expr)] + $this->walk($loc, $x, $val, $path, array(&$this, "_callback_05")); + } + else if (preg_match("/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/", $loc)) { // [start:end:step] phyton slice syntax + $this->slice($loc, $x, $val, $path); + } + else if (preg_match("/,/", $loc)) { // [name1,name2,...] + for ($s = preg_split("/'?,'?/", $loc), $i = 0, $n = count($s); $i < $n; $i++) + $this->trace($s[$i] . ";" . $x, $val, $path); + } + } else { + $this->store($path, $val); + } + } + + private function _callback_03($m, $l, $x, $v, $p) + { + $this->trace($m . ";" . $x, $v, $p); + } + + + private function _callback_04($m, $l, $x, $v, $p) + { + if (is_array($v[$m])) { + $this->trace("..;" . $x, $v[$m], $p . ";" . $m); + } + } + + private function _callback_05($m, $l, $x, $v, $p) + { + if ($this->evalx(preg_replace("/^\?\((.*?)\)$/", "$1", $l), $v[$m])) { + $this->trace($m . ";" . $x, $v, $p); + } + } + + private function walk($loc, $expr, $val, $path, $f) + { + foreach ($val as $m => $v) { + call_user_func($f, $m, $loc, $expr, $val, $path); + } + } + + private function slice($loc, $expr, $v, $path) + { + $s = explode(":", preg_replace("/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/", "$1:$2:$3", $loc)); + $len = count($v); + $start = (int)$s[0] ? $s[0] : 0; + $end = (int)$s[1] ? $s[1] : $len; + $step = (int)$s[2] ? $s[2] : 1; + $start = ($start < 0) ? max(0, $start + $len) : min($len, $start); + $end = ($end < 0) ? max(0, $end + $len) : min($len, $end); + for ($i = $start; $i < $end; $i += $step) { + $this->trace($i . ";" . $expr, $v, $path); + } + } + + /** + * @param string $x filter + * @param array $v node + * + * @param string $vname + * @return string + */ + private function evalx($x, $v, $vname = null) + { + $name = ""; + $expr = preg_replace(array("/\\$/", "/@/"), array("\$this->obj", "\$v"), $x); + $expr = preg_replace("#\[([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\]#", "['$1']", $expr); + + $res = eval("\$name = $expr;"); + + if ($res === false) { + print("(jsonPath) SyntaxError: " . $expr); + } else { + return $name; + } + } + + private function toObject($array) + { + //$o = (object)''; + $o = new \stdClass(); + + foreach ($array as $key => $value) { + if (is_array($value)) { + $value = $this->toObject($value); + } + + $o->$key = $value; + } + + return $o; + } + + /** + * Search one of the given needs in the array + * @param string $haystack + * @param array $needles + * @return bool|string + */ + private function strpos_array($haystack, array $needles) + { + $closer = 10000; + foreach($needles as $needle) { + if (false !== $pos = strpos($haystack, $needle)) { + if ($pos < $closer) { + $closer = $pos; + } + } + } + + return 10000 === $closer ? false : $closer; + } +} + +?> diff --git a/3rdparty/JsonPath/JsonStore.php b/3rdparty/JsonPath/JsonStore.php new file mode 100644 index 0000000..4773483 --- /dev/null +++ b/3rdparty/JsonPath/JsonStore.php @@ -0,0 +1,226 @@ +jsonPath = new JsonPath(); + $this->setData($data); + } + + /** + * Sets JsonStore's manipulated data + * @param string|array|\stdClass $data + */ + public function setData($data) + { + $this->data = $data; + + if (is_string($this->data)) { + $this->data = json_decode($this->data, true); + } else if (is_object($data)) { + $this->data = json_decode(json_encode($this->data), true); + } else if (!is_array($data)) { + throw new \InvalidArgumentException(sprintf('Invalid data type in JsonStore. Expected object, array or string, got %s', gettype($data))); + } + } + + /** + * JsonEncoded version of the object + * @return string + */ + public function toString() + { + return json_encode($this->data); + } + + /** + * Returns the given json string to object + * @return \stdClass + */ + public function toObject() + { + return json_decode(json_encode($this->data)); + } + + /** + * Returns the given json string to array + * @return array + */ + public function toArray() + { + return $this->data; + } + + /** + * Gets elements matching the given JsonPath expression + * @param string $expr JsonPath expression + * @param bool $unique Gets unique results or not + * @return array + */ + public function get($expr, $unique = false) + { + if ((($exprs = $this->normalizedFirst($expr)) !== false) && + (is_array($exprs) || $exprs instanceof \Traversable) + ) { + $values = array(); + + foreach ($exprs as $expr) { + $o =& $this->data; + $keys = preg_split( + "/([\"'])?\]\[([\"'])?/", + preg_replace(array("/^\\$\[[\"']?/", "/[\"']?\]$/"), "", $expr) + ); + + for ($i = 0; $i < count($keys); $i++) { + $o =& $o[$keys[$i]]; + } + + $values[] = & $o; + } + + if (true === $unique) { + if (!empty($values) && is_array($values[0])) { + array_walk($values, function(&$value) { + $value = json_encode($value); + }); + + $values = array_unique($values); + array_walk($values, function(&$value) { + $value = json_decode($value, true); + }); + + return array_values($values); + } + + return array_unique($values); + } + + return $values; + } + + return self::$emptyArray; + } + + /** + * Sets the value for all elements matching the given JsonPath expression + * @param string $expr JsonPath expression + * @param mixed $value Value to set + * @return bool returns true if success + */ + function set($expr, $value) + { + $get = $this->get($expr); + if ($res =& $get) { + foreach ($res as &$r) { + $r = $value; + } + + return true; + } + + return false; + } + + /** + * Adds one or more elements matching the given json path expression + * @param string $parentexpr JsonPath expression to the parent + * @param mixed $value Value to add + * @param string $name Key name + * @return bool returns true if success + */ + public function add($parentexpr, $value, $name = "") + { + $get = $this->get($parentexpr); + if ($parents =& $get) { + + foreach ($parents as &$parent) { + $parent = is_array($parent) ? $parent : array(); + + if ($name != "") { + $parent[$name] = $value; + } else { + $parent[] = $value; + } + } + + return true; + } + + return false; + } + + /** + * Removes all elements matching the given jsonpath expression + * @param string $expr JsonPath expression + * @return bool returns true if success + */ + public function remove($expr) + { + if ((($exprs = $this->normalizedFirst($expr)) !== false) && + (is_array($exprs) || $exprs instanceof \Traversable) + ) { + foreach ($exprs as &$expr) { + $o =& $this->data; + $keys = preg_split( + "/([\"'])?\]\[([\"'])?/", + preg_replace(array("/^\\$\[[\"']?/", "/[\"']?\]$/"), "", $expr) + ); + for ($i = 0; $i < count($keys) - 1; $i++) { + $o =& $o[$keys[$i]]; + } + + unset($o[$keys[$i]]); + } + + return true; + } + + return false; + } + + private function normalizedFirst($expr) + { + if ($expr == "") { + return false; + } else { + if (preg_match("/^\$(\[([0-9*]+|'[-a-zA-Z0-9_ ]+')\])*$/", $expr)) { + print("normalized: " . $expr); + + return $expr; + } else { + $res = $this->jsonPath->jsonPath($this->data, $expr, array("resultType" => "PATH")); + + return $res; + } + } + } +} + +?> diff --git a/README.md b/README.md index 7b8afce..4a2d9d8 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,24 @@ ## plugin-googlecast -Plugin for Open Source domotic solution Jeedom. +Plugin for Jeedom Open Source domotic solution. -Remotely command Google Cast devices. +Remotely control Google Cast devices directly from Jeedom. +
![Logo Jeedom](docs/assets/images/logo.png "Logo Jeedom") ![Logo plugin](docs/images/logoplugin.png "Logo plugin") -Plugin [user documentation (fr)](docs/fr_FR/index.md). +
+- Plugin [user documentation - french](docs/fr_FR/index.md) +- Plugin [user documentation - english](docs/en_US/index.md) + +
+##### Base projects + +- pychromecast (balloob/pychromecast) +- pydub (jiaaro/pydub) +- plexapi (pkkid/python-plexapi) +- Jeedom core (jeedom/core) +- Jeedom plugins (gcast, blea...) +- python gtts diff --git a/core/class/googlecast.class.php b/core/class/googlecast.class.php index 7fad1a9..6c04ba9 100644 --- a/core/class/googlecast.class.php +++ b/core/class/googlecast.class.php @@ -605,6 +605,20 @@ public function postSave() { $cmd->setEqLogic_id($this->getId()); $cmd->save(); + $logid = "cmd=notif|value=bigben1.mp3|vol=100"; + $cmd = $this->getCmd(null, $logid); + if (!is_object($cmd)) { + $cmd = new googlecastCmd(); + $cmd->setLogicalId($logid); + $cmd->setName(__('Notif', __FILE__)); + $cmd->setIsVisible(1); + $cmd->setOrder($order++); + } + $cmd->setType('action'); + $cmd->setSubType('other'); + $cmd->setEqLogic_id($this->getId()); + $cmd->save(); + $cmd = $this->getCmd(null, 'cmd=getconfig|data=opencast_pin_code'); if (!is_object($cmd)) { $cmd = new googlecastCmd(); @@ -1086,13 +1100,22 @@ public function getInfoHttp($cmdLogicalId, $showError=false, $errorRet=false, $f return $errorRet; } + // for test + //$httpret = '{"alarm":[{"date_pattern":{"day":15,"month":1,"year":2018},"time_pattern":{"hour":6,"minute":50,"second":0},"fire_time":1515995400000.0,"id":"alarm/xxx","status":1}]}'; + //$arrayret = json_decode($httpret, true); + if (isset($data['data'])) { $dataItemList = explode (',',$data['data']); $retArray = array(); foreach ($dataItemList as $dataItem) { - $pathList = explode ('/',$dataItem); - array_push($retArray, $this->recursePath($arrayret, $pathList, $errorRet)); + if ( strpos($dataItem, '$') === 0 ) { // jsonpath type + array_push($retArray, googlecast_utils::getJsonPathResult($httpret, $dataItem)); + } + else { // legacy using '/' as separator + $pathList = explode ('/',$dataItem); + array_push($retArray, $this->recursePath($arrayret, $pathList, $errorRet)); + } } if (isset($data['format'])) { $format = $data['format']; @@ -1101,11 +1124,9 @@ public function getInfoHttp($cmdLogicalId, $showError=false, $errorRet=false, $f $ret = json_encode($retArray); } elseif ( $format=='string' ) { - $ret = ''; - foreach ($retArray as $retElem) { - $ret .= $sep . trim($retElem); - } - $ret = substr($ret, 1); + $flattenAr = $this->array_flatten($retArray); + //log::add('googlecast','debug',"getInfoHttp : Debug format string : " . print_r($flattenAr, true)); + $ret = join($sep, $flattenAr); } else { $ret = ''; @@ -1154,6 +1175,7 @@ private function array_flatten($array) { return $return; } + // old version using '/' as path sepearator private function recursePath($array, $pathList, $errorRet) { $pathItem = array_shift($pathList); if ( is_null($pathItem) ) { @@ -1261,8 +1283,17 @@ public function execute($_options = null) { if ($listCmd == "customcmd") { $listCmd = trim($_options['message']); } + # special case of 'action' command with subtype 'list' starting with 'cmdlist_' + if ( strpos($listCmd, 'cmdlist_')===0 ) { + $listCmd = str_replace('^', '|', trim($_options['select'])); // replace ^ by | + if ($listCmd=='') { // case of default value ('none' selected) + $listCmd='quit_app'; + } + } + // if this is a command 'info' to retrieve google cast device configuration using http if ($this->getType() != 'action') { + // command must contains string 'cmd=getconfig' if (stristr($listCmd, 'cmd=getconfig')!=false) { $eqLogic->getInfoHttpSimple($listCmd, $this->getLogicalId()); log::add('googlecast','debug',"Envoi d'une commande GoogleCast API http depuis Jeedom"); @@ -1272,17 +1303,21 @@ public function execute($_options = null) { } } + // 'refresh' type command if ($listCmd == "refreshconfig" || $listCmd == "refresh") { + // both refresh commands require refreshing conifguration command refresh foreach ($eqLogic->getCmd('info') as $cmd) { $logicalId = googlecast_utils::getCmdTranslation($cmd->getLogicalId()); if (stristr($logicalId, 'cmd=getconfig')!=false) { $eqLogic->getInfoHttpSimple($logicalId, $cmd->getLogicalId()); } } + if ($listCmd == "refreshconfig") { - return; + return; // do not need to go further in that case } } + // if this is a command 'action' to modify google cast device configuration using http if (stristr($listCmd, 'cmd=setconfig')!=false) { log::add('googlecast','debug',"Envoi d'une commande GoogleCast API http depuis Jeedom (set)"); $eqLogic->setInfoHttpSimple($listCmd, null); @@ -1290,14 +1325,14 @@ public function execute($_options = null) { } $datalist=array(); - $cmdgroups = explode('$$', $listCmd); + $cmdgroups = explode('$$', $listCmd); // split multiple commands (sequences) foreach ($cmdgroups as $listCmd) { $data = array(); - $values = explode('|', $listCmd); + $values = explode('|', $listCmd); // split commands foreach ($values as $value) { $value = explode('=', $value); - if (count($value) == 2) { - switch ($this->getSubType()) { + if (count($value) == 2) { // X=Y + switch ($this->getSubType()) { // manage placeholder replacement case 'slider': $data[trim($value[0])] = trim(str_replace('#slider#', $_options['slider'], $value[1])); break; @@ -1309,14 +1344,18 @@ public function execute($_options = null) { break; case 'message': $data[trim($value[0])] = trim(str_replace('#message#', $_options['message'], $value[1])); - $data[trim($value[0])] = trim(str_replace('#title#', $_options['title'], $data[trim($value[0])])); - $data[trim($value[0])] = trim(str_replace('#volume#', $_options['volume'], $data[trim($value[0])])); + if ( isset($_options['title']) ) { + $data[trim($value[0])] = trim(str_replace('#title#', $_options['title'], $data[trim($value[0])])); + } + if ( isset($_options['volume']) ) { + $data[trim($value[0])] = trim(str_replace('#volume#', $_options['volume'], $data[trim($value[0])])); + } break; default: $data[trim($value[0])] = trim($value[1]); } } - elseif (count($value) == 1) { + elseif (count($value) == 1) { // // X only, then assume this is a command $data['cmd'] = trim($value[0]); switch ($this->getSubType()) { case 'slider': @@ -1331,12 +1370,13 @@ public function execute($_options = null) { } } } - if (count($data) == 0) { + if (count($data) == 0) { // something is wrong because no value return; } - array_push($datalist, $data); + array_push($datalist, $data); // push in the sequence array (even if only one command) } + // generate the query to be sent $fulldata = array( 'apikey' => jeedom::getApiKey('googlecast'), 'cmd' => 'action', @@ -1348,7 +1388,6 @@ public function execute($_options = null) { ); log::add('googlecast','debug',"Envoi d'une commande depuis Jeedom"); googlecast::socket_connection( json_encode($fulldata) ); - } diff --git a/core/class/googlecast_utils.inc.php b/core/class/googlecast_utils.inc.php index 2206345..41a137c 100644 --- a/core/class/googlecast_utils.inc.php +++ b/core/class/googlecast_utils.inc.php @@ -1,4 +1,7 @@ get($path); + } + public static function getFncResult($data, $fnc) { if (is_null($fnc)) { return $data; diff --git a/core/i18n/en_US.json b/core/i18n/en_US.json new file mode 100644 index 0000000..c9b37dc --- /dev/null +++ b/core/i18n/en_US.json @@ -0,0 +1,109 @@ +{ + "plugins\/googlecast\/desktop\/php\/googlecast.php": { + "Rechercher": "Search", + "Gestion": "Manage", + "Lancer Scan": "Run Scan", + "Configuration": "Configuration", + "Santé": "Health", + "Mes équipements GoogleCast": "My GoogleCast equipments", + "Sauvegarder": "Save", + "Supprimer": "Delete", + "Configuration avancée": "Advanced configuration", + "Dupliquer": "Duplicate", + "Equipement": "Equipment", + "Commandes": "Commands", + "Nom de l'équipement": "Equipment name", + "Objet parent": "Parent objet", + "Aucun": "None", + "Catégorie": "Category", + "Activer": "Enable", + "Visible": "Disable", + "UUID": "UUID", + "Nom diffusé": "Broadcast name", + "Modèle": "Model", + "Constructeur": "Manufacturer", + "Type": "Type", + "Ignorer contrôle CEC": "Ignore CEC control", + "Si le statut 'Occupé' ne remonte pas bien": "Is status doesn't work well", + "Ajouter une commande": "Add a new command", + "Nom": "Name", + "Liste de commandes": "Command list", + "Paramètres": "Parameters", + "Options": "Options", + "IP": "IP" + }, + "plugins\/googlecast\/core\/class\/googlecast.class.php": { + "Statut Name": "Status Name" + }, + "plugins\/googlecast\/core\/class\/googlecast_utils.inc.php": { + "Alarme 1": "Alarm 1", + "Statut Alarme 1": "Statut Alarm 1", + "Aujourd'hui": "Today", + "Demain": "Tomorrow" + }, + "plugins\/googlecast\/desktop\/modal\/googlecast.health.php": { + "Nom": "Name", + "Nom diffusé": "Broadcast name", + "UUID": "UUID", + "Modèle": "Model", + "Type": "Type", + "Online": "Online", + "Occupé": "Busy", + "Dernière com": "Last com", + "Date création": "Creation date", + "Offline": "Offline", + "Non": "No", + "Rafraichir": "Refresh" + }, + "plugins\/googlecast\/desktop\/js\/googlecast.js": { + "Santé GoogleCast": "Health GoogleCast", + "Arrêter le scan": "Stop scan", + "Mode scan en cours pendant 1 minute... (Cliquer sur arrêter pour stopper avant)": "Scan pending for one minute... (Clic to stop before)", + "Lancer Scan": "Run Scan", + "Un GoogleCast vient d\\'être inclu\/exclu. Veuillez réactualiser la page": "A GoogleCast has just been included. Refresh the page", + "Commande": "Command", + "Afficher": "Show", + "Historiser": "Historize", + "Inverser": "Inverse", + "Valeur retour d\\'état": "State return value", + "Durée avant retour d\\'état (min)": "Duration before state return (min)" + }, + "plugins\/googlecast\/plugin_info\/configuration.php": { + "Démon": "Deamon", + "Port socket interne (modification dangereuse)": "Socket port intern (hasardous modification)", + "55012": "55012", + "Configuration spéciale (eg: Docker, VM)": "Special configuration (eg: Docker, VM)", + "Fréquence de rafraissement": "Refresh frequency", + "Rapide": "High", + "Normal (recommandé)": "Normal (recommanded)", + "Basse": "Low", + "Très basse": "Very low", + "TTS - Text To Speech": "TTS - Text To Speech", + "Utiliser l'adresse Jeedom externe": "Use external Jeedom address", + "Langue par défaut": "Défaut language", + "Français": "French", + "Anglais": "English", + "Espagnol": "Spanish", + "Allemand": "Dutsh", + "Italien": "Italien", + "Moteur par défaut": "Default engine", + "PicoTTS (local)": "PicoTTS (local)", + "Google Translate API (internet requis)": "Google Translate API (internet required)", + "Google Speech API (clé api & internet requis)": "Google Speech API (API key & internet required)", + "Google Speech API - dev (clé api & internet requis)": "Google Speech API - dev (API key & internet required)", + "Key Google Speech API": "Key Google Speech API", + "Vitesse de parole": "Speech speed", + "Très lent": "Very slow", + "Lent": "slow", + "Normal": "Normal", + "Très rapide": "Very quick", + "Encore plus rapide": "Still more rapid", + "Ne pas utiliser le cache (déconseillé)": "Don't use cache (not recommended)", + "Nettoyer tout le cache": "Clean all cache", + "Suppression automatique du cache de plus de X jours": "Remove cache older than X days", + "Nombre en jour": "Number of days", + "Notifications": "Notifications", + "Désactiver notif pour nouveaux GoogleCast": "Disable notification for new GoogleCast", + "Réussie": "Successed" + } +} diff --git a/core/i18n/fr_FR.json b/core/i18n/fr_FR.json index 120816f..e818f22 100644 --- a/core/i18n/fr_FR.json +++ b/core/i18n/fr_FR.json @@ -27,7 +27,7 @@ "Si le statut 'Occupé' ne remonte pas bien": "Si le statut 'Occupé' ne remonte pas bien", "Ajouter une commande": "Ajouter une commande", "Nom": "Nom", - "Logical ID (info) ou Commande brute (action)": "Logical ID (info) ou Commande brute (action)", + "Liste de commandes": "Liste de commandes", "Paramètres": "Paramètres", "Options": "Options", "IP": "IP" diff --git a/core/php/googlecast.iftt.php b/core/php/googlecast.iftt.php deleted file mode 100644 index 6abf27d..0000000 --- a/core/php/googlecast.iftt.php +++ /dev/null @@ -1,77 +0,0 @@ -. - */ - -error_reporting(E_ALL); -ini_set('display_errors', 'On'); -require_once dirname(__FILE__) . '/../../../../core/php/core.inc.php'; - -try { - - $querystr = parse_url(urldecode($_SERVER["REQUEST_URI"])); - parse_str($querystr['query'], $queryparams); - - $apikey=null; - if ( isset($queryparams['apikey']) ) { - $apikey=$queryparams['apikey']; - } - - if ($apikey!==null && !jeedom::apiAccess($apikey, 'googlecast')) { - echo __('Clef API non valide, vous n\'êtes pas autorisé à effectuer cette action (gcast)', __FILE__); - die(); - } - - $content = file_get_contents('php://input'); - $json = json_decode($content, true); - - $googlecast = googlecast::byLogicalId($uuid); - if (!is_object($googlecast)) { - echo json_encode(array('text' => __('UUID inconnu : ', __FILE__) . $uuid)); - die(); - } - - $query = ""; - if ( isset($queryparams['query']) ) { - $query = $queryparams['query']; - } - else { - echo __('Pas de query !', __FILE__); - die(); - } - - log::add('googlecast', 'debug', 'Query received ' . $query); - - $parameters['plugin'] = 'googlecast'; - $customcmd = $googlecast->getCmd(null, 'customcmd'); - if (is_object($cmd) && $customcmd->askResponse($query)) { - log::add('googlecast', 'debug', 'Répondu à un ask en cours'); - die(); - } - - $reply = interactQuery::tryToReply(trim($query), $parameters); - log::add('googlecast', 'debug', 'Interaction ' . print_r($reply, true)); - - $queryTransform = str_replace(array('[',']') , ' ', $reply['reply']); - $cmd = "cmd=tts|value=".$queryTransform; - #$googlecast->helperSendCustomCmd($cmd, null, 'mqtt', null, null); - $customcmd->execCmd(array('message' => $cmd)); - die(); - - /* * *********Catch exeption*************** */ -} catch (Exception $e) { - ajax::error(displayException($e), $e->getCode()); -} diff --git a/core/php/googlecast.ifttt.php b/core/php/googlecast.ifttt.php new file mode 100644 index 0000000..ba2e0d1 --- /dev/null +++ b/core/php/googlecast.ifttt.php @@ -0,0 +1,131 @@ +. + */ + +//error_reporting(E_ALL); +//ini_set('display_errors', 'On'); + +require_once dirname(__FILE__) . '/../../../../core/php/core.inc.php'; + +try { + + $querystr = parse_url(urldecode($_SERVER["REQUEST_URI"])); + parse_str($querystr['query'], $queryparams); + + $apikey=null; + if ( isset($queryparams['apikey']) ) { + $apikey=$queryparams['apikey']; + } + + if ($apikey!==null && !jeedom::apiAccess($apikey, 'googlecast')) { + echo json_encode(array('text' => 'No API Key provided !')); + die(); + } + + $content = file_get_contents('php://input'); + $json = json_decode($content, true); + + if ( isset($queryparams['uuid']) ) { + $uuid=$queryparams['uuid']; + } + else { + echo json_encode(array('text' => 'No UUID provided')); + die(); + } + + $googlecast = googlecast::byLogicalId($uuid,'googlecast'); + if (!is_object($googlecast)) { + echo json_encode(array('text' => 'Unkown UUID : ')); + die(); + } + if ( $googlecast->getIsEnable() == 0) { + echo json_encode(array('text' => 'Google Cast is disabled !')); + die(); + } + + $action = 'interact'; + if ( isset($queryparams['action']) ) { + $action = $queryparams['action']; + } + + if ( isset($queryparams['query']) ) { + $query = $queryparams['query']; + } + else { + echo json_encode(array('text' => 'No query provided !')); + die(); + } + + if ( $action == 'interact' ) { + log::add('googlecast', 'debug', 'IFTTT Query received ' . $query); + + $parameters['plugin'] = 'googlecast'; + $customcmd = $googlecast->getCmd(null, 'customcmd'); + if (is_object($cmd) && $customcmd->askResponse($query)) { + log::add('googlecast', 'debug', 'Répondu à un ask en cours'); + die(); + } + + $reply = interactQuery::tryToReply(trim($query), $parameters); + log::add('googlecast', 'debug', 'IFTTT Interaction ' . print_r($reply, true)); + + if ( isset($queryparams['vol']) ) { + $vol=$queryparams['vol']; + } + if ( isset($queryparams['noresume']) ) { + $has_noresume=true; + } + if ( isset($queryparams['quit']) ) { + $has_quit=true; + } + if ( isset($queryparams['silence']) ) { + $silence=$queryparams['silence']; + } + + $queryTransform = str_replace(array('[',']') , ' ', $reply['reply']); + $cmd = "cmd=tts|value=".$queryTransform; + if ( isset($vol) ) { + $cmd .= '|vol=' . $vol; + } + if ( isset($has_noresume) ) { + $cmd .= '|noresume=1'; + } + if ( isset($has_quit) ) { + $cmd .= '|quit=1'; + } + if ( isset($silence) ) { + $cmd .= '|silence=' . $silence; + } + log::add('googlecast', 'debug', 'IFTTT Interaction reply cmd : ' . $cmd); + $customcmd->execCmd(array('message' => $cmd)); + //echo json_encode(array('text' => 'OK !')); + die(); + } + elseif ( $action == 'customcmd' ) { + log::add('googlecast', 'debug', 'IFTTT Custom action : ' . $query); + $googlecast->helperSendCustomCmd($query, null, 'ifttt', null, null); + //echo json_encode(array('text' => 'OK !')); + die(); + } + else { + echo json_encode(array('text' => 'Action not implemented !')); + die(); + } + /* * *********Catch exeption*************** */ +} catch (Exception $e) { + ajax::error(displayException($e), $e->getCode()); +} diff --git a/desktop/images/notif.png b/desktop/images/notif.png new file mode 100644 index 0000000..cc7739c Binary files /dev/null and b/desktop/images/notif.png differ diff --git a/desktop/js/googlecast.js b/desktop/js/googlecast.js index e53dc4c..ba31809 100644 --- a/desktop/js/googlecast.js +++ b/desktop/js/googlecast.js @@ -48,6 +48,12 @@ $('#bt_healthrefresh').on('click', function () { }); }); +$('.bt_sidebarToogle').on('click', function () { + $('.sidebar-container').toggle(); + $('.equipement-container').toggleClass('col-lg-10'); + $('.equipement-container').toggleClass('col-lg-12'); +}); + $('body').on('googlecast::includeState', function (_event,_options) { if (_options['mode'] == 'learn') { if (_options['state'] == 1) { @@ -115,31 +121,36 @@ function addCmdToTable(_cmd) { if (!isset(_cmd)) { var _cmd = {configuration: {}}; } - var tr = ''; + var tr = ''; if ( (_cmd.logicalId != null) && _cmd.configuration.googlecast_cmd!==undefined ) { + tr = ''; tr += ''; - tr += '
'; - tr += '
'; + tr += '
'; tr += ' Icône'; - tr += ''; - tr += '
'; - tr += '
'; - tr += ''; + tr += ''; + tr += ''; tr += '
'; - tr += '
'; - tr += ''; tr += ''; tr += ''; tr += ''; tr += ''; tr += ''; - tr += '' + init(_cmd.type) + ''; + //tr += '
' + init(_cmd.type) + '
'; + //tr += '
' + init(_cmd.subType) + '
'; + tr += '
{{' + init(_cmd.type) + '}}
'; + tr += '
({{' + init(_cmd.subType) + '}})
'; tr += ''; - tr += '
'; + tr += '
'; tr += ''; tr += ''; tr += ' '; - tr += ' '; + if (init(_cmd.type)=='info') { + tr += ' '; + } + else { + tr += '{{Historiser}} '; + } tr += ''; tr += ''; tr += ''; @@ -149,17 +160,18 @@ function addCmdToTable(_cmd) { tr += ' '; tr += ' Tester'; } - //tr += ''; } else { // is new created command + if (!is_numeric(_cmd.id)) { + tr = ''; + } + else { + tr = ''; + } tr += ''; - tr += '
'; - tr += '
'; + tr += '
'; tr += ' Icône'; - tr += ''; - tr += '
'; - tr += '
'; - tr += ''; - tr += '
'; + tr += ''; + tr += ''; tr += '
'; tr += '
'; + tr += '
'; + tr += ''; tr += ''; tr += ''; tr += ' '; tr += ' '; tr += ' '; - tr += '
'; - tr += ''; + tr += '
'; + tr += ''; tr += ''; tr += ''; - tr += ''; tr += ''; tr += ''; - tr += ''; - tr += ''; - tr += ''; - tr += ' '; - tr += ''; + tr += ''; + tr += ''; + tr += ' '; + tr += ''; tr += ''; tr += ''; if (is_numeric(_cmd.id)) { @@ -195,7 +207,7 @@ function addCmdToTable(_cmd) { tr += ' Tester'; } if (_cmd.configuration.googlecast_cmd_mod===undefined) { - tr += ''; + tr += '
'; } } tr += ''; diff --git a/desktop/php/googlecast.php b/desktop/php/googlecast.php index 3d57617..3468e71 100644 --- a/desktop/php/googlecast.php +++ b/desktop/php/googlecast.php @@ -17,7 +17,7 @@ function sortByOption($a, $b) { ?>
-
+ -
- {{Gestion}} +
+ +   {{Gestion}}
{{Santé}}
- {{Mes équipements GoogleCast}} +   {{Mes équipements Google Cast}}
"; echo '' . $eqLogic->getHumanName(true, true) . ''; echo '
'; - $url = network::getNetworkAccess('external') . '/plugins/googlecast/core/php/googlecast.ajax.php?apikey=' . jeedom::getApiKey('googlecast') . '&id=' . $eqLogic->getId(); } ?>
- diff --git a/docs/en_US/changelog.md b/docs/en_US/changelog.md new file mode 100644 index 0000000..29ff9a6 --- /dev/null +++ b/docs/en_US/changelog.md @@ -0,0 +1,97 @@ +# Changelog + +Version list of googlecast plugin. + +*[Back to documentation] (index.md)* + +## Version of June 12 juin, 2018 (stable) + +- Beta to stable (see below) + +## Version of June 12 juin, 2018 (beta) + +- IFTTT interactions and webhooks +- Doc update (toc and ifttt) +- English doc translation + +## Version of June 11, 2018 (beta) + +- Cleaning / optimization of the order configuration page +- Compatibility command action of list type +- New command 'notif' (similar to tts but to play a local mp3) +  Test with `cmd=notif|value=bigben1.mp3|vol=100` +- Possibility to play local files for media app +- fix getconfig (jsonpath compatibility) +- fix update of the command info title / artist / player_state +- Bug fixes and maj doc + +## Version of 09 June 2018 (stable) + +- Change from beta to stable (see beta changes) +- Resume of the previous stream after TTS when launched via the plugin only +- Default 'speak' widget +- Bug fixes and maj doc + +## Version of 09 June 2018 (beta) + +- Fix when no volume +- Now 'resume' is the default behavior. You must use noresume = 1 to disable it +- For the summary, a 'play' is forced +- Fix for failover picotts when no internet +- In case of error, 'status_text' is 'ERROR', 'CMD UNKNOWN' is the command does not exist or 'NOT CONNECTED' if offline + +## Version of June 08, 2018 (beta) + +- Implemention function 'resume' for TTS. Only available for applications launched via the plugin (Google limitation) +- Fix for GH alarm return +- Minor bug fixes +- Plugin icon change (by Alois) +- Doc shift (example php block for scenario) + +## Version of June 07, 2018 (beta) + +- First version of the 'resume' function for TTS +- Dedicated Widget for TTS +- Minor bug fixes + +## Version of June 06, 2018 + +- Beta to stable (see below) +- Features: TTS (4 engines), Plex, Google Cast config recovery +- Bug fixes and doc maj + +## Version of June 04, 2018 (beta) + +- Added Google Speech API engine (key needed) +- Added PLEX management +- Bug fixes and doc maj + +## Version of May 29, 2018 (beta) + +- Added speed of speech management for TTS +- Recovery / modification of equipment configuration (eg: Google Home alarm / timer) +- Bug fixes and doc maj + +## Version of May 25, 2018 (beta) + +- Added 'info' commands: title, artist, player_state +- Added 'vol' option for each command to modify the volume (see doc) +- Added TextToSpeach (TTS) with Google Translate and PicoTTS engine (see doc) +- Possibility of launching a command sequence (see doc) +- Bug fixes and doc maj + +## Version of April 25, 2018 + +- Fix installation of dependencies for certain systems +- Fix impossibility of (re) connection after a few hours +- Reduction of memory tile (leakage) + +## Version of April 23, 2018 + +- Fix installation of dependencies for raspberry +- Fix impossibility of (re) connection +- Added possibility to adjust the refresh rate + +## Version of April 18, 2018 + +Initial stable release diff --git a/docs/en_US/index.md b/docs/en_US/index.md new file mode 100644 index 0000000..2fb6d7b --- /dev/null +++ b/docs/en_US/index.md @@ -0,0 +1,648 @@ +GoogleCast plugin (googlecast) +============================= + +! [Logo plugin] (../ images / logoplugin.png "Logo plugin") + +Plugin to control Google Cast enabled devices. + + +**Features:** + +- Sound control (mute, +/-) +- Media control (play / pause / stop ...) +- Stop app in progress, reboot +- Broadcast a web page on a screen +- Play audio and video files via url +- Return of status on the main functionalities +- Display of the current reading +- Text To Speech (TTS) +- Recovery / modification of equipment configuration + + +![Logo plugin](../ images / chromecast.png "Chromecast") + +**Google Cast compatible models** +- Chromecast Audio / Video +- Android TV, Nexus Player, TV (Vizio, Sharp, Sony, Toshiba, Philips) +- Google Home +- Soundbars and speakers (Vizio, Sony, LG, Philips +B&O Play, Grundig, Polk Audio, Bang & Olufsen, Onkyo, Pioneer ...) +- Other models labeled *Google Cast* + +![GoogleCast Logo](../images/googlecast_logo.png "GoogleCast Logo") +![Android TV](../images/tv.png "Android TV") + +**Other links** +- Wikipedia GoogleCast +- List of Google Cast enabled applications + + +Dashboard +======================= + +![Dashboard view](../images/dashboard.png "Dashboard view") +![Dashboard view 2](../images/dashboard2.png "Dashboard view 2") + +Quick Start +======================= + +The plugin is normally functional from the installation with the default setting. + +In a few steps: +1. Install the market plugin, the dependencies and start the daemon, +2. Start a scan of Google Cast available on the network, +3. Save the equipment found, +4. Go to the dashboard and test the 'demo' buttons (media, web ...), +5. To change / adapt the setting, read the rest of the documentation. + +Toble of Content +======================= + +- [GoogleCast plugin (googlecast)](#googlecast-plugin--googlecast-) +- [Dashboard](#dashboard) +- [Quick Start](#quick-start) +- [Toble of Content](#toble-of-content) +- [Plugin Configuration](#plugin-configuration) +- [Configuration des équipements](#configuration-des--quipements) + + [Onglet Commandes](#onglet-commandes) + + [Afficheur Lecture en cours (widget)](#afficheur-lecture-en-cours--widget-) + + [TTS Widget for text entry and volume control](#tts-widget-for-text-entry-and-volume-control) +- [Custom commands](#custom-commands) + + [Special applications](#special-applications) + + [Advanced commands](#advanced-commands) + - [Syntax for raw commands](#syntax-for-raw-commands) + - [Possible parameters for *play_media* en mode *media* :](#possible-parameters-for--play-media--en-mode--media---) + - [Possible parameters for *load_url* en mode *web* :](#possible-parameters-for--load-url--en-mode--web---) + - [Possible parameters for *play_media* en mode *plex* :](#possible-parameters-for--play-media--en-mode--plex---) + - [Possible parameters for *tts* :](#possible-parameters-for--tts---) + - [Possible parameters for *notif* :](#possible-parameters-for--notif---) + - [Command sequences](#command-sequences) + - [Device advanced configuration](#device-advanced-configuration) + * [Retreive a configuration](#retreive-a-configuration) + + [available parameters for *getconfig* cmd :](#available-parameters-for--getconfig--cmd--) + * [Modify a configuration](#modify-a-configuration) + + [available parameters for *setconfig* cmd :](#available-parameters-for--setconfig--cmd--) + * [Pre-defined configuration commands](#pre-defined-configuration-commands) + + [Adding a command *action* of type *List*](#adding-a-command--action--of-type--list-) + + [Use in scenarios](#use-in-scenarios) + - [With dedicated command *Custom Cmd*](#with-dedicated-command--custom-cmd-) + - [With php bloc code](#with-php-bloc-code) + + [Use with interactions and IFTTT](#use-with-interactions-and-ifttt) + - [Interactions](#interactions) + - [Custom CMD](#custom-cmd) +- [Known limitations and bugs](#known-limitations-and-bugs) +- [FAQ](#faq) +- [Changelog](#changelog) + + +Plugin Configuration +======================= + +After downloading the plugin: +- Activate the plugin +- Start the installation of dependencies +- Recommended log level: info +- Launch the demon. + +Configuration settings do not usually need to be changed +- **Demon** +  - Internal communication socket port. Only modify if necessary (ex: if it is already taken by another plugin) +  - Special configuration (eg: Docker, VM). Only change if it does not work without the option. +  - Refresh frequency. Only change if the normal frequency has a significant impact on overall performance +- **TTS** +  - Use the external Jeedom address: by default uses the internal Jeedom web address +  - Default language: TTS engine language used by default +  - Default engine: the TTS engine used (PicoTTS, Google Translate, Speach API, Google Speach API dev) +  - Speech rate: speed of pronunciation of the text +  - Do not use cache: disables use of Jeedom cache (deprecated) +  - Clean cache: cleans the temporary directory for generating sound files +  - Automatic cache deletion of more than X days: deletes unused TTS sound files for X days (daily cron). 0 deletes all the cache. +- **Notifications** +  - Disable notifications for new Google Cast: These are notifications when discovering new Google Cast unconfigured + +> **Notes for TTS (Text To Speech)** +> - PicoTTS does not require an internet connection, the Google Translate API requires web access and rendering is better. +> - For Google Speech API, a key is required (see FAQ). Rendering is better than Google Translate API. +> - A cache mechanism makes it possible to generate the sound reproduction only if it does not already exist in memory (RAM). The cache is deleted when the server is restarted. +> - In the event of failure on one of the motors other than picotts (ex: problem of Internet connection), the command will be launched via picotts. + +![Configuration Plugin](../images/configuration_plugin.png "Configuration Plugin") + +Configuration des équipements +============================= + +Google Cast device setup is available from the *Plugins> Multimedia> Google Cast* menu. + +![Configuration](../images/configuration.png "Configuration") + +Once the devices are connected, run a scan to detect and add them automatically. If no equipment appears, check that the equipment is accessible and powered. + +The view 'Health' allows to have a synthetic view of equipment and their current states. + +> **Note** +> It's not possible to manually add a Google Cast + +### Onglet Commandes + +Basic commands are generated automatically. + +You can also add new commands (see section below). + +![Alt text](../images/commands.png "Custom command") + +List of non-visible commands by default: +- *Player Status*: info displaying the media playback status (eg PLAYING / PAUSED); +- *Title*: Title of the current media; +- *Artist*: Artist of the current media; +- *Custom Cmd*: This component is intended to be used via a scenario or for testing (see section [Use in a scenario] (# use-in-a-scenario)); +- *Pincode*: pincode for quick association (example of advanced configuration) + +To see them on the dashboard, you have to activate 'Show' in the commands tab. + +> **Notes on order info 'Status' (*status_text*)** +> - *status_text* returns the current status of the Google Cast. +> - In case of error when launching an order, *status_text* is at +> 'CMD UNKNOWN' if the command does not exist, +> 'NOT CONNECTED' if offline or +> 'ERROR' for other errors +> - At rest (no action in progress), *status_text* = `& nbsp;` + + +### Afficheur Lecture en cours (widget) + +The information type command called 'Playing Widget' (visible by default) displays the image of the current playback. + +The display refreshes every 20 seconds by default. + +![Display 1](../images/display1.png "Display 1") + +Installation / configuration: +- Displayed by default after installation. Disable the display to hide. +- For use in a dashboard, it is possible to use a virtual by creating a command of the type *info / others* with the command *Playing Widget* (not internal *nowplaying*) of the Google Cast. Then apply the dashboard widget *googlecast_playing* (via tab *View* of the advanced configuration of the command) +- For use in a design, add the *Playing Widget* command directly into the design. + +optional CSS settings (via '*Optional Widget Settings'): +- *fontSize* (ex: 35px, default = 25px): basic font size +- *fontColor* (ex: blue, default = white): color of the display +- *fontFamily* (ex: 'Arial'): change the font of the display +- *backColor* (ex: blue, default = black): color of the bottom of the display +- *playingSize* (ex: 300px, default 250px): width and height of the current playback picture +- *contentSize* (ex: 70px, default 50px): height of the textual part +- *additionalCss* (css format, ex: '.blabla {...}'): to add / modify other CSS (advanced user) + +![Configuration CSS](../images/configuration_css.png "Configuration CSS") + +> **Notes** +> Not available for mobile + +### TTS Widget for text entry and volume control + +A widget is available for action type and message subtype commands to allow you to enter text for the TTS and adjust the volume. + +![Speak Widget](../images/widget_speak.png "Speak Widget") + +Installation / configuration: +- An example is displayed by default after installation to test the TTS function. +- For use in a dashboard, it is possible to use a virtual one by creating a command of the type *action / message* with value the command *Custom Cmd* of Google Cast. Then apply the dashboard widget *googlecast_speak* (via tab *View* of the advanced configuration of the command) +- The contents of the action command (subtype message) can contain the variables *#message#* and *#volume#* + +optional CSS settings (via '*Optional Widget Settings'): +- *width* (ex: 35px, default = 150px): size of the widget +- *default_volume* (ex: blue, default = 100): default valume +- *default_message* (ex: 'Test'): default text in the widget +- *additionalCss* (css format, ex: '.blabla {...}'): to add / modify other CSS (advanced user) + +> **Notes** +> Not available for mobile + + +Custom commands +============================= + +### Special applications + +- *Web*: Display a web page on a Google Cast. The available parameters are url, force, and reload time (ex: value = 'https://google.com',False,0 to load Google without forcing (necessary for some sites) and without reloading) +- *Media*: play an audio or video file from a URL +- *YouTube*: display a video with a video ID (at the end of the URL) => Does not work at the moment +- *Backdrop*: show the Google Cast wallpaper or screen saver (depending on model) +- *Plex*: play a file or playlist from a Plex server + +> **Notes** +> - See the buttons created by default for an example of use +> - Youtube is not functional at the moment + + +### Advanced commands + +#### Syntax for raw commands +They must be seperated by *|* +``` +- app : name of application (web/backdrop/youtube/media) +- cmd : name of command (dépend of application) + * tts : text to speech, use value to pass text + * notif : send sound notification based on existing media file (ex: mp3) + * refresh + * reboot : reboot the Google Cast + * volume_up + * volume_down + * volume_set : use value (0-100) + * mute_on + * mute_off + * quit_app + * start_app : use value to pass app id + * play + * stop + * rewind : go back to media start + * skip : got to next media + * seek : use value in seconds. Can use +/- to use relative seek (ex: +20 to pass 20 seconds) + * pause + For application dependant commands + * web : load_url (default) + * media : play_media (default) + * youtube : play_video (default)/add_to_queue/remove_video/play_next + * backdrop : no command + * plex : play_media (default)/play/stop/pause +- value : chain of parameters separated by ',' (depending of command) +- vol (optional, between 1 et 100) : adjust volume for the command +- sleep (optional, int/float) : add a break after end of command in seconds (eg: 2, 2.5) +- uuid (optional) : redirect to other google cast uuid in new thread (parallel processing). Useful when using sequences on several device. +- nothread (optional) : if uuid provided, disable use of thread for parallel processing. (eg: nothread=1) + +ex web : app=web|cmd=load_url|vol=90|value='http://pictoplasma.sound-creatures.com',True,10 +ex TTS : cmd=tts|vol=100|value=Mon text a dire +``` + +> **Notes** +> String length for commands are limited in Jeedom to 128 characters. Use scenarios (see below to override this limitation) + +#### Possible parameters for *play_media* en mode *media* : +``` +- url: str - url of the media (mandatory). +- content_type: str - mime type. Example: 'video/mp4' (optional). + Possible values: 'audio/aac', 'audio/mpeg', 'audio/ogg', 'audio/wav', 'image/bmp', + 'image/gif', 'image/jpeg', 'image/png', 'image/webp','video/mp4', 'video/webm'. +- title: str - title of the media (optional). +- thumb: str - thumbnail image url (optional, default=None). +- current_time: float - seconds from the beginning of the media to start playback (optional, default=0). +- autoplay: bool - whether the media will automatically play (optional, default=True). +- stream_type: str - describes the type of media artifact as one of the following: "NONE", "BUFFERED", "LIVE" (optional, default='BUFFERED'). +- subtitles: str - url of subtitle file to be shown on chromecast (optional, default=None). +- subtitles_lang: str - language for subtitles (optional, default='en-US'). +- subtitles_mime: str - mimetype of subtitles (optional, default='text/vtt'). + Possible values: 'application/xml+ttml', 'text/vtt'. +- subtitle_id: int - id of subtitle to be loaded (optional, default=1). + +ex short : app=media|cmd=play_media|value='http://contentlink','video/mp4','Video name' +ex short : app=media|cmd=play_media|value='http://contentlink',title:'Video name' +ex short : app=media|value='http://contentlink','video/mp4','Video name' (implicit play_media command call) + +ex long : app=media|cmd=play_media|value='http://contentlink','video/mp4',title:'Video name', + thumb:'http://imagelink',autoplay:True, + subtitles:'http://subtitlelink',subtitles_lang:'fr-FR', + subtitles_mime:'text/vtt' +``` + +> **Notes** +> - Les url et chaînes de caractères sont entourées de guillements simples ('). Les autres valeurs possibles sont True/False/None ainsi que des valeurs numériques entières. +> - Il est nécessaire de remplacer le signe '=' dans les url par '%3D' +> - Un média local situé dans le répertoire */plugins/googlecast/localmedia/* peux être utilisé en appelant l'url *local://* (ex: local://bigben1.mp3) + +#### Possible parameters for *load_url* en mode *web* : +``` +- url: str - website url. +- force: bool - force mode. To be used if default is not working. (optional, default False). +- reload: int - reload time in seconds. 0 = no reload. (optional, default 0) + +ex 1 : app=web|cmd=load_url|value='http://pictoplasma.sound-creatures.com',True,10 +ex 2 : app=web|cmd=load_url|value='http://mywebsite/index.php?apikey%3Dmyapikey' +ex 3 : app=web|value='http://mywebsite/index.php?apikey%3Dmyapikey' (implicit load_url command call) +``` + +> **Notes** +> - Urls and strings are surrounded by single quotes ('). Other possible values are True/False/None and integer numeric values. +> - It is necessary to replace the sign '=' dans les url par '%3D' + +#### Possible parameters for *play_media* en mode *plex* : +``` +- value: str - search query. It will play the first element returned. +- type: str - type of content. Example: 'video/audio' (optional, default=video). +- server: str - URL if token is provided, friendly name of Plex server if user & pass provided. +- user: str - account login possibly as an email account (optional if token provided). +- pass: str - account password (optional if token provided). +- token: str - token if any (optional if user & pass provided). +- shuffle: 0/1 - shuffle playlist if several media (optional, default=0). +- repeat: 0/1 - repeat media (optional, default=0). +- offset: int - media offset (optional, default=0). + +ex using user & pass : + app=plex|cmd=play_media|user=XXXXXX|pass=XXXXXXXXXXX|server=MyPlexServer|value=Playlist Jeedom|shuffle=1|type=audio +ex using token : + app=plex|cmd=play_media|token=XXXXXXXXX|server=http://IP:32400|value=Playlist Jeedom +ex using token with implicit play_media command call : + app=plex|token=XXXXXXXXX|server=http://IP:32400|value=Playlist Jeedom +``` + +> **Notes** +> - When using user & pass, internet access is required +> - Token value is displayed in logs (debug) when user & pass has been used the first time +> - you can simulate result of search query (value) in main search field of Plex web UI + +#### Possible parameters for *tts* : +``` +- value: str - text +- lang: str - fr-FR/en-US or any compatible language (optional, default is configuration) +- engine: str - picotts/gtts/gttsapi/gttsapidev. (optional, default is configuration) +- quit: 0/1 - quit app after tts action. +- forcetts: 1 - do not use cache (useful for testing). +- speed: float (default=1.2) - speed of speech (eg: 0.5, 2). +- vol: int (default=previous) - set the volume for the time TTS message is broadcast. Previous volume is resumed when done. +- sleep: float (default=0) - add time in seconds after tts is finished (before volume resume) +- silence: int (default=300, 600 for group cast) - add a short silence before the speech to make sure all is audible (in milliseconds) +- generateonly: 1 - only generate speech file in cache (no action on device) +- forcevol: 1 - Set volume also if the current volume is the same (useful for TTS synchronisation in multithreading) +- noresume: 1 - disable recovery of previous state before playing TTS. +- forceapplaunch: 1 - will try to force launch of previous application even if not lauched by plugin (to be used with 'resume'). + +ex : cmd=tts|value=My text|lang=en-US|engine=gtts|quit=1 +ex : cmd=tts|value=Mon texte|engine=gtts|speed=0.8|forcetts=1 +``` + +> **Notes** +> - By default, the plugin will try to resume previous app launched (will only work when previous application has been launched by the plugin). +> - You can try to force resume to any application using 'forceapplaunch=1' but there is a good chance that it will not resume correctly. + +#### Possible parameters for *notif* : +``` +- value: str - local media filename (located in '/plugins/googlecast/localmedia/' folder) +- quit: 0/1 - quit app after notif action. +- vol: int (default=previous) - set the volume for the time notif message is broadcast. Previous volume is resumed when done. +- duration: float (default=file duration) - override play duration of notification. +- sleep: float (default=0) - add time in seconds after notif is finished (before volume resume) +- forcevol: 1 - Set volume also if the current volume is the same (useful for notif synchronisation in multithreading) +- noresume: 1 - disable recovery of previous state before playing notif. +- forceapplaunch: 1 - will try to force launch of previous application even if not lauched by plugin (to be used with 'resume'). + +ex : cmd=notif|value=bigben1.mp3|vol=100 +``` + +> **Notes** +> - By default, the plugin will try to resume previous app launched (will only work when previous application has been launched by the plugin). +> - You can try to force resume to any application using 'forceapplaunch=1' but there is a good chance of failure. +> - Existing sounds in plugin : house_firealarm.mp3, railroad_crossing_bell.mp3, submarine_diving.mp3, tornado_siren.mp3, bigben1.mp3, bigben2.mp3 + +#### Command sequences +It's possible to launch several orders afterwards by separating by *$$* + +``` +ex 1 : cmd=tts|sleep=2|value=Je lance ma vidéo$$app=media|cmd=play_video|value='http://contentlink','video/mp4','Video name' +ex 2 : app=media|cmd=play_video|value='http://contentlink','video/mp4','Video name',current_time:148|sleep=10$$cmd=quit_app +ex TTS command on several google cast in parallel making sure the file is already cached : + cmd=tts|value=My TTS message|generateonly=1$$uuid=XXXXXXXXXXX|cmd=tts|value=My TTS message$$uuid=YYYYYYYYYYY|cmd=tts|value=My TTS message +``` +> **Note** +> adding 'uuid' parameter will redirect to this uuid device in new thread. This can be used to send a sequence to several device in one command. + +#### Device advanced configuration + +##### Retreive a configuration +Some configurations can be retrieved in an info command (*cmd=getconfig*). + +These commands are refreshed every 15 minutes or manually via the 'refreshconfig' command (not visible by default) + +A list is available by connecting to the equipment: +http://IP:8008/setup/eureka_info?options=detail + +For more info, check out https://rithvikvibhu.github.io/GHLocalApi/ + +###### available parameters for *getconfig* cmd : +``` +- value: str - uri base after 'setup/' based on API doc (default is 'eureka_info'). If starts with 'post:', a POST type request will be issued. +- data: str - json path to be returned separated by '/'. To get several data, separate by ','. Alternatively, JsonPath format can be used ( http://goessner.net/articles/JsonPath). +- sep: str - seperator if several data is set (default = ','). +- format: json/string/custom - output format (default = 'string'). 'custom' follows 'sprintf' php function format (ex: %d, %s). +- error: 1 - seperator if several data is set (default = ','). +- reterror: str - value to be returned if connection fails. Default will not change previous state. + +Exemples: +- Récupération du pincode d'une Google Chromecast : +cmd=getconfig|data=opencast_pin_code +- Google Home : Récupération de l'état de la première alarme (-1 en cas de problème ou non existant) : +cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].status|reterror=-1 +- Google Home : Récupération de la date et heure de la première alarme au format JJ-MM-AAAA HH:MM : +cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].fire_time|fnc=ts2long|reterror=00-00-0000 00:00 +- Google Home : Récupération de la date et heure de l'alarme avec id connu au format JJ-MM-AAAA HH:MM : +cmd=getconfig|value=assistant/alarms|data=$.alarm.[?(@.id=alarm/xxxxxx)].fire_time|fnc=ts2long|reterror=00-00-0000 00:00 +- Changer le nom du Google cast : +cmd=setconfig|data={"name":"Mon nouveau nom"} +- Google Home : Désactiver le mode nuit : +cmd=setconfig|value=assistant/set_night_mode_params|data={"enabled": false} +- Google Home : Changer luminosité du mode nuit : +cmd=setconfig|value=assistant/set_night_mode_params|data={"led_brightness": 0.2} +``` + +##### Modify a configuration +Some configurations can be modified in an action type command (* cmd = setconfig *). + +See the Google API on this link for what is editable : https://rithvikvibhu.github.io/GHLocalApi/ + +###### available parameters for *setconfig* cmd : +``` +- value: str - uri base after 'setup/' based on API doc. +- data: str - json data. + +Exemples: +- Disable notification on Google home : +cmd=setconfig|value=assistant/notifications|data={'notifications_enabled': false} +- Google Home : Volume au plus bas pour alarme : +cmd=setconfig|value=assistant/alarms/volume|data={'volume': 1} +``` + +##### Pre-defined configuration commands + +The following commands can be used in an 'info' or scenario command (via fonction *getInfoHttpSimple()*) : + +- *gh_get_alarms_date* : retourne la date de toutes les alarmes. +- *gh_get_alarms_id* : retourne les identifiants uniques de toutes les alarmes et timers. +- *gh_get_alarm_date_#* (#=numéro, commence par 0) : retourne la date de la prochaine alarme au format dd-mm-yyyy HH:mm. +- *gh_get_alarm_datenice_#* (#=numéro, commence par 0) : retourne la date de la prochaine alarme au format {'Today'|'Tomorrow'|dd-mm-yyyy} HH:mm. +- *gh_get_alarm_timestamp_#* (#=numéro, commence par 0) : retourne le timestamp de la prochaine alarme. +- *gh_get_alarm_status_#* (#=numéro, commence par 0) : statut de l'alarme (1 = configuré, 2 = sonne). +- *gh_get_timer_timesec_#* (#=numéro, commence par 0) : retourne le nombre de secondes avant déclenchement du timer. +- *gh_get_timer_time_#* (#=numéro, commence par 0) : retourne la date de déclenchement du timer. +- *gh_get_timer_duration_#* (#=numéro, commence par 0) : retourne la durée originale configurée du timer. +- *gh_get_timer_status_#* (#=numéro, commence par 0) : statut du timer (1 = configuré, 3 = sonne). +- *gh_get_donotdisturb* : retourne l'état de la fonction 'Do Not Disturb'. +- *gh_get_alarms_volume* : récupère le volume des alarmes et timers. +- *conf_pincode* : retourne le code pin d'association. +- *conf_getbonded_bluetooth* : retourne tous les équipements bluetooth enregistrés. +- *conf_getconnected_wifi* : retourne le nom du réseau wifi configuré. + +Les commandes suivantes peuvent être utilisé dans une commande 'action' ou scénario (via fonction *setInfoHttpSimple()* ou commande *Custom Cmd*) : + +- *gh_set_donotdisturb_on* : active la fonction 'Do Not Disturb'. +- *gh_set_donotdisturb_off* : désactive la fonction 'Do Not Disturb'. +- *gh_set_donotdisturb_#* (#=true/false) : active/désavtive la fonction 'Do Not Disturb' +- *gh_set_alarms_volume_#* (# = entre 0 et 100 (eg: 10)) : configure le volume des alarmes et timers. +- *bt_connectdefault* : connecte l'équipement bluetooth configuré par défaut. +- *bt_connect_X* (#=adresse mac au format xx:xx:xx:xx:xx:xx) : connecte l'équipement bluetooth donné en paramètre. +- *bt_disconnectdefault* : déconnecte l'équipement bluetooth configuré par défaut. + +``` +Exemples: +- info type command +gh_get_alarm_date_0 +- action type command +gh_set_alarms_volume_80 +``` + +### Adding a command *action* of type *List* + +To create an *action* command of type *List* whose several parameters change, the command must imperatively be called *cmdlist_XXXX* with XXXX being able to be replaced by a name (example cmdlist_radio). + +The *Value List* field must contain the list of commands and follow the format `|;|;;...` +The separator needs to be changed from '|' to '^'. + +```` +Example for web sites : +app=web^cmd=load_url^value='https://google.com'|Google; +app=web^cmd=load_url^value='https://facebook.com'|Facebook + +Example for webradio : +app=media^value='http://urlFluxRadio1/flux.mp3','audio/mpeg','Radio 1'|Radio 1; +app=media^value='http://urlFluxRadio2/flux.mp3','audio/mpeg','Radio 2'|Radio 2; +app=media^value='http://urlFluxRadio3/flux.mp3','audio/mpeg','Radio 3'|Radio 3 +```` + +![Command action of type list](../images/commands_list.png "Command action of type list") + +> **Note** +> For simpler commands (only one parameter changes), it is still possible to use the placeholder *#listValue#* in a command. +> Example : `app=web|cmd=load_url|value=#listValue#` avec comme liste de valeurs `https://google.com|Google;https://facebook.com|Facebook` + + +### Use in scenarios + +#### With dedicated command *Custom Cmd* +The command called *Custom Cmd* is used to launch a raw command from a scenario. + +For example, to launch Google on a Google Cast from a scenario, add the command with the desired value in the 'message' field. +``` +app=web|cmd=load_url|value='https://google.com',True,10 +``` + +![Scenario](../images/scenario.png "Scenario") + +#### With php bloc code + +Examples using php bloc : + +```php +$googlecast = googlecast::byLogicalId('XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX', 'googlecast'); +if ( !is_object($googlecast) or $googlecast->getIsEnable()==false ) { + $scenario->setData("_test", "None"); + // variable _test contains 'None' if google cast does not exist or is disable +} +else { + // Run a command + $ret = $googlecast->helperSendCustomCmd('cmd=tts|value=Test Scénario PHP|vol=100'); + $scenario->setData("_test", $ret); + // Command launched + // $ret = true if command has been ran, false if deamon is not running + // variable _test contains 1 if true, 0 if false + + // Configure a Google Home equipement + $ret = $googlecast->setInfoHttpSimple('bt_connect_xx:xx:xx:xx:xx:xx'); + // or $googlecast->helperSendCustomCmd('bt_connect_xx:xx:xx:xx:xx:xx'); (return false if deamon not running, true otherwise) + // Try to connect a bluetooth device (mac=xx:xx:xx:xx:xx:xx) to Google Home + // $ret = true if command has been launched, false if failed (Google Home not accessible) + + // Get curent GH alarm time (via long command) + $ret = $googlecast->getInfoHttpSimple('cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].fire_time|fnc=ts2long|reterror=Undefined'); + $scenario->setData("_test",$ret); + // variable _test contains dd-mm-yyyy HH:mm (Undefined if failed) + + // Get curent GH alarm time (via pre-configured command) + $ret = $googlecast->getInfoHttpSimple('gh_get_alarm_date_0'); + // or $googlecast->helperSendCustomCmd('gh_get_alarm_date_0'); (return false if deamon not running, true otherwise) + $scenario->setData("_test",$ret); + // variable _test contains dd-mm-yyyy HH:mm (Undefined if failed) + + // Get curent GH alarm date only (via long command with formatting) + $ret = $googlecast->getInfoHttpSimple('cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].date_pattern|format=%02d%02d%04d|reterror=00000000'); + $scenario->setData("_test",$ret); + // variable _test contains JJMMAAAA (00000000 if failed) +} +``` + +### Use with interactions and IFTTT + +#### Interactions +Compatibility with IFTTT using the following url in the configuration: +``` +http(s)://#JEEDOM_DNS#/plugins/googlecast/core/php/googlecast.ifttt.php?apikey=#GCASTPLUGIN_APIKEY#&uuid=#GCAST_UUID#&query=<<{{TextField}}>> +Optional : + &vol=X (between 1 and 100) + &noresume + &quit + &silence=X (between 1 and 100) +``` +Documentation Jeedom et IFTTT : https://jeedom.github.io/plugin-gcast + +#### Custom CMD +Send a custom command using webhooks +``` +http(s)://#JEEDOM_DNS#/plugins/googlecast/core/php/googlecast.ifttt.php?apikey=#GCASTPLUGIN_APIKEY#&uuid=#GCAST_UUID#&action=customcmd&query=#CUSTOM_CMD# +Notes : + #CUSTOM_CMD# : as defined in documentation (eg : app=web|cmd=load_url|value='http://pictoplasma.sound-creatures.com') + It may be necessary to encode #CUSTOM_CMD# using https://www.url-encode-decode.com/ +``` + + +Known limitations and bugs +============================= + +- PicoTTS engine does not handle accented sentences (removed) + + +FAQ +============================= + +#### No detection during the scan + +- Check that the Google Cast is available from an application allowing the visualization of compatible devices; +- Jeedom must be on the same network as Google Cast devices +(for Docker, the container must be configured to be on the same network, in VM, the machine is in bridge mode); +- Check that there are no blockages at the firewall for discovery via the 'Zeroconf' protocol; +- To put Docker on the same network, see https://github.com/guirem/plugin-googlecast/issues/8 + +#### No commands seems to work + +- Check that Google Cast works with other devices; +- Check that nothing has changed since the scan; + +#### Some commands do not work + +- It may depend on the model and the application using it; + +#### Dependencies can't install properly + +- Check the logs for the source of the error. The plugin requires the installation of python3 and pip3. + +#### Text To Speech (TTS) doen't work + +- Try with the following parameters: 'Use external Jeedom address' or 'Do not use cache' +- If Jeedom does not have web access, use the picoTTS engine +- Check the logs for the nature of the error + +#### Broadcast Jeedom without authentication on a Google Cast + +This is possible via the web mode. To manage the authentication automatically, use the plugin 'autologin' (see doc of the plugin). + +#### Get an API Key for Google Speech API + +The steps to get this key are on this link : http://domotique-home.fr/comment-obtenir-google-speech-api-et-integrer-dans-sarah/ + +Changelog +============================= + +[See dedicated page](changelog.md). diff --git a/docs/fr_FR/changelog.md b/docs/fr_FR/changelog.md index 24224df..724417e 100644 --- a/docs/fr_FR/changelog.md +++ b/docs/fr_FR/changelog.md @@ -4,6 +4,27 @@ Liste des versions du plugin googlecast. *[Retour à la documentation](index.md)* +## Version du 12 juin 2018 + +- Passage de la beta en stable (voir dessous) + +## Version du 12 juin 2018 (beta) + +- Interaction IFTTT et webhooks +- Maj doc (toc et ifttt) +- English doc translation + +## Version du 11 juin 2018 (beta) + +- Nettoyage/optimisation de la page de configuration des commandes +- Compatibilité commande action de type liste +- Nouvelle commande 'notif' (similaire à tts mais pour jouer un mp3 local) + Tester avec `cmd=notif|value=bigben1.mp3|vol=100` +- Possiblité de jouer des fichiers en local pour app media +- fix getconfig (compatiblité jsonpath) +- fix update des command info title/artist/player_state +- Correction de bugs and maj doc + ## Version du 09 juin 2018 (stable) - Passage de la beta en stable (voir les changements beta) diff --git a/docs/fr_FR/index.md b/docs/fr_FR/index.md index 617d7f4..ce35104 100644 --- a/docs/fr_FR/index.md +++ b/docs/fr_FR/index.md @@ -13,7 +13,7 @@ Plugin pour commander les équipements compatibles Google Cast. - Arrêt appli en cours, reboot - Diffuser une page web sur un écran - Lecture de fichiers audio et vidéo via url -- Retour d'état sur les principales Fonctionnalités +- Retour d'état sur les principales fonctionnalités - Affichage de la lecture en cours - Text To Speech (TTS) - Récupération/modification de configuration d'équipements @@ -43,6 +43,57 @@ Dashboard ![Visuel du dashboard](../images/dashboard.png "Visuel du dashboard") ![Visuel du dashboard 2](../images/dashboard2.png "Visuel du dashboard 2") +Quick Start +======================= + +Le plugin est normalement fonctionnel dès l'installation avec le paramêtrage par défaut. + +En quelques étapes : +1. Installer le plugin du market, les dépendances puis démarrer le démon, +2. Lancer un scan des Google Cast disponibles sur le réseau, +3. Sauvegarder les équipements trouvés, +4. Aller sur le dashboard et tester les boutons 'démo' (media, web...), +5. Pour changer/adapter le paramètrage, lire le reste de la documentation. + +Table des matières +======================= + +- [Plugin GoogleCast (googlecast)](#plugin-googlecast--googlecast-) +- [Dashboard](#dashboard) +- [Quick Start](#quick-start) +- [Table des matières](#toble-des-matieres) +- [Configuration du plugin](#configuration-du-plugin) +- [Configuration des équipements](#configuration-des--quipements) + + [Onglet Commandes](#onglet-commandes) + + [Afficheur Lecture en cours (widget)](#afficheur-lecture-en-cours--widget-) + + [Widget TTS pour saisie de texte et control du volume](#widget-tts-pour-saisie-de-texte-et-control-du-volume) +- [Commandes personnalisées](#commandes-personnalis-es) + + [Applications spéciales](#applications-sp-ciales) + + [Commandes avancées](#commandes-avanc-es) + - [Syntaxe des commandes brutes](#syntaxe-des-commandes-brutes) + - [Paramètres possibles pour *play_media* en mode *media* :](#param-tres-possibles-pour--play-media--en-mode--media---) + - [Paramètres possibles pour *load_url* en mode *web* :](#param-tres-possibles-pour--load-url--en-mode--web---) + - [Paramètres possibles pour *play_media* en mode *plex* :](#param-tres-possibles-pour--play-media--en-mode--plex---) + - [Paramètres possibles pour cmd *tts* :](#param-tres-possibles-pour-cmd--tts---) + - [Paramètres possibles pour cmd *notif* :](#param-tres-possibles-pour-cmd--notif---) + - [Séquence de commandes](#s-quence-de-commandes) + - [Configuration avancée des équipements](#configuration-avanc-e-des--quipements) + * [Récupérer une configuration](#r-cup-rer-une-configuration) + + [paramètres possibles pour cmd *getconfig* :](#param-tres-possibles-pour-cmd--getconfig---) + * [Modifier une configuration](#modifier-une-configuration) + + [paramètres possibles pour cmd *setconfig* :](#param-tres-possibles-pour-cmd--setconfig---) + * [Commande configuration pré-définies](#commande-configuration-pr--d-finies) + + [Création dune commande *action* de type *Liste*](#cr-ation-dune-commande--action--de-type--liste-) + + [Utilisation dans un scénario](#utilisation-dans-un-sc-nario) + - [Avec commande dédiée *Custom Cmd*](#avec-commande-d-di-e--custom-cmd-) + - [Avec bloc code php](#avec-bloc-code-php) + + [Utilisation avec interactions et IFTTT](#utilisation-avec-interactions-et-ifttt) + - [Interactions](#interactions) + - [Custom CMD](#custom-cmd) +- [Limitations et bug connus](#limitations-et-bug-connus) +- [FAQ](#faq) +- [Changelog](#changelog) + Configuration du plugin ======================= @@ -115,6 +166,7 @@ Pour les voir sur le dashboard, il faut activer 'Afficher' dans l'onglet des com > 'ERROR' pour les autres erreurs > - Au repos (pas d'action en cours), *status_text* = ` ` + ### Afficheur Lecture en cours (widget) La commande de type information appelée 'Playing Widget' (visible par défaut) permet d'afficher l'image de la lecture en cours. @@ -187,6 +239,7 @@ Elles doivent être séparés par *|* - app : name of application (web/backdrop/youtube/media) - cmd : name of command (dépend of application) * tts : text to speech, use value to pass text + * notif : send sound notification based on existing media file (ex: mp3) * refresh * reboot : reboot the Google Cast * volume_up @@ -203,11 +256,11 @@ Elles doivent être séparés par *|* * seek : use value in seconds. Can use +/- to use relative seek (ex: +20 to pass 20 seconds) * pause For application dependant commands - * web : load_url - * media : play_media - * youtube : play_video/add_to_queue/remove_video/play_next + * web : load_url (default) + * media : play_media (default) + * youtube : play_video (default)/add_to_queue/remove_video/play_next * backdrop : no command - * plex : play_media/play/stop/pause + * plex : play_media (default)/play/stop/pause - value : chain of parameters separated by ',' (depending of command) - vol (optional, between 1 et 100) : adjust volume for the command - sleep (optional, int/float) : add a break after end of command in seconds (eg: 2, 2.5) @@ -223,7 +276,7 @@ ex TTS : cmd=tts|vol=100|value=Mon text a dire #### Paramètres possibles pour *play_media* en mode *media* : ``` -- url: str - url of the media. +- url: str - url of the media (mandatory). - content_type: str - mime type. Example: 'video/mp4' (optional). Possible values: 'audio/aac', 'audio/mpeg', 'audio/ogg', 'audio/wav', 'image/bmp', 'image/gif', 'image/jpeg', 'image/png', 'image/webp','video/mp4', 'video/webm'. @@ -239,6 +292,8 @@ ex TTS : cmd=tts|vol=100|value=Mon text a dire - subtitle_id: int - id of subtitle to be loaded (optional, default=1). ex short : app=media|cmd=play_media|value='http://contentlink','video/mp4','Video name' +ex short : app=media|cmd=play_media|value='http://contentlink',title:'Video name' +ex short : app=media|value='http://contentlink','video/mp4','Video name' (implicit play_media command call) ex long : app=media|cmd=play_media|value='http://contentlink','video/mp4',title:'Video name', thumb:'http://imagelink',autoplay:True, @@ -249,6 +304,7 @@ ex long : app=media|cmd=play_media|value='http://contentlink','video/mp4',title: > **Notes** > - Les url et chaînes de caractères sont entourées de guillements simples ('). Les autres valeurs possibles sont True/False/None ainsi que des valeurs numériques entières. > - Il est nécessaire de remplacer le signe '=' dans les url par '%3D' +> - Un média local situé dans le répertoire */plugins/googlecast/localmedia/* peux être utilisé en appelant l'url *local://* (ex: local://bigben1.mp3) #### Paramètres possibles pour *load_url* en mode *web* : ``` @@ -258,6 +314,7 @@ ex long : app=media|cmd=play_media|value='http://contentlink','video/mp4',title: ex 1 : app=web|cmd=load_url|value='http://pictoplasma.sound-creatures.com',True,10 ex 2 : app=web|cmd=load_url|value='http://mywebsite/index.php?apikey%3Dmyapikey' +ex 3 : app=web|value='http://mywebsite/index.php?apikey%3Dmyapikey' (implicit load_url command call) ``` > **Notes** @@ -280,6 +337,8 @@ ex using user & pass : app=plex|cmd=play_media|user=XXXXXX|pass=XXXXXXXXXXX|server=MyPlexServer|value=Playlist Jeedom|shuffle=1|type=audio ex using token : app=plex|cmd=play_media|token=XXXXXXXXX|server=http://IP:32400|value=Playlist Jeedom +ex using token with implicit play_media command call : + app=plex|token=XXXXXXXXX|server=http://IP:32400|value=Playlist Jeedom ``` > **Notes** @@ -289,6 +348,7 @@ ex using token : #### Paramètres possibles pour cmd *tts* : ``` +- value: str - text - lang: str - fr-FR/en-US or any compatible language (optional, default is configuration) - engine: str - picotts/gtts/gttsapi/gttsapidev. (optional, default is configuration) - quit: 0/1 - quit app after tts action. @@ -296,7 +356,7 @@ ex using token : - speed: float (default=1.2) - speed of speech (eg: 0.5, 2). - vol: int (default=previous) - set the volume for the time TTS message is broadcast. Previous volume is resumed when done. - sleep: float (default=0) - add time in seconds after tts is finished (before volume resume) -- silence: int (default=300) - add a short silence before the speech to make sure all is audible (in milliseconds) +- silence: int (default=300, 600 for group cast) - add a short silence before the speech to make sure all is audible (in milliseconds) - generateonly: 1 - only generate speech file in cache (no action on device) - forcevol: 1 - Set volume also if the current volume is the same (useful for TTS synchronisation in multithreading) - noresume: 1 - disable recovery of previous state before playing TTS. @@ -307,8 +367,27 @@ ex : cmd=tts|value=Mon texte|engine=gtts|speed=0.8|forcetts=1 ``` > **Notes** -> By default, the plugin will try to resume previous app launched (will only work when previous application has been launched by the plugin). -> You can try to force resume of any application using 'forceapplaunch=1' but there is a good chance that it will not resume correctly. +> - By default, the plugin will try to resume previous app launched (will only work when previous application has been launched by the plugin). +> - You can try to force resume to any application using 'forceapplaunch=1' but there is a good chance that it will not resume correctly. + +#### Paramètres possibles pour cmd *notif* : +``` +- value: str - local media filename (located in '/plugins/googlecast/localmedia/' folder) +- quit: 0/1 - quit app after notif action. +- vol: int (default=previous) - set the volume for the time notif message is broadcast. Previous volume is resumed when done. +- duration: float (default=file duration) - override play duration of notification. +- sleep: float (default=0) - add time in seconds after notif is finished (before volume resume) +- forcevol: 1 - Set volume also if the current volume is the same (useful for notif synchronisation in multithreading) +- noresume: 1 - disable recovery of previous state before playing notif. +- forceapplaunch: 1 - will try to force launch of previous application even if not lauched by plugin (to be used with 'resume'). + +ex : cmd=notif|value=bigben1.mp3|vol=100 +``` + +> **Notes** +> - By default, the plugin will try to resume previous app launched (will only work when previous application has been launched by the plugin). +> - You can try to force resume to any application using 'forceapplaunch=1' but there is a good chance of failure. +> - Existing sounds in plugin : house_firealarm.mp3, railroad_crossing_bell.mp3, submarine_diving.mp3, tornado_siren.mp3, bigben1.mp3, bigben2.mp3 #### Séquence de commandes Il est possible de lancer plusieurs commandes à la suite en séparant par *$$* @@ -337,7 +416,7 @@ Pour plus d'info voir https://rithvikvibhu.github.io/GHLocalApi/ ###### paramètres possibles pour cmd *getconfig* : ``` - value: str - uri base after 'setup/' based on API doc (default is 'eureka_info'). If starts with 'post:', a POST type request will be issued. -- data: str - json path to be returned separated by '/'. To get several data, separate by ','. +- data: str - json path to be returned separated by '/'. To get several data, separate by ','. Alternatively, JsonPath format can be used ( http://goessner.net/articles/JsonPath). - sep: str - seperator if several data is set (default = ','). - format: json/string/custom - output format (default = 'string'). 'custom' follows 'sprintf' php function format (ex: %d, %s). - error: 1 - seperator if several data is set (default = ','). @@ -347,9 +426,11 @@ Exemples: - Récupération du pincode d'une Google Chromecast : cmd=getconfig|data=opencast_pin_code - Google Home : Récupération de l'état de la première alarme (-1 en cas de problème ou non existant) : -cmd=getconfig|value=assistant/alarms|data=alarm/0/status|reterror=-1 +cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].status|reterror=-1 - Google Home : Récupération de la date et heure de la première alarme au format JJ-MM-AAAA HH:MM : -'cmd=getconfig|value=assistant/alarms|data=alarm/0/fire_time|fnc=ts2long|reterror=00-00-0000 00:00 +cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].fire_time|fnc=ts2long|reterror=00-00-0000 00:00 +- Google Home : Récupération de la date et heure de l'alarme avec id connu au format JJ-MM-AAAA HH:MM : +cmd=getconfig|value=assistant/alarms|data=$.alarm.[?(@.id=alarm/xxxxxx)].fire_time|fnc=ts2long|reterror=00-00-0000 00:00 - Changer le nom du Google cast : cmd=setconfig|data={"name":"Mon nouveau nom"} - Google Home : Désactiver le mode nuit : @@ -379,6 +460,8 @@ cmd=setconfig|value=assistant/alarms/volume|data={'volume': 1} Les commandes suivantes peuvent être utilisées dans une commande 'info' ou scénario (via fonction *getInfoHttpSimple()*) : +- *gh_get_alarms_date* : retourne la date de toutes les alarmes. +- *gh_get_alarms_id* : retourne les identifiants uniques de toutes les alarmes et timers. - *gh_get_alarm_date_#* (#=numéro, commence par 0) : retourne la date de la prochaine alarme au format dd-mm-yyyy HH:mm. - *gh_get_alarm_datenice_#* (#=numéro, commence par 0) : retourne la date de la prochaine alarme au format {'Today'|'Tomorrow'|dd-mm-yyyy} HH:mm. - *gh_get_alarm_timestamp_#* (#=numéro, commence par 0) : retourne le timestamp de la prochaine alarme. @@ -411,6 +494,31 @@ gh_get_alarm_date_0 gh_set_alarms_volume_80 ``` +### Création dune commande *action* de type *Liste* + +Pour créer une commande *action* de type *Liste* dont plusieurs paramètres changent, la commande doit impérativement s'appeler *cmdlist_XXXX* avec XXXX pouvant être remplacé par un nom (example cmdlist_radio). + +Le champs *Liste de valeurs* doit contenir la liste de commandes enière et suivre le format `|;|;...` +Le séparateur de commande devra être changé de '|' pour '^'. + +```` +Exemple site web : +app=web^cmd=load_url^value='https://google.com'|Google; +app=web^cmd=load_url^value='https://facebook.com'|Facebook + +Exemple pour webradio : +app=media^value='http://urlFluxRadio1/flux.mp3','audio/mpeg','Radio 1'|Radio 1; +app=media^value='http://urlFluxRadio2/flux.mp3','audio/mpeg','Radio 2'|Radio 2; +app=media^value='http://urlFluxRadio3/flux.mp3','audio/mpeg','Radio 3'|Radio 3 +```` + +![Command action of type list](../images/commands_list.png "Command action of type list") + +> **Note** +> Pour des commandes plus simples (un seul paramètre change), il est toujours possible d'utiliser le placeholder *#listValue#* dans une commande. +> Exemple : `app=web|cmd=load_url|value=#listValue#` avec comme liste de valeurs `https://google.com|Google;https://facebook.com|Facebook` + + ### Utilisation dans un scénario #### Avec commande dédiée *Custom Cmd* @@ -448,7 +556,7 @@ else { // $ret = true if command has been launched, false if failed (Google Home not accessible) // Get curent GH alarm time (via long command) - $ret = $googlecast->getInfoHttpSimple('cmd=getconfig|value=assistant/alarms|data=alarm/0/fire_time|fnc=ts2long|reterror=Undefined'); + $ret = $googlecast->getInfoHttpSimple('cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].fire_time|fnc=ts2long|reterror=Undefined'); $scenario->setData("_test",$ret); // variable _test contains dd-mm-yyyy HH:mm (Undefined if failed) @@ -459,12 +567,35 @@ else { // variable _test contains dd-mm-yyyy HH:mm (Undefined if failed) // Get curent GH alarm date only (via long command with formatting) - $ret = $googlecast->getInfoHttpSimple('cmd=getconfig|value=assistant/alarms|data=alarm/0/date_pattern|format=%02d%02d%04d|reterror=00000000'); + $ret = $googlecast->getInfoHttpSimple('cmd=getconfig|value=assistant/alarms|data=$.alarm.[0].date_pattern|format=%02d%02d%04d|reterror=00000000'); $scenario->setData("_test",$ret); // variable _test contains JJMMAAAA (00000000 if failed) } ``` +### Utilisation avec interactions et IFTTT + +#### Interactions +Compatibilité avec interaction IFTTT en utilisant l'url suivant dans la configuration : +``` +http(s)://#JEEDOM_DNS#/plugins/googlecast/core/php/googlecast.ifttt.php?apikey=#GCASTPLUGIN_APIKEY#&uuid=#GCAST_UUID#&query=<<{{TextField}}>> +Optional : + &vol=X (between 1 and 100) + &noresume + &quit + &silence=X (between 1 and 100) +``` +Documentation Jeedom et IFTTT : https://jeedom.github.io/plugin-gcast + +#### Custom CMD +Envoyer une commande à partir d'un webhook +``` +http(s)://#JEEDOM_DNS#/plugins/googlecast/core/php/googlecast.ifttt.php?apikey=#GCASTPLUGIN_APIKEY#&uuid=#GCAST_UUID#&action=customcmd&query=#CUSTOM_CMD# +Notes : + #CUSTOM_CMD# : as defined in documentation (eg : app=web|cmd=load_url|value='http://pictoplasma.sound-creatures.com') + It may be necessary to encode #CUSTOM_CMD# using https://www.url-encode-decode.com/ +``` + Limitations et bug connus ============================= @@ -480,7 +611,7 @@ FAQ - Jeedom doit se trouver sur le même réseau que les équipements Google Cast (pour Docker, le container doit être configuré pour être sur le même réseau ; en VM, la machine est en mode bridge) ; - Vérifier qu'il n'y a pas de blocages au niveau du firewall pour la découverte via le protocol 'Zeroconf' ; -- Pour mettre Docker sur le même réseau, voir https://github.com/guirem/plugin-googlecast/issues/8 +- Pour mettre Docker sur le même réseau, voir #8 #### Aucune commande ne semble fonctionner diff --git a/docs/images/commands.png b/docs/images/commands.png index 2b3e20d..8259ac6 100644 Binary files a/docs/images/commands.png and b/docs/images/commands.png differ diff --git a/docs/images/commands_list.png b/docs/images/commands_list.png new file mode 100644 index 0000000..26458d5 Binary files /dev/null and b/docs/images/commands_list.png differ diff --git a/docs/images/configuration_plugin.png b/docs/images/configuration_plugin.png index 102b1f6..b178549 100644 Binary files a/docs/images/configuration_plugin.png and b/docs/images/configuration_plugin.png differ diff --git a/localmedia/README.md b/localmedia/README.md new file mode 100644 index 0000000..65392e7 --- /dev/null +++ b/localmedia/README.md @@ -0,0 +1,2 @@ +Local Media files goes here ! +Don't forget to make this folder writable if you need to add new assets (using chmod) diff --git a/localmedia/bigben1.mp3 b/localmedia/bigben1.mp3 new file mode 100644 index 0000000..be7dd91 Binary files /dev/null and b/localmedia/bigben1.mp3 differ diff --git a/localmedia/bigben2.mp3 b/localmedia/bigben2.mp3 new file mode 100644 index 0000000..80b7bc8 Binary files /dev/null and b/localmedia/bigben2.mp3 differ diff --git a/localmedia/house_firealarm.mp3 b/localmedia/house_firealarm.mp3 new file mode 100644 index 0000000..8a3bf00 Binary files /dev/null and b/localmedia/house_firealarm.mp3 differ diff --git a/localmedia/railroad_crossing_bell.mp3 b/localmedia/railroad_crossing_bell.mp3 new file mode 100644 index 0000000..db031e5 Binary files /dev/null and b/localmedia/railroad_crossing_bell.mp3 differ diff --git a/localmedia/submarine_diving.mp3 b/localmedia/submarine_diving.mp3 new file mode 100644 index 0000000..1a2d585 Binary files /dev/null and b/localmedia/submarine_diving.mp3 differ diff --git a/localmedia/tornado_siren.mp3 b/localmedia/tornado_siren.mp3 new file mode 100644 index 0000000..0db3885 Binary files /dev/null and b/localmedia/tornado_siren.mp3 differ diff --git a/plugin_info/info.json b/plugin_info/info.json index 70b16d0..c39e742 100644 --- a/plugin_info/info.json +++ b/plugin_info/info.json @@ -7,7 +7,7 @@ "hasOwnDeamon" : true, "hasDependency" : true, "maxDependancyInstallTime" : 20, - "version" : "2.4", + "version" : "2.5", "require" : "3.0", "category" : "multimedia", "changelog" : "https://github.com/guirem/plugin-googlecast/blob/develop/docs/fr_FR/changelog.md", diff --git a/resources/globals.py b/resources/globals.py index ce5c131..b7e8821 100644 --- a/resources/globals.py +++ b/resources/globals.py @@ -52,6 +52,8 @@ cycle = 0.5 cycle_main = 2 +disable_mediastatus = False + tts_language = 'fr-FR' tts_engine = 'picotts' tts_cacheenabled = True @@ -62,6 +64,9 @@ tts_gapi_key = 'none' tts_gapi_haskey = False +localmedia_folder = 'localmedia' +localmedia_fullpath = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), localmedia_folder)) + log_level = "info" pidfile = '/tmp/googlecast.pid' apikey = '' diff --git a/resources/googlecast.py b/resources/googlecast.py index 00d4c6e..d7f58d9 100644 --- a/resources/googlecast.py +++ b/resources/googlecast.py @@ -106,6 +106,7 @@ def __init__(self, gcast, options=None, scan_mode=False): self.previous_playercmd = {} self.sessionid_storenext = False self.sessionid_current = '' + self.previous_nowplaying = {} self.nowplaying_lastupdated = 0 self.gcast.media_controller.register_status_listener(self) self.gcast.register_status_listener(self) @@ -180,8 +181,11 @@ def new_cast_status(self, new_status): def new_media_status(self, new_mediastatus): #logging.debug("JEEDOMCHROMECAST------ New media status " + str(new_mediastatus)) - if self.now_playing==True and new_mediastatus.player_state!="BUFFERING" and not self.disable_notif : - self._internal_send_now_playing() + if new_mediastatus.player_state!="BUFFERING" and not self.disable_notif : + if not globals.disable_mediastatus : + self._internal_send_now_playing_statusupdate(new_mediastatus) + if self.now_playing==True : + self._internal_send_now_playing() def new_connection_status(self, new_status): # CONNECTING / CONNECTED / DISCONNECTED / FAILED / LOST @@ -388,11 +392,11 @@ def _internal_get_status(self): "display_name" : self.gcast.status.display_name if self.gcast.status.display_name is not None else globals.DEFAULT_NODISPLAY, "status_text" : self.gcast.status.status_text if self.gcast.status.status_text!="" else globals.DEFAULT_NOSTATUS, "is_busy" : not self.gcast.is_idle, - "title" : "" if playStatus is None else playStatus.title, - "artist" : "" if playStatus is None else playStatus.artist, - 'series_title': "" if playStatus is None else playStatus.series_title, - "stream_type" : "" if playStatus is None else playStatus.stream_type, - "player_state" : "" if playStatus is None else playStatus.player_state, + "title" : "" if playStatus.title is None else playStatus.title, + "artist" : "" if playStatus.artist is None else playStatus.artist, + 'series_title': "" if playStatus.series_title is None else playStatus.series_title, + "stream_type" : "" if playStatus.stream_type is None else playStatus.stream_type, + "player_state" : "" if playStatus.player_state is None else playStatus.player_state, } return status else : @@ -425,6 +429,28 @@ def _internal_status_different(self, new_status): return True return False + def _internal_send_now_playing_statusupdate(self, new_nowplaying): + prev_nowplaying = self.previous_nowplaying + test_dif = False + if 'title' not in prev_nowplaying : + test_dif = True + elif prev_nowplaying['title'] != new_nowplaying.title : + test_dif = True + elif prev_nowplaying['player_state'] != new_nowplaying.player_state and new_nowplaying.player_state!=['UNKNOWN'] : + test_dif = True + + if test_dif==True : + mediastatus = { + "uuid" : self.uuid, + "title" : '' if new_nowplaying.title is None else new_nowplaying.title, + "artist" : '' if new_nowplaying.artist is None else new_nowplaying.artist, + 'series_title': '' if new_nowplaying.series_title is None else new_nowplaying.series_title, + "player_state" : '' if new_nowplaying.player_state is None else new_nowplaying.player_state, + } + logging.debug("JEEDOMCHROMECAST------ NOW PLAYING STATUS SEND " + str(mediastatus)) + self.previous_nowplaying = mediastatus + globals.JEEDOM_COM.add_changes('devices::'+self.uuid, {'uuid': self.uuid, 'typemsg': 'info', 'status': mediastatus}) + def getDefinition(self): status = { "friendly_name" : self.gcast.device.friendly_name, @@ -615,11 +641,14 @@ def action_handler(message): gcast = jcast.gcast try: if app == 'media' : # app=media|cmd=play_media|value=http://bit.ly/2JzYtfX,video/mp4,Mon film + if cmd == 'NONE' : + cmd = 'play_media' possibleCmd = ['play_media'] if cmd in possibleCmd : if 'offset' in command and float(command['offset'])>0 and 'current_time' not in value : value = value + ',current_time:'+ str(command['offset']) + value = value.replace('local://', globals.JEEDOM_WEB+'/plugins/googlecast/'+globals.localmedia_folder+'/') fallbackMode=False player = jcast.loadPlayer('media', { 'quitapp' : quit_app_before}) eval( 'player.' + cmd + '('+ gcast_prepareAppParam(value) +')' ) @@ -627,6 +656,8 @@ def action_handler(message): elif app == 'web': # app=web|cmd=load_url|value=https://news.google.com,True,5 force_register=True + if cmd == 'NONE' : + cmd = 'load_url' possibleCmd = ['load_url'] if cmd in possibleCmd : fallbackMode=False @@ -647,6 +678,8 @@ def action_handler(message): logging.error("ACTION------ YouTube not availble on Google Cast Audio") elif app == 'spotify': # app=spotify|cmd=launch_app|user=XXXXXX|pass=YYYY|value + if cmd == 'NONE' : + cmd = 'play_media' possibleCmd = ['play_media'] if cmd == 'play_media' : fallbackMode=False @@ -695,6 +728,8 @@ def action_handler(message): elif app == 'plex': # app=plex|cmd=pause quit_app_before=False + if cmd == 'NONE' : + cmd = 'play_media' possibleCmd = ['play_media','play', 'stop', 'pause', 'next', 'previous'] if cmd in possibleCmd : fallbackMode=False @@ -818,6 +853,94 @@ def action_handler(message): logging.debug("ACTION------Mute off action") gcast.set_volume_muted(False) fallbackMode=False + + elif cmd == 'notif': + logging.debug("ACTION------NOTIF action") + forcevol = False + if 'forcevol' in command : + forcevol = True + type = 'audio' + if 'type' in command : + type = command['type'] + resume = True + if 'noresume' in command : + resume = False + durationparam = 0 + if 'duration' in command : + durationparam = float(command['duration']) + curvol = jcast.getCurrentVolume() + if curvol == vol and not forcevol : + vol = None + need_duration=False + if vol is not None or quit==True or resume==True : + need_duration=True + + url,duration,mp3filename=get_notif_data(value, need_duration) + if durationparam > 0 : + duration = durationparam + if url is not None : + thumb=globals.JEEDOM_WEB + '/plugins/googlecast/desktop/images/notif.png' + jcast.disable_notif = True + if resume: + jcast.prepareTTSplay() + player = jcast.loadPlayer(app, { 'quitapp' : False, 'wait': wait}) + if vol is not None : + gcast.media_controller.pause(); + time.sleep(0.1) + gcast.set_volume(vol/100) + time.sleep(0.1) + if type == 'audio' : + player.play_media(url, 'audio/mp3', 'NOTIF', thumb=thumb, add_delay=0.1, stream_type="LIVE") + else : + player.play_media(url, 'video/mp4', 'NOTIF', thumb=thumb, add_delay=0.1, stream_type="LIVE") + player.block_until_active(timeout=2); + jcast.disable_notif = False + sleep_done = False + if vol is not None : + time.sleep(duration+1) + if sleep>0 : + time.sleep(sleep) + sleep=0 + gcast.set_volume(curvol/100) + vol = None + sleep_done = True + if durationparam > 0 : + if sleep_done==False : + time.sleep(duration) + sleep_done = True + gcast.media_controller.stop() + if quit : + if vol is None : + time.sleep(duration+1) + sleep_done = True + gcast.quit_app() + if resume: + if vol is None and sleep_done==False : + time.sleep(duration+1) + forceapplaunch = False + if 'forceapplaunch' in command : + forceapplaunch = True + prevcommand = jcast.getPreviousPlayerCmd(forceapplaunch) + if prevcommand is not None : + newMessage = { + 'cmd' : 'action', + 'delegated' : True, + 'resume' : True, + 'device' : {'uuid' : uuid, 'source' : message['device']['source'] }, + 'command' : prevcommand + } + logging.debug("NOTIF------DELEGATED RESUME AFTER NOTIF for uuid : " + uuid) + time.sleep(0.3) + jcast.resetPreviousPlayerCmd() + thread.start_new_thread( action_handler, (newMessage,)) + else : + logging.debug("NOTIF------Resume is not possible!") + else : + logging.debug("NOTIF------Error while getting local media !") + sendErrorDeviceStatus(uuid, 'ERROR') + + fallbackMode=False + elif cmd == 'tts': logging.debug("ACTION------TTS action") lang = globals.tts_language @@ -857,17 +980,20 @@ def action_handler(message): if generateonly == False : url,duration,mp3filename=get_tts_data(value, lang, engine, speed, forcetts, need_duration, silence) if url is not None : - if vol is not None : - gcast.set_volume(vol/100) - time.sleep(0.1) thumb=globals.JEEDOM_WEB + '/plugins/googlecast/desktop/images/tts.png' jcast.disable_notif = True if resume: jcast.prepareTTSplay() player = jcast.loadPlayer(app, { 'quitapp' : False, 'wait': wait}) + if vol is not None : + gcast.media_controller.pause(); + time.sleep(0.1) + gcast.set_volume(vol/100) + time.sleep(0.1) player.play_media(url, 'audio/mp3', 'TTS', thumb=thumb, add_delay=0.1, stream_type="LIVE"); player.block_until_active(timeout=2); jcast.disable_notif = False + vol_done = False if vol is not None : time.sleep(duration+(silence/1000)+1) if sleep>0 : @@ -875,12 +1001,14 @@ def action_handler(message): sleep=0 gcast.set_volume(curvol/100) vol = None + vol_done = True + if quit : if vol is None : time.sleep(duration+(silence/1000)+1) gcast.quit_app() if resume: - if vol is None : + if vol is None and vol_done==False : time.sleep(duration+(silence/1000)+1) forceapplaunch = False if 'forceapplaunch' in command : @@ -900,9 +1028,9 @@ def action_handler(message): thread.start_new_thread( action_handler, (newMessage,)) else : logging.debug("TTS------Resume is not possible!") - else : - logging.debug("TTS------File generation failed !") - sendErrorDeviceStatus(uuid, 'ERROR') + else : + logging.debug("TTS------File generation failed !") + sendErrorDeviceStatus(uuid, 'ERROR') else : logging.error("TTS------Only generating TTS file without playing") get_tts_data(value, lang, engine, speed, forcetts, False, silence) @@ -1151,6 +1279,33 @@ def get_tts_data(text, language, engine, speed, forcetts, calcduration, silence= filenamemp3=None return urltoplay, duration_seconds, filenamemp3 +def get_notif_data(mediafilename, calcduration): + try: + urltoplay = globals.JEEDOM_WEB+'/plugins/googlecast/'+globals.localmedia_folder+'/'+mediafilename + filename = os.path.join(globals.localmedia_fullpath,mediafilename) + if os.path.isfile(filename) : + if calcduration : + extension = os.path.splitext(filename)[1] + if extension.lower()=='.mp3' : + notifSound = AudioSegment.from_mp3(filename) + else : + notifSound = AudioSegment.from_file(filename, extension.lower().replace('.','')) + duration_seconds = notifSound.duration_seconds + else: + logging.debug("CMD-NOTIF------File doesn't exist (" + filename + ')') + urltoplay=None + duration_seconds=0 + filename=None + + except Exception as e: + logging.error("CMD-NOTIF------Error processing file (%s)" % str(e)) + logging.debug(traceback.format_exc()) + urltoplay=None + duration_seconds=0 + filename=None + logging.debug("CMD-NOTIF------NOTIF debug : " + urltoplay + ", duration: " + str(duration_seconds)) + return urltoplay, duration_seconds, filename + def logByTTS(text_id): lang = globals.tts_language engine = globals.tts_engine @@ -1466,10 +1621,12 @@ def scanner(name): globals.SCAN_PENDING = False def sendErrorDeviceStatus(uuid, message, online=True): + # send to plugin errorstatus = { "uuid" : uuid, "display_name" : message, "status_text" : message } globals.JEEDOM_COM.add_changes('devices::'+uuid, {'uuid': uuid, 'typemsg': 'error', 'status': errorstatus}) + # send to now playing widget data = { "uuid" : uuid, "online" : online, "is_active_input" : False, "is_stand_by" : False,