Azure function to send telemetry data to iotDevice - node.js

I am trying to develop a azure function that receives messages from one the built-in eventhub process it and send the result to another IoT Device configured in the Azure IoT Hub.
Below is the code:
module.exports = function (context, IoTHubMessages) {
var Mqtt = require('azure-iot-device-mqtt').Mqtt;
var DeviceClient = require('azure-iot-device').Client
var Message = require('azure-iot-device').Message
var connectionString = '{connectionstring of the target device}';
var acRoom1 = DeviceClient.fromConnectionString(connectionString, Mqtt);
var totalPerson = 0;
var events = IoTHubMessages.length;
context.log(JSON.stringify(IoTHubMessages));
context.log(Array.isArray(IoTHubMessages));
context.log(`Number of entries: ${IoTHubMessages.length}`);
IoTHubMessages.forEach(message => {
context.log(`Processed message: ${JSON.stringify(message)}`);
totalPerson = totalPerson + message.personCount;
context.log(`Total count: ${totalPerson}`);
});
var avgPersonCount = Math.round( totalPerson / events );
context.log(`Average person count: ${avgPersonCount}`);
var temp = 24;
if ( avgPersonCount > 5){
temp = 20;
}
else if ((avgPersonCount>2) && (avgPersonCount <= 5)){
temp = 22;
}
else {
temp = 24;
}
var msg = new Message(`Setting temperature to ${temp} C`);
context.log('Sending message: ' + msg.getData());
context.log(`Temeperature set to ${temp} C`);
acRoom1.sendEvent(msg);
context.done();
};
The issue I have is that the event that I am sending to device is coming back to this azure functions again. I believe, i need to do something in the Message Routing, but not sure what needs to be done.
The flow of the entire solution ( that I want to achieve ) is as below
Camera -- > Azure IOT Hub --> Azure Function --> AC

So please follow as below example shows on Message routing.
Routing on Message Body If you are routing on $body.property
You have to add the property in the body payload which is being sent by the device (device code is not shown here, only portal query is shown here).
and you can test it out by...
Routing on system property
The Iot Hub will assign this property on every message , so simply do setting on Portal side.(just give device name in the query, for quick you can test by using it on portal side)
App Property as said by Matthijs in his response, below snap shows on device C# sample code. And then you have to write the query which matches the app property.
Verify on Destination side In my example the destination is Blob container.

You could filter events by device ID, but a more scalable way would be to add an appProperty. If you want to send all the AC events to a different endpoint, you can add an appProperty to the message the AC is sending. Example:
var msg = new Message(`Setting temperature to ${temp} C`);
msg .properties.add('eventType', 'AC');
context.log('Sending message: ' + msg.getData());
context.log(`Temeperature set to ${temp} C`);
acRoom1.sendEvent(msg);
After that, you can go to your IoT Hub and add a new route. You can route these events to a different endpoint like so:
Because your camera doesn't send this appProperty, it will rely on the fallback route and your Azure Function will still handle those events. Another, perhaps more reliant option is to send only the camera messages to a specific route. But either will work!

I figured out the answer. Thanks to #Matthijs van der Veer for the hint.
1. Firstly disable the fall back rule. Now I have a 2 route
Instead of azure-iot-device package, i shifted to azure-iothub package.
module.exports = function (context, IoTHubMessages) {
var Client = require('azure-iothub').Client;
var Message = require('azure-iot-common').Message;
var connectionString = '{connection-string-policy-service}';
var targetDevice = '{destination-deviceid}';
var serviceClient = Client.fromConnectionString(connectionString);
serviceClient.open(function (err) {
if (err) {
context.log('Could not connect: ' + err.message);
}
});
var totalPerson = 0;
var events = IoTHubMessages.length;
context.log(JSON.stringify(IoTHubMessages));
context.log(`Number of entries: ${IoTHubMessages.length}`);
IoTHubMessages.forEach(message => {
context.log(`Processed message: ${JSON.stringify(message)}`);
totalPerson = totalPerson + message.personCount;
context.log(`Total count: ${totalPerson}`);
});
var avgPersonCount = Math.round( totalPerson / events );
context.log(`Average person count: ${avgPersonCount}`);
var temp = 24;
if ( avgPersonCount > 5){
temp = 20;
}
else if ((avgPersonCount>2) && (avgPersonCount <= 5)){
temp = 22;
}
else {
temp = 24;
}
var msg = new Message(`Setting temperature to ${temp} C`);
msg .properties.add('eventType', 'AC');
context.log('Sending message: ' + msg.getData());
context.log(`Temeperature set to ${temp} C`);
serviceClient.send(targetDevice, msg);
context.done();
};

Related

Sending messages to IoT hub from Azure Time Trigger Function as device

At the moment Im simulating device where every 30 seconds I send telemetry data to IoT hub.
Here is simple code:
s_deviceClient = DeviceClient.Create(s_iotHubUri, new DeviceAuthenticationWithRegistrySymmetricKey(s_myDeviceId, s_deviceKey), TransportType.Mqtt);
using var cts = new CancellationTokenSource();
var messages = SendDeviceToCloudMessagesAsync(cts.Token);
await s_deviceClient.CloseAsync(cts.Token);
await messages;
cts.Cancel();
And function to send message:
string combinedString = fileStrings[0] + fileStrings[1];
var telemetryDataString = converter.SerializeObject(combinedString);
using var message = new Message(Encoding.UTF8.GetBytes(telemetryDataString))
{
ContentEncoding = "utf-8",
ContentType = "application/json",
};
await s_deviceClient.SendEventAsync(message);
await Task.Delay(interval);
Everything works fine and I created .exe file that was running without problems. But computer where code is running tends to shut-off from time to time which is problematic. So I tried to move this to Azure Time Trigger Function. While in logs everything looks ok, messages aren't actually posted to IoT hub.
I tried to find solution but have not been able to find anything. Is it possible to send messages as device with azure function?
You seem to be closing your DeviceClient before you start using it to send messages. Try the following:
public async Task Do()
{
// Using statement will dispose your client after you're done with it.
// No need to close it manually.
using(var client = DeviceClient.Create(s_iotHubUri, new DeviceAuthenticationWithRegistrySymmetricKey(s_myDeviceId, s_deviceKey), TransportType.Mqtt))
{
// Send messages, await for completion.
await SendDeviceToCloudMessagesAsync(client);
}
}
private async Task SendDeviceToCloudMessagesAsync(DeviceClient client)
{
string combinedString = fileStrings[0] + fileStrings[1];
var telemetryDataString = converter.SerializeObject(combinedString);
using var message = new Message(Encoding.UTF8.GetBytes(telemetryDataString))
{
ContentEncoding = "utf-8",
ContentType = "application/json",
};
await client.SendEventAsync(message);
await Task.Delay(interval);
}

I'm receiving a Stripe exception "No such payout: 'po_1KJ6pFQ**********YsFVzT4'" when fetching balance transactions

I'd like to fetch all the Stripe Transfers that make up a Payout. I'm following this Stackoverflow post here that says fetch the balance transactions and pass in a payout ID and set the type to "transfer".
In my Stripe dashboard I can see multiple payouts and I'm just copying/pasting different ID's to test this call.
Problem - I keep getting the same error message from Stripe saying "No such payout: 'po_1KJ6pFQ**********YsFVzT4'"
Here's how I'm calling the balance transactions.
var options = new BalanceTransactionListOptions
{
Payout = "po_1KJ6pFQ**********YsFVzT4",
// Type = "transfer",
// Limit = 100,
};
var service = new BalanceTransactionService();
try {
StripeList<BalanceTransaction> balanceTransactions = service.List(options);
foreach(BalanceTransaction balTransaction in balanceTransactions) { // do something }
}
} catch(StripeException ex) {
var e = ex;
}
No such (object) error messages occurs when the object you're attempting to access does not exist on the Stripe account.
By default, the request would be made on the Stripe account whose API key you're using. If you're using Connect and you need to access an object on a connected account, you should use your platform's API key and the Stripe-Account header.
var requestOptions = new RequestOptions();
requestOptions.StripeAccount = "{{CONNECTED_ACCOUNT_ID}}";
var options = new BalanceTransactionListOptions
{
Payout = "po_1KJ6pFQ**********YsFVzT4",
// Type = "transfer",
// Limit = 100,
};
var service = new BalanceTransactionService();
try {
StripeList<BalanceTransaction> balanceTransactions = service.List(options, requestOptions);
foreach(BalanceTransaction balTransaction in balanceTransactions) { // do something }
}
} catch(StripeException ex) {
var e = ex;
}

Is there any way to capture and save end-to-end conversation data into blob storage or cosmos DB in bot framework using SDK V4 Nodedjs

I want to store the conversation data to the storage account or cosmos DB. By trying this https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript#using-blob-storage
I am able to send the utteranceslog into blob storage. But I want to store end-to-end conversation data which includes data of both users as well as bot responses using javascript.
I tried using saving user state and conversation state but didn't achieve the desired output.
I created a custom logger (based on an old botduilder-samples sample that isn't there anymore) that accomplishes this using TranscriptLoggerMiddleware. I chose CosmosDB instead of Blob Storage because I felt it was easier to store (and retrieve) as a JSON document. But you could tweak this concept to use any DB. Here is what I did.
First, create your custom logger code. As mentioned, I used CosmosDB so you might have to change some things if you're using a different DB. The timing of the activities was creating concurrency issues, so instead of working around that, I'm storing the transcript object locally and overwriting the DB object on each turn. Maybe not the most elegant, but it works. Also, I've found my wait function to be required. Otherwise you only get one side of the conversation. I've been told this type of wait function is not a best practice, but awaiting a promise or other methods of creating a delay did not work for me. Here is the code:
customerLogger.js
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { CosmosDbStorage } = require('botbuilder-azure');
const path = require('path');
/**
* CustomLogger, takes in an activity and saves it for the duration of the conversation, writing to an emulator compatible transcript file in the transcriptsPath folder.
*/
class CustomLogger {
/**
* Log an activity to the log file.
* #param activity Activity being logged.
*/
// Set up Cosmos Storage
constructor(appInsightsClient) {
this.transcriptStorage = new CosmosDbStorage({
serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'bot-transcripts'
});
this.conversationLogger = {};
this.appInsightsClient = appInsightsClient;
this.msDelay = 250;
}
async logActivity(activity) {
if (!activity) {
throw new Error('Activity is required.');
}
// Log only if this is type message
if (activity.type === 'message') {
if (activity.attachments) {
var logTextDb = `${activity.from.name}: ${activity.attachments[0].content.text}`;
} else {
var logTextDb = `${activity.from.name}: ${activity.text}`;
}
if (activity.conversation) {
var id = activity.conversation.id;
if (id.indexOf('|') !== -1) {
id = activity.conversation.id.replace(/\|.*/, '');
}
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var fileName = `${datestamp}_${id}`;
var timestamp = Math.floor(Date.now()/1);
// CosmosDB logging (JK)
if (!(fileName in this.conversationLogger)) {
this.conversationLogger[fileName] = {};
this.conversationLogger[fileName]['botName'] = process.env.BOTNAME;
}
this.conversationLogger[fileName][timestamp] = logTextDb;
let updateObj = {
[fileName]:{
...this.conversationLogger[fileName]
}
}
// Add delay to ensure messages logged sequentially
await this.wait(this.msDelay);
try {
let result = await this.transcriptStorage.write(updateObj);
} catch(err) {
this.appInsightsClient.trackTrace({message: `Logger ${err.name} - ${path.basename(__filename)}`,severity: 3,properties: {'botName': process.env.BOTNAME, 'error':err.message,'callStack':err.stack}});
}
}
}
}
async wait(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds) {
break;
}
}
}
}
exports.CustomLogger = CustomLogger;
Now you need to attach this to the botframework adapter in your index.js file. The relevant pieces of code are:
index.js
const { TranscriptLoggerMiddleware } = require('botbuilder');
const { CustomLogger } = require('./helpers/CustomLogger');
//
//Your code to create your adapter, etc.
//
const transcriptLogger = new TranscriptLoggerMiddleware(new CustomLogger(appInsightsClient));
adapter.use(transcriptLogger);
I'm assuming here you already have your index.js file figured out, but if you need any assistance getting that set up and getting the transcript logger to work with it, just let me know.
EDIT: By request, here is what the object looks like in CosmosDB. Normally I would have the "from name" displayed, but because of the way I was testing the bot it came through "undefined".
{
"id": "2020-3-21_IfHK46rZV42KH5g3dIUgKu-j",
"realId": "2020-3-21_IfHK46rZV42KH5g3dIUgKu-j",
"document": {
"botName": "itInnovationBot",
"1584797671549": "Innovation Bot: Hi! I'm the IT Innovation Bot. I can answer questions about the innovation team and capture your innovation ideas. Let me know how I can help!",
"1584797692355": "undefined: Hello",
"1584797692623": "Innovation Bot: Hello.",
"1584797725223": "undefined: Tell me about my team",
"1584797725490": "Innovation Bot: The innovation team is responsible for investigating, incubating, and launching new technologies and applications. The innovation focus areas are:\n\n* Chatbots\n\n* Augmented Reality/Virtual Reality\n\n* Blockchain\n\n* Robotic Process Automation\n\n* AI & Machine Learning\n\nLet me know if you want to learn more about any of these technologies!",
"1584797746279": "undefined: Thanks",
"1584797746531": "Innovation Bot: You're welcome."
},
"_rid": "OsYpALLrTn2TAwAAAAAAAA==",
"_self": "dbs/OsYpAA==/colls/OsYpALLrTn0=/docs/OsYpALLrTn2TAwAAAAAAAA==/",
"_etag": "\"a4008d12-0000-0300-0000-5e7618330000\"",
"_attachments": "attachments/",
"_ts": 1584797747
}
To read the conversation back (even if still in the middle of the conversation), you just create a connector in your bot, recreate the key, and read the file as below (in this case id is passed into my function and is the conversation id):
const transcriptStorage = new CosmosDbStorage({
serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'bot-transcripts',
partitionKey: process.env.BOTNAME
});
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var filename = `${datestamp}_${id}`;
var transcript = await transcriptStorage.read([filename]);

Send and Receive Blob + Visual in Video tag (WebRTC/Socket.IO/NodeJS)

I am creating a Video Chat App with WerbRTC + NodeJS + Socket.io but I have problems to recover the video and show in a video HTML element.
Client 1, I capture the stream blob with next code
this.mediaRecorder = new MediaRecorder(this.stream, options);
this.mediaRecorder.ondataavailable = dataAvailable.bind(this);
this.mediaRecorder.start(10);
function dataAvailable(e){
socket.emit('streaming', e.data);
};
Server I just receive the bytes and resend to the client2
this.streaming = function(data){
for (var key in clients) {
if(key != data.clientID){
var client = clients[key];
client.emit('streaming', data);
};
};
}.bind(this); socket.on('streaming', this.streaming);
All above is working, but my question is when I receive the ArrayBuffer how I process it to display the streaming again in a video tag elements, I try with the next code, but not work.
Client 2
this.type = 'video/webm;codecs=vp9';
this.blob = new Blob([], { type:this.type});
this.receiveStream = function(data){
var stream = data.stream;
if(stream.byteLength <= 0) return;
this.blob = new Blob([this.blob, new Uint8Array(stream)], { type:this.type});
this.nodes.video.src = URL.createObjectURL(this.blob);
}
Thank you....

Office 365 'Create Event' Rest API is giving error

I am new user on stackoverflow as well as in office 365 development using node.js.
I am successfully getting User(my own office 365 account) mails,calendar events using this tutorial (https://dev.outlook.com/RestGettingStarted/Tutorial/node)
but when i am trying to Create an Event in my calender it gives me below error
"{"error":{"code":"ErrorAccessDenied","message":"Access is denied. Check credentials and try again."}}"
Please provide me suggestions on the same.
Below is the code for creating event which i copied from [https://msdn.microsoft.com/office/office365/APi/calendar-rest-operations#CreateEvents] here
function createEvent(response, request) {
var cookieName = 'node-tutorial-token';
var cookie = request.headers.cookie;
// if (cookie && cookie.indexOf(cookieName) !== -1) {
console.log("Cookie: ", cookie);
// Found our token, extract it from the cookie value
var start = cookie.indexOf(cookieName) + cookieName.length + 1;
var end = cookie.indexOf(';', start);
end = end === -1 ? cookie.length : end;
var token = cookie.substring(start, end);
console.log("Token found in cookie: " + token);
var event = new outlook.Microsoft.OutlookServices.Event();
event.subject = 'Your Subject';
event.start = new Date("October 30, 2014 11:13:00").toISOString();
event.end = new Date("October 30, 2014 12:13:00").toISOString();
// Body
event.body = new outlook.Microsoft.OutlookServices.ItemBody();
event.body.content = 'Body Content';
event.body.contentType = outlook.Microsoft.OutlookServices.BodyType.Text;
// Location
event.location = new outlook.Microsoft.OutlookServices.Location();
event.location.displayName = 'Location';
// Attendee
var attendee1 = new outlook.Microsoft.OutlookServices.Attendee();
var emailAddress1 = new outlook.Microsoft.OutlookServices.EmailAddress();
emailAddress1.name = "abc";
emailAddress1.address = "abc#abcdt.onmicrosoft.com";
attendee1.emailAddress = emailAddress1;
event.attendees.push(attendee1);
var outlookClient = new outlook.Microsoft.OutlookServices.Client('https://outlook.office365.com/api/v1.0',
authHelper.getAccessTokenFn(token));
outlookClient.me.calendar.events.addEvent(event)
.then(function (response) {
console.log(response._Id);
}, function (error) {
console.log(error);
});
}
Make sure your app has requested for calendar.readwrite permission and you need this to create new events. In the example you followed, your app registered for only Calendar.Read permissions (see below).
You should instead go to https://dev.outlook.com/AppRegistration to register an app with Calendar.ReadWrite permission which is required to create new events.

Resources