I don't see these commented lines in the Plex Communicator player. Maybe I am using an older version? The header says version 2018-11-17 2.0
@ckronengold I am using the correct device to monitor. I am getting JSON events from the Apple TV Plex App. They just don't trigger anything. I just realized that maybe I am using some older code. Is there a link somewhere to the most recent release perhaps so I can compare versions?
@jebbett So I figured out what the issue is. It seems like it's related to using multiple MediaScene rooms. I wiped out all the apps and drivers and started from scratch with everything. As long as the Apple TV is the first device to add to the Media Scene app, everything works. If you start with another device/room and then try to add a second Room or third device to monitor, I get an error even though the Media Scene room gets created. Only the first room created works.
When clicking Done to create the second Room, I get
"Unexpected Error
An unexpected error has occurred trying to load the app. Check [Logs] for more information."
There is nothing in the logs to show why this error pops up.
I was having the same thing happen when adding 4 rooms recently. They seem to be working properly in the limited testing I did, but the errors were surprising.
Thanks for confirming this. I thought I was losing my mind today lol
I've fixed it, stupid error but no idea how the same error wasn't triggered when opening the main app..
MediaScene:
==================
PARENT APP
/**
* Media Scene
*
* Copyright 2018 Jake Tebbett
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* VERSION CONTROL - Media Scene
* ###############
*
* v1.0 - Initial Release
*
*/
definition(
name: "MediaScene",
namespace: "jebbett",
author: "Jake Tebbett",
description: "Control scenes based on media state and type",
category: "My Apps",
iconUrl: "https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png",
iconX2Url: "https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png",
iconX3Url: "https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png"
)
def installed() {
state.installedOK = true
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
}
preferences {
page name: "installedCheck"
page name: "installPage"
page name: "mainMenu"
}
def installedCheck(){
if (state?.installedOK){
mainMenu()
}else{
installPage()
}
}
def installPage() {
dynamicPage(name: "installPage", title: "Rooms", install: true, uninstall: false, submitOnChange: true) {
section(""){
paragraph "<h1><img src='https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png' width='64' height='64' /> <strong><span style='color: #ff6600;'>MediaScene</span></strong></h1>"
paragraph "Press 'Done' to install..."
}
}
}
def mainMenu() {
dynamicPage(name: "mainMenu", install: true, uninstall: true, submitOnChange: true) {
section(""){
paragraph "<h1><img src='https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png' width='64' height='64' /> <strong><span style='color: #ff6600;'>MediaScene</span></strong></h1>"
paragraph "<b>MediaScene allows you to control devices based on the playback state of a media device</b>"
paragraph "This can be used with devices created by Plex Communicator or other media devices"
}
section {
app(name: "childapp", appName: "MediaSceneChild", namespace: "jebbett", title: "Create New Room", multiple: true)
}
section {
input(name: "debugLogging", type: "bool", title: "Enable Debugging", required: false)
}
}
}
CHILD APP
/**
* Media Scene
*
* Copyright 2018 Jake Tebbett
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* VERSION CONTROL - Media Scene Child
* ###############
*
* v1.0 - Initial Release
* v1.1 - Removed Routines
*/
definition(
name: "MediaSceneChild",
namespace: "jebbett",
author: "Jake Tebbett",
description: "Control scenes based on media state and type",
category: "My Apps",
parent: "jebbett:MediaScene",
iconUrl: "https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png",
iconX2Url: "https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png",
iconX3Url: "https://github.com/jebbett/MediaScene/raw/master/Icons/MediaScene.png"
)
def installed() {
initialize()
}
def updated() {
unsubscribe()
initialize()
}
def initialize() {
state.catcherRunning = false
subscribe(playerDT, "status", PlayerDTCommandRecieved)
}
preferences {
page name: "childPage"
//Child Pages
page name: "pageDoThis"
page name: "pageWhenThis"
page name: "pageMediaSettings"
}
def childPage() {
dynamicPage(name: "childPage", uninstall: true, install: true) {
section() {
label title: "<b>Room Name</b>", defaultValue: app.label, required: false
}
section ("<b>For This Device</b>"){
input(name: "playerDT", type: "capability.musicPlayer", title: "Media Player Device", multiple: false, required:false)
}
section("<b>Lights</b>") {
input "dimmers1", "capability.switchLevel", title: "Adjust level of these bulbs", multiple: true, required: false, submitOnChange: true
input "hues1", "capability.colorControl", title: "Adjust level and color of these bulbs", multiple:true, required:false, submitOnChange: true
if(hues1||dimmers1) {
input(name: "iLevelOnPlay1", type: "number", title: "Level on Play", defaultValue:0)
input(name: "iLevelOnPause1", type: "number", title: "Level on Pause", defaultValue:30)
input(name: "iLevelOnStop1", type: "number", title: "Level on Stop", defaultValue:100)
}
if(hues1) {
input "colorOnPlay", "enum", title: "Hue Bulbs > Color On Play", required: false, multiple: false, submitOnChange: true,
options: ["Soft White", "White", "Daylight", "Warm White", "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink"]
input "colorOnPause", "enum", title: "Hue Bulbs > Color On Pause", required: false, multiple: false, submitOnChange: true,
options: ["Soft White", "White", "Daylight", "Warm White", "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink"]
input "colorOnStop", "enum", title: "Hue Bulbs > Color On Stop", required: false, multiple: false, submitOnChange: true,
options: ["Soft White", "White", "Daylight", "Warm White", "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink"]
input(name: "tempOnPlay", description: "1000..9999", type: "number", range: "1000..9999", title: "Color Temperature on Play (°K)", required: false)
input(name: "tempOnPause", description: "1000..9999", type: "number", range: "1000..9999", title: "Color Temperature on Pause (°K)", required: false)
input(name: "tempOnStop", description: "1000..9999", type: "number", range: "1000..9999", title: "Color Temperature on Stop (°K)", required: false)
}
input(name: "bDimOnlyIfOn1", type: "bool", title: "Dim bulbs only if they're already on", required: false)
}
section("<b>Switches</b>") {
input "switches2", "capability.switch", title:"Switches On when Playing", multiple: true, required: false
input "switches1", "capability.switch", title:"Switches Off when Playing", multiple: true, required: false
input(name: "bReturnState1", type: "bool", title: "Switches return to original state when Stopped", required: false)
input(name: "bSwitchOffOnPause1", type: "bool", title: "Switches use Play config when Paused", required: false)
input(name: "switchOnPlay", type: "bool", title: "Switches only change on 'Play'", required: false)
paragraph "The below switches do not toggle off when state becomes inactive, ideal for tiggering external App scenes"
input "mSwitchPlay", "capability.switch", title:"Momentary switch on Play", multiple: true, required: false
input "mSwitchPause", "capability.switch", title:"Momentary switch on Pause", multiple: true, required: false
input "mSwitchStop", "capability.switch", title:"Momentary switch on Stop", multiple: true, required: false
}
section("<b>Modes</b>") {
input "playMode1", "mode", title: "Mode when playing", required:false
input "pauseMode1", "mode", title: "Mode when paused", required:false
input "stopMode1", "mode", title: "Mode when stopped", required:false
}
section("<b>Media Settings</b>") {
input(name: "bTreatTrailersAsPause1", type: "bool", title: "Use pause config for movie trailers", required: false)
input(name: "stopDelay", type: "number", title: "Delay stop action", required:false, defaultValue:0)
input(name: "pauseDelay", type: "number", title: "Delay pause action", required:false, defaultValue:0)
}
section("<b>Restrictions</b>") {
input "mediaTypeOk", "enum", title: "Only for media types:", multiple: true, submitOnChange: true, required: false,
options: ['movie', 'episode', 'clip', 'track']
input "disabled", "capability.switch", title: "Switch to disable when On", required: false, multiple: false
input "activeMode", "mode", title: "Only run in selected modes", multiple: true, required:false
}
}
}
// Recieve command from MusicPlayer device type
def PlayerDTCommandRecieved(evt){
def mediaType = playerDT.currentplaybackType ?: null
if(evt.value=="playing"){ AppCommandRecieved("onplay", mediaType)}
else if(evt.value=="stopped"){ AppCommandRecieved("onstop", mediaType)}
else if(evt.value=="paused"){ AppCommandRecieved("onpause", mediaType)}
}
def AppCommandRecieved(command, mediaType) {
// Stop running if disable switch is activated
if (disabled != null) {if(disabled.currentSwitch == "on") {logWriter ("Disabled via switch"); return}}
if (activeMode != null && !activeMode.contains(location.mode)) {logWriter ("Disabled via invalid mode"); return}
// Check if Media Type is correct
if(mediaTypeOk){
def mediaTypeFound = mediaTypeOk.find { item -> item == mediaType}
if(mediaTypeFound == null) {logWriter ("Match NOT found for media type: ${mediaType}"); return}
}
//Translate play to pause if bTreatTrailersAsPause is enabled for this room
if(settings?.bTreatTrailersAsPause1 && mediaType == "clip" && command == "onplay") {command = "onpause"}
// Unschedule delays
unschedule(StopCommand)
unschedule(PauseCommand)
// Play, Pause or Stop
if (command == "onplay") {
logWriter ("Playing")
PlayCommand()
}
else if (command == "onpause") {
logWriter ("Paused")
if(!settings?.pauseDelay || pauseDelay == "0"){
PauseCommand()
}else{
logWriter ("Pause Action Delay")
runIn(settings?.pauseDelay.value, PauseCommand)
}
}
else if (command == "onstop") {
logWriter ("Stopped")
if(!settings?.stopDelay || stopDelay == "0"){
StopCommand()
}else{
logWriter ("Stop Action Delay")
runIn(settings?.stopDelay.value, StopCommand)
}
}
}
def PlayCommand(){
if(!state.catcherRunning){
catchState("switches1")
catchState("switches2")
state.catcherRunning = true
}
if(settings?.playMode1){setLocationMode(playMode1)}
SetLevels(iLevelOnPlay1, colorOnPlay, tempOnPlay)
SetSwitchesOff()
mSwitchPlay?.on()
}
def PauseCommand(){
if(settings?.pauseMode1){setLocationMode(pauseMode1)}
SetLevels(iLevelOnPause1, colorOnPause, tempOnPause)
mSwitchPause?.on()
if(settings?.bSwitchOffOnPause1) {
SetSwitchesOff()
} else {
if(state.catcherRunning && settings?.bReturnState1){
returnToState("switches1")
returnToState("switches2")
state.catcherRunning = false
}else{
SetSwitchesOn()
state.catcherRunning = false
}
}
}
//Stop command
def StopCommand(){
if(settings?.stopMode1){setLocationMode(settings?.stopMode1)}
SetLevels(iLevelOnStop1, colorOnStop, tempOnStop)
mSwitchStop?.on()
if(state.catcherRunning && settings?.bReturnState1){
returnToState("switches1")
returnToState("switches2")
state.catcherRunning = false
}else{
SetSwitchesOn()
state.catcherRunning = false
}
}
// Actions
def SetSwitchesOn() {
if(!switchOnPlay){
switches1?.on()
switches2?.off()
}
}
def SetSwitchesOff() {
switches1?.off()
switches2?.on()
}
def SetLevels(level, acolor, temp) {
// If color specified set hues
if (level != null) {
def hueColor = 23
def saturation = 56
switch(acolor) {
case "White":
hueColor = 52
saturation = 19
break;
case "Daylight":
hueColor = 53
saturation = 91
break;
case "Soft White":
hueColor = 23
saturation = 56
break;
case "Warm White":
hueColor = 20
saturation = 80 //83
break;
case "Blue":
hueColor = 70
break;
case "Green":
hueColor = 35
break;
case "Yellow":
hueColor = 25
break;
case "Orange":
hueColor = 10
break;
case "Purple":
hueColor = 75
break;
case "Pink":
hueColor = 83
break;
case "Red":
hueColor = 100
break;
}
if (settings?.bDimOnlyIfOn1){
if(acolor != null){ hues1?.each { hue -> if ("on" == hue.currentSwitch) { hue.setColor([hue: hueColor, saturation: saturation, level: level]) } } }
else if(temp != null){ hues1?.each { hue -> if ("on" == hue.currentSwitch) { hue.setColorTemperature(temp) } } }
else { hues1?.each { hue -> if ("on" == hue.currentSwitch) { hue.setLevel(level) } } }
dimmers1?.each { bulb -> if ("on" == bulb.currentSwitch) { bulb.setLevel(level) } }
}else{
// color takes priority over temperature, dimmers will still set temperature if available
if(acolor != null){ hues1?.setColor([hue: hueColor, saturation: saturation, level: level]) }
else if(temp != null){ hues1?.setColorTemperature(temp) }
dimmers1?.setLevel(level)
}
}
}
//Save state
private catchState(switches) {
settings."${switches}"?.each { switcher -> state."${switcher.id}State" = switcher.currentValue("switch")
logWriter (switcher.currentValue("switch"))
}
}
//Return to state
private returnToState(switches) {
settings."${switches}"?.each {switcher ->
if(state."${switcher.id}State" == "on") {switcher.on()}
if(state."${switcher.id}State" == "off") {switcher.off()}
}
}
//// GENERIC CODE
private def logWriter(value) {
if(parent.debugLogging) {log.debug "Media Scene [${app.label}] >> ${value}"}
}
@jebbett thank you so much! So do I have to just update the code, or do I have to delete the App and the Room I already created and re-create them from scratch?
Just update the code, no problem
Works perfectly! Thank you so much sir! Do you accept donations of any type? I would be happy to contribute towards your time spent on this.
The offer is appreciated, but just do it mostly for my own learning and to automate my own home, other people have helped me in the past, just spend some time helping someone else
@jebbett well, your work is appreciated.
Btw, I can use plexJSON.Metadata.title to grab the title of the Movie playing. But I can't figure out how to set a string global variable from within the app itself so I can use it for other reasons, like notifications or reporting to see what my kids watch for example. As an experiment I am using Pushover notifications whenever they play a movie in their room. I can write title to debug. See screenshot.
Do you know the syntax I should use to set a global variable's value to the movie's title and then call it from somewhere else? Do you have any suggestions?
You need to output the value to the driver, you'll need to update the code, it's completely untested , but had a few mins at work:
Add the first line, amend the second to pull the variable from the webhook:
def mediaTitle = plexJSON.Metadata.title
def plexEvent = [:] << [ id: "$playerID", type: "$mediaType", status: "$status", user: "$userName", title: "$mediaTitle" ]
Add to the DTH OUTPUT bit of the code to send to the driver:
pcd.playbackTitle(event.title)
Then update the driver:
Add an attribute, which you can call from other apps:
attribute "playbackTitle", "string"
Add something to handle the request from the app to the driver and update the attribute:
def playbackTitle(title) {
sendEvent(name: "playbackTitle", value: title);
log.debug "Title set to $title"
}
Oh...so an attribute is basically similar to a global variable you can create on the fly? That's pretty cool. Thanks for this. I really appreciate it!
Every device has a number of attributes that can be called, I don't think there are global variables that exist at app level that can be called across apps (although that may have changed), you can get around this to an extent by publishing global events that other apps can pick up, I've used this for cross app communication in the past for other apps I've not published.. one of which allowed me to control my smart home from facebook messenger.. only to see if I could..
One thing to note with the solution I have provided it's likely its not within spec of the "MusicPlayer" device type, so after a quick hunt the device driver should have the attribute "trackDescription", if you update this in the send event 'name' field too then the update will be within spec, and should* work properly if used in the dashboard..
Let me how you get on with my code changes and if it works, if so I'll update the post on here with the new code..
Your code works great btw, and the attribute is getting set properly. Thanks for that. But I am still not educated enough on how to pass the value to another rule. I named mine PlexTitle to keep it clear like this.
def playbackTitle(PlexTitle) {
sendEvent(name: "trackDescription", value: PlexTitle);
log.debug "Title set to $PlexTitle"
Logs are showing the correct title but when I use another rule to send notifications, I get either null or $PlexTitle or {PlexTitle}. I'm not sure how else to pass it.
trackDescription is the variable and attribute.. you're calling the wrong thing
Changing the other variable does nothing..
lol. I'm an idiot.
I do get a null value when I use trackDescription in another rule though.
Not really sure exactly what you are doing and I'm in a hotel currently so can't check on my setup, but I'm guessing you are trying to pull as a global variable, rather than an attribute on a device? You need to specifically pull the attribute from the device you want to monitor, I'm guessing then you need to pass that to a variable to use how you want, but I'm not sure as I've never done what you are trying to do..
So to be clear GlobalVariables in rule machine are not global across hubitat, just across rule machine rules I believe, global within an application not cross application.. if you need to call something from else where you can call the attribute, or set the attribute as a trigger and use that to update a rule machine variable.. Hope that points you in the right direction, but tell them you have an attribute trackDescription and how you want to use it and one of the rule machine experts on here will tell you exactly what you need to do..
Yeah, that's exactly what I am trying to do. Pass that attribute value to another rule to use it in a push alert to my phone so I know when my kids start a movie and show the title of the movie.
But your portion of the code works great. You are awesome, and thank you again for your help and time.
@jebbett I forgot to include this screenshot to show you that the attribute of the music device gets updated correctly.