Device Handler calling another device?

virtualdevice

#1

I am trying to write a device handler to toggle switch, so I have created a simple device based on a switch which has a preference setting using the actual switch:

preferences {
  section
  {
		input "toggleSwitch", "capability.switch", title: "Switch to toggle?", multiple: false
  }
}

It can get the current switch state OK and knows to toggle, but the only thing that is failing is calling the actual switch from my virtual switch on/off.
if (state.required == “on”)
{
toggleSwitch.on()
}
else
{
toggleSwitch.off()
}

I always get this error from the toggleSwitch.on() / off():
java.lang.IllegalArgumentException: Expected installed smartapp and got physicalgraph.device.Device

Any ideas what this means?


(John) #2

Two devices can “talk” via a SmartApp, not a device handler.


#3

Mmm so it’s going to be tricky to create a IFTTT Do toggle switch then.


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #4

Not tricky at all… Common scenario…


(sidjohn1) #5

I doubt it, it just has to be planed out well… What exactly are you trying to accomplish?


#6

Well the only Do Button recipes I can see let me turn on or turn off a SmartThings switch.
What I actually want to do is just toggle the switch from the Do Button, as I am doing this from Android Wear.
I must admit I am new to both IFTTT Do Buttons and SmartThings, so please point me at what I have missed :smile:


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #7

Oh… The dreaded lack of a toggle() Command for Capability Switch has cropped up again (the official spec for the ZigBee Switch Cluster has 3 commands… On, Off and Toggle).

The absolute easiest was is to copy the Device Type Handler code and customize it with one extra ad hoc Command “toggle”, since then you already have access to the current State of the Switch.

Unfortunately, otherwise, you have to have a Virtual Switch (or Virtual Momentary button) and a SmartApp instance for each one.

I wonder if the SmartApp must already exist (or Rule Machine, or maybe Smart Lighting). The SmartApp just needs to subscribe to the Virtual Switch and then check the real Switch’s current State to decide whether to issue On or Off Command.

Sorry… It really ought to be less complicated and every Switch Device should have Toggle built in.


(Mike Maxwell) #8

Rule Machine includes a built in toggle action…


#9

Thanks for everyones help.
I was worried about RuleMachine as I use Android and it has warnings about that, so I have ended up using a Momentary Button Tile and monitoring that from a SmartApp which toggles the real switch.
This seemed the simplest and has actually worked, so I can toggle the switches from my Android Wear watch via a Do Button recipe, just like I can my Hue lights :smile:


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #10

Glad you got a simple solution working, but I’m curious about these “warnings” you mention.

I haven’t heard anything specifically related to Android and Rule Machine … or are you just referring to the current rash of “An Unexpected Error Occurred” or this past week’s inability to install / uninstall quite a few different SmartApps…?


(Mike Maxwell) #11

The “warnings” refer to androids inconsistent application of submitOnChange requests.
RM makes heavy use of aggregate input values based on other selected inputs. When android doesn’t properly apply this flag these dependent inputs don’t get updated the way that they should.


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #12

Grrrrrr. :confounded:


#13

Can you tell me how to “talk” from a device handler to a SmartApp?
I have been trying to come up with a “virtual” garage door opener, as the BMW Connected SmartThings app only likes a GarageDoor device.
To get this to work I need to trigger a separate ZWave relay.
I really, really think life would be so much easier if a device handler can just call another device handler!


(John) #14

A smart app can read/set attributes or call public methods of a device handler.

Typically your app will monitor one device and then trigger the other.


#15

Ah OK.
What I have done this time is create a “doorControl” event and a door monitor app, so that when the motor control is needed by the virtual door opener, then I send the door control event. This is seen by the app which sends the ZWave relay control.

Why can’t SmartThings just change virtual device handlers so they can just use call other device handlers, as all these hacks would be unnecessary?


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #16
  1. “Virtual Device Handlers” were, seriously, likely not even considered to be something that would ever be used except for during SmartApp testing. Certainly we are using them much more than SmartThings dreamt!

  2. SmartThings architecture isolates Device Instances from each other for various good reasons, but security is a particularly key one. When the user install and configure or reconfigure a SmartApp Instance, the user must explicitly select which Devices the SmartApp is permitted to access. Thus preventing a “Virtual Switch X” from opening the “Front Door Lock” unless the user installs and configures a SmartApp Instance with those exact devices.

  3. Sure … the security authorization aspect of #2 could have been somehow built into the Device Type Handlers too, but SmartThings decided to make a clear separation of duties between DTHss and SmartApps – it is one of the most consistent design elements of the system; and the design is not particularly onerous.

  4. Heck … I think SmartThings could build even more stuff following this Architecture. “Mode”, for example, should be a Device Type … then a Location could have multiple Mode Instances. Long story, but currently, any SmartApp can change the Mode of your Location. That is a security vulnerability!!!

  5. Now that Virtual Switches are so common … perhaps SmartThings ought to consider ways to better encapsulate this concept if they “evolve” the architecture. But there’s no need to throw away the other concepts of #2, #3.


(John) #17

I understand you characterizing it as a hack, but it was by design.


(ilker Aktuna) #18

Hi,

I am trying to write a device handler which will get a “text” attribute to “speak” command, process TTS and then send URI to my Sonos speaker.
Everything was going well until I got this error which states that I should not call a device from another device.

Now I understand (from this thread) that I need to use a SmartApp between those devices.
But I don’t know how to write a basic one which will read the attribute from device. Can anyone guide me plase ?

my device handler is:

/** 
 *  Alexa Simulator
 *
 *  Author: SmartThings 
 *
 */

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException; 

preferences {
		section {
			input "sonos", "capability.musicPlayer", title: "On this Speaker", required: true,multiple:true
			input "alexaApiKey", "text", title: "Alexa Access Key", defaultValue:"millave"
		}
}
metadata {
	// Automatically generated. Make future change here.
	definition (name: "Alexa Device", namespace: "alexa", author: "ooo") {
		capability "Speech Synthesis"
	
		command "speak", ["string"]
	}

	// Main
	standardTile("main", "device.status", width: 1, height: 1, canChangeIcon: true) {
		state "playing", label:'Playing', action:"music Player.stop", icon:"st.Electronics.electronics16", nextState:"paused", backgroundColor:"#79b821"
		state "stopped", label:'Stopped', action:"music Player.play", icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
		state "paused", label:'Paused', action:"music Player.play", icon:"st.Electronics.electronics16", nextState:"playing", backgroundColor:"#ffffff"
		state "no_media_present", label:'No Media', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
        state "no_device_present", label:'No Present', icon:"st.Electronics.electronics16", backgroundColor:"#b6b6b4"
		state "grouped", label:'Grouped', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
	}

	standardTile("status", "device.status", width: 1, height: 1, decoration: "flat", canChangeIcon: true) {
		state "playing", label:'Playing', action:"music Player.stop", icon:"st.Electronics.electronics16", nextState:"paused", backgroundColor:"#ffffff"
		state "stopped", label:'Stopped', action:"music Player.play", icon:"st.Electronics.electronics16", nextState:"playing", backgroundColor:"#ffffff"
		state "no_media_present", label:'No Media', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
		state "no_device_present", label:'No Present', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
		state "paused", label:'Paused', action:"music Player.play", icon:"st.Electronics.electronics16", nextState:"playing", backgroundColor:"#ffffff"
	}

	valueTile("currentSong", "device.trackDescription", inactiveLabel: true, height:1, width:3, decoration: "flat") {
		state "default", label:'${currentValue}', backgroundColor:"#ffffff"
	}

	
	standardTile("refreshPlayer", "device.status", inactiveLabel: false, decoration: "flat") {
		state "default", label:"", action:"refresh", icon:"st.secondary.refresh", backgroundColor:"#ffffff"
	}

	main "main"

	details([
		"status","refreshPlayer"
	])
}


def installed() {
	sendEvent(name:"model",value:getDataValue("model"),isStateChange:true)
}



def timer(){
    def eventTime = new Date().time
	state.gapTime = refreshFrequency > 0 ? (refreshFrequency? (refreshFrequency as Integer):0) * 60  : (parent.refreshMRInterval? (parent.refreshMRInterval as Integer):0) * 60 
    if ((state.lastRefreshTime ?:0) + (state.lastChange ? state.gapTime * 1000 : 300000)  <=  eventTime ){
    	refresh()
    }
}
def refresh() {
    def eventTime = new Date().time

    if( eventTime > state.secureEventTime ?:0)
    {
        if ((state.lastRefreshTime ?: 0) > (state.lastStatusTime ?:0)){
            sendEvent(name: "status", value: "no_device_present", data: "no_device_present", displayed: false)
        }
        state.lastRefreshTime = eventTime
        log.trace "Refresh()"
        def result = []
        //result << unsubscribe()
        //result << delayAction(10000)
        result << subscribe()
        result << getCurrentStatus()
        result << getVolume()
        result << getPlayMode()
        result << getCurrentMedia() 
        result.flatten()
    }else{
        log.trace "Refresh skipped"
    }

}

def speak(String msg){
	  def speech
      def uri
      speech = safeTextToSpeech(normalizeMessage(msg))
      log.debug ("URI:" + speech.uri)
      	sonos.each {
        it.playTrack(speech.uri)
        }
      //sonos.playTrack(speech.uri)
}
				
def normalizeMessage(message){
    if (message){
    	message = message.replace("#mode", location.mode)
    	message = message.replace("#location", location.name)
        message = message.replace("#s", ttsMode == "Alexa" ? "Alexa, Simon says":"")

    }
return message
}


private safeTextToSpeech(message) {
message = message?:"You selected the Text to Speach Function but did not enter a Message"
   	[uri: "x-rincon-mp3radio://tts.freeoda.com/alexa.php/" + "?key=$alexaApiKey&text=" + URLEncoder.encode(message, "UTF-8").replaceAll(/\+/,'%20') +"&sf=//s3.amazonaws.com/smartapp-" , duration: "${5 + Math.max(Math.round(message.length()/12),2)}"]
}  

I want to send the attribute of speak command to another device in line “sonos.playTrack”


(ilker Aktuna) #19

I’ve solved the issue.

thx.


(Jason "The Enabler" as deemed so by @Smart) #20

What did you do to fix this?