Coding Security Camera Monitoring Software – Part 3

Introduction

This is the last in my three-part look at how to write your own security camera monitoring software (see part 1 and part 2 here if you missed them).  I have CCTV around my house and wanted something easy to watch the feeds on without needing to install anything or to pay for software.  I wanted it to just sit on a spare monitor in my office and thought it would be pretty easy to write the software to do it.

In the previous two videos we have pretty much finished everything apart from the bells and whistles which is all we’ve got left this week.  Still to go we have to be able to toggle the video size on a click between taking a quarter of the screen and full screen and then we just need to gracefully to deal with error handling.

So let’s get coding.

Part 3: The Bells and Whistles

I always find this the least fun part of an application – making it work how it is supposed to.  Hacking a few things together with tape, glue and a hammer always feels more enjoyable than precision surgery.  Nonetheless it is time to finish off what we started.

The first thing we need to do is go back in to ControlWindow.xaml.cs and add a new method called ToggleSize.  This method takes in a single camera ID (from 1 to 4) and determines which row and column that camera should be in (i.e. camera 1 is in row 0, column 0, camera 4 is in row 1, column 1, etc).  It then looks to see if the feed is currently there and not spanning more than one cell in which case it’s currently not maximised, so we need to maximise it – otherwise it knows we need to put it back where it is supposed to be.  When maximising we just put it in column 0, row 0, make it a 2×2 size and hide all other elements on screen.

        /// <summary>
/// Toggle the size (from full screen to small and back) of a particular camera feed overlay.
/// </summary>
/// <param name=”cameraId”>
/// The ID of the camera feed overlay to toggle.
/// </param>
private void ToggleSize(int cameraId)
{
TextBlock nameLabel = _nameLabels[cameraId – 1];
int defaultRow = ((cameraId – 1) / 2);
int defaultColumn = cameraId % 2 == 0 ? 1 : 0;
TextBlock[] labelsToToggle = _nameLabels.Where(tb => tb != nameLabel).Concat(_errorLabels.Where(el => el != _errorLabels[cameraId – 1])).ToArray();
bool isMaximised = Grid.GetRowSpan(nameLabel) == 2;
Grid.SetRow(nameLabel, isMaximised ? defaultRow : 0);
Grid.SetColumn(nameLabel, isMaximised ? defaultColumn : 0);
Grid.SetRowSpan(nameLabel, isMaximised ? 1 : 2);
Grid.SetColumnSpan(nameLabel, isMaximised ? 1 : 2);
foreach (UIElement element in labelsToToggle)
element.Visibility = isMaximised ? Visibility.Visible : Visibility.Hidden;
}

We also need to add a counterpart method to VideoStreamWindow.xaml.cs to do exactly the same but this time just toggling the VLC feed rather than the control elements.

        /// <summary>
/// Toggle the size of a particular camera stream.
/// </summary>
/// <param name=”cameraId”>
/// The camera stream to toggle from full screen to windowed mode.
/// </param>
internal void ToggleSize(int cameraId)
{
// Get a handle to host containign stream that we’re toggling and determine where it should be shown in the case of non-maximised view
WindowsFormsHost formsHost = _formsHosts[cameraId – 1];
int defaultRow = ((cameraId – 1) / 2);
int defaultColumn = cameraId % 2 == 0 ? 1 : 0;
WindowsFormsHost[] otherFormHosts = _formsHosts.Where(fh => fh != formsHost).ToArray();

// Determine current state of this control
bool isMaximised = Grid.GetRowSpan(formsHost) == 2;

// Toggle the visibility of this control
Grid.SetRow(formsHost, isMaximised ? defaultRow : 0);
Grid.SetColumn(formsHost, isMaximised ? defaultColumn : 0);
Grid.SetRowSpan(formsHost, isMaximised ? 1 : 2);
Grid.SetColumnSpan(formsHost, isMaximised ? 1 : 2);
foreach (UIElement element in otherFormHosts)
element.Visibility = isMaximised ? Visibility.Visible : Visibility.Hidden;
}

Once that is done we need to replace our temporary code for handling button clicks in ControlWindow.xaml.cs with a call to the methods we’ve just written so let’s replace CameraAreaOnMouseLeftButtonClick as below.  This method determines which video feed has been clicked and first of all checks it isn’t in an error state (we don’t want to maximise a video feed when there is no video).  If there’s no error it just makes a call to the methods we’ve just created.

        /// <summary>
/// Handle the mouse being clicked on the one of the camera areas.
/// </summary>
/// <param name=”sender”>
/// The event sender.
/// </param>
/// <param name=”e”>
/// The event arguments.
/// </param>
private void CameraAreaOnMouseLeftButtonClick(object sender, MouseButtonEventArgs e)
{
// Determine which camera was clicked
int foundIndex = -1;

for (int i =0; i < _nameLabels.Length; i++)
if (_nameLabels[i] == sender)
{
foundIndex = i;
break;
}
if (foundIndex < 0)
return;

// Check for error and don’t allow this
string errorText = _errorLabels[foundIndex].Text;
if (!string.IsNullOrEmpty(errorText))
return;

// We know which camera we’ve clicked on and it’s valid – so let’s toggle its size
ToggleSize(foundIndex + 1) ;
_videoStreamWindow.ToggleSize(foundIndex + 1);
}

You should now be able to run your code and click on any of the video feeds or their corresponding labels to toggle the size.  Good stuff and almost there.

Next we need to deal with errors – first displaying them and then retrying failed video streams to get around temporary errors (such as network link failures).

Since our error text is displayed in the ControlWindow our first stop is to add a new method in to ControlWindow.xaml.cs.

        /// <summary>
/// Set the status message for a particular camera feed.
/// </summary>
/// <param name=”cameraId”>
/// The ID of the camera (starting at 1) to set the text for.
/// </param>
/// <param name=”message”>
/// The status message to set.
/// </param>
internal void SetStatusMessage(int cameraId, string message)
{
// Call back through dispatcher if required
if (!Dispatcher.CheckAccess())
{
Action act = () => SetStatusMessage(cameraId, message);
Dispatcher.Invoke(act);
return;
}

// If this is an error being set and the camera in question is full screen then we want to unmaximise
if (!string.IsNullOrEmpty(message) && Grid.GetRowSpan(_nameLabels[cameraId – 1]) > 1)
{
ToggleSize(cameraId);
_videoStreamWindow.ToggleSize(cameraId);
}

// Set the error text
_errorLabels[cameraId – 1].Text = message;
}

On the surface of it the few lines are pretty simple – if this is an error message (rather than an empty message clearing an error state) and the camera is maximised then we want to unmaximize it first by calling the methods we just wrote.  Finally we want to set the appropriate error text to the message that is passed in.  But what about the rest of this method?

This is where you need to know a little something about threading.  In basic terms a thread is one continuous execution of code.  A really simple program that is just a console (command prompt, PowerShell, etc.) style application will have a single thread and all the code you write is in that thread.  In more complex applications different threads do different things – so a web server may have a different thread for each person that has connected to it.  Threads run independently so if you have a lot of CPU cores you can run multiple threads at the same time to speed up your application.  Threads can also be used to segregate code.  Whenever we have a GUI application (on almost any platform I can think of from mobile to desktop) there tends to be a “UI Thread” that deals with actually drawing everything on screen, handling button clicks, etc.  There are then potentially additional threads doing other things.  Since an individual thread can only do one thing at once think about downloading a file in a web browser – if there were no threads you couldn’t browse any other websites or even scroll up and down in the current one whilst a download was occurring you’d just get that “[Application] Has Stopped Responding” message.  For this reason long-running tasks happen in one thread and the user interface in another.  In our example here each VLC video feed is one thread and the UI is in another thread.

There is one very important rule of threads – have extreme caution when doing anything between them.  There is an even more important rule of UI threads – never the two shall meet!  If you try to do something to the UI from outside the UI thread pretty much every platform will throw a big error (or just ignore it).  This is to ensure consistency (what happens if one thread changes a button whilst another is dealing with pressing it – who knows what state it is actually in?)

In our case whenever we have an error come in from VLC that comes in on one of the four VLC threads.  We want to call SetStatusMessage – but if the error has come from VLC then we cannot call it directly because when we go to set the error label text (which is considered a user interface component) we’ll crash the application.

WPF has a solution.  Welcome to the Dispatcher!  The Dispatcher does what it says on the tin, it dispatches things to the UI thread.  The first four lines of this method are just there to deal with all this threading weirdness.  The first line just says do the dispatcher “Am I running on the UI thread?” if the answer is yes it carries on.  If the answer is no it asks the dispatcher to call back this same method with the same parameters under the dispatcher, then returns.  The dispatcher will then honour that as soon as it can (we’re talking microseconds here on a well-functioning system – not years) and then the UI can successfully update.

Voila an introduction to threading and the SetStatusMessage code is now finished.  Now we just want to set the status events in VideoSttreamWindow.xaml.cs to call this new method.

In OnStreamStopped add this as the first line of the method:

_controlWindow.SetStatusMessage((int)((VlcControl)sender).Tag, “Stopped…”);

In OnStreamMediaChanged add this as the first line of the method:

 _controlWindow.SetStatusMessage((int)((VlcControl)sender).Tag, “Media changed…”);

In OnStreamOpening add this as the first line of the method:

_controlWindow.SetStatusMessage((int)((VlcControl)sender).Tag, “Opening…”);

In OnStreamError add this as the first line of the method:

_controlWindow.SetStatusMessage((int)((VlcControl)sender).Tag, “Error loading stream”);

In OnStreamPlaying add this as the first line of the method:

_controlWindow.SetStatusMessage((int)((VlcControl)sender).Tag, null);

All of these set a status message apart from OnStreamPlaying which clears a message.  Elsewhere in the application you’ll see us using arrays and counting to find out camera number from a UI control – the tag is an alternative.  Way back in VideoStreamWIndow’s construct we set it to an integer to allow us to uniquely identify it so this is just an example of another way to do the same thing.

If you now run the app again you should see the loading text appear before it vanishes on a successful start and if any of your streams are in an error state that should remove.

Which leads us to our last task – retrying on a failure from a stream.  We’re going to do this pretty simply and every 15 seconds run a check that says “has anything failed?  If so let’s reset it’s status to not failed and reload the stream on it”.  We’re going to do all this in VideoStreamWindow.

First let’s add three new member-level declarations.  The first, _isStreamFailed, is just a way for us to mark a failure on an individual stream.  We aren’t maintaining graceful state in this application since it’s all a bit dirty and hacky (ideally we’d have all this encapsulated in objects) but the easiest way for us to do this is just record somewhere whether or not a stream is currently in an error state – we can do that with a Boolean array.  The next object (_streamRetryTimer) is what we will use to automatically trigger retries every 15 seconds and the final object is something we’re going to use to manage our own thread state.  Since errors come in on one thread from VLC and our timer will be on yet another thread (different once more from the UI thread) we have to manage our own concurrency issues.  Locking is the easiest way to do this in C# (and many other languages) and it works by each thread having a key.  We’re just going to wrap code with a notation that says “any calling code must wait to exclusively have the key before it can run” and this means that no two threads can run the same code at the same time – an easy fix.  If we didn’t do this an our timer reset a status at the same time that VLC marked an error state we may get all kinds of unexpected behaviour happening.

        /// <summary>
/// Defines an array that specifies whether a corresponding control has failed to initialise.
/// </summary>
private readonly bool[] _isStreamFailed = new bool[4];

/// <summary>
/// Defines a timer that retries failed streams.
/// </summary>
private readonly Timer _streamRetryTimer;

/// <summary>
/// Defines a locker to ensure consistency between timer event.
/// </summary>
private readonly object _streamReinitialiseLocker = new object();

In our constructor right after we’ve created the control window we want to set our timer up.  We tell it to run every 15 seconds, to not keep running every 15 seconds verbatim (we’ll specifically tell it when we’re ready for it to start counting again) and tell it what code to run:

            // Create our retry timer
_streamRetryTimer = new Timer
{
Interval = TimeSpan.FromSeconds(15).TotalMilliseconds,
AutoReset = false
};
_streamRetryTimer.Elapsed += OnStreamRetryTimer;

Then we just need to create the OnStreamRetryTimer method:

        /// <summary>
/// Handle the regular retry timer kicking in.
/// </summary>
/// <param name=”sender”>
/// The event sender.
/// </param>
/// <param name=”e”>
/// The event arguments.
/// </param>
private void OnStreamRetryTimer(object sender, ElapsedEventArgs e)
{
lock (_streamReinitialiseLocker)
{
for (int i = 0; i < _vlcControls.Length; i++)
{
// If this stream hasn’t failed, skip it
if (!_isStreamFailed[i])
continue;

// Mark this stream as in a good state and reinitialise it
_isStreamFailed[i] = false;
_controlWindow.SetStatusMessage(i + 1, “Retrying…”);
_vlcControls[i].Stop();
_vlcControls[i].Play();
}
}

// Reenable the timer
_streamRetryTimer.Enabled = true;
}

You will notice the use of the lock keyword right at the start of this method with all the remaining code enclosed in curly braces.  This says that when calling code hits there it must ask for the key and only one bit of code can have it at a time and holds it until it has completely finished execution the code.  Within the code we then just look for any failed streams, reset their status and stop/start the video to get it to retry.

The last two things we need to do are modify OnStreamStopped to set an error state (notice it again must get a key so it is not running at the same time as other code is running OnStreamRetryTimer for example):

            // Mark this stream as needing a retry
lock (_streamReinitialiseLocker)
_isStreamFailed[(int)((VlcControl)sender).Tag – 1] = true;

Then we modify OnStreamError in the same way:

            // Mark this stream as needing a retry
lock (_streamReinitialiseLocker)
_isStreamFailed[(int)((VlcControl)sender).Tag – 1] = true;

With that we are done – run the code again and you’ve got error handling fully working along with retries every 15 seconds and thread safety implemented.  A thing of hideous beauty.

 Conclusion

This was certainly not the most gracious code I’ve ever written but under an hour to put together a CCTV monitoring application that can easily be expanded to any number of cameras isn’t bad.  If you’ve no idea about WPF or Windows desktop development then hopefully this shows you how easy it is to put together a really useful application.

I didn’t appreciate how long it was going to take to actually type up and create a video for this little project.  If you found this at all useful please let me know and I’ll try and make some more coding videos in the future.

You can download all the code here.

part-3-app

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s