Plex Webhooks


#21

When I try to control play back nothing happens. I'll get some logs today.


#22

@sgrayban

Quoted from my post with the code..

Control is a real PITA as there are 3 or 4 different control methods and each device will usually only support one of them if any at all.. I would have to port over Ph4r's code from his fork of Plex Manager, and there is a lot of code required to do this.. and I don't really have the time to invest in doing this..

Plex Communicator is largely based on PlexPlus which is for subscribing to Plex device states only.. but adds device handling similar to Plex HT Manager, but much better as gets around some of the limitations of the plex API, by discovering some devices that do not appear in the devices list, however the ability to control was not ported.

Personally I didn't see the need for control, which is why I haven't invested the time in to it, I just needed it to control my lighting based on what was playing.. I've also got another app that I need to port which is specifically for the lighting side of things, but this can be achieved via rule machine.. just much easier via my app as it handles a lot of things which are less easy to cater for, such as the small gap between TV episodes can cause issues unless you build in a buffer delay..

Sorry probably not the answer you were looking for, and have no issue if someone wants to do a pull request on the code when I load it to Git to add the functionality.. but unless Ph4r moves to hubitat, then I'm not sure many people will have the time / knowledge to do this easily..


#23

Ok.. well it works half way then. Don't give up ! It's a awesome start !


#24

Haha, I haven't given up, just busy with buying a house, moving and work.. and I have 4 more apps I need to port over at some point that I haven't yet got round to.. just to get my system ported over and off ST..

I honestly wish I had the free time to work on this stuff, the only reason I had time to put in this update was because I was off sick from work with food poisoning :nauseated_face:


#25

I've got the app installed and working. Curious what everyone is using to in conjunction with it to set things like light levels, etc?

Thanks!


#26

Currently I’m not, (because I’m waiting for Google assistant and I’ve been moving house) however I have a custom written app on ST designed specifically for this purpose... I’ll port over if I have some free time to do so...

Otherwise I expect everyone else is using rule machine..


#27

Thanks!

I looked into using Rule Machine. Looks like it would mostly work, with one exception. In Smartthings I have it setting different lights based on the playback type. Unfortunately I don’t see that capability in Rule Machine as an option.


#28

I’m pretty sure even if it doesn’t exist you can just use a custom event and do it that way.. but not that I’ve used RM since the ST days


#29

I don't see a way to make "playbackType" attribute a rule within RM. I can see "playbackType" being set to either moive/tv depending on the content in the logs but no way to use that info within RM.

Is there a way to turn on/off some virtual switches to indicate content type so we can use it within RM?


#30

Sorry I forgot to reply to this message, if RM doesn’t support then at present no. I would perhaps check in the RM thread though, if it doesn’t exist then it probably should.

Otherwise you have a couple of options, either write an app to handle your use case or wait for me to port mine over.. which I will definitely do, but just moved in to a new house and very busy on that at the moment, will likely be before Christmas though.. or if you feel confident to port it over I can share the ST code with you?


#31

I have your ST code running on ST right now to handle some of content type automations so I'll try to port over but not much experience coding so we'll see how things go.


#32

I have an unpublished version which separates the scene control app to its own app.. I’ll have a little time today so I’ll take a look and have a quick look at porting it over..


#33

Did you ever get a chance to work on this?


#34

No sorry,...but I've just had a go at porting it over.. but I am also unable to test it!

It should* work, but there is a good chance it wont.. So give it a go and send me any logging where there are errors and I'll aim to fix quickly!

Parent

/**
 *  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() {
    initialize()
}

def updated() {
    unsubscribe()
    initialize()
}

def initialize() {
}

preferences {
    page name: "parentPage"
}

def parentPage() {
    dynamicPage(name: "parentPage", title: "Rooms", install: true, uninstall: true, submitOnChange: true) {              
        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

/**
 *  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: "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: "Enter Room Name", defaultValue: app.label, required: false
        }
        section ("For This Device"){

            input(name: "playerDT", type: "capability.musicPlayer", title: "Media Player Device", multiple: false, required:false)      
      	}
        section("Lights") {
			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("Switches") {
        	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("Modes") {
			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("Routines") {
        	def actions = location.helloHome?.getPhrases()*.label
        	input "playRoutine", "enum", title: "Routine when playing", required: false, options: actions
            input "pauseRoutine", "enum", title: "Routine when paused", required: false, options: actions
            input "stopRoutine", "enum", title: "Routine when stopped", required: false, options: actions
        }
        section("Media Settings") {	
			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("Restrictions") {
			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()
    if(playRoutine) { location.helloHome?.execute(playRoutine) }
}

def PauseCommand(){
    if(settings?.pauseMode1){setLocationMode(pauseMode1)}
   	SetLevels(iLevelOnPause1, colorOnPause, tempOnPause)
    mSwitchPause?.on()
    if(pauseRoutine) { location.helloHome?.execute(pauseRoutine) }
    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(stopRoutine) { location.helloHome?.execute(stopRoutine) }
    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}"}
}

#35

@jpark Let me know how you get on with this, I did give a couple of tests last night and seems to be working!


#36

Working great!

I can finally get rid of my SmartThings!

thanks!


#37

Sorry spoke too soon.

Media Type restriction doesn't seem to be working.
The log says it's NULL no matter what is played.

image


#38

Will take a look.. what method are you using for updates? Webhooks?


#39

Webhooks.

The Plex Communicator device is showing the correct media type in the logs when I play content.


#40

I can't figure out the issue, I can't get the current state for this variable.. I've posted in another thread to see if anyone else has any idea..