Plex Webhooks

thats OK.
saved me the frustration of looking.
thanks for the help!

Question for you regarding the media scene app. So I have a set of accent lights right now that are setup as a simple rule to turn on at 4:30pm and off at 11:30pm. I have the media scene for my living room to turn off the accent lighting when a movie starts which works. However, if its after 11:30pm and the accent lighting was already off, and the movie starts, and then ends, the accent lighting is turned back on, even though it was already off before the movie began. And yes, I have the option selected to remember the switch state when playback ends. Thoughts on why it's turning on the switch that is set to off even though it was off before playback began? Anyway to get it to evaluate against another rule?

Are you seeing anything in the logs, it's likely that there's a bug, I haven't tested that feature since migrating from ST, so there is likely a difference required in the code between systems, from memory I think current state is called slightly differently, I might not be able to look myself until Monday..

Is there any logs for media scene? If so I’m not seeing any.

Apologies for the NOOB question but I am a total NOOB here. this is my first post EVER! I am a Wink Refugee dipping my toes into Hubitat for the first time. I was looking for Plex integrations and this is the only thread I have come across. I copied the parent and child Code from the Nov '18 post and created the apps for them but I am still trying to figure out the integration of the media player. Is there a step by step or how to on making this work? TIA for any responses.

Welcome to hubitat, everyone has to start somewhere!

Do you have Plex pass or free Plex? If pass then you need to setup your Plex server webhooks, otherwise you will need to poll for updates, how you do this is dependant on your setup.. what are you running Plex server on?

I do have plex pass and I'm running on Windows 10. I am unfamiliar with Webhooks but I have played with Zapier to make them work on Discord.

It should be as simple as copying the webhook URL from Plex communicator and pasting in to the Plex server.. it should then all just work..

Like I said total NOOB!
I didn't know what Plex Communicator was. I tried to copy the github code and create the app and driver but I received error messages.
I got this when I tried to create the Plex Communicator Device: "Metadata Error: Capability 'musicPlayer' not found."
I got this when I tried to create Plex Communicator App:"unable to resolve class physicalgraph.device.HubAction @ line 262, column 22."
I know what neither of these things are nor am I mentally equipped to correct them on my own. Is there a front to back installation guide or should I just leave this alone?

Hi: Another noob here...

I have taken the "parent" and "Child" code from the Nov 18 post. and added them both as apps. After that I am stuck...

I have plex pass so can use webhooks on my server but I cannot work out what the webhook should be from the app.

Open the app, press connection methods then press Plex webhook settings, it will give you the webhook URL to paste in to Plex server, if you don't know how to do this then Google is your friend :slight_smile:

So I've just re-read you message, you mention GitHub code, the Hubitat version does not exist on GitHub.. so make sure you use the code in this post, you need to copy both the app and the driver to their respective locations..

I think you are using the SmartThings version, hence the error stating musicPlayer and not "Music Player"

Unless I'm reading your issue wrong and you can't even create the app? If that's the case, it's likely because you haven't copied all the code or copied a bit too much, i.e. some of the post rather than just the code bit..

So I've just tested this on my setup and it appears to be working.. so I suspect it may be related to misunderstanding of settings? So under all of the settings there should be nothing under the "stop" actions (bulbs or switches) for the items you want to revert.

This will only revert switch states so whatever you want to revert you will need to put under "switches on when playing" or "switches off when playing" if that doesn't solve your issue then if you can post your settings I'll see what I can do..

I'm getting an error when saving rooms currently although it appears to save an work correctly.. EDIT: Fixed code below

Updated Code For Media Scene below, I have removed Routines which don't exist in Hubitat!

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: "parentPage"
	page name: "parentPage"
}

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' />&nbsp;<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' />&nbsp;<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}"}
}
2 Likes

Hi Jebbett,

Thank you - I have got it working now...

Needed to install connector app and driver and media scene apps.
I misunderstood and thought mediascene was an updated version of the original.

Many thanks.

1 Like

Actually Same^^^^^
Thanks so much for your help and time developing this.

1 Like

Thanks for the update!

1 Like

Maybe I'm just being dense, but how do I get the "connector app"? I've installed the app and driver code.

He meant communicator, you should just need communicator and the driver if you have a Plex pass..

Media scene is a generic additional application I wrote to control lighting etc based on the state of a media device..

This works great, thanks jebbett!