DIY video monitoring system, Node.js, Raspberry Pi

DIY video monitoring system – Part III Camera script refactoring

In this follow-up to the Part II of our DIY video monitoring system project, we’re gonna refactor the code we wrote previously and prepare a reusable camera module.

This is part III of the “DIY video monitoring system” series. If you haven’t read the previous entries, I highly recommend doing so.

Issues

Our previous script, apart from being hardly maintainable, had a couple of flaws – no way of controlling the image parameters and no support for concurrency.

Concurrency

Let’s start with the second one. If you try to load the static.jpg image a couple of times at once, you may run into troubles. We can quickly simulate that by running 5 curl commands at once:

As you can see, some requests were not responded (empty reply from the server) and some of the connection got reset by peer. Moreover, our script has crashed!

Well, that’s not good, will try to fix it in a minute, but first, let’s have another look at our script:

The issue seems to come from line 27, where we use the capture() method to cache the camera’s current frame. That command, apparently, doesn’t handle concurrency well, so we have to make sure it’s called just once at time.

Image control

At the moment, the script only captures images using our camera’s default settings. That’s OK for some very basic scenarios, but in our monitoring system we want to be prepared for all situations. Thankfully, the v4l2camera module comes with a set of methods allowing us to control the available camera settings.

In case of Raspberry Pi Camera this includes:

  • Brightness
  • Contrast
  • Saturation
  • Red Balance
  • Blue Balance
  • Horizontal Flip
  • Vertical Flip
  • Power Line Frequency
  • Sharpness
  • Color Effects
  • Rotate

We can also adjust the image format a bit, changing the width and height.

There’s a number of ways how we could pass these settings to the script. We could either use a config file (e.g. a JSON file), or pass them during runtime using a CLI. We’ll try both approaches in our refactored example.

Reusability

The script in its current form isn’t reusable at all. To fix that, we’ll extract the camera-specific code to a separate module and import it in the main, HTTP server script.

Another module will be responsible for the script configuration. It will either use a config file or parse the CLI arguments and return the configuration object to the main script.

Implementation

We’ll start with cleaning up our source code directory. First, let’s run npm init to create an empty package.json file. This will be useful to save all the Node.js dependencies that we have/will have.

Then, create a src/ directory, move our capture.js script and prepare a couple of empty JavaScript files there. You should end up with a following structure:

Once that’s done, we can start refactoring the code.

Camera module

We’ll start with a reusable camera module. It’s gonna expose a Class with a bunch of methods we’ll be able extend in the future.

Apart from the inline comments, a couple of notable things here – in line 4 we’re creating a hash map that will simplify the translation of internal image settings names to the control names provided by v4l2.

In the capture method (line 34) we’re using a Promise for convenience – that’s to mitigate the concurrency issues we had before. During subsequent capture() calls, instead of hitting the cam.capture() method multiple times, we return the existing Promise that would resolve for all of them at once.

The configure method (line 50) takes the options object and uses them to adjust camera settings. Here, we go through all the keys of our options hash map and apply the provided values or fall back to defaults if undefined.

One quirk to note here is the behaviour of the width/height settings. As there is no default values for them, once we change any of them, they will stay like that until we restart the device or change the values again.

Finally, the destroy methods simply disconnects the camera passing an empty callback (that’s to avoid the module throwing an error as it always expects a callback function).

Config module

For this refactoring, we’ll try implementing two different approaches to script configuration – config file based and CLI based.

File-based configuration

Let’s start with the code:

In this module, we start with defining configuration defaults – in our case this would be the server’s listening address and port. Then we attempt to load the config JSON file called camera-config.json, which should be located in the project’s root directory. As this is an optional file, if we can’t load it, we fall back to an empty config object which then gets merged with the default configuration using Object.assign() function. All that combined gets exported from the module.

CLI-based configuration

Implementing a command line arguments parser is not that complicated, but requires a lot of effort, especially if we want to be able to generate some usage information screens for them. That’s why we’ll be using an existing argument parser – subcommander – which you may have read about in one of my previous posts.

To install it, simply run npm install subcommander --save in the project’s root directory.

Here’s the code for the CLI config module:

The cool part is you don’t have to remember all those argument names as subcommander is able to generate usage information on-the-fly. You can see it by running the script with the -h flag:

The API also provides an easy way of passing default values for configurable attributes, you can see an example of those in the server options – address and port.

Main module

Finally, let’s move to the main module – capture.js. Here, we’ll remove all the hardcoded parts, load our modules and use them to re-implement the functionality from the previous post, but this time with no concurrency issues and fully configurable.

Notice line’s 31 and 41, where we append Promise’s then and catch callbacks to handle it’s resolution. The then callback gets executed once the promise gets fulfilled, and the catch one gets executed whenever the promise is rejected. We use the se callbacks to either respond with an actual image or a server error.

Finally, to try out different ways of script configuration, comment out either config-cli-parser or config-file-parser module in lines 5:6.

Testing

Now that we have all this stuff refactored, let’s try running our code. Go back to the project’s root directory and run:

Note that 0.0.0.0 means the server is listening on all available interfaces by default, so use the address you SSH into your Pi instead and try opening that in your browser.

You should see the camera image snapshot just like before. Now, let’s try out some configuration options.

Assuming you have the CLI config parser enabled, let’s do:

This should give us an image that is resized to 640px x 480px and rotated by 180 degrees.

Finally, let’s see if we can make a couple of concurrent calls at once without crashing our server:

Sweet, no issues this time and the server’s still running!

Summary

This wraps up our camera script refactoring. In this post we’ve learned how to:

  • split Node.js code into modules
  • implement an ES2015 Class
  • create and use Promises
  • adjust image settings using the v4l2camera module
  • read JSON files
  • create and configure a command line argument parser

You can download this project from my Github repository – rpi-capture-static.

In the next post we’ll modify this new code to implement MJPEG-based video streaming.

Thanks for reading!

by Greg

No Comments »

Leave a Reply

Your email address will not be published. Required fields are marked *