diff --git a/README.md b/README.md index a79d7d1..0b75b35 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ Plugin for Jeedom Open Source domotic solution. 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") diff --git a/core/class/googlecast.class.php b/core/class/googlecast.class.php index e7479c7..3fccc01 100644 --- a/core/class/googlecast.class.php +++ b/core/class/googlecast.class.php @@ -17,8 +17,8 @@ */ /* * ***************************Includes********************************* */ -//error_reporting(E_ALL); -//ini_set('display_errors', 'On'); +error_reporting(E_ALL); +ini_set('display_errors', 'On'); require_once dirname(__FILE__) . "/googlecast_utils.inc.php"; @@ -713,6 +713,10 @@ public function getChromecastIP() { return $this->getConfiguration('ip', ''); } + public function getUUID() { + return $this->getConfiguration('uuid', ''); + } + public function getChromecastURI() { return $this->getConfiguration('uri', ''); } @@ -935,6 +939,7 @@ public function refreshStatusByUUID($uuid) { } public function helperSendSimpleCmd($command_name, $value=null, $_callback=null, $_source='googlecast', $_app='media', $_appid='CC1AD845') { + $command_name = googlecast_utils::getFullCmdTranslation($command_name); $fulldata = array( 'apikey' => jeedom::getApiKey('googlecast'), 'cmd' => 'action', @@ -954,13 +959,16 @@ public function helperSendSimpleCmd($command_name, $value=null, $_callback=null, return self::socket_connection( json_encode($fulldata) ); } + // send commands as php array of by sequence (seperated by $$) public function helperSendCustomCmd($_commands, $_callback=null, $_source='googlecast', $_app='media', $_appid='CC1AD845') { $datalist = array(); $commandlist = $_commands; if ( !is_array($_commands) ) { - $commandlist = array($_commands); + $commandlist = explode('$$', $_commands); } foreach ($commandlist as $commandstring) { + $commandstring = googlecast_utils::getFullCmdTranslation($commandstring); + $data = array(); $splitcmd = explode('|', $commandstring); $splitcount = count($splitcmd); @@ -988,6 +996,7 @@ public function helperSendCustomCmd($_commands, $_callback=null, $_source='googl } array_push($datalist, $data); } + log::add('googlecast','debug','helperSendCustomCmd : ' . print_r($datalist, true)); $fulldata = array( 'apikey' => jeedom::getApiKey('googlecast'), @@ -1014,7 +1023,7 @@ public function helperSendConfigInfoCmd($_commands, $_destLogicalId, $setType=fa } public function getInfoHttpSimple($cmdLogicalId, $destLogicalId=null) { - $cmdLogicalIdTranslate = googlecast_utils::getCmdTranslation($cmdLogicalId); + $cmdLogicalIdTranslate = googlecast_utils::getFullCmdTranslation($cmdLogicalId); if (is_null($destLogicalId)) { $destLogicalId = ($cmdLogicalIdTranslate!=$cmdLogicalId ? $cmdLogicalId : null); } @@ -1229,7 +1238,7 @@ private function recursePath($array, $pathList, $errorRet) { } public function setInfoHttpSimple($cmdLogicalId, $destLogicalId=null) { - $cmdLogicalId = googlecast_utils::getCmdTranslation($cmdLogicalId); + $cmdLogicalId = googlecast_utils::getFullCmdTranslation($cmdLogicalId); return $this->setInfoHttp($cmdLogicalId, false, $destLogicalId); } @@ -1293,6 +1302,26 @@ public function setInfoHttp($cmdLogicalId, $showError=false, $destLogicalId=null } } + public function getInfoValue($logicalID) { + $cmd = $this->getCmd('info', $logicalID); + if (!is_object($cmd) ) { + return null; + } + $ret = $cmd->execCmd(); + if ( is_null($ret) ) { + return ''; + } + return $ret; + } + + public function isOnline() { + $ret = $this->getInfoValue('online'); + if ($ret==1 or $ret===true ) { + return true; + } + return false; + } + } class googlecastcmd extends cmd { @@ -1303,84 +1332,99 @@ class googlecastcmd extends cmd { /* * *********************Methode d'instance************************* */ public function execute($_options = null) { - $listCmd = googlecast_utils::getCmdTranslation($this->getLogicalId()); - $eqLogic = $this->getEqLogic(); + $originalLogicalId = $this->getLogicalId(); - # special case of custom command - 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'; - } - } + $originalLogicalId = googlecast_utils::getFullCmdTranslation($originalLogicalId); - // 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"); - } - else { - return; - } - } + if ( $this->getType() == 'action' ) { + # special case of custom command + if ($originalLogicalId == "customcmd") { + $originalLogicalId = trim($_options['message']); + } - // '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()); + # special case of 'action' command with subtype 'list' starting with 'cmdlist_' + elseif ( strpos($originalLogicalId, 'cmdlist_')===0 ) { + $originalLogicalId = str_replace('^', '|', trim($_options['select'])); // replace ^ by | + if ($originalLogicalId=='') { // case of default value ('none' selected) + $originalLogicalId='quit_app'; } - } + } - if ($listCmd == "refreshconfig") { - return; // do not need to go further in that case + if ($originalLogicalId=='') { + return; } } - // 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); - return; + $listCmd = $originalLogicalId; + + $eqLogic = $this->getEqLogic(); + + switch ($this->getSubType()) { // manage placeholder replacement + case 'slider': + $listCmd = str_replace('#slider#', $_options['slider'], $listCmd); + break; + case 'color': + $listCmd = str_replace('#','',str_replace('#color#', $_options['color'], $listCmd)); + break; + case 'select': + $listCmd = str_replace('#listValue#', $_options['select'], $listCmd); + break; + case 'message': + $listCmd = str_replace('#message#', $_options['message'], $listCmd); + if ( isset($_options['title']) ) { + $listCmd = str_replace('#title#', $_options['title'], $listCmd); + } + if ( isset($_options['volume']) ) { + $listCmd = str_replace('#volume#', $_options['volume'], $listCmd); + } + break; } $datalist=array(); $cmdgroups = explode('$$', $listCmd); // split multiple commands (sequences) + foreach ($cmdgroups as $listCmd) { + $originalListCmd = $listCmd; + $listCmd = googlecast_utils::getFullCmdTranslation($originalListCmd); $data = array(); + + // 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, $originalListCmd); + log::add('googlecast','debug',"Envoi d'une commande GoogleCast API http depuis Jeedom"); + } + else { + continue; + } + } + + // '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::getFullCmdTranslation($cmd->getLogicalId()); + if (stristr($logicalId, 'cmd=getconfig')!=false) { + $eqLogic->getInfoHttpSimple($logicalId, $cmd->getLogicalId()); + } + } + + if ($listCmd == "refreshconfig") { + continue; // 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); + continue; + } + $values = explode('|', $listCmd); // split commands foreach ($values as $value) { $value = explode('=', $value); 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; - case 'color': - $data[trim($value[0])] = str_replace('#','',trim(str_replace('#color#', $_options['color'], $value[1]))); - break; - case 'select': - $data[trim($value[0])] = trim(str_replace('#listValue#', $_options['select'], $value[1])); - break; - case 'message': - $data[trim($value[0])] = trim(str_replace('#message#', $_options['message'], $value[1])); - 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]); - } + $data[trim($value[0])] = trim($value[1]); } elseif (count($value) == 1) { // // X only, then assume this is a command $data['cmd'] = trim($value[0]); @@ -1388,17 +1432,19 @@ public function execute($_options = null) { case 'slider': $data['value'] = $_options['slider']; break; + /* case 'select': $data['value'] = trim($_options['select']); break; case 'message': $data['value'] = trim($_options['message']); break; + */ } } } if (count($data) == 0) { // something is wrong because no value - return; + continue; } array_push($datalist, $data); // push in the sequence array (even if only one command) } diff --git a/core/class/googlecast_utils.inc.php b/core/class/googlecast_utils.inc.php index 9721982..4faf94d 100644 --- a/core/class/googlecast_utils.inc.php +++ b/core/class/googlecast_utils.inc.php @@ -4,7 +4,7 @@ class googlecast_utils { - public static function getCmdTranslation($logicalId) { + public static function getFullCmdTranslation($logicalId) { $ret = $logicalId; if ( $logicalId == 'speak' ) { @@ -21,7 +21,6 @@ public static function getCmdTranslation($logicalId) { $radioname = strtolower(str_replace("radio_", "", $logicalId)); $radioArray = json_decode(file_get_contents(dirname(__FILE__) . "/../webradios/radiolist.json"), true); if ( isset($radioArray[$radioname]) ) { - $radio = $radioArray[$radioname]; $ret = "app=media|value='".$radio['location']."','audio/mpeg',title:'".$radio['title']."',thumb:'".$radio['image']."'"; } @@ -131,6 +130,11 @@ public static function getCmdTranslation($logicalId) { return $ret; } + + public static function getCmdTranslation($cmd) { + return $cmd; + } + public static function getJsonPathResult($json, $path) { $store = new JsonStore($json); return $store->get($path); diff --git a/core/webradios/radiolist.json b/core/webradios/radiolist.json index 209e966..df4c117 100644 --- a/core/webradios/radiolist.json +++ b/core/webradios/radiolist.json @@ -91,8 +91,8 @@ }, "pulsradio_trance": { "location": "http://icecast.pulsradio.com:80/pulstranceHD.mp3", - "title": "PulsRadio Trance", - "image": "http://www.pulsradio.com/trance/logo.png" + "title": "PulsRadio Trance", + "image": "http://www.pulsradio.com/trance/logo.png" }, "pulsradio_lounge": { "location": "http://icecast.pulsradio.com/relaxHD.mp3", diff --git a/docs/fr_FR/index.md b/docs/fr_FR/index.md index b238117..5823d98 100644 --- a/docs/fr_FR/index.md +++ b/docs/fr_FR/index.md @@ -268,9 +268,11 @@ Elles doivent être séparés par *|* - 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) +- brodcast (optional) : 'all' to broadcast to all connected devices or seperated by ',' (ex: 'uuid1,uuid2') 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 +ex broadcast : cmd=quit_app|broadcast=all ``` > **Notes** @@ -574,7 +576,7 @@ if ( !is_object($googlecast) or $googlecast->getIsEnable()==false ) { // variable _test contains 'None' if google cast does not exist or is disable } else { - // Run a command + // Run a command as single command or sequence (seperated by $$) or by sending a php array of commands $ret = $googlecast->helperSendCustomCmd('cmd=tts|value=Test Scénario PHP|vol=100'); $scenario->setData("_test", $ret); // Command launched @@ -605,6 +607,34 @@ else { } ``` +Exemple d'attente de fin de commande TTS ou NOTIF : + +```php +$uuid = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX'; +$maxwait = 30; // 30 sec of max waiting +$retrydelay = 500*1000; // will retry every 500 ms + +$googlecast = googlecast::byLogicalId($uuid, 'googlecast'); +if ( !is_object($googlecast) or $googlecast->getIsEnable()==false or $googlecast->isOnline()==false ) { + return; +} +else { + $command_string = 'cmd=tts|value=Test Scénario PHP pour gérer le délai !|vol=100'; + $googlecast->helperSendCustomCmd($command_string); + sleep(2); // make sure command has started + $status = $googlecast->getInfoValue('status_text'); + $starttime = time(); + while ($status=='Casting: TTS' or $status=='Casting: NOTIF' ) { + usleep($retrydelay); // or sleep(1); // 1 sec + $status = $googlecast->getInfoValue('status_text'); + if ( (time()-$starttime)>$maxwait ) { + break; + } + } + return; +} +``` + ### Utilisation avec interactions et IFTTT #### Interactions diff --git a/resources/googlecast.py b/resources/googlecast.py index be8b517..e471f89 100644 --- a/resources/googlecast.py +++ b/resources/googlecast.py @@ -573,6 +573,26 @@ def action_handler(message): for command in commandlist : uuid = srcuuid + + if 'broadcast' in command : + broadcastList = command['broadcast'] + if broadcastList == 'all' : + uuidlist = list(globals.GCAST_DEVICES.keys()) + else : + uuidlist = broadcastList.split(',') + for newUuid in uuidlist : + newcmd = command.copy() + del newcmd['broadcast'] + newMessage = { + 'cmd' : 'action', + 'delegated' : True, + 'device' : {'uuid' : newUuid, 'source' : message['device']['source'] }, + 'command' : newcmd + } + logging.debug("ACTION------DELEGATED command to other uuid : " + newUuid) + thread.start_new_thread( action_handler, (newMessage,)) + continue + if 'uuid' in command : if 'nothread' in command and command['uuid'] in globals.GCAST_DEVICES: uuid = command['uuid'] @@ -944,20 +964,8 @@ def action_handler(message): 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 : + resumeOk = manage_resume(uuid, message['device']['source'], forceapplaunch, 'NOTIF') + if resumeOk==False : logging.debug("NOTIF------Resume is not possible!") else : logging.debug("NOTIF------Error while getting local media !") @@ -1037,20 +1045,9 @@ def action_handler(message): 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("TTS------DELEGATED RESUME AFTER TTS for uuid : " + uuid) - time.sleep(0.3) - jcast.resetPreviousPlayerCmd() - thread.start_new_thread( action_handler, (newMessage,)) - else : + + resumeOk = manage_resume(uuid, message['device']['source'], forceapplaunch, 'TTS') + if resumeOk==False : logging.debug("TTS------Resume is not possible!") else : logging.debug("TTS------File generation failed !") @@ -1100,6 +1097,16 @@ def action_handler(message): logging.debug("ACTION------Stop action") player.pause() fallbackMode=False + elif cmd == 'resume': + forceapplaunch = False + if 'forceapplaunch' in command : + forceapplaunch = True + resumeOk = manage_resume(uuid, message['device']['source'], forceapplaunch, 'ACTION') + if resumeOk==False : + logging.debug("ACTION------Resume is not possible!") + else : + logging.debug("ACTION------Resume OK") + fallbackMode=False elif cmd == 'sleep': logging.debug("ACTION------Sleep") time.sleep(float(value)) @@ -1140,6 +1147,24 @@ def manage_callback(uuid, callback_type): # todo things for callback before returning value return True +def manage_resume(uuid, source='googlecast', forceapplaunch=False, origin='TTS'): + jcast = globals.GCAST_DEVICES[uuid] + prevcommand = jcast.getPreviousPlayerCmd(forceapplaunch) + if prevcommand is not None : + newMessage = { + 'cmd' : 'action', + 'delegated' : True, + 'resume' : True, + 'device' : {'uuid' : uuid, 'source' : source }, + 'command' : prevcommand + } + logging.debug("RESUME------DELEGATED RESUME AFTER "+origin+" for uuid : " + uuid) + time.sleep(0.3) + jcast.resetPreviousPlayerCmd() + thread.start_new_thread( action_handler, (newMessage,)) + return True + return False + def get_tts_data(text, language, engine, speed, forcetts, calcduration, silence=300): srclanguage = language if not globals.tts_gapi_haskey and (engine=='gttsapi' or engine=='gttsapidev') :