IoT & Microsoft Dynamics AX – Final

In this final part of the series I will be bringing the IoT device data into Microsoft Dynamics AX7 CTP8. This is the pre-release of the new version of Dynamics AX which I’m running on a MacBook with 16GB RAM inside VMWare.

I’m new to developing in AX and documentation is still scarce for the new version which is fully web-based. AX 2012 is three-tiered with a “fat” client, so there are numerous architectural changes between the two releases. For a start, everything now runs server-side; so any ideas about having an IoT device communicate via a serial port to the AX client goes right out the window.

Fortunately, Microsoft has brought us Azure, and the combination of features (for developers like myself) and excellent documentation makes it in my mind a no-brainer; combine this with the AX ERP product which can also of course be hosted in Azure and I believe they are on the winning track.

One unfortunate aspect of AX 7, from what I have seen, is that it does not take advantage of the advanced storage options offered by Azure, but is rather a straight SQL Server to SQL Azure port. Nevertheless, it does not stop any developer from building data structures using those storage facilities in Azure and using them from within AX, which is what we will do in this final post.

Recall from Parts 1 and 2 that our IoT device communicated via a base station to an Azure Event Hub. In this post we will change that slightly, and have the worker role log messages into an SQL Azure database instead. This requires minor changes to our code, as shown below. I have merely replaced the Event Hub messaging with a simple SQL INSERT statement.

using System;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.ServiceRuntime;
using System.Net.Sockets;
using System.IO;
using System.Data.SqlClient;
using System.Text;

namespace FiniteWorker
{
    public class WorkerRole : RoleEntryPoint
    {
        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
        private AutoResetEvent connectionWaitHandle = new AutoResetEvent(false);
        private string dbConnectionString = "Server=tcp:z2e7xwwdy1.database.windows.net,1433;Database=IoT;User ID=xxx;Password=xxx;Trusted_Connection=False;Encrypt=False;Connection Timeout=30;";
        private SqlConnection connection = null;

        public override void Run()
        {
            TcpListener listener = null;

            try
            {
                listener = new TcpListener(RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["FiniteEndPoint"].IPEndpoint);
                listener.ExclusiveAddressUse = false;
                listener.Start();
            }
            catch (SocketException)
            {
                Trace.Write("Finite server could not start.", "Error");
                return;
            }

            while (true)
            {
                 IAsyncResult result = listener.BeginAcceptTcpClient(HandleAsyncConnection, listener); 
                connectionWaitHandle.WaitOne();
            }
        }

        private void HandleAsyncConnection(IAsyncResult result)
        {
            TcpListener listener = (TcpListener)result.AsyncState;
            TcpClient client = listener.EndAcceptTcpClient(result);
            connectionWaitHandle.Set();

            while (client.Connected)
            {
                NetworkStream netStream = client.GetStream();
                StreamReader reader = new StreamReader(netStream);

                byte[] buffer = new byte[5];
                int bytesRead = client.Client.Receive(buffer,0 ,5, SocketFlags.None);
                if (bytesRead == 5) // worker id + light sensor reading
                {
                    byte[] bWorker = new byte[3];
                    byte[] bLight = new byte[2];
                    bWorker[0] = buffer[0];
                    bWorker[1] = buffer[1];
                    bWorker[2] = buffer[2];
                    bLight[0] = buffer[3];
                    bLight[1] = buffer[4];

                    try
                    {
                        SqlCommand insert = new SqlCommand("INSERT INTO IOTTable (WorkerID, LightLevel) VALUES (@workerid, @light)", connection);
                        SqlParameter workerid = new SqlParameter("@workerid", Encoding.UTF8.GetString(bWorker, 0, 3));
                        SqlParameter light = new SqlParameter("@light", Convert.ToInt32(Encoding.UTF8.GetString(bLight, 0, 2)));
                        insert.Parameters.Add(workerid);
                        insert.Parameters.Add(light);
                        insert.ExecuteNonQuery();
                    }
                    catch
                    {
                        // log exception here and handle it
                    }
                }
            }
            client.Close();
        }

        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections
            ServicePointManager.DefaultConnectionLimit = 12;
            bool result = base.OnStart();
            connection = new SqlConnection(dbConnectionString);
            connection.Open();
            Trace.TraceInformation("FiniteWorker has been started");
            return result;
        }

        public override void OnStop()
        {
            Trace.TraceInformation("FiniteWorker is stopping");
            connection.Close();
            this.cancellationTokenSource.Cancel();
            this.runCompleteEvent.WaitOne();
            base.OnStop();
            Trace.TraceInformation("FiniteWorker has stopped");
        }

        private async Task RunAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                Trace.TraceInformation("Working");
                await Task.Delay(1000);
            }
        }
    }
}

 

Due to the fact that I am new to AX development and AX 7 is a pre-release product hosted in IIS, my gut instinct was that keeping a verbose message pipe alive directly between AX and an Azure Event Hub would not be a smart move.

A more cautious approach at this stage would be to replicate the latest (whatever you decide that to be) transactions received from the device into a cloud storage facility as a staging option, and replicate those on-demand from within AX. So this is the approach we’ll follow here as well.

The basic process is:

  • When messages are received from the base station, log it into the SQL Azure database.
  • When a user opens or refreshes the IoT Form in AX, replicate the required portion of results back into an AX table and display on-screen, truncating historical records prior to the refresh.

So let’s fire up Visual Studio from within the AX 7 virtual image, and create our IoT Form. As is typical in AX, we will step through the normal process and:

  • Create the required configuration items including a license. I made the HR module the parent license to keep things simple.
  • Create the EDT’s we’ll need to display the data on the form and store it in a local table.
  • Create the required Labels.
  • Create the AX table, simply called IOTTable.
  • Set fields using our EDT’s, field groups, indexes, etc.
  • Create a new Simple List form, and following that specified pattern we are guided in the overall UI design through to completion.
  • Hook it all up to the datasource (IOTTable) and we are good to go.

The screen below shows what we end up with, and running a build leaves us with two Best Practice deviations for our missing developer documentation, which we can safely ignore for this exercise.

AXDesignVS

To make this all work, I will override the OnQueryExecuting event of our data source. This allows us to truncate old data from the table, and then connect to the Azure database to bring the latest set across into the local table prior to it being loaded into the form.

Again, I am new to AX development and there might be numerous better ways to do this, and I’d love to hear all about them. For now, this works quite well. I’ve seen examples before adding “Refresh” buttons onto the AX form, but I find that rather lame manual.

One particular hiccup I found was in the date data types supported by AX, and the fact that none of them stores milliseconds. This is a problem, as high-frequency messages coming from our IoT device might give us a problem here and trigger duplicate key exceptions when trying to store them in the local AX table. So apart from loosing that portion (milliseconds), be aware of how your unique constraints are configured, as I learned the hard way as well.

So, a simple event handler in X++ as shown below works well for this.

class IOTClass
{
    /// <summary>
    /// Trap the OnQueryExecuting event so we can refresh the data
    /// </summary>
    /// <param name="sender">Ignored</param>
    /// <param name="e">Not used</param>
    [FormDataSourceEventHandler(formDataSourceStr(IOTForm, IOTTable), FormDataSourceEventType::QueryExecuting)]
    public static void IOTTable_OnQueryExecuting(FormDataSource sender, FormDataSourceEventArgs e)
    {
        IOTTable iotTable;
        System.Data.SqlClient.SqlConnection connection;
        System.Data.SqlClient.SqlCommand command;
        System.Data.SqlClient.SqlDataReader reader;

        // truncate table before refresh
        ttsbegin;
        delete_from iotTable;
        ttscommit;
        
        // retrieve updated data from SQL Azure
        connection = new System.Data.SqlClient.SqlConnection("Server=tcp:z2e7xwwdy1.database.windows.net,1433;Database=IoT;User ID=xxx;Password=xxx;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
        connection.Open();

        command = new System.Data.SqlClient.SqlCommand("SELECT * FROM IoTTable", connection);
        reader = command.ExecuteReader();
        while (reader.Read())
        {
            ttsbegin;
            try
            {
                select forupdate iotTable;
                iotTable.IOTTransactionDateTime = reader.GetDateTime(1);
                iotTable.IOTWorkerID = reader.GetString(2);
                iotTable.IOTLightMeasurement = reader.GetInt32(3);
                iotTable.insert(); 
                ttscommit;
            }
            catch
            {
                ttsabort;
            }
            finally
            {
            }
        }
        reader.Close();
        reader.Dispose();
        connection.Close();
        connection.Dispose();
    }
}

Now let’s run the form and view the results. I’ve made the form read-only, so it serves as display only, as seen below.

AXFormPreview

That’s it! There are numerous improvements we can make on this project, and in future posts I’ll focus on that. Ideas I want to explore will include:

  • AES encryption between monitor, station and the Azure worker role.
  • Device authentication.
  • Adding GPS to the monitor, or alternatively the base station.
  • Improving the UI and interaction from the AX side.

So how practical is any of this? I can think of a number of use-cases within Dynamics AX:

  • Meeting-room monitoring, reporting the status – “occupied”, “available”.
  • Tracking workers who work in dangerous environments (using GPS, reporting back in a feed).
  • Vehicle monitoring and tracking (deliveries, dangerous goods).
  • Tracking stock movement, using RFID tags.
  • Temperature monitoring and heat-sensitive environments.
  • Detecting specific gasses, alerting if detected.
Advertisements

IoT & Microsoft Dynamics AX – Part 2

In the second part of this series I will focus on building the base station and enabling the data transfer to Microsoft Azure Event Hubs.

For the base station we will use a Freetronics EtherTen module, basically an Arduino Uno with onboard Ethernet. The setup is very simple with the NRF905 connected to the MISO, MOSI and SCK ports on digital pins 11, 12 and 13.

The NRF905 uses a large number of digital ports (9 in total) plus GND and 3.3V leaving little remaining on the Uno, so if your project requires more you might want to switch to a Mega board instead. As before, the onboard Ethernet blocks digital port 10, so I’ve re-arranged the wiring to accommodate this.

In this series I am not going to go into depth with configuring the boards or setting up the various portions in Azure – there are many good articles written to cover this, and nothing I do here in this project can improve on those. My focus is on prototyping end to end projects and exploring whether IoT solves actual business problems. I’m an entrepreneur, and unless I can provide real business value or solve serious customer pain with IoT, it remains nothing more than a cool technology or a weekend hobby.

So let’s dive right into it!

Below is the wired-up Arduino connected to Ethernet and powered by a 9V rechargeable battery. One of the biggest issues I faced early on when working with these controllers is voltage drop when using non-rechargeable batteries, resulting in flaky communications with the RF modules, particularly when I used the NRF24L01 module, and a standard recommendation is to set a capacitator between VCC and GND on the RF module, typically 100nF to 5uF. My current setup with the NRF905 works just fine so it is not required.

Station2

The logic for the base station is pretty straightforward and involves the following process:

  • Setup the NRF905 and Ethernet by using a fixed MAC address.
  • Establish a connection to the worker role service running in Azure. Recall from Part 1 that due to the UNO not supporting HTTPS, we will have to route via a worker role.
  • Once connected to Azure, wait for incoming messages from a remote monitor.
  • As a message comes in, strip out the data we need and pass this to the Azure worker role for routing and send back an ACK signal to the monitor.

The C source code is shown below. There are several important things missing in the code, but it is a PROTOTYPE after all:

  • Code security – we are not checking for any buffer overflow.
  • Encryption – data is sent clear between devices and to the Azure worker role.
  • Managing dropped Ethernet connections.
  • No authentication scheme between the base station and the Azure worker role.
#include <nRF905.h>
#include <SPI.h>
#include <Ethernet.h>

#define RXADDR {0x58, 0x6F, 0x2E, 0x10} // Address of this station
#define TXADDR {0xFE, 0x4C, 0xA6, 0xE5} // Address of monitor device
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0xBC, 0xAE };
EthernetClient client;
bool connected = false;

void setup()
{
	Serial.begin(9600);

	nRF905_init();
	byte addr[] = RXADDR;
	nRF905_setRXAddress(addr);
	nRF905_receive();
	Serial.println("Communications up...");

	Ethernet.begin(mac);
	Serial.println("Ethernet up...");
	Serial.println("Base station running...");
}

void loop()
{
	if (!connected)
	{
		Serial.println("Trying to connect");
		char* host = "finitemindsserver.cloudapp.net"; // your Azure service or event hub details go here
		client.setTimeout(10000);
		connected = (client.connect(host, 10100) == 1);
		if (connected) {
			Serial.println("Connected to Azure...");
		}
		else
		{
			Serial.println("Connection unsuccessful...");
			client.stop();
		}
	}
	if (connected)
	{
		// get latest data from remote device
		byte buffer[NRF905_MAX_PAYLOAD];
		while (!nRF905_getData(buffer, sizeof(buffer)));

		// got data inside buffer, check for packet signature + worker id + light measurement
		if ((buffer[0] == 'G') && (buffer[1] == 'V'))
		{
			// extract what we need and send to Azure worker role
			byte sendbuffer[5];
			for (int i = 0; i < 5; i++)
			{
				sendbuffer[i] = buffer[i + 2];
			}
			client.write(sendbuffer, sizeof(sendbuffer));
		}
		
		// send back as an echo to ACK
		byte addr[] = TXADDR;
		nRF905_setTXAddress(addr);
		nRF905_setData(buffer, sizeof(buffer));
		while (!nRF905_send());
		nRF905_receive();
	}
}

 

At this point we should be good to go, and can move on to building our worker role in Azure. I’ll use C# for this and the code is below.

Again, very simple and it merely listens for an incoming TCP connection on port 10100, and once accepted proceeds to log incoming messages as events in our Azure Event Hub.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using System.Net.Sockets;
using System.IO;
using System.Globalization;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.Messaging;
using Newtonsoft.Json;
using System.Text;
using System.Runtime.Serialization;

namespace FiniteWorker
{
    public class WorkerRole : RoleEntryPoint
    {
        private string eventHubName = "finitehub";
        private string connectionString = "Endpoint=sb://finiteminds.servicebus.windows.net/;SharedAccessKeyName=SendRule;SharedAccessKey=<your key here>";
        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
        private AutoResetEvent connectionWaitHandle = new AutoResetEvent(false);

        public override void Run()
        {
            TcpListener listener = null;

            try
            {
                listener = new TcpListener(RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["FiniteEndPoint"].IPEndpoint);
                listener.ExclusiveAddressUse = false;
                listener.Start();
            }
            catch (SocketException)
            {
                Trace.Write("Finite server could not start.", "Error");
                return;
            }
            while (true)
            {
                 IAsyncResult result = listener.BeginAcceptTcpClient(HandleAsyncConnection, listener); 
                connectionWaitHandle.WaitOne();
            }
        }

        private void HandleAsyncConnection(IAsyncResult result)
        {
            var eventHubClient = EventHubClient.CreateFromConnectionString(connectionString, eventHubName);
            TcpListener listener = (TcpListener)result.AsyncState;
            TcpClient client = listener.EndAcceptTcpClient(result);
            connectionWaitHandle.Set();

            while (client.Connected)
            {
                NetworkStream netStream = client.GetStream();
                StreamReader reader = new StreamReader(netStream);

                byte[] buffer = new byte[5];
                int bytesRead = client.Client.Receive(buffer,0 ,5, SocketFlags.None);
                if (bytesRead == 5) // worker id + light sensor reading
                {
                    byte[] bWorker = new byte[3];
                    byte[] bLight = new byte[2];
                    bWorker[0] = buffer[0];
                    bWorker[1] = buffer[1];
                    bWorker[2] = buffer[2];
                    bLight[0] = buffer[3];
                    bLight[1] = buffer[4];

                    DeviceMessage msg = new DeviceMessage();
                    try
                    {
                        msg.Worker = Encoding.UTF8.GetString(bWorker, 0, 3);
                        msg.Light = Convert.ToInt32(Encoding.UTF8.GetString(bLight, 0, 2));
                    }
                    catch
                    {
                        // log exception here and handle it
                    }
                    string json = Newtonsoft.Json.JsonConvert.SerializeObject(msg);
                    eventHubClient.Send(new EventData(Encoding.UTF8.GetBytes(json)));
                }
            }
            client.Close();
        }

        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections
            ServicePointManager.DefaultConnectionLimit = 12;
            bool result = base.OnStart();
            Trace.TraceInformation("FiniteWorker has been started");
            return result;
        }

        public override void OnStop()
        {
            Trace.TraceInformation("FiniteWorker is stopping");
            this.cancellationTokenSource.Cancel();
            this.runCompleteEvent.WaitOne();
            base.OnStop();
            Trace.TraceInformation("FiniteWorker has stopped");
        }

        private async Task RunAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                Trace.TraceInformation("Working");
                await Task.Delay(1000);
            }
        }
    }

    [DataContract]
    public class DeviceMessage
    {
        [DataMember]
        public string Worker { get; set; }
        [DataMember]
        public int Light { get; set; }
    }
}

 

Let’s connect both Arduino’s to our USB ports so we can watch for messages via the serial interface. The screenshot below shows both devices connected with the base station on COM4 and the monitor on COM5.

VisualStudioView

Both are communicating fine and messages are being sent the Azure worker role as shown below.

AzureEventHub

So let’s go ahead and consume these messages by creating an Azure stream job. The input is of course our Event Hub, and I’ve set the output to be Microsoft Power BI. The query is a simple “SELECT FROM INTO” literally passing everything from the Event Hub directly to Power BI as output with no filtering.

IoTStream

The setup in Microsoft Power BI is relatively simple as shown below. Note the spike in the bottom chart at 3:52AM – this was detected as I switched on additional lighting, and it simply shows us the light sensor on the monitor is working as expected.

PowerBISetup

Once configured we can go ahead and view the report on our device of choice, in my case an Apple iPad as shown below.

TabletView

Nothing groundbreaking so far, but the objective of this project is to get data from a remote device eventually into Microsoft Dynamics AX7, and my next post in this series will focus on achieving that.

IoT & Microsoft Dynamics AX – Part 1

In this multipart series of posts I’m going to explore ideas around data gathering using Arduino microcontrollers and using that within Microsoft Dynamics AX7 CTP8 (Preview).

I personally prefer and use the Arduino range as I find them compact, affordable and well-supported in the programming community. Arduino’s are programmed using C/C++ and I use Microsoft Visual Studio 2013 running on Windows 8.1 inside a VMWare image on my MacBook Pro. It sounds like a complicated setup but the Mac seems more forgiving with constant plugging stuff in and out of the USB ports, and the simple truth is my other laptops have either too little RAM for running AX, or too few USB ports – so this setup works for me.

For this project I’m using the Freetronics range of products including an EtherTen (Arduino Uno with Ethernet onboard) and a Freetronics EtherMega (Arduino Mega clone with Ethernet onboard). I’m also using a light sensor, RC522 RFID card reader and two NRF905 transmitter/receiver modules to communicate between the two.

Only the EtherTen will be networked (to Windows Azure) to simulate more realistic conditions; my assumption is that “in the field” you are unlikely to have Ethernet or WiFi, so sooner or later you will need to communicate using RF and the NRF905 modules are great for this with a range of up to 300m.

So the basic setup is this – a monitor station will gather light data and transmit this via RF to a base station, which in turn sends the data to Windows Azure Event Hubs via Ethernet. The monitor station is enabled and disabled using an RFID card or tag containing the worker identifier. This simulates an employee tagging on and off to a device to enable operation. Once the monitor is enabled, it begins to transmit light readings via RF to the base station on a 500ms interval, which acknowledges the message by returning an “ACK” signal back to the monitor. The base station has a connection to an event hub running in Microsoft Azure and continuously sends the data to the hub until the employee swipes off at the end of their shift.

Multiple monitoring stations can be linked to talk to any number of base stations within range.

One issue I find with Azure Event Hubs is the requirement for communication via HTTPS. Unfortunately, very few Arduino controllers support HTTPS. So the workaround is to build a worker role hosted as a service in Azure that accepts incoming TCP connections on port 10100 and then routes these via HTTPS to the Event Hub. Recently Microsoft released IoT Hubs which supports bi-directional communications between the hub and IoT devices, but again with the same requirement – HTTPS, only supported on Arduino Yun controllers.

My own personal view of IoT devices falls in the small, cheap, compact and low-power spectrum, and how that will be supported in IoT Hubs is not clear to me, so I personally find IoT Hubs rather useless for my own projects. Give me an SDK that I can squeeze onto an Arduino Uno or Micro and STILL use IoT Hubs and I’ll be sold on the idea, until then – pass. If my assumptions on IoT Hubs are incorrect please let me know.

For constructing the monitor, I used an EtherMega but this can probably work on an Uno as well. The Mega simply gives me more physical work space as it has bigger dimensions and I have clumsy hands and a short temper. The tricky part on the monitor was combining both an RFID reader with the NRF905 as they have to share the MOSI, MISO and SCK ports, selectable via the SS (Slave Select) pin.

Getting the RFID reader working is a blog post on it’s own, and so is the NR905. However Zak Kemble provides an excellent library at http://blog.zakkemble.co.uk/author/Zak/ which saved me many hours of work and frustration.

As we will be simulating an employee tagging on and off using the RFID reader, the first part of the process requires us to write some personal information onto a blank tag. For this I used a Duinotech RFID kit which ships with reader and two blank tags from my local Jaycar shop here in Brisbane. These tags have their security keys defaulted to FFFFFFFFFFFF. I’ll leave the default as-is but embed the worker name into one of the tags using data blocks 1 and 2.

So let’s have a look at a sample C program to detect a card swipe, perform authentication using the default key, write to blocks 1 and 2 and then verify that by reading the data back again:

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN         9
#define SS_PIN          4

MFRC522 mfrc522(SS_PIN, RST_PIN);

void setup() {
	Serial.begin(9600);
	SPI.begin();
	mfrc522.PCD_Init();
}

void loop() 
{
	MFRC522::MIFARE_Key key; // default to FFFFFFFFFFFF
	key.keyByte[0] = 0xFF;
	key.keyByte[1] = 0xFF;
	key.keyByte[2] = 0xFF;
	key.keyByte[3] = 0xFF;
	key.keyByte[4] = 0xFF;
	key.keyByte[5] = 0xFF;

	// Loop until card is presented
	if (!mfrc522.PICC_IsNewCardPresent()) {
		return;
	}

	if (!mfrc522.PICC_ReadCardSerial())    return;


	byte readbuffer1[18];
	byte readbuffer2[18];

	byte writebuffer1[16];
	byte writebuffer2[16];

	byte block;
	MFRC522::StatusCode status;
	byte len;
	byte sizeread = sizeof(readbuffer1);
	byte sizewrite = sizeof(writebuffer1);

	block = 0;
	Serial.println(F("Authenticating using key A..."));
	status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block, &key, &(mfrc522.uid));
	if (status != MFRC522::STATUS_OK) {
		Serial.print(F("PCD_Authenticate() failed: "));
		Serial.println(mfrc522.GetStatusCodeName(status));
		return;
	}
	else Serial.println(F("PCD_Authenticate() success: "));

	// clear buffers
	for (int i = 0; i < 16; i++)
	{
		writebuffer1[i] = 0x00;
		writebuffer2[i] = 0x00;
	}
	for (int i = 0; i < 18; i++)
	{
		readbuffer1[i] = 0x00;
		readbuffer2[i] = 0x00;
	}

	// write "VOS" in block 1
	writebuffer1[0] = 'V';
	writebuffer1[1] = 'O';
	writebuffer1[2] = 'S';

	status = mfrc522.MIFARE_Write(1, writebuffer1, sizewrite);
	if (status != MFRC522::STATUS_OK) {
		Serial.print(F("MIFARE_Write() failed: "));
		Serial.println(mfrc522.GetStatusCodeName(status));
		return;
	}

	// read it back
	status = mfrc522.MIFARE_Read(1, readbuffer1, &sizeread); // VOS
	if (status != MFRC522::STATUS_OK) {
		Serial.print(F("MIFARE_Read() failed: "));
		Serial.println(mfrc522.GetStatusCodeName(status));
		return;
	}
	dump_byte_array(readbuffer1, sizeof(readbuffer1) - 2); Serial.println();

	// write "GIDEON" in block 2
	writebuffer2[0] = 'G';
	writebuffer2[1] = 'I';
	writebuffer2[2] = 'D';
	writebuffer2[3] = 'E';
	writebuffer2[4] = 'O';
	writebuffer2[5] = 'N';

	status = mfrc522.MIFARE_Write(2, writebuffer2, sizeof(writebuffer2));
	if (status != MFRC522::STATUS_OK) {
		Serial.print(F("MIFARE_Write() failed: "));
		Serial.println(mfrc522.GetStatusCodeName(status));
		return;
	}

	// read it back
	status = mfrc522.MIFARE_Read(2, readbuffer2, &sizeread); // VOS
	dump_byte_array(readbuffer2, sizeof(readbuffer2) - 2); Serial.println();

	Serial.println(" ");
	mfrc522.PICC_HaltA(); // Halt PICC
	mfrc522.PCD_StopCrypto1();  // Stop encryption on PCD
}

void dump_byte_array(byte *buffer, byte bufferSize) {
	for (byte i = 0; i < bufferSize; i++) {
		Serial.print(buffer[i] < 0x10 ? " 0" : " ");
		Serial.print(buffer[i], HEX);
	}
}

 

The process is:

  • Wait for a card to be swiped
  • If a card is detected, read the card serial
  • Authenticate using Key A and optionally Key B
  • If authenticated, write block 1 (worker surname)
  • Read back block 1 and verify
  • Write block 2 (worker first name)
  • Read back block 2 and verify
  • Shut down and wait for next card swipe

The setup with RFID reader and NRF905 is shown below, with a small light sensor connected to analog pin A0. As all three require 3.3V I had to stick it on a small breadboard (my soldering skills are sub-par). The setup I found works best is to have the RFID on the ICSP header with the NRF905 sharing MISO, MOSI and SCK via digital ports 2 – 9. On the EtherMega port 10 is used by the onboard Ethernet. Although we won’t be using Ethernet on the monitor it is still blocked.

Monitor

The light sensor is as simple as it comes requiring GRN, 3.3V and a single analog port. I’ll be happy to provide exact pinouts if you need it.

Finally, below is the C code for the monitor. The only part of interest is the data transmission once an employee has tagged-on. In this section of code, we get a light reading, and send that with the employee id (001) and a 2-byte signature (GV) to the base station (refer Part 2 coming soon). Instead of the ‘GV’ signature you might want to send a unique monitor id instead (A,B,C,1,2,3 whatever). As this is a prototype (read: “too lazy”) I am skipping the employee verification and mapping to employee id.

#include <nRF905.h>
#include <SPI.h>
#include <MFRC522.h>

// RC522 RFID Tag Reader
#define RST_PIN         4
#define SS_PIN          5
MFRC522 mfrc522(SS_PIN, RST_PIN);
bool taggedOn = false; // only transmit if tagged on
MFRC522::MIFARE_Key key;

// NRF905 Communications
#define RXADDR {0xFE, 0x4C, 0xA6, 0xE5} // Address of this device
#define TXADDR {0x58, 0x6F, 0x2E, 0x10} // Address of base station
char data[NRF905_MAX_PAYLOAD] = { 0 }; // buffer to send
byte buffer[NRF905_MAX_PAYLOAD]; // buffer for ACK reply

void setup()
{
	Serial.begin(9600);

	// MIFARE Classic cards ship with default security keys. We'll just use the default of FFFFFFFFFFFF
	for (int i = 0; i < 6; i++)
	{
		key.keyByte[i] = 0xFF;
	}

	SPI.begin();
	mfrc522.PCD_Init();
	Serial.println("RFID up...");

	nRF905_init();
	byte addr[] = RXADDR;
	nRF905_setRXAddress(addr);
	nRF905_receive();
	Serial.println("Communications up...");

	Serial.println("IoT office monitor running...");
}

void loop()
{
	// Loop until a card is presented to reader
	if (mfrc522.PICC_IsNewCardPresent())
	{
		if (mfrc522.PICC_ReadCardSerial())
		{
			byte readbuffer1[18];
			byte readbuffer2[18];

			MFRC522::StatusCode status;
			byte sizeread = sizeof(readbuffer1);

			// authenticate with security key A, should be FFFFFFFFFFFF
			status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 0, &key, &(mfrc522.uid));
			if (status == MFRC522::STATUS_OK)
			{
				// read the card data and verify
				// for this example we skip verification and just send default worker id of '001' to station
				status = mfrc522.MIFARE_Read(1, readbuffer1, &sizeread); // block 1 stores Surname
				if (status == MFRC522::STATUS_OK)
				{
					status = mfrc522.MIFARE_Read(2, readbuffer2, &sizeread); // block 2 stores Firstname
					if (status == MFRC522::STATUS_OK)
					{
						// typically here we will verify or lookup the worker id based on Surname/Firstname we've read from card
						// alternatively just store worker id in block 1
						// toggle taggedOn status. Arriving or leaving office
						// ideally report via station to Azure as "arrive/depart"
						if (taggedOn) 
						{
							taggedOn = false;
						}
						else
						{
							taggedOn = true;
						}
					}
				}
			}
		}
		mfrc522.PICC_HaltA(); // Halt PICC
		mfrc522.PCD_StopCrypto1();  // Stop encryption on PCD
	}

	if (taggedOn)
	{
		int lightLevel = analogRead(A0); // read office room light via light sensor on analog port 0
		sprintf(data, "GV001%d", lightLevel); // signature 'GV' + worker id '001' + light reading

		byte addr[] = TXADDR;
		nRF905_setTXAddress(addr);
		nRF905_setData(data, sizeof(data));

		// Send payload (send fails if other transmissions are going on, keep trying until success)
		while (!nRF905_send());
		Serial.println("sent");

		// Put into receive mode to retrieve ACK signal from base station
		nRF905_receive();

		// Wait for reply with timeout
		while (1)
		{
			if(nRF905_getData(buffer, sizeof(buffer)))
			{
				Serial.println("ACK");
				break;
			}
		}
		delay(500); // give card reader some time
	}
}

 

That’s it for Part 1; Part 2 will continue with the base station setup before moving on to Azure Event Hubs and then plotting all the data into Power BI before finally ending up in Microsoft Dynamics AX 7.