Business Central and IoT. Integration with Arduino.
Hello Team!
Basically this post is a description what we showed on #NavTechDays in last demo(Link).
The idea was to get values from analog meter to cloud Business Central and use this values for some scenario. We used MQ3 gas analyzer as alcohol detector, Arduino Uno controller with Ethernet shield and bottle of wine as measurement material. Scenario was that drunk user can't post sales orders (in other scenario user must be drunk :) to post them).
Demo created just for fun. The core idea is to show you how to integrate your Business Central solution with hardware devices and get some metrics from them.
Needed hardware parts:
1. Arduino Uno controller with USB cable (you can use analog, price is ~5-7$)
2. Ethernet shield for Arduino Uno and ethernet cable (also 5-7$)
3. MQ3 meter with connection wires (3-5$)
4. Any alcohol for tests (price depends)
5. Connect Arduino Uno with ethernet shield next way:
6. Connect MQ3 pins to ethernet shield next way (MQ3 DOUT pin not used):
| MQ3 pin | Shield pin |
| +5V | 5V |
| AOUT | A0 |
| GND | GND |
7. Download and install Arduino IDE.
8. Connect Arduino USB and ethernet cables to your laptop.
9. Now, the hardest part :) Open Windows Control panel - Network and Internet - Network connections
10. Open your network connection (WiFi) to internet and let your ethernet connection access
11. Open properties of Ethernet connection, IPv4 configuration and fill next data (ip could be different, this used for my demo):
12. Now, create Data table and page in Business Central:
table 50105 "Mq3 data"
{
DataClassification = ToBeClassified;
fields
{
field(1; EntryNo; Integer)
{
DataClassification = ToBeClassified;
}
field(2; "MQ3 value"; Integer)
{
DataClassification = ToBeClassified;
}
field(3; "Creation date"; Date)
{
DataClassification = ToBeClassified;
}
field(4; "Creation time"; Time)
{
DataClassification = ToBeClassified;
}
}
keys
{
key(PK; EntryNo)
{
Clustered = true;
}
}
trigger OnInsert()
var
Mq3: Record "Mq3 data";
begin
If Mq3.findlast() then
EntryNo := Mq3.EntryNo + 1
else
EntryNo := 1;
"Creation date" := DT2Date(CurrentDateTime());
"Creation time" := DT2Time(CurrentDateTime());
end;
}
page 50105 "MQ3 values"
{
PageType = List;
ApplicationArea = All;
UsageCategory = Lists;
SourceTable = "Mq3 data";
SourceTableView = order(descending);
layout
{
area(Content)
{
repeater(Group)
{
field(EntryNo; EntryNo)
{
ApplicationArea = All;
}
field("MQ3 value"; "MQ3 value")
{
ApplicationArea = All;
}
field("Creation date"; "Creation Date")
{
ApplicationArea = All;
}
field("Creation time"; "Creation Time")
{
ApplicationArea = All;
}
}
}
}
}
13. Create API page:
page 50106 "MQ3"
{
PageType = API;
Caption = 'MQvalues';
APIPublisher = 'ntd18';
APIGroup = 'MQValues';
APIVersion = 'beta';
EntityName = 'MQValue';
EntitySetName = 'MQValues';
SourceTable = "Mq3 data";
DelayedInsert = true;
ODataKeyFields = EntryNo;
layout
{
area(Content)
{
repeater(GroupName)
{
field("MQvalue"; "MQ3 value")
{
Caption = 'mq';
ApplicationArea = All;
}
}
}
}
}
14. Create Setup table and page. Limit value used to detect drunk condition. Normally MQ3 shows from 30 to 60 units, but when it detects alcohol - it shows about 300-500 units. I used limit value = 100. Condition option sets should be user drunk or not to post sales orders.
table 50106 "MQ3 setup"
{
DataClassification = ToBeClassified;
fields
{
field(1; Entry; Integer)
{
DataClassification = ToBeClassified;
}
field(2; "Limit Value"; integer)
{
DataClassification = ToBeClassified;
}
field(3; Condition; Option)
{
OptionMembers = notdrunk,drunk;
OptionCaption = 'Not drunk, Drunk';
}
}
keys
{
key(PK; Entry)
{
Clustered = true;
}
}
}
page 50107 "Mq3 setup"
{
PageType = Card;
ApplicationArea = All;
UsageCategory = Administration;
Caption = 'MQ3 Setup';
SourceTable = "MQ3 setup";
layout
{
area(Content)
{
group(GroupName)
{
field(Condition; Condition)
{
ApplicationArea = All;
}
field(Name; "Limit Value")
{
ApplicationArea = All;
}
}
}
}
}
15. And finally - event codeunit, which checks is user drunk (Last 5 entries should be upper or lower than Limit value in setup):
codeunit 50105 "MQ3 management"
{
trigger OnRun()
begin
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Sales-Post (Yes/No)", 'OnBeforeConfirmSalesPost', '', false, false)]
local procedure MyProcedure()
var
MQ3Values: Record "Mq3 data";
MQ3Setup: Record "MQ3 setup";
LastEntryNo: Integer;
begin
MQ3Setup.get();
If MQ3Values.FindLast() then
LastEntryNo := MQ3Values.EntryNo;
MQ3Values.SetRange(EntryNo, LastEntryNo - 5, LastEntryNo);
case MQ3Setup.Condition of
MQ3Setup.Condition::notdrunk:
begin
MQ3Values.Setfilter("MQ3 value", '>%1', MQ3Setup."Limit Value");
If MQ3Values.FindFirst() then
Error(Error001);
end;
MQ3Setup.Condition::drunk:
begin
MQ3Values.Setfilter("MQ3 value", '<=%1', MQ3Setup."Limit Value");
If MQ3Values.FindFirst() then
Error(Error002);
end;
end;
end;
var
Error001: Label 'You are drunk, orders posting prohibited!';
Error002: Label 'You are not drunk, orders posting prohibited!';
}
16. Compile and publish your App. Here you can download my AL project.
17. Second part of integration. Azure function. We need it to get values from Arduino and pass them to Business Central API. I couldn't pass values directly from Arduion to BC by lots of reasons, maybe you'll try to do it. Open Azure portal, create new function APP. Create new C# HTTP Trigger function with next code:
#r "Newtonsoft.Json"
using System.Net;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
string mq3 = req.Query["mq3"];
var results = await SendArduinoMessage(mq3);
return mq3 != null
? (ActionResult)new OkObjectResult($"Result is: {mq3}")
: new BadRequestObjectResult("Please pass a mq-3 value on the query string");
}
public static async Task<string> SendArduinoMessage(string mq3)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Basic USER-WEB-SERVICE-KEY");
Dictionary<string, string> dictionary = new Dictionary<string, string>();
dictionary.Add("MQvalue", mq3);
string json = JsonConvert.SerializeObject(dictionary);
var requestData = new StringContent(json, Encoding.UTF8, "application/json");
var url = "https://api.businesscentral.dynamics.com/v1.0/YOUR-environment-ID/sandbox/api/ntd18/MQValues/beta/companies(your-company-GUID)/MQValues";
var response = await client.PostAsync(url, requestData);
var result = await response.Content.ReadAsStringAsync();
return result;
}
}
API address used for cloud BC. For on-premise or NAV2018 it will be a little bit different. How to get API addresses and other API values you can read here.
Also you need to open your user card in BC and create Web Service key for your user.
18. Now environment is ready and we just need to upload program (Sketch) to Arduino. Open Arduino environment, press Tools and select your Arduino port.
19. Use this code for your sketch:
#include <Ethernet.h>
int mq3_analogPin = A0;
byte mac[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 }; //any values
byte ip[] = { 192, 168, 137, 171 }; //+1 to Ethernet IP
byte sdns[] = { 8, 8, 8, 8 };
byte gw[] = { 192, 168, 137, 170 }; //IP which used in p.11
byte subnet[] = { 255, 255, 255, 0 };
char server[] = "ntd18.azurewebsites.net"; //your AZ function app address
EthernetClient client;
int failedcounter = 0;
boolean lastconnected = false;
void setup()
{
Serial.begin(9600);
StartEthernet();
delay(1000);
}
void loop()
{
int mq3_value = analogRead(mq3_analogPin);
//Serial.println(mq3_value);
delay(5000); //Slow down the output.
SendData(String(mq3_value));
String c = client.readStringUntil('>');
Serial.print(c);
if (!client.connected() && lastconnected)
{
Serial.println("...Disconnected");
Serial.println();
client.stop();
}
if (failedcounter > 3)
{
StartEthernet();
failedcounter = 0;
}
lastconnected = client.connected();
}
void StartEthernet()
{
client.stop();
Serial.println("Connecting Arduino to network...");
Serial.println();
delay(1000); //Keeps the connection from freezing
Ethernet.begin(mac, ip, sdns, gw, subnet);
Serial.println(Ethernet.localIP());
delay(1000);
}
void SendData(String data)
{
if (client.connect(server, 80))
{
client.print("GET /api/Arduino?mq3="); //your AZ function name + mq3 parameter
client.print(data);
client.println(" HTTP/1.1");
client.println("Host: ntd18.azurewebsites.net"); // your AZ function app name
client.println();
if (client.connected())
{
Serial.println("Connected");
Serial.println();
failedcounter = 0;
}
else
{
failedcounter++;
Serial.println("Connection unsuccesful");
Serial.println();
}
}
else
{
failedcounter++;
Serial.println("Connection unsuccesful");
Serial.println();
}
}
20. Upload sketch, run Tools - Monitor port. You'll see next picture:
21. Put MQ3 meter near the alcohol - and you'll see how values are changing:
22. Now open MQ3 Setup page and select Not drunk condition with Limit value = 100
23. Open Sales orders list and try to post any order (MQ3 meter should be near alcohol to imitate drunk user (obviously, you can just simply drink and breathe on it :) )
24. Now remove meter from alcohol (stop breathe on it) , change Condition value and check that values in MQ3 values table are lower than Limit value
25. Now try to post the order:
:)
26. You are really a hero if you read this, post a quite long. I think it's a weirdest ever demo for Business Central, but it worth it. Hope you understood how to use Arduino's http client to send data and you'll use it in your projects.
Comments
-
Hi Arturas!
I tried many ways to send request directly from Arduino to NAV API - but it didn't work. And I still not investigated why it couln't connect. With Azure function everything runs smoothly.
Firstly it didn't work because I used https and self-signed certificate, but even with cloud BC and normal https it can't send request.
So the official reason - I used Azure function to monitor API calls :)
-
Hi,
Interesting read, thanks for the post!
I have but one question - is there any reason you didn't call NAV API directly from Arduino and had to make a middleman app/Azure function?
You'd use POST method on Arduino, and after finishing the header (client.println("Connection: close");), you'd add an empty line (client.println("Connection: close");) and then add the body (JSON or just raw value).
Or is there something with NAV APIs that prevent such functionality?

Like
Report
*This post is locked for comments