Categories
arduino c# networks

Streaming real-time sensor data to an ASP.NET Core app from an Arduino

In my previous posts on Modding my Rowing Machine, I got started with an Arduino, and started collecting speed sensor data. The goal of this post is to connect to the WiFi network and upload sensor data to a server application I’ve got running on my laptop in as close to real-time as I can make it.

Connecting to WiFi

My Arduino Uno WiFi Rev 2 board has got a built-in WiFi module; it was considerably easier than I expected to get everything connected.

I first needed to install the necessary library to support the board, the WiFiNINA library:

Then you can just include the necessary header file and connect to the network:

#include <WiFiNINA.h>
#define NETSSID "MYNETWORK"
#define NETPASS "SECRETPASSWORD"
WiFiClient client;
void setup()
{
Serial.begin(9600);
// Start connecting...
WiFi.begin(NETSSID, NETPASS);
// Give it a moment...
delay(5000);
if(WiFi.status() == WL_CONNECTED)
{
Serial.println("Connected!");
}
}
view raw app.ino hosted with ❤ by GitHub

To be honest, that code probably isn’t going to cut it, because WiFi networks don’t work that nicely. You need a retry mechanism with timeouts to keep trying to connect. Let’s take a look at the full example:

#include <WiFiNINA.h>
#define NETSSID "MYNETWORK"
#define NETPASS "SECRETPASSWORD"
#define TOTAL_WAIT_TIME 60000 // 1 minute
#define ATTEMPT_TIME 5000 // 5 seconds between attempts
WiFiClient client;
void setup()
{
Serial.begin(9600);
unsigned long startTime = millis();
unsigned long lastAttemptTime = 0;
int wifiStatus;
// attempt to connect to Wifi network in a loop,
// until we connect.
while (wifiStatus != WL_CONNECTED)
{
unsigned long currentTime = millis();
if(currentTime - startTime > TOTAL_WAIT_TIME)
{
// Exceeded the total timeout for trying to connect, so stop.
Serial.println("Failed to connect");
while(true);
}
else if(currentTime - lastAttemptTime > ATTEMPT_TIME)
{
// Exceeded our attempt delay, initiate again.
Serial.println("Attempting Wifi Connection");
lastAttemptTime = currentTime;
wifiStatus = WiFi.begin(NETSSID, NETPASS);
}
else
{
// wait 500ms before we check the WiFi status.
delay(500);
}
}
Serial.println("Connected!");
}
view raw app.ino hosted with ❤ by GitHub

The Server

To receive the data from the Arduino, I created a light-weight ASP.NET Core 3.0 web application with a single controller endpoint to handle incoming data, taking a timestamp and the speed:

public class DataController : Controller
{
private readonly SampleWriter sampleWriter;
public DataController(SampleWriter sampleWriter)
{
this.sampleWriter = sampleWriter;
}
[HttpPost]
public async Task<ActionResult> ProvideReading(uint milliseconds, double speed)
{
// sampleWriter is just a singleton dependency with an open file stream,
// writing each record to a CSV file as it arrives.
await sampleWriter.ProvideSample(milliseconds, speed);
return StatusCode(200);
}
}
view raw DataController.cs hosted with ❤ by GitHub

Then, in my Arduino, I put the following code in a method to send data to my application:

#define SERVER "MYSERVER"
#define SERVERPORT 5000
WiFiClient client;
void sendData(unsigned long timestamp, double speed)
{
// Host and port
if(client.connect(SERVER, SERVERPORT))
{
char body[64];
// Clear the array to zeroes.
memset(body, 0, 64);
// Arduino sprintf does not support floats or doubles.
sprintf(body, "milliseconds=%lu&speed=", timestamp);
// Use the dtostrf to append the speed.
dtostrf(speed, 2, 3, &body[strlen(body)]);
int bodyLength = strlen(body);
// Specify the endpoint
client.println("POST /data/providereading HTTP/1.1");
// Write Host: SERVER:SERVERPORT
client.print("Host: ");
client.print(SERVER);
client.print(":");
client.println(SERVERPORT);
// Close the connection after the request
client.println("Connection: close");
// Write the amount of body data
client.print("Content-Length: ");
client.println(bodyLength);
client.println("Content-Type: application/x-www-form-urlencoded");
client.println();
client.print(body);
// Wait for the response
delay(100);
// Read the response (but we don't care what is in it)
while(client.read() != -1);
}
}
view raw app.ino hosted with ❤ by GitHub

I just want to briefly mention one part of the above code, where I’m preparing body data to send.

// Arduino sprintf does not support floats or doubles.
sprintf(body, "milliseconds=%lu&speed=", timestamp);
// Use the dtostrf to append the speed.
dtostrf(speed, 2, 3, &body[strlen(body)]);
view raw app.ino hosted with ❤ by GitHub

The Arduino libraries do not support the %f specifier (for a float) in the sprintf method, so I can’t just add the speed as an argument there. Instead, you have to use the dtostrf method to insert a double into the string, specifying the number of decimal points you want.

Also, if you specify %d (int) instead of %lu (unsigned long) for the timestamp, the sprintf method treats the value as a signed int and you get very strange numbers being sent through for the timestamp.

Once that was uploaded, I started getting requests through!

Performance

We now have HTTP requests from the Arduino to our ASP.NET Core app. But I’m not thrilled with the amount of time it takes to execute a single request.

If we take a look at the WireShark trace (I love WireShark), you can see that each request from start to finish is taking in the order of 100ms!

This is loads, and I can’t have my Arduino sitting there for that long.

ASP.NET Core Performance

You can see in the above trace that the web app handling the request is taking 20ms to return the response, which is a lot. I know that ASP.NET Core can do better than that.

Turns out this problem was actually due to the fact I had console logging switched on. Due to the synchronisation that takes place when writing to the console, it can add a lot of time to requests to print all that information-level data.

Once I turned the logging down from Information to Warning in my appsettings.json file, it got way better.

That’s better!

That actually gives us sub-millisecond response times from the server, which is awesome.

TCP Handshake Overhead

Annoyingly, each request is still taking up to 100ms from start of connection to the end. How come?

If you look at those WireShark traces, we spend a lot of time in the TCP handshaking process. Opening a TCP connection does generally come with lots of network overhead, and that call to client.connect(SERVER, SERVERPORT) in my code blocks until the TCP connection is open; I don’t want to sit there waiting for that every time I want to send a sample.

The simple solution to this is to make the connection stay open between samples, so we can just repeatedly sent data on the same connection, only needing to do the handshake once.

Let’s rework our previous sendData code on the Arduino to keep the connection open:

void sendData(unsigned long timestamp, double speed)
{
char body[64];
int success = 1;
// If we're not connected, open the connection.
if(!client.connected())
{
success = client.connect(SERVER, SERVERPORT);
}
if(success)
{
// Empty the buffer
// (I still don't really care about the response)
while(client.read() != -1);
memset(body, 0, 64);
sprintf(body, "milliseconds=%lu&speed=", timestamp);
dtostrf(speed, 2, 3, &body[strlen(body)]);
int bodyLength = strlen(body);
client.println("POST /data/providereading HTTP/1.1");
client.print("Host: ");
client.print(SERVER);
client.print(":");
client.println(SERVERPORT);
// This tells the server we want to leave the
// connection open.
client.println("Connection: keep-alive");
client.print("Content-Length: ");
client.println(bodyLength);
client.println("Content-Type: application/x-www-form-urlencoded");
client.println();
client.print(body);
}
}
view raw app.ino hosted with ❤ by GitHub

In this version, we ask the server to leave the connection open after the request, and only open the connection if it is closed. I’m also not blocking waiting for a response.

This gives us way better behaviour, and we’re now down to about 40ms total:

There’s one more thing that I don’t love about this though…

TCP Packet Fragmentation

So, what’s left to look at?

TCP segments

I’ve got a packet preceding each of my POST requests, that seems to hold things up by around 40ms. What’s going on here? Let’s look at the content of that packet:

Wireshark data view

What I can tell from this is that rather than wait for my HTTP request data, the Arduino is not buffering for long enough, and is just sending what it has after the first println call containing POST /data/providereading HTTP/1.1. This packet fragmentation slows everything up because the Arduino has to wait for an ACK from the server before it continues.

I just wanted to point out that it looks like the software in the Arduino libraries isn’t responsible for the fragmentation; it looks all the TCP behaviour is handled by the hardware WiFi module, that’s what is splitting my packets.

To stop this packet fragmentation, let’s adjust the sending code to prepare the entire request and send it all at once:

void sendData(unsigned long timestamp, double speed)
{
int success = 1;
char request[256];
char body[64];
if(!client.connected())
{
success = client.connect(server, 5000);
}
if(success)
{
// Empty the buffer
// (I still don't really care about the response)
while(client.read() != -1);
// Clear the request data
memset(request, 0, 256);
// Clear the body data
memset(body, 0, 64);
sprintf(body, "milliseconds=%lu&speed=", timestamp);
dtostrf(speed, 2, 3, &body[strlen(body)]);
char* currentPos = request;
// I'm using sprintf for the fixed length strings here
// to make it easier to read.
currentPos += sprintf(currentPos, "POST /data/providereading HTTP/1.1\r\n");
currentPos += sprintf(currentPos, "Host: %s:%d\r\n", server, 5000);
currentPos += sprintf(currentPos, "Connection: keep-alive\r\n");
currentPos += sprintf(currentPos, "Content-Length: %d\r\n", strlen(body));
currentPos += sprintf(currentPos, "Content-Type: application/x-www-form-urlencoded\r\n");
currentPos += sprintf(currentPos, "\r\n");
strcpy(currentPos, body);
// Send the entire request
client.print(request);
// Force the wifi module to send the packet now
// rather than buffering any more data.
client.flush();
}
}
view raw app.ino hosted with ❤ by GitHub

Once uploaded, let’s look at the new WireShark trace:

No TCP Fragmentation

There we go! Sub-millisecond responses from the server, and precisely hitting my desired 50ms window between each sample send.

There’s still ACKs going on obviously, but they aren’t blocking packet issuing, which is the important thing.

Summary

It’s always good to look at the WireShark trace for your requests to see if you’re getting the performance you want, and don’t dismiss the overhead of opening a new TCP connection each time!

Next Steps

Next up in the ‘Modding my Rowing Machine’ series, I’ll be taking this speed data and generating a real-time graph in my browser, that updates continuously! Stay tuned…

Categories
c#

Value Tuples for passing Lists of Key-Value Pairs in C# 7

Prior to C# 7, I have many, many times found myself needing to pass a hard-coded list of key-value pairs to a method, and I’ve had to do it painfully:

void SomeMethod(List<KeyValuePair<string, string>> pairs)
{
// Use the values
}
void Caller() {
// Yuck!
SomeMethod(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("key1", "val1"),
new KeyValuePair<string, string>("key2", "val2")
});
}
view raw program.cs hosted with ❤ by GitHub

We can make this marginally better with a factory function:

// Forgive me for this function name
KeyValuePair<string, string> KVP(string key, string val)
{
return new KeyValuePair<string, string>(key, val);
}
void Caller()
{
// Still pretty bad
SomeMethod(new List<KeyValuePair<string, string>> {
KVP("key1", "val1"),
KVP("key2", "val2")
});
}
view raw program.cs hosted with ❤ by GitHub

Then we can swap the list with an array to improve it a little more:

void SomeMethod(KeyValuePair<string, string>[] pairs)
{
}
void Caller()
{
// Ok, so I hate myself a little less
SomeMethod(new[]
{
KVP("key1", "val1"),
KVP("key2", "val2")
});
}
view raw program.cs hosted with ❤ by GitHub

Finally, we can use ‘params’ on the method so we don’t need to declare an array on the caller:

void SomeMethod(params KeyValuePair<string, string>[] pairs)
{
}
void Caller()
{
// params are great, but I still don't love it.
SomeMethod(
KVP("key1", "val1"),
KVP("key2", "val2")
);
}
view raw program.cs hosted with ❤ by GitHub

Okay, so it’s not too bad, but we still have that factory function which I’m not a fan of.

Value Tuples

Briefly, Value Tuples let you do things like this:

(string value1, string value2) SomeFunc()
{
return ("val1", "val2");
}
void CallerFunction()
{
var result = SomeFunc();
Console.WriteLine(result.value1); // val1
Console.WriteLine(result.value2); // val2
}
view raw program.cs hosted with ❤ by GitHub

You get a small value type containing your two values, that you can declare inline, and access the individual values in the result.

You don’t just have to use these in return types, you can use them anywhere a type definition could be used.

I’ve generally been a little reticent to use this new-ish feature too heavily, because I’m worried about people using them where they should actually be defining types.

That being said…

Making Our Parameters Nicer

In C# 7, now we get to pass those key-value lists the nice way:

void SomeMethod(params (string key, string value)[] pairs)
{
}
void Caller()
{
// Now that's more like it!
SomeMethod(
("key1", "val1"),
("key2", "val2")
);
}
view raw program.cs hosted with ❤ by GitHub

Look at that! No factory function required, we can simply pass in the pairs, just as I’ve wanted to be able to do for years, and it’s super readable.

Just define the array type of your params argument as the tuple you want, and away you go!

I’m still a little wary of Value Tuples generally, but this is definitely a lovely use case for them.

Categories
arduino

Modding my Rowing Machine with an Arduino – Part 2 – Reading the Speed Sensor

If you didn’t see my previous post on setting up an Arduino, then to catch you up, I’ve got a rowing machine, and I am trying to replace the basic LCD display on it with my own; to do this I need to read the sensor data it collects and process it myself.

This is my ‘competition’ right now

Just so everyone is aware, this post could also be titled, “How I struggled with basic electronics” or “How you shouldn’t make assumptions when reverse engineering something”.

When I started this little project, I looked at the existing board that provides the LCD display for the rowing machine, and saw the two connectors. One for speed (which goes to the fan wheel at the front) and one for count (which goes to a sensor under the seat).

The connectors on the back of the PCB.

I made the assumption that because speed is an analog value, then the incoming data should be analog as well. This turned out to not be true, but first I’ll show you the wiring/code that told me it was not true.

Wiring

The connector for the speed sensor is actually a 3.5mm mono female jack:

The jack on the right is the speed sensor, the one in the middle goes to the seat movement sensor.

So, I got a cheap 3.5mm cable, cut the end off it, then wired it to a junction.

The wired mono jack.

I was momentarily confused by the fact there were 3 cables rather than 2 for a mono connector, but it turned out that the yellow wire was wired straight to the red, so I just ignored that one.

Reading an Analog Value

I thought that the speed sensor might be some sort of variable resistor, so that as the speed increased, the voltage allowed through the connection would increase.

So to start with, I connected the 5V line on the Arduino to the red wire on the junction, and the black wire (GND) to the A0 analog input pin on the Arduino:

Wiring to the analog input pin.

Then, I wrote some code to read the analog value (in volts) and write it to the Serial connector:

void setup()
{
// Initialise my serial port with 9600 baud
Serial.begin(9600);
}
void loop()
{
// Analog values are read as integer values between 1 and 1023.
// Each 1 is a fraction of 5V. So 0 = 0V, 1023 = 5V, and the scale
// goes in-between.
const float VOLTAGE_MULTIPLIER = (5.0 / 1023.0);
// Read the analog value on pin A0 (as fractions of 5V).
int sensorValue = analogRead(A0);
// Convert to volts using our multiplier.
float sensorVoltage = sensorValue * VOLTAGE_MULTIPLIER;
// Write the current voltage to the Serial port.
Serial.println(sensorVoltage);
}
view raw app.ino hosted with ❤ by GitHub

Before you connect the sensor, you just get analog ‘noise’ because there’s nothing connected to the A0 pin:

Voltage data from a floating analog input

Once I connected the 3.5mm jack to the rowing machine’s speed sensor I got:

So that already tells me that the circuit is complete, and I’m seeing the 5V from the Arduino coming back.

When I pull on the rowing machine, my numbers change, but only very slightly (and not really in a way that relates to the speed):

Not much change in the voltage…

I was definitely expecting more change in values from this; and as soon as the wheel of the rower starts to slow down, the number goes back to 5V.

What’s going on here? It doesn’t so much look like the voltage is changing with speed, it’s more like something is slightly interfering with a stable voltage reading…given the nature of the analog value sampling, it stands to reason that a switch rapidly changing from on to off and back might look like this…

Using a switch to read speed

So how can you use a switch to read speed? Well, I suspect there is a magnetic reed switch inside the wheel of the rowing machine, with a magnet attached to one of the blades of the fan inside the wheel.

Each time the magnet passes the switch, it will cause the circuit to break temporarily. The faster the blades spin, the more often the circuit will break.

To detect this, I need to change my approach a bit, and rather than read analog values, I need to read a digital value.

In order to get this working I had to sort out some basic electronics; I used the guide found here, which gave me the circuit I needed to make it work. It’s not too complicated to wire up, here’s the final diagram:

Circuit Diagram of the switch reader

It looks much less neat than that on the breadboard, but here it is:

The circuit to detect the state of the switch.

Then, I added the following code, to sample the digital input and watch for transitions.

const int SPEEDPIN = 2;
// Sample every 500ms
const int SAMPLEFREQ = 500;
// Variables for storing state.
int switchState = 0;
int lastSwitchState = 0;
// Variable for remembering when we last sampled.
unsigned long lastSampleMillis = 0;
// Keep track of our transitions
unsigned int transitionCount = 0;
void setup()
{
Serial.begin(9600);
}
void loop()
{
// Each loop, read the state of the pin.
switchState = digitalRead(SPEEDPIN);
// When we transition from HIGH to LOW, the circuit has been broken
if(lastSwitchState == HIGH && switchState == LOW)
{
// Increment our count of transitions.
transitionCount++;
}
lastSwitchState = switchState;
if(millis() - lastSampleMillis > SAMPLEFREQ)
{
// Once per SAMPLEFREQ milliseconds, determine the
// speed (in terms of transitions per second).
int speed = transitionCount * (1000 / SAMPLEFREQ);
// Print out the speed of the sample
Serial.println(speed);
// Reset the count ready for the next sample.
transitionCount = 0;
lastSampleMillis = millis();
}
}
view raw digitalspeed1.ino hosted with ❤ by GitHub

Now, when I plug this into the rowing machine and I give it a good couple of pulls, I get some meaningful speed readings out that correlate with how hard I pull it!

Pulling on the rower gives me increases in speed!

Next post will cover getting the data off the Arduino; connecting up the WiFi on my board and sending an HTTP request with my speed data!

Categories
arduino Uncategorized

Modding my Rowing Machine with an Arduino – Part 1 – Arduino Basics

I’ve got a rowing machine in my garage that I use pretty regularly, and it displays some statistics on a basic little read-out, including speed, calories burnt, number of ‘strokes’ and so on.

The rowing machine read-out.

My mission is to replace the simple LED read-out with my own board that will capture the sensor data it uses, upload it somewhere and then do some fun stuff with that data.

For background, I have a little past experience in embedded programming, but I haven’t used it in years. I understand the basics of GPIO pins and similar, but I place myself firmly in the ‘beginner’ category with embedded development.

I hadn’t touched an Arduino before today, when I started writing this post, so this is going to cover just getting to grips with Arduino basics, and subsequent updates will go through all the steps needed to mod my rowing machine!

Picking an Arduino Board

My only real criteria for picking an Arduino board is that I can connect the sensors on my rowing machine, and also that I can connect to WiFi to get data off it.

The back of the rowing machine display.

From the connections on my rowing machine, I can tell I need two connections on the board that can read analog sensor values, so I knew I would need two ADCs (Analog to Digital Converters) on my board, one for the speed and one for the counter.

Going through the Arduino website to figure out which board I wanted (turns out there’s a lot of choice), I picked the Arduino Uno Wifi Rev2, which:

  • Has 2 ADCs (for reading the sensors on my rowing machine).
  • A built-in WiFi module for connectivity (so I can upload my data).
  • A pretty attractive price of about £35 (as of writing in May 2019) plus shipping.

Shipping only took a couple of days, and once it arrived I was ready to go. You will also need a USB Type B cable. You can pick one up off Amazon for about £5, but I had an old one lying around.

Setup

After plugging in the Arduino board with the USB connector, I went to the getting started guide for my particular board (the site has different pages for each one).

I believe that a lot of the following content should work for most Arduino boards that don’t have WiFi, but I am not certain.

From there I tried out both the web-based and desktop IDE Arduino provide, but I decided I want to work in a familiar environment. Luckily, it turns out that trusty VS Code can come to my rescue, because it has an extension for working with Arduino, with the added bonus of having decent intellisense.

You will need to download the regular Arduino desktop IDE from here before you can use VS Code with your Arduino; the extension needs to use the tools it supplies.

Go ahead and install the VS Code Arduino extension (by Microsoft), then go and configure the extension if you need to set the installation path for your Arduino installation to the non-default setting.

What is an Arduino Program?

What actually is an Arduino Program anyway, and how is it different from a console application on my desktop?

Basically, all Arduino ‘programs’ run C++ code you write. Each program fundamentally consists of a setup and a loop.

void setup()
{
// Do your setup here,
// configure your board, and so on.
}
void loop()
{
// Called continuously. This is where you
// will do the bulk of your work.
}
view raw app.ino hosted with ❤ by GitHub

You can sort of think of setup as your ‘main’ method, but you exit it immediately and start looping.

One thing that you need to remember with an embedded program is that your program never stops; it runs until the device doesn’t have power, you reset the board, or you upload a new program. There is no ‘exit’.

In an Arduino program you can do a lot of the things you’d expect from a C++ program, like having additional C++ code files (files with a .cpp extension work just fine). You do have a lot less memory to play with though; the processor on my Arduino only has 6KB of RAM. As someone who tends to work on web applications that consume hundreds of MB, it’s a bit jarring, but 6KB is actually plenty for my needs.

When you want to run your program, you compile it as you would with a normal program; the Arduino components on your desktop then upload the program to the device (over the USB connection) and store it in Flash memory. Again, you can’t have massive applications; my board has 48KB of Flash to fit the program in.

My First Arduino Program

First off, I’m going to make an LED flash. Big stuff, I know. My Arduino (and most of them I think), have a built-in LED that you can turn on and off from your program.

Let’s make a new project in VS Code. Create a new folder somewhere and open it in VS Code.

Then, to start a new ‘project’, run Arduino: Initialize from the command palette. This created a blank app.ino file and let me select my board, which got me started.

I found that I got an intellisense error in the created ino file:

Clicking on the ‘fix-it’ bulb took me to the C++ include path settings:

After a quick search of the Arduino install directory for ‘pgmspace.h’, turned out I was missing an additional include path:

C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\**

After I added that to the list with the necessary extra backslashes, closed and then re-opened my app.ino file, no more squiggles, and I get nice intellisense.

Controlling an LED

My built-in LED is on PIN #13 of the processor (I used the Arduino Board reference to check what was available); in your program there is an LED_BUILTIN constant already supplied that references it.


A bit of embedded basics here; everything you do in embedded programming is basically turning pins on and off on your processor.

The big arrow is pointing to a pin

Want to turn on an LED? Set the ‘level’ of the pin connected to the LED to HIGH (which means it has voltage on it). To turn it off, set the level to LOW (which means it has no/negligible voltage on it).


I had to configure the LED pin to be an output pin in my setup method, and then set it HIGH and LOW in my loop. Put some delays in there and you have a flickering LED!

void setup()
{
// Configure the LED pin
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
// Set the PIN to 'high'
digitalWrite(LED_BUILTIN, HIGH);
// Wait 1 second (1000ms)
delay(1000);
// Set the PIN to 'low'
digitalWrite(LED_BUILTIN, LOW);
// Wait again
delay(1000);
}
view raw app.ino hosted with ❤ by GitHub

You can upload the program from the command palette (or CTRL+ALT+U).

GIF of an LED blinking (very exciting!)

You might encounter one or more problems getting that program downloaded (like I did):

COM Port

I had problems getting the COM Port (the Arduino connects as a virtual COM Port over USB) to work first time, so my code wouldn’t upload. I had to go into the main Arduino IDE and install the package it recommended for my board. This installed different drivers, which changed the COM Port to Atmel rather than Microsoft.

I also had to go into Windows Device Manager, go into the settings, and change the COM Port to not be the default COM3. Only then would it actually upload.

Select the Right Board!

Pro Tip – make sure you pick the right board in the VSCode footer, and select the processor.

Mine is the Arduino Uno WiFI Rev2, with the A4809 processor.

Hello World

Next step, I want to do the tried and tested programming exercise of writing Hello World to the ‘Console’.

The Arduino doesn’t have a console to write to in the normal sense you might be familiar with; what it can do however is write to the Serial connection you use to upload your programs, and the Arduino extension in VS Code can display it.


More embedded basics; Remember how I said that everything in embedded programming is turning pins on (HIGH) and off (LOW)? Well, that’s true of writing text to a Serial connection too, but we do it extremely quickly.

Luckily, we don’t have to do all the pin changes ourselves (phew); there are functions already supplied by the Arduino libraries that will do this for us.

When we configure our Serial port, we need to set its ‘baud’, or speed. This tells the Serial code how fast we want to send characters. 9600 baud means that we are sending approximately 9600 bytes/characters per second. Whatever is ‘listening’ on the other end needs to know the speed, otherwise you’ll just get junk out.


void setup()
{
// Initialise my serial port with 9600 baud
Serial.begin(9600);
}
void loop()
{
// Wait a second
delay(1000);
// Write "Hello World!" to my serial port.
Serial.println("Hello World!");
}
view raw app.ino hosted with ❤ by GitHub

So once we’ve setup our Serial Port, in our loop, once per second we print our Hello World to the Serial port.

Once you’ve copied that code into your app.ino, upload the program as you would normally.

Once you’ve uploaded your program, it’s initially a bit anti-climatic, nothing is obviously happening. To see the output, we need to open the Serial Monitor. Click on the little connection logo in the VS Code footer:

This will bring up the Serial Monitor. At this point, you will probably just be getting nonsense, so you will want to change the ‘baud’ for the Serial Monitor to 9600.

Once you’ve done that, you should get your Hello World being displayed!

Next Steps

Ok, so that’s it for now. Subsequent posts will cover:

  • Wiring up my sensors and reading analogue values.
  • Using the WiFi module to sent HTTP requests
  • Using the data I collect!
Categories
Uncategorized

Implementing a shared resource lock in your load-balanced application with MongoDB in C#

In your load-balanced cluster of nice, performant application servers, you may occasionally find that there is an outside resource that requires access be synchronised between each server in the cluster (ideally not too often, because it’s a pain, but there you go).

We encountered this recently when dealing with multiple clients trying to access and update a reporting component we interface with via our application. Each client issued HTTP API requests to the application that would:

  1. Read a resource from the reporting system (by the ID)
  2. Do something
  3. Update the resource

The problem is that no two tasks should be allowed to get past step 1 at the same time for a given named resource. This is starting to look pretty familiar, right?

We see this in a single-server multi-threaded model sometimes (although not too often hopefully; I find synchronous locks are generally bad for performance if used too liberally in web requests).

lock(someSharedResource)
{
// Do some work using the resource
// and maybe update it.
}

The problem with the above code is that it only locks a resource in the current process; a different HTTP request, routed to a different server by the load balancer, would happily acquire it’s own lock.

Establishing a distributed lock

What we need now is to lock the resource across our entire cluster, not just on the one server.

We use MongoDB for general shared state between servers in our cluster, so I looked into how to use MongoDB to also synchronise access to our resource between the application servers.

Luckily, it turns out that by using existing MongoDB functionality, this is pretty straightforward to create a short-lived resource lock.

Note
I’ve written this solution in C#, using the official MongoDB C# client, but there’s no reason this wouldn’t apply to a different MongoDB client implementation in any language.

Want to jump to the end?
https://github.com/alistairjevans/mongodb-locks

Create the Collection

First up, I want to create a new MongoDB collection to hold my locks. I’ll create a model and then get an instance of that collection.

public class LockModel
{
// We only need an ID right now
public string Id { get; set; }
}
view raw LockModel.cs hosted with ❤ by GitHub
public class LockProvider
{
private readonly IMongoCollection<LockModel> collection;
public LockProvider(string mongodbConnString)
{
// Create a lock collection
var client = new MongoClient(mongodbConnString);
var database = client.GetDatabase("mydb");
// Get our collection
collection = database.GetCollection<LockModel>("resourceLocks");
}
}
view raw LockProvider.cs hosted with ❤ by GitHub

That’s pretty basic stuff, you’d have to do that generally to access any MongoDB collection.

Next we’re going to add the function used to acquire a lock, AcquireLock. This method is responsible for the ‘spin’ or retry on the lock, waiting to acquire it.

public class LockProvider
{
private readonly IMongoCollection<LockModel> collection;
public LockProvider(string mongodbConnString) {} // Collapsed
public async Task<IDisposable> AcquireLock(string resourceId)
{
// Determine the id of the lock
var lockId = $"lock_{resourceId}";
var distributedLock = new DistributedLock(collection, lockId);
var startLockAcquireTime = DateTime.Now;
// Try and acquire the lock
while (!await distributedLock.AttemptGetLock())
{
// If we failed to acquire the lock, wait a moment.
await Task.Delay(100);
// Only try to acquire the lock for 10 seconds
if ((DateTime.Now - startLockAcquireTime).TotalSeconds > 10)
{
throw new ApplicationException($"Could not acquire lock for {resourceId} within the timeout.");
}
}
// This will only return if we have the lock.
return distributedLock;
}
}
view raw LockProvider.cs hosted with ❤ by GitHub

The AcquireLock method:

  1. Creates a ‘lock id’ from the resource id.
  2. Creates a ‘DistributedLock’ object, which is where the locking mechanism happens (more on this in a moment).
  3. Attempts to get the lock in a while loop.
  4. Waits up to a 10 second timeout to acquire the lock, attempting again every 100ms.
  5. Returns the DistributedLock once the lock is acquired (but only as an IDisposable).

Next let’s look at what is going on inside the DistributedLock class.

DistributedLock

The DistributedLock class is responsible for the actual MongoDB commands, and attempting the lock.

public class DistributedLock : IDisposable
{
private readonly IMongoCollection<LockModel> collection;
private readonly string lockId;
public DistributedLock(IMongoCollection<LockModel> collection, string lockId)
{
this.collection = collection;
this.lockId = lockId;
}
public async Task<bool> AttemptGetLock()
{
var response = await collection.FindOneAndUpdateAsync<LockModel>(
// Find a record with the lock ID
x => x.Id == lockId,
// If our 'upsert' creates a document, set the ID to the lock ID
Builders<LockModel>.Update.SetOnInsert(x => x.Id, lockId),
new FindOneAndUpdateOptions<LockModel>
{
// If the record doesn't exist, create it.
IsUpsert = true,
// Specifies that the result we want is the record BEFORE it
// was created (this is important).
ReturnDocument = ReturnDocument.Before
});
// If the result of the FindOneAndUpdateAsync is null, then that means there was no record
// before we ran our statement, so we now have the lock.
// If the result is not null, then it means a document for the lock already existed, so someone else has the lock.
if (response == null)
{
return true;
}
return false;
}
public void Dispose()
{
// Delete the document with the specified lock ID, effectively releasing the lock.
collection.DeleteOne(x => x.Id == lockId);
}
}
view raw DistributedLock.cs hosted with ❤ by GitHub

Let’s break down what happens here. The AttemptGetLock method issues a FindOneAndUpdate MongoDB command that does the following:

  1. Looks for a record with an ID the same as the provided lock ID.
  2. If it finds it, it returns it without doing anything (because our update is only a SetOnInsert, not a Set).
  3. If it doesn’t find it, it creates a new document (because IsUpsert is true), with the expected ID.

We’ve set the ReturnDocument option to ‘Before’, because that means the result of the FindOneAndUpdateAsync is null if there was no existing lock document. If there was no existing document, there will be one now, and we have acquired the lock!

When you dispose of the lock, we just delete the lock document from the collection, and hey presto, the lock has been released, and the next thread to try to get the lock will do so.

Using It

Because the AcquireLock method returns an IDisposable (via a Task), you can just use a ‘using’ statement in a similar manner to how you would use the ‘lock’ statement.

using (await LockProvider.AcquireLock(id))
{
// Do some work
}

Do you see that await inside the using definition? Right, do not forget to use it. If you do forget, all hell will break loose, because Task<IDisposable> also implements IDisposable! So you’ll end up instantly entering the using block, and then disposing of the task afterwards at some point while the lock attempts are happening. This causes many bad things.

Make sense? Good. Unfortunately, we’re not quite done…

Handing Concurrent Lock Attempts

So, while the above version of DistributedLock works pretty well most of the time, at some point it will inevitably throw an exception when handling multiple concurrent lock attempts:

Duplicate keys…

Why does this happen? Well, the upsert functionality in findAndModify is not technically atomic in the way you might expect; the find and the insert are different operations internally to MongoDB, so two lock attempts might both attempt to insert a record.

When that happens, the default ID index on the MongoDB collection will throw an E11000 duplicate key error.

This is actually OK; one of the threads that attempted to acquire a lock will get it (the first one to complete the insert), and the second one will get an exception, so we just need to amend our code to say that the thread with the exception failed to get the lock.

try
{
var response = await collection.FindOneAndUpdateAsync<LockModel>(
// Collapsed
);
// If the result of the FindOneAndUpdateAsync is null, then that means there was no record
// before we ran our statement, so we now have the lock.
// If the result is not null, then it means a document for the lock already existed, so someone else has the lock.
if (response == null)
{
return true;
}
return false;
}
catch (MongoCommandException ex)
{
// 11000 == MongoDB Duplicate Key error
if (ex.Code == 11000)
{
// Two threads have tried to acquire a lock at the exact same moment on the same key,
// which will cause a duplicate key exception in MongoDB.
// So this thread failed to acquire the lock.
return false;
}
throw;
}
view raw DistributedLock.cs hosted with ❤ by GitHub

Handling a Crash

The last problem we have to solve is what happens if one of the application servers crashes part-way through a piece of work?

If a thread has a lock, but the server crashes or is otherwise disconnected from MongoDB, it can’t release the resource, meaning no-one else will ever be able to take a lock on the resource.

We need to put in some safeguard against this that allows the lock to eventually be released even if the application isn’t able to do it correctly.

To do this, we can use one of my favourite MongoDB features, TTL Indexes, which allows MongoDB to ‘age out’ records automatically, based on a column that contains an ‘expiry’ time.

Let’s update the original LockModel with an expiry property, and add a TTL index to our collection.

public class LockModel
{
public string Id { get; set; }
/// <summary>
/// I'm going to set this to the moment in time when the lock should be cleared.
/// </summary>
public DateTime ExpireAt { get; set; }
}
view raw LockModel.cs hosted with ❤ by GitHub
// In our LockProvider constructor...
// Specify a TTL index on the ExpiryPoint field.
collection.Indexes.CreateOne(new CreateIndexModel<LockModel>(
Builders<LockModel>.IndexKeys.Ascending(l => l.ExpireAt),
new CreateIndexOptions
{
ExpireAfter = TimeSpan.Zero
}
));
view raw LockProvider.cs hosted with ❤ by GitHub

In the above index creation instruction, I’m specifying an ExpireAfter of TimeSpan.Zero, meaning that as soon as the DateTime specified in ExpireAt of the lock document passes, the document will be deleted.

Finally, we’ll update the MongoDB FindOneAndUpdate instruction in DistributedLock to set the ExpireAt property to the current time plus 1 minute.

var response = await collection.FindOneAndUpdateAsync<LockModel>(
// Find a record with the lock ID
x => x.Id == lockId,
// If our 'upsert' creates a document, set the ID to the lock ID
Builders<LockModel>.Update
.SetOnInsert(x => x.Id, lockId)
.SetOnInsert(x => x.ExpireAt, DateTime.UtcNow.AddMinutes(1)),
new FindOneAndUpdateOptions<LockModel>
{
// If the record doesn't exist, create it.
IsUpsert = true,
// Specifies that the result we want is the record BEFORE it
// was created (this is important).
ReturnDocument = ReturnDocument.Before
});
view raw DistributedLock.cs hosted with ❤ by GitHub

Now, if a lock isn’t released after 1 minute, it will automatically be cleaned up by MongoDB, and another thread will be able to acquire the lock.

Notes on TTL Indexes and Timeouts

  • The TTL index is not a fast way of releasing these locks; the accuracy on it is low, because by default the MongoDB thread that checks for expired documents only runs once every 60 seconds.
    We’re using it as a safety net for an edge case, rather than a predictable mechanism. If you need a faster recovery than that 60 second window, then you may need to look for an alternative solution.
  • You may notice that I’m using DateTime.UtcNow for the ExpireAt value, rather than DateTime.Now; this is because I have had a variety of problems storing C# DateTimes with a timezone in MongoDB in a reliable way, so I tend to prefer storing UTC values whenever possible (especially when it’s not a user-entered value).

Sample Project

I’ve created a github repo with an ASP.NET Core project at https://github.com/alistairjevans/mongodb-locks that has a complete implementation of the above, with an example API controller that outputs timing information for the lock acquisition.