IoT Agent written in .NET 6
IoT Agent integrates on-premise devices with Azure-based IoT services to create a blazingly fast and scalable system for factory operations.
Download .zip
file or run commads from your favourite CLI
git clone https://github.com/ZDSDD/IoT_OpcAgent.git
cd IoT_OpcAgent
To run the application, you can build it yourself, or run the OpcAgent.exe
As the above, or deploy it to Azure so it can run constantly and listen for the requests.
This section will show which local variables need to be set up, both on Azure App and on the OPC Agent.
This sample local.settings.json file should be located in the root folder of the FunctionAppsDemo solution.
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "AzureWebJobsStorageConnectionStringValue",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"ServiceBusConnectionString": "<my_service_bus_connection_string>",
"QueueNameProduction": "<name_of_my_created_queue1>",
"QueueNameErrorEvent": "<name_of_my_created_queue2>",
"QueueNameThreeErrors": "<name_of_my_created_queue3>",
"ThreeErrorsBlobContainerName": "<blob_container_name_in_azure>",
"productionBlobContainerName": "<blob_container_name_in_azure>",
"IoTHubConnectionString": "<my_IoTHuB_connection_string>",
"Storage": "<my_storage_connection_string>",
"CommunicationServiceConnectionString": "<my_communication_service_connection_string>",
"senderAddress": "<email_that_sends_emails>",
"emailTo": "<some@mail.com>"
}
}
If deployed on Azure, navigate to Settings -> Environment variables -> Add application setting.
Official Microsoft documentation app-service-settings link
To configure the IoT Agent locally, you must provide the secrets.json
file for the solution. How to init secrets
{
"ConnectionStrings": {
"serverAddress": "<server_adress>", //i.e. "opc.tcp://localhost:4840/",
"IoTHub": "<IoTHub_connection_string>"
},
"Devices": [
{
"DeviceNodeId": "<device_node_id>", // i.e. "DeviceNodeId": "ns=2;s=Device 1"
"DeviceConnectionString": "<iot_hub_device_connection_string1>"
},
{
"DeviceNodeId": "<device_node_id>",
"DeviceConnectionString": "<iot_hub_device_connection_string2>"
},
{
"DeviceNodeId": "<device_node_id>",
"DeviceConnectionString": "<iot_hub_device_connection_string4>"
},
{
"DeviceNodeId": "<device_node_id>",
"DeviceConnectionString": "<iot_hub_device_connection_string5>"
}
//... and more pre-defined devices as you need.
]
}
In order for our buissness logic to work, we need to utilize ASA on Azure.
Azure Stream Analytics is a fully managed stream processing engine that is designed to analyze and process large volumes of streaming data with sub-millisecond latencies. - Microsoft
That's how the query should look like.
/*
Here are links to help you get started with Stream Analytics Query Language:
Common query patterns - https://go.microsoft.com/fwLink/?LinkID=619153
Query language - https://docs.microsoft.com/stream-analytics-query/query-language-elements-azure-stream-analytics
*/
SELECT
System.Timestamp() AS WindowStartTime,
IoTHub.ConnectionDeviceId,
AVG(Temperature) AS AverageTemperature,
MIN(Temperature) AS MinTemperature,
MAX(Temperature) AS MaxTemperature
INTO
[OUTPUT] -- Blob storage
FROM
[INPUT] -- IoT Hub name i.e. [iot-seba]
WHERE
System.Timestamp() >= DATEADD(minute, -5, System.Timestamp()) -- Data from the last 5 minutes
GROUP BY
IoTHub.ConnectionDeviceId,
TumblingWindow(minute, 1)
-- query 2
SELECT
System.Timestamp() AS EventTime,
IoTHub.ConnectionDeviceId
INTO
[OUTPUT] -- Service Bus queue name
FROM
[INPUT] -- IoT Hub name i.e. [iot-seba]
WHERE
Event = 'error' AND ErrorsIncreased = 1
GROUP BY
IoTHub.ConnectionDeviceId,
TumblingWindow(second, 60)
HAVING
COUNT(*) >= 3
-- Query 3: Production KPIs
SELECT
System.Timestamp() AS WindowStartTime,
IoTHub.ConnectionDeviceId as DeviceId,
SUM(CASE WHEN GoodCount IS NULL THEN 0 ELSE GoodCount END) as TotalGoodCount,
SUM(CASE WHEN BadCount IS NULL THEN 0 ELSE BadCount END) as TotalBadCount
INTO
[OUTPUT] -- Service Bus queue name
FROM
[INPUT] -- IoT Hub name i.e. [iot-seba]
GROUP BY
IoTHub.ConnectionDeviceId,
TumblingWindow(minute, 5)
-- query 4
SELECT
*
INTO
[OUTPUT]
FROM
[INPUT]
WHERE
Event = 'error'
To store data calculations, there is need to create containers on storage acount. Tha'st where data calculations will be stored.
Every 1 minute give me the average, minimum and maximum temperature over the last 5 minutes (grouped by device).
See query 1
{"WindowStartTime":"2024-05-17T19:10:00.0000000Z","ConnectionDeviceId":"device2","AverageTemperature":61.00682340912079,"MinTemperature":60.42769792801131,"MaxTemperature":61.86731380333519}
Situations whenever a device experiences more than 3 errors in under 1 minute. See ASA query 2
{"EventTime":"2024-05-17T19:35:00.0000000Z","ConnectionDeviceId":"device3"}
% of good production in total volume, grouped by device in 5-minute windows. See ASA query 3
{"WindowStartTime":"2024-05-17T19:35:00.0000000Z","DeviceId":"device4","TotalGoodCount":0.0,"TotalBadCount":0.0}
Sample device twin on Azure IoT Hub
{
//
"properties": {
"desired": {
"$metadata": {
//
},
"$version": 33,
"ProductionRate": 50,
"telemetryConfig": {
"sendFrequency": "10s"
}
},
"reported": {
"$metadata": {
//
},
//
"DeviceErrors": 1,
"ProductionRate": 50
}
},
///
},
///
}
The agent retrieves device connection data from secrets.json
and attempts to establish a connection. It will reject a device if there is no connection available.
Agent sends telemetry data to the IoT Hub at fixed time intervals configured in the device twin on Azure. You can configure it under "properties" -> "desired" -> "telemetryConfig" ->"sendFrequency".
Example values: 10s
, 5m
, 2h
, 1420s
, 96m
.
ContentEncoding: UTF8
{
...
"properties": {
"desired": {
"$metadata": {
...
},
"$version": 32,
"ProductionRate": 0,
"telemetryConfig": {
"sendFrequency": "10s"
}
},
"reported": {
...
}
}
{
"body": {
"ProductionStatus": 1,
"WorkorderId": "c18358be-7f98-426b-862b-7d29c0c386fe",
"GoodCount": 10,
"BadCount": 1,
"TotalGoodCount": 16,
"TotalBadCount": 1,
"Temperature": 77.23789701115987
},
"enqueuedTime": "Fri May 17 2024 17:29:38 GMT+0200 (czas środkowoeuropejski letni)"
}
When a device encounters an error, it will send a single message to the IoTHub. This message will be processed by Azure Analytics Stream.
{
"body": {
"Errors": 14,
"DeviceNode": "Device 4",
"Event": "error",
"ErrorsIncreased": 1 // 0 if errors decreased.
},
"enqueuedTime": "Fri May 17 2024 17:31:28 GMT+0200 (czas środkowoeuropejski letni)"
}
- Method name: "EmergencyStop"
- required parameters: none
- what it does: it stops production on the machine.
{
"status": 500,
"payload": {
"status": 500,
"payload": {
"message": "ns=2;s=Device 1: exception occured during emergency stop.",
"exception_message": "Sample exception message"
}
}
}
{
"status": 200,
"payload": {
"status": 200,
"payload": {
"message": "Emergency stop handled successfully."
}
}
}
- Method name: "ResetErrorStatus"
- required parameters: none
- what id does: it resets every error there is on the device
{
"status": 500,
"payload": {
"status": 500,
"payload": {
"message": "ns=2;s=Device 1: exception occured during Reset Error Status..",
"exception_message": "Sample exception message"
}
}
}
{
"status": 200,
"payload": {
"status": 200,
"payload": {
"message": "Reset Error Status handled successfully"
}
}
}
If a Device Error occurs (of any type), send an email to predefined address.
This is done thanks to Communication Service.
There was an error with Device 1 Device error code: 5
- Function App
- Storage account
- Service Bus Queue
- Service Bus Namespace
- IoT Hub
- Stream Analytics job
- Communication Service