node.jsaws-lambdaalexa-skills-kitmqtt.js

Trying to integrate MQTT into Alexa AWS Lambda function


I am trying to get my Alexa skill to publish to an MQTT Broker in the lambda code. However it only publishes sporadically. Could somebody tell me what I am missing? Here is my code:

const Alexa = require('ask-sdk-core');
const mqtt = require("mqtt");


const protocol = 'mqtt'
const host = 'm21.cloudmqtt.com'
const port = '14297'
const clientId = `mqtt_${Math.random().toString(16).slice(3)}`

const connectUrl = `${protocol}://${host}:${port}`
console.log(connectUrl);
const client = mqtt.connect(connectUrl, {
  clientId,
  clean: false,
  connectTimeout: 4000,
  username: '****',
  password: '***',
  reconnectPeriod: 1000,
})
client.on('connect',  function(connack) { 
        console.log("MQTT Client connected " + JSON.stringify(connack));
        // publish a message to a topic
        client.publish('/IoTmanager/YardESP/Garage_close3/control', '{"status":0}', function() {
            console.log("Message is published");
            client.end(); // Close the connection when published
        });

        });
client.on('end', () => { 
  console.log('Connection to MQTT broker ended');
});
//
const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
  },
  handle(handlerInput) {
    const speechText = 'Would you like to open ur shut the Garage Door?';
    console.log("MQTT Connecting");
    /*client.on("connect", () => {
        console.log("MQTT Connected");
        client.publish("presence", "Hello mqtt");
    });*/

    //
    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('Would you like to open ur shut the Garage Door?', speechText)
      .getResponse();
  }
};
const GarageDoorCloseIntentHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
      && Alexa.getIntentName(handlerInput.requestEnvelope) === 'GarageDoorCloseIntent';
  },
  handle(handlerInput) {
    const speechText = 'Closing The garage door!';
    console.log("MQTT Options: ", speechText);
    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Closing The garage door!', speechText)
      .getResponse();
  }
};
const GarageDoorOpenIntentHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
      && Alexa.getIntentName(handlerInput.requestEnvelope) === 'GarageDoorOpenIntent';
  },
  handle(handlerInput) {
    const speechText = 'Opening The garage door!';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Opening The garage door!', speechText)
      .getResponse();
  }
};
const HelpIntentHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
      && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
  },
  handle(handlerInput) {
    const speechText = 'You can ask me the weather!';

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('You can ask me the weather!', speechText)
      .getResponse();
  }
};
const CancelAndStopIntentHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
      && (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
        || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
  },
  handle(handlerInput) {
    const speechText = 'Goodbye!';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Goodbye!', speechText)
      .withShouldEndSession(true)
      .getResponse();
  }
};
const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
  },
  handle(handlerInput) {
    // Any clean-up logic goes here.
    return handlerInput.responseBuilder.getResponse();
  }
};
const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);

    return handlerInput.responseBuilder
      .speak('Sorry, I don\'t understand your command. Please say it again.')
      .reprompt('Sorry, I don\'t understand your command. Please say it again.')
      .getResponse();
  }
};
let skill;

exports.handler = async function (event, context) {
  console.log(`REQUEST++++${JSON.stringify(event)}`);
  console.log(context);
  if (!skill) {
    skill = Alexa.SkillBuilders.custom()
      .addRequestHandlers(
        LaunchRequestHandler,
        GarageDoorCloseIntentHandler,
        GarageDoorOpenIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
      )
      .addErrorHandlers(ErrorHandler)
      .create();
  }


  const response = await skill.invoke(event, context);
  console.log(`RESPONSE++++${JSON.stringify(response)}`);

  return response;
};

I try invoking the Alexa skill and I get a proper response from/to Alexa but only sporadic messages publishing to the MQTT Broker.


Solution

  • Ok I have resolved my issue thanks to these two answers. How to use async functions within Alexa skill properly? and https://github.com/mqttjs/async-mqtt. It looks like the MQTT has to be wrapped in a async funtion a bit like this:

    const Alexa = require('ask-sdk-core');
    const mqtt = require("mqtt");
    
    var options = {
        port: 14297,
        username: "****",
        password: "***",
        connectTimeout: 3000,
        debug: true
    };
    
    var client;
    
    LaunchRequestHandler = {
        canHandle(handlerInput) {
            return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
        },
        handle(handlerInput) {
            const speechText = 'Would you like to open ur shut the Garage Door?';
    
    
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .withSimpleCard('Would you like to open ur shut the Garage Door?', speechText)
                .getResponse();
        }
    };
    const GarageDoorCloseIntentHandler = {
        canHandle(handlerInput) {
            return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
                Alexa.getIntentName(handlerInput.requestEnvelope) === 'GarageDoorCloseIntent';
        },
        async handle(handlerInput) {
            const speechText = 'Closing the garage door!';
            client = await mqtt.connectAsync('mqtt://m21.cloudmqtt.com', options);
            try {
                await client.publish("wow/so/cool", "It works!");
                // This line doesn't run until the server responds to the publish
                await client.end();
                // This line doesn't run until the client has disconnected without error
                console.log("Done");
            } catch (e) {
                // Do something about it!
                console.log(e.stack);
                process.exit();
            }
            return handlerInput.responseBuilder
                .speak(speechText)
                .withSimpleCard('Closing the garage door!', speechText)
                .getResponse();
        }
    };
    const GarageDoorOpenIntentHandler = {
        canHandle(handlerInput) {
            return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
                Alexa.getIntentName(handlerInput.requestEnvelope) === 'GarageDoorOpenIntent';
        },
        async handle(handlerInput) {
            const speechText = 'Opening the garage door!';
            client = await mqtt.connectAsync('mqtt://m21.cloudmqtt.com', options);
            try {
                await client.publish("wow/so/cool", "It works!");
                // This line doesn't run until the server responds to the publish
                await client.end();
                // This line doesn't run until the client has disconnected without error
                console.log("Done");
            } catch (e) {
                // Do something about it!
                console.log(e.stack);
                process.exit();
            }
    
            return handlerInput.responseBuilder
                .speak(speechText)
                .withSimpleCard('Opening The garage door!', speechText)
                .getResponse();
        }
    };
    const HelpIntentHandler = {
        canHandle(handlerInput) {
            return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
                Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
        },
        handle(handlerInput) {
            const speechText = 'You can ask me to open or shut garage door!';
    
            return handlerInput.responseBuilder
                .speak(speechText)
                .reprompt(speechText)
                .withSimpleCard('You can ask me to open or shut garage door!', speechText)
                .getResponse();
        }
    };
    const CancelAndStopIntentHandler = {
        canHandle(handlerInput) {
            return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
                (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent' ||
                    Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
        },
        handle(handlerInput) {
            const speechText = 'Goodbye!';
    
            return handlerInput.responseBuilder
                .speak(speechText)
                .withSimpleCard('Goodbye!', speechText)
                .withShouldEndSession(true)
                .getResponse();
        }
    };
    const SessionEndedRequestHandler = {
        canHandle(handlerInput) {
            return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
        },
        async handle(handlerInput) {
            // Any clean-up logic goes here.
            client = await mqtt.connectAsync('mqtt://m21.cloudmqtt.com', options);
            try {
                await client.publish("wow/so/cool", "It works!");
                // This line doesn't run until the server responds to the publish
                await client.end();
                // This line doesn't run until the client has disconnected without error
                console.log("Done");
            } catch (e) {
                // Do something about it!
                console.log(e.stack);
                process.exit();
            }
            return handlerInput.responseBuilder.getResponse();
        }
    };
    const ErrorHandler = {
        canHandle() {
            return true;
        },
        handle(handlerInput, error) {
            console.log(`Error handled: ${error.message}`);
    
            return handlerInput.responseBuilder
                .speak('Sorry, I don\'t understand your command. Please say it again.')
                .reprompt('Sorry, I don\'t understand your command. Please say it again.')
                .getResponse();
        }
    };
    let skill;
    
    exports.handler = async function(event, context) {
        console.log(`REQUEST++++${JSON.stringify(event)}`);
        if (!skill) {
            skill = Alexa.SkillBuilders.custom()
                .addRequestHandlers(
                    LaunchRequestHandler,
                    GarageDoorCloseIntentHandler,
                    GarageDoorOpenIntentHandler,
                    HelpIntentHandler,
                    CancelAndStopIntentHandler,
                    SessionEndedRequestHandler,
                )
                .addErrorHandlers(ErrorHandler)
                .create();
        }
    
    
        const response = await skill.invoke(event, context);
        console.log(`RESPONSE++++${JSON.stringify(response)}`);
    
    
        return response;
    };