btreinders Posted February 26, 2019 Posted February 26, 2019 I have tried everything I found searching the internet with https calls in node.js to make a REST call to the portal from the AWS Lambda function and nothing will activate a program. Does anyone know if this is possible? I am using the URL from the portal with my username and password. I am able to trigger a program from Chrome using the URL. I don't know much about node.js so please be gentle. Thanks! Quote
bmercier Posted February 26, 2019 Posted February 26, 2019 For sure, this is possible. Here's untested code that could get you started. There are easier ways to make an http calls, using the request module for example, but this one below has the advantage that you can do inline editing of your lambda function, as opposed to always having to update your code via an upload. "use strict" const https = require('https'); const username = 'portal user' const password = 'portal password' https.globalAgent = new https.Agent({ keepAlive: true }); exports.handler = function (event, context) { // console.log('Input:', event); var options = { protocol: 'https:', hostname: 'my.isy.io', port: 443, path: '/set/your/path/here', method: 'GET', rejectUnauthorized: true, auth: username + ':' + password } var req = https.request(options); // when the response comes back req.on('response', function(res){ res.body = ''; res.setEncoding('utf-8'); res.on('data', function(chunk) { res.body += chunk; }); // when the response has finished res.on('end', function() { // console.log('DATA:', res.body) context.succeed(res.body); }); res.on('error', function (err) { console.log('ERROR:', err); context.fail(JSON.stringify({ error: 'error' })); }); }); // write data to request body //req.write(body); req.end(); // Can't recall if you need this for a GET } If your lambda function only has to trigger a program, there's an easier and more secure way too. In ISY Portal, go to Connectivity | IFTTT / Webhooks. Click "Set Key" to set an API key Create an entry for your program Click on the small blue icon, which will give you a URL Use this URL to trigger the program. Please note that you need to use POST (Not GET) 1 Quote
btreinders Posted February 27, 2019 Author Posted February 27, 2019 @bmercierThanks for help! I tried this and I don't get any errors (it seems to execute fine) but the program in the ISY does not run. I was seeing the same with other code from the web that I have tried including the request module. I had also tried IFTTT before with the result. Executes but the program does not run. I have not tried the IFTTT way with this code because I was not sure what to do with the username and password. Just leave them blank? Any ideas why the rest call isn't working? Thanks! Quote
bmercier Posted February 27, 2019 Posted February 27, 2019 Uncomment this line and check the log: // console.log('DATA:', res.body) You may be getting an error message from ISY. Also, what's the URL? Are you using runif or runthen? If you use IFTTT, you don't need to pass a username or password. The key is what identifies you. Benoit Quote
btreinders Posted February 27, 2019 Author Posted February 27, 2019 @bmercier I don't see a difference in the logs with it uncommented. Should the logs look like code? I am using runthen. I tried ifttt this time and the program still did not execute. Quote
bmercier Posted February 27, 2019 Posted February 27, 2019 Is the lambda function triggered at all? I would suggest to add console.log() at a few places to see where it fails. Benoit Quote
btreinders Posted February 27, 2019 Author Posted February 27, 2019 (edited) @bmercierYes, it does get triggered, I put speech outputs after the call and it does execute them. I am using speech outputs as debug but I do notice that it will only execute the first one it comes to. I don't know if it stops at that point or continues to execute. Right now I just have it say Made It right after the https code you gave me and it does say Made it. Strangely if I do not have a speech output in there at all I get "There was a problem with the requested skill's response". What I am trying to accomplish is create a skill where Alexa will demo my home automation system. It will ask the user what system do you want to demo or demo the whole house. I would then have Alexa say something like, ok, demoing the lights, or here is a demo of cameras being cast to the TV, or I can lock the front door, etc....and make rest calls to execute the programs/devices/scenes etc.. in the ISY. Edited February 27, 2019 by btreinders Quote
bmercier Posted February 28, 2019 Posted February 28, 2019 I have not seen the code. But if you simply added code at the "end" to return some Alexa voice, then the call to portal probably does not go through at all. If so, your code at the end probably does a context.succeed() which basically ends the lambda function, before the call to portal has a chance to go through. Benoit Quote
btreinders Posted February 28, 2019 Author Posted February 28, 2019 @bmercier Yes, I believe you are correct. The function is terminating before the http request is finished. Any idea on how to force it to wait? I don't see a context.succeed() after the call but the function does exit. Quote
btreinders Posted March 1, 2019 Author Posted March 1, 2019 @bmercier After more research I have found that the handler function is exiting because the this.emit is being executed. I have tried using async and promise with the same result. The function is just exiting too soon. Any ideas? This is driving me crazy. Thanks! Quote
bmercier Posted March 2, 2019 Posted March 2, 2019 I need to see the code to help.Sent from my iPhone using Tapatalk Quote
btreinders Posted March 2, 2019 Author Posted March 2, 2019 (edited) // 1. Text strings ===================================================================================================== // Modify these strings and messages to change the behavior of your Lambda function let speechOutput; let reprompt; let welcomeOutput = "Welcome to my home automation demo, You can ask me to demo the whole house, or just certain systems like lights, locks, cameras, audio video systems, ceiling fans, etc..."; let welcomeReprompt = "You can say something like demo the whole home, or demo the lights. I will give you short demo of the system of your choice."; // 2. Skill Code ======================================================================================================= "use strict"; const Alexa = require('alexa-sdk'); const APP_ID = undefined; // TODO replace with your app ID (OPTIONAL). speechOutput = ''; const handlers = { 'LaunchRequest': function () { this.emit(':ask', welcomeOutput, welcomeReprompt); }, 'AMAZON.HelpIntent': function () { speechOutput = 'Placeholder response for AMAZON.HelpIntent.'; reprompt = ''; this.emit(':ask', speechOutput, reprompt); }, 'AMAZON.CancelIntent': function () { speechOutput = 'Placeholder response for AMAZON.CancelIntent'; this.emit(':tell', speechOutput); }, 'AMAZON.StopIntent': function () { speechOutput = 'Ok, stopping my home demo'; this.emit(':tell', speechOutput); }, 'SessionEndedRequest': function () { speechOutput = 'Demo Ended'; //this.emit(':saveState', true);//uncomment to save attributes to db on session end this.emit(':tell', speechOutput); }, 'AMAZON.NavigateHomeIntent': function () { speechOutput = ''; //any intent slot variables are listed here for convenience //Your custom intent handling goes here speechOutput = "This is a place holder response for the intent named AMAZON.NavigateHomeIntent. This intent has no slots. Anything else?"; this.emit(":ask", speechOutput, speechOutput); }, 'playdemointent': function () { var speechOutput = ''; var speechReprompt = ''; //any intent slot variables are listed here for convenience let system_typeSlot = resolveCanonical(this.event.request.intent.slots.system_type); console.log(system_typeSlot); let lengthSlot = resolveCanonical(this.event.request.intent.slots.length); console.log(lengthSlot); var demos = { 'lights' : { 'short' : '/isy/<isy address>/rest/programs/0024/runthen', 'long': '/isy/<isy address>/rest/programs/0024/runthen' }, 'cameras' : { 'short' : '/isy/<isy address>/rest/programs/013F/runThen', 'long': '/isy/<isy address>/rest/programs/013F/runThen' }, 'fans' : { 'short' : '/isy/<isy address>/rest/programs/0367/runThen', 'long': '/isy/<isy address>/rest/programs/0367/runThen' }, 'pool' : { 'short' : '/isy/<isy address>/rest/programs/02C9/runThen', 'long': '/isy/<isy address>/rest/programs/02C9/runThen' }, 'Lock' : { 'short' : '/isy/<isy address>/rest/programs/0439/runIf', 'long': '/isy/<isy address>/rest/programs/0439/runIf' } } var lengths = [ 'short', 'long' ]; var demo = ''; var length = ''; if(!system_typeSlot) { this.emit(':ask', 'I did not hear a system. Which system do you want to demo?', 'You can say something like cameras, lights, audio video system, etc..'); } else { demo = system_typeSlot.toLowerCase(); this.attributes['demo'] = demo; } if(lengthSlot) { length = lengthSlot.toLowerCase(); } if(demos[demo]) { if(lengthSlot && lengths.indexOf(lengthSlot) > -1) { length = lengthSlot; } else { length = 'short'; } const url = demos[demo][length]; "use strict"; const https = require('https'); const username = 'xxxxxxx@hotmail.com'; const password = 'xxxxxxxx'; https.globalAgent = new https.Agent({ keepAlive: true }); exports.handler = function (event, context) { console.log('Input:', event); var options = { protocol: 'https:', hostname: 'my.isy.io', port: 443, path: url, method: 'GET', rejectUnauthorized: true, auth: username + ':' + password }; // console.log(); var req = https.request(options); // when the response comes back req.on('response', function(res){ res.body = ''; res.setEncoding('utf-8'); res.on('data', function(chunk) { res.body += chunk; }); // when the response has finished res.on('end', function() { console.log('DATA:', res.body); context.succeed(res.body); }); res.on('error', function (err) { console.log('ERROR:', err); context.fail(JSON.stringify({ error: 'error' })); }); }); // write data to request body //req.write(body); req.end(); // Can't recall if you need this for a GET }; speechOutput = 'Ok, here is a ' + length + ' demo of the ' + demo; this.emit(":tell", speechOutput); } else { speechOutput = 'Sorry, the system you asked for is not supported yet.'; speechReprompt = 'I support lights, locks, ceiling fans, audio video systems, cameras, pool equipment, etc..'; } this.emit(":tell", speechOutput); }, 'Unhandled': function () { speechOutput = "The skill didn't quite understand what you wanted. Do you want to try something else?"; this.emit(':ask', speechOutput, speechOutput); } }; exports.handler = (event, context) => { const alexa = Alexa.handler(event, context); alexa.appId = APP_ID; // To enable string internationalization (i18n) features, set a resources object. //alexa.resources = languageStrings; alexa.registerHandlers(handlers); //alexa.dynamoDBTableName = 'DYNAMODB_TABLE_NAME'; //uncomment this line to save attributes to DB alexa.execute(); }; // END of Intent Handlers {} ======================================================================================== // 3. Helper Function ================================================================================================= function resolveCanonical(slot){ //this function looks at the entity resolution part of request and returns the slot value if a synonyms is provided let canonical; try{ canonical = slot.resolutions.resolutionsPerAuthority[0].values[0].value.name; }catch(err){ console.log(err.message); canonical = slot.value; }; return canonical; }; function delegateSlotCollection(){ console.log("in delegateSlotCollection"); console.log("current dialogState: "+this.event.request.dialogState); if (this.event.request.dialogState === "STARTED") { console.log("in Beginning"); let updatedIntent= null; // updatedIntent=this.event.request.intent; //optionally pre-fill slots: update the intent object with slot values for which //you have defaults, then return Dialog.Delegate with this updated intent // in the updatedIntent property //this.emit(":delegate", updatedIntent); //uncomment this is using ASK SDK 1.0.9 or newer //this code is necessary if using ASK SDK versions prior to 1.0.9 if(this.isOverridden()) { return; } this.handler.response = buildSpeechletResponse({ sessionAttributes: this.attributes, directives: getDialogDirectives('Dialog.Delegate', updatedIntent, null), shouldEndSession: false }); this.emit(':responseReady', updatedIntent); } else if (this.event.request.dialogState !== "COMPLETED") { console.log("in not completed"); // return a Dialog.Delegate directive with no updatedIntent property. //this.emit(":delegate"); //uncomment this is using ASK SDK 1.0.9 or newer //this code necessary is using ASK SDK versions prior to 1.0.9 if(this.isOverridden()) { return; } this.handler.response = buildSpeechletResponse({ sessionAttributes: this.attributes, directives: getDialogDirectives('Dialog.Delegate', null, null), shouldEndSession: false }); this.emit(':responseReady'); } else { console.log("in completed"); console.log("returning: "+ JSON.stringify(this.event.request.intent)); // Dialog is now complete and all required slots should be filled, // so call your normal intent handler. return this.event.request.intent; } } function randomPhrase(array) { // the argument is an array [] of words or phrases let i = 0; i = Math.floor(Math.random() * array.length); return(array[i]); } function isSlotValid(request, slotName){ let slot = request.intent.slots[slotName]; //console.log("request = "+JSON.stringify(request)); //uncomment if you want to see the request let slotValue; //if we have a slot, get the text and store it into speechOutput if (slot && slot.value) { //we have a value in the slot slotValue = slot.value.toLowerCase(); return slotValue; } else { //we didn't get a value in the slot. return false; } } //These functions are here to allow dialog directives to work with SDK versions prior to 1.0.9 //will be removed once Lambda templates are updated with the latest SDK function createSpeechObject(optionsParam) { if (optionsParam && optionsParam.type === 'SSML') { return { type: optionsParam.type, ssml: optionsParam['speech'] }; } else { return { type: optionsParam.type || 'PlainText', text: optionsParam['speech'] || optionsParam }; } } function buildSpeechletResponse(options) { let alexaResponse = { shouldEndSession: options.shouldEndSession }; if (options.output) { alexaResponse.outputSpeech = createSpeechObject(options.output); } if (options.reprompt) { alexaResponse.reprompt = { outputSpeech: createSpeechObject(options.reprompt) }; } if (options.directives) { alexaResponse.directives = options.directives; } if (options.cardTitle && options.cardContent) { alexaResponse.card = { type: 'Simple', title: options.cardTitle, content: options.cardContent }; if(options.cardImage && (options.cardImage.smallImageUrl || options.cardImage.largeImageUrl)) { alexaResponse.card.type = 'Standard'; alexaResponse.card['image'] = {}; delete alexaResponse.card.content; alexaResponse.card.text = options.cardContent; if(options.cardImage.smallImageUrl) { alexaResponse.card.image['smallImageUrl'] = options.cardImage.smallImageUrl; } if(options.cardImage.largeImageUrl) { alexaResponse.card.image['largeImageUrl'] = options.cardImage.largeImageUrl; } } } else if (options.cardType === 'LinkAccount') { alexaResponse.card = { type: 'LinkAccount' }; } else if (options.cardType === 'AskForPermissionsConsent') { alexaResponse.card = { type: 'AskForPermissionsConsent', permissions: options.permissions }; } let returnResult = { version: '1.0', response: alexaResponse }; if (options.sessionAttributes) { returnResult.sessionAttributes = options.sessionAttributes; } return returnResult; } function getDialogDirectives(dialogType, updatedIntent, slotName) { let directive = { type: dialogType }; if (dialogType === 'Dialog.ElicitSlot') { directive.slotToElicit = slotName; } else if (dialogType === 'Dialog.ConfirmSlot') { directive.slotToConfirm = slotName; } if (updatedIntent) { directive.updatedIntent = updatedIntent; } return [directive]; } Edited March 2, 2019 by btreinders Quote
bmercier Posted March 3, 2019 Posted March 3, 2019 This code is not triggering the request to ISY because the request is not actually called. At line 118, where you have: exports.handler = function (event, context) { You are reassigning a new function to exports.handler (The lambda starting point). You can't do that. It's already assigned at line 183, which is your main lambda function. And since you are just assigning a function to a variable, you are not calling the function at all. So, just remove the line "exports.handler = function (event, context) {", including the closing bracket, and try it again. Benoit Quote
btreinders Posted March 6, 2019 Author Posted March 6, 2019 (edited) @bmercier I removed them and it still seems to return before the call is made. I have tried using a Promise but I don't really understand it. Sure appreciate all the help! I am also having trouble making the call from Chrome now too so I am not sure if I am using the right url. Does it look correct? Thanks! Edited March 6, 2019 by btreinders Quote
bmercier Posted March 8, 2019 Posted March 8, 2019 On 3/6/2019 at 5:24 PM, btreinders said: @bmercier I removed them and it still seems to return before the call is made. I have tried using a Promise but I don't really understand it. Sure appreciate all the help! I am also having trouble making the call from Chrome now too so I am not sure if I am using the right url. Does it look correct? Thanks! Try the URL in a browser. Those are GET requests, so that's an easy way of test them. Benoit Quote
btreinders Posted March 8, 2019 Author Posted March 8, 2019 (edited) @bmercier It does work in Firefox. Chrome pops up the username and password box even those are in the URL and Edge does the same. If I put a console.log after the res.on it never makes it there. Edited March 8, 2019 by btreinders Quote
bmercier Posted March 10, 2019 Posted March 10, 2019 On 3/7/2019 at 8:33 PM, btreinders said: @bmercier It does work in Firefox. Chrome pops up the username and password box even those are in the URL and Edge does the same. If I put a console.log after the res.on it never makes it there. Then I don't know what it is. It may not reach the request at all either. Adding console.log just before would help to find where the issue is. Benoit Quote
btreinders Posted March 10, 2019 Author Posted March 10, 2019 (edited) @bmercier I have successfully made a call to IFTTT using async, promise and await. This is with the exact same code with just port changed to 80, https changed to http, username and password commented out and method changed to POST. Could it be something with the http versus https or my.isy.io just isn't responding? Here is the response I get back with GET and POST: <?xml version="1.0" encoding="UTF-8"?><RestResponse succeeded="false"><status>404</status></RestResponse> With POST: ?xml version="1.0" encoding="UTF-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body><UDIDefaultResponse><status>715</status><info>n/a</info></UDIDefaultResponse></s:Body></s:Envelope> Edited March 10, 2019 by btreinders Quote
bmercier Posted March 21, 2019 Posted March 21, 2019 On 3/10/2019 at 9:46 AM, btreinders said: @bmercier I have successfully made a call to IFTTT using async, promise and await. This is with the exact same code with just port changed to 80, https changed to http, username and password commented out and method changed to POST. Could it be something with the http versus https or my.isy.io just isn't responding? Here is the response I get back with GET and POST: <?xml version="1.0" encoding="UTF-8"?><RestResponse succeeded="false"><status>404</status></RestResponse> With POST: ?xml version="1.0" encoding="UTF-8"?><s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body><UDIDefaultResponse><status>715</status><info>n/a</info></UDIDefaultResponse></s:Body></s:Envelope> Those are responses from the ISY. The calls are getting throught. Now it's a question of having the correct URL to achieve the result that you want. If you try the same URLs with GET and POST directly to the ISY, without portal, you should be getting the exact same result. In the first case, you are getting a 404. I believe the URL is not valid. Try it in a browser. In the second case, I assume it's a post to /service. If I recall correctly, the 715 is an error code related to subscriptions. Benoit 1 Quote
btreinders Posted March 22, 2019 Author Posted March 22, 2019 Thanks so much! That helped me figure it out. I had runthen vice runThen. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.