Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to subscribe to aws IOT topic in lambda?

Newbie question. Can't find a previous answer.

I want to build a simple pump controller with Alexa. Would like Alexa to report pump state.

Simplest approach is Alexa -> lambda -> publish_to_Iot. And then, or perhaps just before the publish, subscribe to another topic to which the local controller would publish pump state that would be passed back thru Alexa.

As near as I can tell its not possible to subscribe to a topic from Lambda... which actually makes sense in the context of a lambda function.

Specific question is, can a lambda function subscribe to an IoT topic?

Yes, I know about IoT shadows, was hoping to avoid some complexity.

like image 429
big Avatar asked Jan 28 '23 22:01

big


2 Answers

It's actually possible to do this, it's just not terribly intuitive. The AWS-SDK doesn't have a subscribe method so you need to use aws-iot-device-sdk. This library typically needs a certificate and lot of other config information.

var device = awsIot.jobs({
  keyPath: "./42fc9544f6-private.pem.key",
  certPath: "./42fc9544f6-certificate.pem.crt",
  caPath: "./AmazonRootCA1.pem",
  clientId: "clientPolicy", // name of the policy
  host: "your-endpoint.iot.us-east-1.amazonaws.com"
});

but it doesn't make sense to use a client certificate in a lambda. The lambda is already running under an IAM user so you should just be able to leverage that right? It turns out you can but it took a little digging. The aws-iot-device-sdk.js library will read the credentials out of the environment variables in Lambda (process.env.AWS_ACCESS_KEY_ID, process.env.AWS_SECRET_ACCESS_KEY). But you HAVE to use the wss protocol.

var awsIot = require('aws-iot-device-sdk');

device = awsIot.jobs({
  host: 'a1jcq6m7bg94jb-ats.iot.us-east-1.amazonaws.com',
  protocol: 'wss'});

device
  .on('connect', function() {
    console.log('connected. subscribing to topic...');
    device.subscribe(topic);
    console.log('subscribed to topic');   
});

One of the pitfalls of this approach is that there is natural latency in Lambda and of course additional latency to establish a connection to the topic. This really slows things down. A nice pattern to follow is to have lambda listen on a topic that is specific to that Lambda instance (e.g. lambda/some-uuid/response) and then when you post a message to your device, you can ask it to respond on that topic. The benefit is that the topic lives as long as the Lambda function is up and running. That could be hours if there is a lot of traffic or if you keep it warm. With this model, there is no latency to establish the connection and subscribe to the topic. In my tests, this is extremely fast with low latency.

This is how I handled the subscription.

var subscriber = null;

const lambdaUUID = uuidv4();
const topic = 'lambda/' + lambdaUUID + '/response';

device.on('message', function(topic, payload) {
  console.log('incoming message on topic' + topic);
  if ( subscriber ) {
    console.log('calling subscriber');
    subscriber(topic, payload);
  } else {
    console.log("no subscriber");
  }
});


exports.handler =  async function(event, context) {
  console.log("EVENT: \n" + JSON.stringify(event, null, 2));

  var deviceRequest = {"some":"stuff", callback: topic};

  const promise = new Promise(function(resolve, reject) {
    subscriber = function(topic, payload ) {
      console.log('subscriber called, resolving promise...');
      resolve("SUCCESS " + payload);
    };
  });  

  device.publish('things/THING1234/incoming', JSON.stringify(deviceRequest,null,''), { qos:0 }, function(err, data) {
    console.log("publishing message to device",err,data);
  });

  return promise;
};
like image 52
Michael Connor Avatar answered Feb 03 '23 23:02

Michael Connor


You can trigger Lambda functions in response to a matching filter using rules (the filter will match the topic).

like image 24
Aaron D Avatar answered Feb 03 '23 22:02

Aaron D