Forum Discussion

Jeff_Woeber's avatar
8 years ago

LogicMonitor API and Alexa voice commands.

Amazon’s Alexa is a fantastic tool for working with APIs including LogicMonitor’s.  The Amazon developer tools make this remarkably easy to script, but it can be a bit of a challenge the first time through. Let’s change that and walk through setting up a Alexa app that will acknowledge LogicMonitor Alerts through a API.  

Credit: I found this Youtube tutorial from Jordan Leigh very informative and used it as a base for building this app

To my understanding, a Alexa Applications has two parts.  

* The Skill which includes the interaction model - setup the intents (functions) the app will execute and Utterances - which are the words and phrases that will trigger the functions  

 

* The function itself that will build and call the API.  

Once done, you will need to load the LogicMonitor Alexa skill by saying "Alexa load LogicMonitor".  

Then perform task by saying the pre-programmed utterance.  Let's build a LogicMonitor Alexa Skill!  

This example uses the RPC API for simplicity and to illustrate how Alexa Skills are configured.  The RPC API will eventually be depreciated, for long-term functionality it is recommended using the REST API.  An example of using the REST API in a Python script is below the JavaScript Skill Function Code.  

*By request and for the brave, a walkthrough of creating a REST API in Python is below the APC Javascript skill

Let’s start by creating the LogicMonitor Alexa Skill

Login to https://developer.amazon.com, create an account if needed.  And click on Alexa link at the top.  Next press the “Get Started” button under “Alexa Skill Kit”

 

For Skill information I used the below values.  

Skill  type = Custom Interaction Model

Name = LogicMonitor

Invocation Name = LogicMonitor

 

7z5jG4f.jpg

Next is the Interaction Model that will set up the intents or what I would call functions.  This is in json format.  We will  create two intents for this walk through.  One to acknowledge Alerts and a second to acknowledge Events, since these are two different API calls.    Let’s take a look at one in detail

 

"intent": "acknowledgeAlert",
     "slots":[
       {
         "name": "AlertID",
         "type": "AMAZON.NUMBER"

 

"intent": "acknowledgeAlert", names the intent.  acknowledgeAlert will need to be defined in the function we will create later as well as the utterances so Alexa will know when to execute this intent. SLOTS are variables or the strings we can pass to our function to include in our API.  They need to be a pre-defined type like AMAZON.NUMBER or a custom slot type.  This walk through will  pass alert numbers and we will use the amazon.number slot.  More information on this can be found at https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/slot-type-reference

The full attached intent schema can be copy\pasted into your intent schema

zeO7sy5.jpg

Next is the Sample Utterances.  This is the fun part where we setup the voice commands.  Let’s take a look

zM82ttN.jpg

acknowledgeAlert - This is the intent to be executed and the utterance has to start with the intent.  The utterance also has to include the Invocation Name we setup previously which is “LogicMonitor”.  The rest are common phrases the end user may use to invoke the intent with the slot passed in {}.  You can setup as many utterances for an intent as needed.  

An example of the spoken utterances will be “Alexa Acknowledge LogicMonitor Alert 64532

This should send an API acknowledging LMD64532

 

Next is the Endpoint Configuration where we call the actual function.  Lets pause here and create the function.  This walk through will create a Java Script function on Amazons LAMBDA service.  

 

Sign into https://aws.amazon.com/, create an account if needed.  Search for “LAMBDA” and press the “Create a Lambda Function”  

 

For the Select Blue print, add “Alexa” to the filter and select “Blank Function”

3PrzSO6.jpg

 

Select “Alexa Skill Set” for Configured triggers

ANY45hG.jpg

 

For the configure Function, lets name it “LogicMonitor” and set the runtime to Node.js.  You can copy\paste the attached LogicMonitor function into the window, but lets take closer look.

 

f9F6XiQ.jpg

This is basically a java script with case statement for “LaunchRequest” a default intent, and IntentRequest to call or two intents.  Lets walk through our “AcknowlegeAlert” intent

        

       case "acknowledgeAlert":
           var AlertIDstr = (event.request.intent.slots.AlertID.value);
           var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=*******&ids=LMD"+AlertIDstr; 
           var body = "";
           https.get(endpoint, (response) => {
             response.on('data', (chunk) => { body += chunk });
             response.on('end', () => {
               var data = JSON.parse(body);
               var status = data.status;
               var errmsg = data.errmsg
                   if (status != 200) {
                       context.succeed(
                           generateResponse(
                           buildSpeechletResponse("Failed to acknowledge alert L M D"+AlertIDSt+" with error "+ errmsg, true)
                           
                           )
                           )
                   } else{
                   
                       context.succeed(
                           generateResponse(
                           buildSpeechletResponse("Alert L M D"+AlertIDstr+" has been acknowledged", true)
                   )
                   )
                   }
                   
             });
           });
           break;


 

case "acknowledgeAlert": this is were we tie the Interaction mode, Utterances, and function together.  

var AlertIDstr = (event.request.intent.slots.AlertID.value);  - This is how we get the Alert ID slot (Variable) defined as AlertIDstr inside of the java script.  

var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts? - this is how we define our API call.  Note the end of the string &ids=LMD"+AlertIDstr;  I am hardcodeing the LMD into the string and only passing the AlertIDstr wich will be a number i.e.64521. You’ll of course need to change the url to match your portal.  I’m using the RPC API and more information can be read at https://www.logicmonitor.com/support/rpc-api-developers-guide/manage-alerts/acknowledge-alerts/

 

The https.get will send the URL and var data = JSON.parse(body); Will load the JSON file returned from LogicMonitor. We need to read the JSON file to ensure the API call was a success or if there was an error.   Status=200 is error, so there is a simple if (status != 200) { statement to verify success and determine an appropriate buildSpeechletResponse return.  If the status=200 Alexa will say “Failed to acknowledge alert L M D"+AlertIDSt+" with error "+ errmsg, true)” or “Failed to acknowlege alert LMD65432 with error [access denied].  If the status is other than 200 Alexa will say “Alert LMD65432 has been acknowledged.’

 

Save the function and use the action on the main page to get the ARN string.  

9U5RaTe.jpg

Now,  back to the Skill, we can add are function (Endpoint) using the ARN string and continue.  Next is testing and this is the fun part where we put everything together.   

 

DQEiEie.jpg

Next is the testing, and this is the another fun part.  

Find an active alert in your LogicMonitor portal and type in the Utterance according to the Intent Schema. I am going to use LMD2033 so In my case it will be “acknowledge logicmonitor alert 2033” and press “Ask LogicMonitor”  


2VHxKXD.jpg

Go ahead and press the “Listen” button to hear Alexa’s response.  


M8vo262.jpg

That’s it, now it’s possible to acknowledge LogicMonitor Events and Alerts using Alexa voice commands.  

 

 

Intent Schema

{
  "intents":[
    {
      "intent": "acknowledgeAlert",
      "slots":[
        {
          "name": "AlertID",
          "type": "AMAZON.NUMBER"
        }
      ]
    },
    {
      "intent": "acknowlegeEvent",
      "slots":[
        {
          "name": "EventID",
           "type": "AMAZON.NUMBER"
        }
      ]
    }
        
  ]
  
}


 

LogicMonitor Java Script Function

var https = require('https');

exports.handler = (event, context) => {

  try {

    if (event.session.new) {
      // New Session
      console.log("NEW SESSION");
    }
    

    switch (event.request.type) {

      case "LaunchRequest":
        // Launch Request
        console.log(`LAUNCH REQUEST`);
        context.succeed(
          generateResponse(
            buildSpeechletResponse("Welcome to LogicMonitor, I can acknowlege alerts and Events", true),
            {}
          )
        );
    
        break;

      case "IntentRequest":
        // Intent Request
        console.log(`INTENT REQUEST`);

        switch(event.request.intent.name) {
         
        case "acknowledgeAlert":
            var AlertIDstr = (event.request.intent.slots.AlertID.value);
            var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=changeme=LMD"+AlertIDstr; 
            var body = "";
            https.get(endpoint, (response) => {
              response.on('data', (chunk) => { body += chunk });
              response.on('end', () => {
                var data = JSON.parse(body);
                var status = data.status;
                var errmsg = data.errmsg
                    if (status != 200) {
                        context.succeed(
                            generateResponse(
                            buildSpeechletResponse("Failed to acknowlege alert L M D"+AlertIDSt+" with error "+ errmsg, true)
                            
                            )
                            )
                    } else{
                    
                        context.succeed(
                            generateResponse(
                            buildSpeechletResponse("Alert L M D"+AlertIDstr+" has been acknowledged", true)
                    )
                    )
                    }
                    
              });
            });
            break;
      
    
        case "acknowlegeEvent":
            var EventIDstr = (event.request.intent.slots.EventID.value);
            var evendpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=changeme&eids=LME"+EventIDstr; 
            var evbody = "";
            https.get(evendpoint, (response) => {
              response.on('data', (chunk) => { evbody += chunk });
              response.on('end', () => {
                var data = JSON.parse(evbody);
                var status = data.status;
                var errmsg = data.errmsg;
                    if (status != 200) {
                        context.succeed(
                            generateResponse(
                            buildSpeechletResponse("Failed to acknowlege event L M E"+EventIDstr+" with error "+ errmsg, true)
                            
                            )
                            );
                    } else{
                    
                        context.succeed(
                            generateResponse(
                            buildSpeechletResponse("Event L M E"+EventIDstr+" has been acknowledged", true)
                    )
                    );
                    }
                    
              });
            });
            
            break;
        
     
      case "SessionEndedRequest":
        // Session Ended Request
        console.log(`SESSION ENDED REQUEST`);
        break;

      default:
        context.fail(`INVALID REQUEST TYPE: ${event.request.type}`);

    }
}
}
   catch(error) { context.fail(`Exception: ${error}`) }

};

// Helpers
buildSpeechletResponse = (outputText, shouldEndSession) => {

  return {
    outputSpeech: {
      type: "PlainText",
      text: outputText
    },
    shouldEndSession: shouldEndSession
  };

};

generateResponse = (speechletResponse, sessionAttributes) => {

  return {
    version: "1.0",
    sessionAttributes: sessionAttributes,
    response: speechletResponse
  };

};

You should now have a working RPC javascript Alexa LogicMonitor Skill set

 

For the brave:

OK that was the easy part, lets take a look at running Python and the LogicMonitor REST API.  

The difficult part with the REST API is the authentication, which requires importing of Modules.  This isn't native to Amazon LAMBDA so we we have to build or own environment.  I used a terminal in a Ubuntu environment for easy access to the PIP installer.  I also used this excellent A minimalist SDK for developing skills for the Amazon Echo's ASK - Alexa Skills Kit using Amazon Web Services's Python Lambda Functions. written by Anjishnu Kumar .  I'm not going to walk through step-by-step as Anjishnu's readme does an excellent job.  I will share my pain-points I had when setting this up.  

Getting the Modules installed.  Amazon's Docs, plus a walkthrough in Anjishunu's readme file. 

Cliff notes version, you need to use PIP to install the modules to a the script directory.  Once everything is installed we will ZIP the directory and upload to LAMBDA.  

First make a directory in your terminal window with mkdir logicmonitor (or whatever you would like to call the skill) and go through Step 1 in the Readme File.  

Next we install the modules.  This two we have to worry about are:

from ask import alexa
import requests

 

The first is just the alexa SDK and as in Anuishunu's readme the command it

pip install ask-alexa-pykit --target logicmonitor 

Net is request and the command is

pip install requests --target logicmonitor 

You will see the Ask and Request folders in the directory.  

You can skip step 2 and 3 as we already have a intent schema from the previous example.  

Step 4 is the creation of the lambda_funtion.py.  You can use mine (Shown below) as a template.  Save the file as lambda_function.py in the logicmonitor folder.   

Step 5 is compressing the logicmonitor folder or as in the Readme ask-lambda.zip, ether name is fine.

Step 6 is creating the LAMBDA funtion.

FUVsEcQ.jpg

 

WATCH THE RUNTIME.  I lost a hour because Amazon switched Python 2.7 back to Node.js, and I notice it kept switching back when I re-uploaded my code.   

The other gotcha is the handler, lambda_function.lambda_handler is expecting a python script named lambda_function just like logicmonitor.lambda_handler would expect a script named logicmonitor.  

Hopefully this is enough information to write your own LogicMonitor Alexa Skill!

-Enjoy!

from ask import alexa
import requests
import hashlib
import base64
import time
import hmac

def lambda_handler(request_obj, context=None):
    '''
    This is the main function to enter to enter into this code.
    If you are hosting this code on AWS Lambda, this should be the entry point.
    Otherwise your server can hit this code as long as you remember that the
    input 'request_obj' is JSON request converted into a nested python object.
    '''

    metadata = {'user_name': 'SomeRandomDude'}  # add your own metadata to the request using key value pairs

    ''' inject user relevant metadata into the request if you want to, here.
    e.g. Something like :
    ... metadata = {'user_name' : some_database.query_user_name(request.get_user_id())}

    Then in the handler function you can do something like -
    ... return alexa.create_response('Hello there {}!'.format(request.metadata['user_name']))
    '''
    return alexa.route_request(request_obj, metadata)


@alexa.default
def default_handler(request):
    """ The default handler gets invoked if no handler is set for a request type """
    return alexa.respond('Just ask').with_card('Hello World')


@alexa.request("LaunchRequest")
def launch_request_handler(request):
    ''' Handler for LaunchRequest '''
    return alexa.create_response(message="Hello Welcome to Logic Monitor, I can Acknowledge alerts and events!")


@alexa.request("SessionEndedRequest")
def session_ended_request_handler(request):
    return alexa.create_response(message="Goodbye!")


@alexa.intent('acknowledgeAlert')
def acknowlege_alert(request):
    alertid = request.slots["AlertID"]
    alertidstr = str(alertid)

    # Account Info
    AccessId = '4R9CX5fQf3MrKvjLS59T'
    AccessKey = 'ye[$3y7)_4g6L6uH2TC72k{V6HBUf]Ys+9!vB)[9'
    Company = 'lmjeffwoeber'

    # Request Info
    httpVerb = 'POST'
    resourcePath = '/alert/alerts/LMD'+alertidstr+'/ack'
    queryParams = ''
    data = '{"ackComment":"acked with Alexa"}'

    # Construct URL
    url = 'https://' + Company + '.logicmonitor.com/santaba/rest' + resourcePath + queryParams

    # Get current time in milliseconds
    epoch = str(int(time.time() * 1000))

    # Concatenate Request details
    requestVars = httpVerb + epoch + data + resourcePath

    # Construct signature
    signature = base64.b64encode(hmac.new(AccessKey, msg=requestVars, digestmod=hashlib.sha256).hexdigest())

    # Construct headers
    auth = 'LMv1 ' + AccessId + ':' + signature + ':' + epoch
    headers = {'Content-Type': 'application/json', 'Authorization': auth}

    # Make request
    response = requests.post(url, data=data, headers=headers)


    if response.status_code !=200:
        return alexa.create_response(message="I'm sorry, I was unable to acknowledge Alert L M D"+alertidstr)
    else:
        return alexa.create_response(message="Alert acknowledge!")



@alexa.intent('acknowlegeEvent')
def acknowlege_event(request):

    eventid =  request.slots["EventID"]
    eventidstr = str(eventid)
    # Account Info
    AccessId = '4R9CX5fQf3MrKvjLS59T'
    AccessKey = 'ye[$3y7)_4g6L6uH2TC72k{V6HBUf]Ys+9!vB)[9'
    Company = 'lmjeffwoeber'

    # Request Info
    httpVerb = 'POST'
    resourcePath = '/alert/alerts/LME'+eventidstr+'/ack'
    queryParams = ''
    data = '{"ackComment":"acked with alexa"}'

    # Construct URL
    url = 'https://' + Company + '.logicmonitor.com/santaba/rest' + resourcePath + queryParams

    # Get current time in milliseconds
    epoch = str(int(time.time() * 1000))

    # Concatenate Request details
    requestVars = httpVerb + epoch + data + resourcePath

    # Construct signature
    signature = base64.b64encode(hmac.new(AccessKey, msg=requestVars, digestmod=hashlib.sha256).hexdigest())

    # Construct headers
    auth = 'LMv1 ' + AccessId + ':' + signature + ':' + epoch
    headers = {'Content-Type': 'application/json', 'Authorization': auth}

    # Make request
    response = requests.post(url, data=data, headers=headers)

    if response.status_code !=200:
        return alexa.create_response(message="I'm sorry, I was unable to acknowledge Event L M E"+eventidstr)
    else:
        return alexa.create_response(message="Event acknowledge!")

 















 

No RepliesBe the first to reply