Broken in Migration: Alexa Skill/Lambda Function

TL;dr Does anybody have their Alexa Skill working in the new platform?

So one of the first thing I noticed after the migration was my Alexa Skil was broken. I initially assumed the cause to be some required inputs involving my no longer working thermostat devices.

So I created a new SmartApp with all traces of the thermostats removed. Here is what the new app looks like:

definition(
	name: "Caspar",
	namespace: "scottinpollock",
	author: "Scottin Pollock",
	description: "Do what Alexa tells us to do",
	category: "My Apps",
	iconUrl: "http://solutionsetcetera.com/stuff/STIcons/ASK.png",
	iconX2Url: "http://solutionsetcetera.com/stuff/STIcons/ASK@2x.png")

preferences {
	page(name: "configure")
}

def configure() {
	dynamicPage(name: "configure", title: "Choose Devices and Routine", install: true, uninstall: true) {
		section("Select your devices") {
			input "outTemp","capability.temperatureMeasurement", title: "Select the Outdoor temp sensor", required: true, multiple: false
			input "medTemp","capability.temperatureMeasurement", title: "Select the Media temp sensor", required: true, multiple: false
			input "frdTemp","capability.temperatureMeasurement", title: "Select the fridge temp sensor", required: true, multiple: false
			input "gTemp","capability.temperatureMeasurement", title: "Select the Garage temp sensor", required: true, multiple: false
			input "outdoorHu","capability.relativeHumidityMeasurement", title: "Select the Outdoor humidity sensor", required: true, multiple: false
			input "gCont","capability.contactSensor", title: "Select the Garage Door contact sensor",required:true, multiple: false
			input "stPres","capability.presenceSensor", title: "Select the Sport Trac presence", required: true, multiple:false
			input "allBat","capability.battery",title: "Select ALL batteries", required: true, multiple: true
		}

		section("Optional HAM Bridge commands"){
			input "HAMBleaving", "text", title: "Command to send when leaving...", required: false
			input "HAMBhome", "text", title: "Command to send when returning...", required: false
			input "server", "text", title: "Server IP", description: "IP Address", defaultValue: "192.168.86.12", required: false
			input "port", "number", title: "Port", description: "Port number", defaultValue: "8080", required: false
		}

		section(title: "App ID") {
			paragraph "Application ID:\n${app.id}"
		}
	}

}

def installed() {}
def updated() {}

mappings { path("/:noun/:operator/:operand"){ action: [GET: "centralCommand"] } }

def centralCommand() {
	log.debug params

	def noun = params.noun
	def op  = params.operator
	def opa = params.operand

	log.debug "Central Command  ${noun} ${op} ${opa}"

	state.talk2me = ""

	if (op == "none") { op = "status" }								//if there is no op, status request
	if (["done","finished"].contains(op)) { op = "status" }			//these are status
	if (["open","closed","locked","unlocked","about","running"].contains(op)) { op = "status" }
	else if (["hot","cold","warm"].contains(op)) { op = "temperature" }

	switch (noun) {
		case "batteries" : 		switch(op) {			//check the status of all batteries
									case "dead"			:
									case "low"			: batteryResponseMulti(allBat,noun,op); break
									default				: batteryResponseMultiNow(allBat,noun,op)
								}
								break


		case "temperature" : 	switch (op) {			//tell me my temps
									case "outdoor"		: temperatureMeasurementResponse(outTemp, noun, op); break 
									case "indoor"		: temperatureMeasurementResponse(medTemp, noun, op); break 
									case "report"		:  
									case "status"		: environmentNounResponse(outTemp,medTemp,frdTemp,gTemp,outdoorHu); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break

		case "good morning" : 	switch (op) {			//Good Morning!
									case "status"		: goodMorningNounResponse(); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break


		case "good night" : 	switch (op) {			//Good Night!
									case "status"		: goodNightNounResponse(); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break

		case "mode" : 	switch (op) {					//Answer mode
									case "status"		: theModeNounResponse(); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break

		case "I am home" : 	switch (op) {				//I am home
									case "status"		: iamhomeNounResponse(); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break

		case "I am leaving" : 	switch (op) {			//I am leaving
									case "status"		: iamleavingNounResponse(); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break

		case "release the hounds" : 	switch (op) {				
									case "status"		: houndsNounResponse(); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break


		case "lighting" : 	switch (op) {				
									case "status"		: lightingNounResponse(noun,op,opa); break 
									default				: defaultResponseUnkOp(noun,op)
								}
								break


		case "bedroom music" : 	switch (op) {				
									case "play"			:
									case "stop"			: uPlaylistNounResponse(noun,op,opa); break 
									case "status"		:
									default				: defaultResponseUnkOp(noun,op)
								}
								break


		case "media music" : 	switch (op) {				
									case "play"			:
									case "stop"			: dPlaylistNounResponse(noun,op,opa); break 
									case "status"		:
									default				: defaultResponseUnkOp(noun,op)
								}
								break


		case "media system" : 	switch (op) {				
									case "on"			:
									case "off"			: mediaNounResponse(noun,op); break 
									case "status"		:
									default				: defaultResponseUnkOp(noun,op)
								}
								break


		case "bedroom system" : 	switch (op) {				
									case "on"			:
									case "off"			: bedroomNounResponse(noun,op); break 
									case "status"		:
									default				: defaultResponseUnkOp(noun,op)
								}
								break


		case "garage system" : 	switch (op) {				
									case "on"			:
									case "off"			: garageNounResponse(noun,op); break 
									case "status"		:
									default				: defaultResponseUnkOp(noun,op)
								}
								break



		case "none" : defaultResponseWhat()
		break

		default : defaultResponseUnkNoun(noun,op)
	}

	return ["talk2me" : state.talk2me]
}

def defaultResponseWhat()
{
	state.talk2me = state.talk2me + "Ask me about something, or to do something with something. But at least you're here.   " 
}

//defaultResponse Unknown Device
def defaultResponseUnkNoun(noun, op)
{
	state.talk2me = state.talk2me + "I can't find a person, place, or thing called ${noun} in the smart app.  " 
}


//defaultResponse Unknown Operator for device
def defaultResponseUnkOp(noun, op)
{
	state.talk2me = state.talk2me + "I haven't been told how to do ${op} with ${noun} yet.  "
}


//capability.switch  ["on", "off"]
def switchResponse(handle, noun, op)
{
	def arg = handle.currentValue("switch")								//value before change 
		if (op == "on") { handle.on(); arg = "turning " + op;}			//switch flips slow in state, so tell them we did Op
		else if (op == "off") { handle.off(); arg = "turning " +op; }	//...or it will report what it was, not what we want
		else if (op == "status") { }									//dont report Op, report the real currentState
		state.talk2me = state.talk2me + "The ${noun} is ${arg}.  "		//talk2me : switch is on (or off) 
}


//capability.battery ALL
def batteryResponseMulti(handle, noun, op)								//handle All battery check differently
{
	state.talk2me = state.talk2me + "SmartThings reports that the following batteries are low:  "
	handle.each {
		def arg = it.currentValue("battery")
		if (arg) {
			if (arg.toInteger() < 50) {
				state.talk2me = state.talk2me + "${it} at ${arg} percent.  "  
			}
		}
	}
}

def batteryResponseMultiNow(handle, noun, op)								//handle All battery check differently
{
	state.talk2me = state.talk2me + "SmartThings batteries are as follows:  "
	handle.each {
		def arg = it.currentValue("battery")
		if (arg) {
				state.talk2me = state.talk2me + "${it} at ${arg} percent.  "  
		}
	}
}

//capability.contactSensor   ["open", "closed"]
def contactSensorResponse(handle, noun, op)
{
	def arg = handle.currentValue("contact")							//lookup the current contact status
	state.talk2me = state.talk2me + "The ${noun} is ${arg}.  "			//talk2me : contact is open or closed
}

//capability.temperatureMeasurement ["degrees"] 
def temperatureMeasurementResponse(handle, noun, op)
{
	def arg = handle.currentValue("temperature")
	state.talk2me = state.talk2me + "The ${op} temperature is ${arg} degrees.  "
    if (op == "outdoor") {
		def outHU = outdoorHu.currentValue("humidity")
        state.talk2me = state.talk2me + "With a relative humidity of ${outHU} percent.  "  
	}
}

//capability.motionSensor ["active", "inactive"]
def motionSensorResponse(handle, noun, op)
{
	def arg = handle.currentValue("motion")
	state.talk2me = state.talk2me + "The ${noun} motion is ${arg}.  "
}

def environmentNounResponse(outTemp,medTemp,frdTemp,gTemp,outdoorHu)
{
	def otemp = outTemp.currentValue("temperature")
	def mtemp = medTemp.currentValue("temperature")
	def fritemp = frdTemp.currentValue("temperature")
	def gtemp = gTemp.currentValue("temperature")
	def outHU = outdoorHu.currentValue("humidity")

	state.talk2me = state.talk2me + "SmartThings reports the outdoor temperature is ${otemp} degrees, with a relative humidity of ${outHU} percent.  "
 	state.talk2me = state.talk2me + "Indoors, it is ${mtemp} degrees.  "
	state.talk2me = state.talk2me + "The garage is ${gtemp}.  "
	state.talk2me = state.talk2me + "The refridgerator is ${fritemp}.  "

}

def goodMorningNounResponse()
{
	location.helloHome?.execute(settings.GMaction)
	
	def theRoutine = settings.GMaction
	def otemp = outTemp.currentValue("temperature")
	def mtemp = medTemp.currentValue("temperature")
	def outHU = outdoorHu.currentValue("humidity")
	def theCOM = "goodMORNING"
	doHAMB(theCOM)
	state.talk2me = state.talk2me + "Good Morning Scott, SmartThings has executed your morning routine.  "
	state.talk2me = state.talk2me + "Inside, it is ${mtemp} degrees.  "
	state.talk2me = state.talk2me + "Outside, it is ${otemp} degrees with a relative humidity of ${outHU} percent.  "
	state.talk2me = state.talk2me + "I hope you have a great day.  "
}

def goodNightNounResponse()
{
	location.helloHome?.execute(settings.GNaction)
	def theRoutine = settings.GNaction
	def theCOM = "goodNIGHT"
	doHAMB(theCOM)
	state.talk2me = state.talk2me + "Good Night Scott, SmartThings has executed your sleeping routine.  "
	state.talk2me = state.talk2me + "I hope you Sleep Well.  "
}

def theModeNounResponse()
{	
	def theMode = location.mode
	state.talk2me = state.talk2me + "SmartThings has confirmed the mode is set to ${theMode}.  "
}

def iamhomeNounResponse()
{	
	def theMode = location.mode
	if (HAMBhome) {
		def theCOM = HAMBcommandOn
		doHAMB(theCOM) 
	}
	state.talk2me = state.talk2me + "Welcome back Scott, smart things mode is ${theMode}.  "
	state.talk2me = state.talk2me + "Firing up your media system.  "
}

def iamleavingNounResponse()
{	
	if (HAMBleaving) {
		def theCOM = HAMBcommandOff
		doHAMB(theCOM) 
	}
	state.talk2me = state.talk2me + "OK scott, enjoy your outing.  "
	state.talk2me = state.talk2me + "SmartThings will shut down all non essential systems.  "
}

def houndsNounResponse()
{
	def theCOM = "dogs"
	doHAMB(theCOM) 
	state.talk2me = state.talk2me + " ok "
}

def lightingNounResponse(noun,op,opa)
{
	def theCOM = opa
	doHAMB(theCOM) 
	state.talk2me = state.talk2me + "Setting the lighting to ${theCOM}.  "
}


def uPlaylistNounResponse(noun,op,opa)
{
	def tCmd = op
	def tListnum = opa
	def theCOM = "PLAYlistUP&"+tListnum
	if (tCmd == "stop") {theCOM = "PLAYstopUP"}
	doHAMB(theCOM) 
	state.talk2me = state.talk2me + "ok, ${tCmd}ing music upstairs.  "
}


def dPlaylistNounResponse(noun,op,opa)
{
	def tCmd = op
	def tListnum = opa
	def theCOM = "PLAYlistDOWN&"+tListnum
	if (tCmd == "stop") {theCOM = "PLAYstopDOWN"}
	doHAMB(theCOM) 
	state.talk2me = state.talk2me + "ok, ${tCmd}ing music downstairs.  "
}

def mediaNounResponse(noun,op)
{
	def tCmd = op
	def theCOM = "mediaAVon"
	if (tCmd == "off") {theCOM = "mediaOFF"}
	doHAMB(theCOM) 
	state.talk2me = state.talk2me + "ok, turning downstairs audio video system ${tCmd}.  "
}

def bedroomNounResponse(noun,op)
{
	def tCmd = op
	def theCOM = "AVupstairsON"
	if (tCmd == "off") {theCOM = "AVupstairsOFF"}
	doHAMB(theCOM) 
	state.talk2me = state.talk2me + "ok, turning upstairs audio video system ${tCmd}.  "
}

def garageNounResponse(noun,op)
{
	def tCmd = op
	def theCOM = "AVgarageON"
	if (tCmd == "off") {theCOM = "AVgarageOFF"}
	doHAMB(theCOM) 
	state.talk2me = state.talk2me + "ok, turning garage system ${tCmd}.  "
}

def doHAMB(theCOM)
{
	def ip = "${settings.server}:${settings.port}"
	sendHubCommand(new physicalgraph.device.HubAction("""GET /?$theCOM HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN)) 
}

I also had to edit the Lambda function as the new app has new OAuth info (which is embedded in the function). I could not make this simple edit as the version of nodeJS I created the original in was deprecated, so I created a new function, deleted the trigger from the old one, added a trigger for the new one with the Skil ID, and transferred the ARN of the new function to the current Alexa Skill. I do seem to be getting to the Lambda function from the Alexa Skill, but I am hitting the following line in the Lambda function below:

var speechText = 'The App on SmartThings did not return any message.';

Here is the entire Lamda function:

'use strict';
exports.handler = function( event, context ) {
	var https = require( 'https' );
	var STprompt = ['How can I help?', 'Smartthings here.', 'Your wish is my command.', 'Smartthings online.', 'Smartthings is listening.', 'Smartthings standing by.'] ;
	var askForNext = ['anything else?', 'need something else?', 'still need help?', 'do you have another command?'] ;
	var signOff = ['Ok then', 'smartthings out!', 'catch ya later', 'Smartthings has left the building.', 'Smartthings offline.', 'Smartthings signing off.'] ;
	var STappID = <Client ID> ;  // AppID from Apps Editor
	var STtoken = <Client Secret>;  //Token from Apps Editor
	//var url='https://graph.api.smartthings.com:443/api/smartapps/installations/' + STappID + '/' ;
	var areWeDone=false;
    if (event.request.type == "LaunchRequest") {
    	var speech = STprompt[Math.floor(Math.random() * STprompt.length)];
        output(speech, context, areWeDone);
   }
   else if (event.request.type === "SessionEndedRequest"){
   }
   else if (event.request.intent.name == "command") {
        var Operator = event.request.intent.slots.Operator.value;
        var Noun = event.request.intent.slots.Noun.value;
        var Operand = event.request.intent.slots.Operand.value;
        if (!Operator) {Operator = "none";}
        if (!Noun) {Noun = "none";}
        if (!Operand) {Operand = "none";}
        var url = 'https://graph.api.smartthings.com/api/smartapps/installations/' + STappID + '/' + 
                 Noun + '/' + Operator + '/'+ Operand  +'?access_token=' + STtoken;
        console.log(url);
        https.get( url, function( response ) {
            response.on( 'data', function( data ) {
                var resJSON = JSON.parse(data);
                var speechText = 'The App on SmartThings did not return any message.';
                if (resJSON.talk2me) { speechText = resJSON.talk2me; }
                console.log(speechText);
                var nextQ = askForNext[Math.floor(Math.random() * askForNext.length)];
                if (areWeDone === false) { speechText = speechText + nextQ; }

                output(speechText, context, areWeDone);
                console.log("after the fact");
            } );
        } );
        
    } else if (event.request.intent.name == "exodus") {
    	areWeDone=true;
    	var soLong = signOff[Math.floor(Math.random() * signOff.length)];
        output(soLong, context, areWeDone);
    } else if (event.request.intent.name == "AMAZON.StopIntent") {
    	areWeDone=true;
        output("", context, areWeDone);
    } else if (event.request.intent.name == "AMAZON.CancelIntent") {
    	areWeDone=true;
        output("Canceling.", context, areWeDone);
    }

};

function output( text, context, areWeDone ) {
   var response = {
      outputSpeech: {
         type: "PlainText",
         text: text
      },
      
   shouldEndSession: areWeDone
   };
   context.succeed( { response: response } );
}

So it seems the SmartApp is for some reason returning nothing to the Lambda function, and I have no idea why.

Any suggestions much appreciated.

-SiP

Tagging @jody.albritton, @erickv and @nayelyz.

Can any of you have a look on the OP and advise?

Thanks. In tracking this down further it appears the skill and the Lambda function are working fine, but the url the Lambda function is calling is returning an invalid token error. That URL is:

https://graph.api.smartthings.com/api/smartapps/installations/<AppOauthClientID>/Noun/Operator/Operand?access_token=<AppOauthClientSecret>

Has the structure of that URL changed?

Is oauth enabled for this SmartApp?

Yup. At least as I have known to do it before… has anything changed? As previously mentioned, the url was constructed with the client Id and secret from the OAuth panel of the app settings.

You might want to move to the new SmartThings API.

Have a look on this thread and follow the links to the documentation.

Only other thing i can think is If you got migrated to a different shard and the host is no longer correct.

Wouldn’t that affect my other endpoints as well? Because they are all working fine.