How To Build Your Own MMS Enabled Motion Activated Security Camera With Linux, Python and S3

September 23, 2014
Written by

Horseman Caught On MMS

Holy biscuits was last week a barrel of monkeys.  All of us at Twilio have had a beastly boatload of fun seeing the stuff you’ve started building with Twilio MMS.  Many of you blazed through Kevin’s Getting Started with MMS tutorial over the weekend to get started on your hacks and, of course, seeing all your mustached faces with the example project we built last week remains a source of never-ending laughs.

Selection_005

This go round, I thought we would have a look at something a little more real world.  Working on the devangel crew, I get to travel around the world meeting all kinds of developers producing rad work.  While on the road, keeping an eye on the apartment back in Brooklyn definitely provides some peace of mind.  For my second hack, I thought I might provide that in real time using MMS.  These kinds of cameras are available on Amazon for $180, but I wanted to see if I could build my own for $0 with my Linux desktop, a webcam I already own and a little Python code.

What does it look like?  Let’s take a look at what happened when a clearly agitated mutant horseman broke into my apartment after deploying the Twilio-powered camera.

Yeesh.  Harrowing.

For this project, we’re going to build a security camera that sends a photo whenever motion is detected using the tools below.

What You Will Need

For this project, we’re going to need a few things before we get started.

How It Works

We’ll leverage a few different technologies to build our MMS enabled security camera.  We’ll use an excellent software motion detector for Linux (aptly called Motion) to watch for any movement in the frame.  When Motion detects some movement, it’ll launch our Python app which will look for an arbitrary number of images over a few seconds to craft into an alert.

Now, if our machine connected to the camera was available on the public Internet, the images would be easily available for Twilio to see.  However, as a quick Google search will show, having your security camera available on the Internet may not be a great idea.  So instead, our script will upload that photo to Amazon S3 so that Twilio can get it.  We’ll then use Twilio MMS to send ourselves a message with the S3 public link as the MediaUrl.

Getting Started

Let’s begin by getting our dependencies installed.  First, we’ll need to install some Python modules to interact with Twilio and with Amazon S3.  Note, if you are not using virtualenv, you will need administrator privileges to install.

pip install twilio boto

If you prefer, you can use a requirements.txt file to do this.

boto==2.32.1
twilio==3.6.7

Next, we’ll install Motion to handle our video.  Installation options for his open source motion detector are here for the Linux distribution of your choice – for this tutorial, we’ll use Ubuntu.

sudo apt-get install motion

 

Finally, we’ll create a new S3 bucket to house our camera.  First, we’ll log into the AWS Management Console and navigate to S3 administration panel.  Then we’ll click the Create Bucket button in the upper left hand corner.

 

Next, we’ll name our bucket.

 

Then we’ll open the properties of our newly created bucket.

 

Then add a new bucket policy in the Permissions dialog:

 

 

Click “Add a new policy”.

Next, we’ll write a new policy to make the photos we upload publicly accessible so Twilio can reach them for our MMS alerts.  Bucket policies are written in JSON and you can use this template as a guide:

{
  "Version":"2012-10-17",
  "Statement":[{
	"Sid":"AddPerm",
        "Effect":"Allow",
	  "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::<put-name-of-bucket-here>/*"
      ]
    }
  ]
}

Finally we’ll click “Save” to apply the policy making sure all the images we upload will be available on S3.

For one last optional step, we can create a new access key for our app.  First we’ll go to the Security Credentials page of our management console and click on “Access Keys.”

And then we can generate a new key.

Click Show Access Key and we can keep our values for our AWS credentials or download the root key csv file.

With our AWS setup completed, we’re ready to get Motion configured.

Configuring Motion

The first piece we need to set up is the configuration of Motion to use our camera.  This will involve manipulating a few of the configuration settings to execute the script that we are going to write together.  We can copy the example below into a file we’ll call local.conf.

Here’s an example with the important values we need:

# Image width - default is very low. Motion does require these values
# to be a multiple of 16, so default HD resolutions will not work.
width 1024       
height 768                                                              

# Target directory of where we want our images to go.                                
target_dir images                                                            

# Script we want to execute when a motion event begins.  Whole lot of
# configuration needs to be done here including:
# - Twilio Account Sid                      
# - Twilio Auth Token           
# - Twilio Phone Number                                                     
# - Receiving Number you wish to get the alerts
# - AWS Access Key ID                          
# - AWS Secret Key                     
# - Name of S3 bucket   
# We also pass the timestamp of the event to use as a key for the image.
#  Numbers must be in E.164 format.       
#  When the event starts, we'll send 3 images that are a second apart
on_event_start python motionalert.py --account_sid=ACxxxxxxx --auth_token=yyyyy --aws_access_key_id=wwwww --aws_secret_key=zzzz --s3_bucket=your-bucket-name-here --twilio_number=+15556667777 --receiving_number=+15558675309 --timestamp==%Y-%m-%d-%H-%M-%S-%q --body="Motion detected at %H:%M!" --motion_target_dir images --num_of_images 3
# When the event ends, we'll send the last frame.
on_event_end python motionalert.py --account_sid=ACxxxxxxx --auth_token=yyyyy --aws_access_key_id=wwwww --aws_secret_key=zzzz --s3_bucket=your-bucket-name-here --twilio_number=+15556667777 --receiving_number=+15558675309 --timestamp==%Y-%m-%d-%H-%M-%S-%q --body="Motion ended at %H:%M." --motion_target_dir images --num_of_images 1

With this configuration, each time period during which Motion detects movement in the frame, it will execute a Python app twice – once at the beginning of the movement and once at the end.  Now all that is left is to write the app itself.

Connecting Motion To Your Phone With Twilio

With our bucket and motion detector configured, we can now cut some code.  Our app will need to do three things:

  1. Get an image that Motion has recorded.
  2. Upload that image to a place where Twilio can retrieve it.
  3. Create a new Twilio message with the location of that image as the MediaUrl.

Let’s create that app together by opening a new file named motionalert.py.

First up, we will import everything we need for the app and create a class to serve as basis for the alerts we want to send from Motion through Twilio to our phones.  We’ll also create an Exception handler so we can gracefully return error information in the command line with a hint to use our command line interface’s --help flag.

 

import os
import glob
import argparse
import logging
from time import sleep

from twilio.rest import TwilioRestClient
from twilio.exceptions import TwilioException

from boto.s3.connection import S3Connection
from boto.s3.key import Key

class MotionAlert(object):
    def __init__(self, account_sid=None, auth_token=None,
                 aws_access_key_id=None, aws_secret_key=None, s3_bucket=None,
                 twilio_number=None, receiving_number=None,
                 motion_target_dir=None, timestamp=None, body=None,
                 num_of_images=None):
        """
        A class that sends Twilio MMS alerts based on input from Motion, a
        software movement detector for Linux.

        Attributes:
            account_sid: Your Twilio Account Sid.
            auth_token: Your Twilio Auth Token.
            aws_access_key_id: Your AWS Access Key Id.
            aws_secret_key: Your AWS Secret Key.
            s3_bucket: The name of the AWS S3 bucket to use for uploading
                       images.
            twilio_number: The Twilio number from which alerts will be sent.
            receiving_number: The number you wish to receive alerts.
            motion_target_dir: Path to where Motion is storing its images.
            timestamp: Current timestamp, generated by the Motion event.
            body: Text of the message you want to send.
            num_of_images: An integer of the number of images you wish
                           to send in your alert.
            twilio_client: A Twilio client object initialized with your
                           credentials.
            s3_connection: A S3 connection object initialized with your
                           credentials.
        """
        self.account_sid = account_sid
        self.auth_token = auth_token
        self.aws_access_key_id = aws_access_key_id
        self.aws_secret_key = aws_secret_key
        self.s3_bucket = s3_bucket
        self.twilio_number = twilio_number
        self.receiving_number = receiving_number
        self.motion_target_dir = motion_target_dir
        self.timestamp = timestamp
        self.body = body
        self.num_of_images = int(num_of_images)

        # Initialize our two API clients with our credentials.
        self.twilio_client = TwilioRestClient(self.account_sid,
                                              self.auth_token)
        try:
            self.s3_connection = S3Connection(self.aws_access_key_id,
                                              self.aws_secret_key)
        except Exception as e:
            raise MotionAlertError("Error connecting to S3: {0}".format(e))

class MotionAlertError(Exception):
    def __init__(self, message):
        """
        An Exception that handles output of errors to the user.

        Arguments:
            message: The message you want to display to the user for the
            exception.
        """
        logging.error("ERROR: {0}".format(message))
        logging.error("Try running with --help for more information.")

Next, we’ll define a function to get an image that Motion recorded.  As Motion operates on a buffer for the motion analysis, we can just grab the most recently created image in the target directory to be reasonably assured it will catch something meaningful in frame.

def get_latest_image_from_directory(self, motion_target_dir):
        """Retrieves the most recently created .jpg file from target directory.

        Arguments:
            motion_target_dir: The directory in which Motion stores its images.

        Returns:
            String with path to most recently created image.
        """
        try:
            # Use a glob generator to find the newest image
            return max(glob.iglob('{0}/*.jpg'.format(motion_target_dir)),
                       key=os.path.getctime)
        except ValueError as e:
            # Raise an error if we did not find any images
            raise MotionAlertError("Could not find any images in motion "
                                   "target directory: "
                                   "{0}".format(motion_target_dir))
        except OSError as e:
            # Raise an error if we cannot access the directory.
            raise MotionAlertError("Could not find the motion target dir: "
                                   "{0}".format(e))

Now that we have code to find images, we need to upload them to S3 so Twilio can find them for the messages we are about to create.  Now, here’s where we start using that S3 bucket we set up earlier in the tutorial.  Let’s define a function on our MotionAlert class to help us upload our images to that bucket.

def upload_image_to_s3(self, image_file_path, bucket_name):
        """Uploads images to Amazon's S3 service.

        Arguments:
            image_file_path: Path to image to upload on local machine.
            bucket_name: Name of the S3 bucket where image should be uploaded.
            key_name: Name of the key for the file on S3 (usually the
                      timestamp).
        """
        try:
            # Attempt to get the S3 bucket with our S3 connection.
            bucket = self.s3_connection.get_bucket(bucket_name)
        except Exception as e:
            # Error out if we're unable to locate the S3 bucket.
            raise MotionAlertError("Error connecting to S3 bucket: "
                                   "{0}".format(e))

        try:
            # Create a new key using image_file_path as the key
            key = Key(bucket)
            key.key = image_file_path 
            key.set_contents_from_filename(image_file_path)
            return key
        except Exception as e:
            # Error out if we're unable to upload the image.
            raise MotionAlertError("Error uploading file to S3: {0}".format(e))

Now that we have the image in a place where Twilio can see it, let’s add another function to our MotionAlert class to send an MMS to our phones.

def send_alert_to_phone_number(self, from_=None, to=None, body=None,
                                   media_url=None):
        """Sends a MMS using Twilio.

        Keyword Arguments:
            from_: The Twilio number from which the alert will be sent.
            to: The phone number that will receive the alert.
            body: Text for the alert.
            media_url: The fully qualified path to the image for the alert
                       available on the Internet.
        """
        try:
            # Send the alert using the Twilio Messages resource.
            self.twilio_client.messages.create(from_=from_, to=to,
                                               body=body, media_url=media_url)
        except TwilioException as e:
            # Error out if the request fails.
            raise MotionAlertError("Error sending MMS with Twilio: "
                                   "{0}".format(e))

Now, let’s tie all that together with a send function to string our three functions together.

def send(self):
        """Send an alert via Twilio MMS from Motion.
        Returns:
            message: a Twilio Message object from a successful request.
        """
        # Let the user know we're working on sending an alert to a phone number.
        logging.info("Sending alert to {0}...".format(self.receiving_number))

        # Get the specified series of images from the camera.
        image_paths = []
        for i in xrange(self.num_of_images):
            image_file_path = \
                self.get_latest_image_from_directory(self.motion_target_dir)
            # Wait 2 seconds to get next image
            image_paths.append(image_file_path)
            if i != self.num_of_images:
                sleep(1)

        # Try to upload that image to S3.
        s3_keys = []
        if image_paths:
            for image_path in reversed(image_paths):
                s3_key = self.upload_image_to_s3(image_path,
                                                 self.s3_bucket)
                s3_keys.append(s3_key)
        else:
            raise MotionAlertError("Could not retrieve an image to send.")

        # Try to send the image uploaded to S3 via Twilio MMS.
        if s3_keys:
            media_urls = []
            for s3_key in s3_keys:
                media_url = "https://s3.amazonaws.com/{0}" \
                            "/{1}".format(self.s3_bucket,
                                          s3_key.key)
                media_urls.append(media_url)
            message = self.send_alert_to_phone_number(from_=self.twilio_number,
                                                      to=self.receiving_number,
                                                      body=self.body,
                                                      media_url=media_urls)
            return message
        else:
            raise MotionAlertError("Could not send image to "
                                   "{0}.".format(self.receiving_number))

        # Confirm to user we are complete sending the alert.
        if message:
            logging.info("Alert sent to {0}.".format(self.receiving_number))
        else:
            logging.error("An unknown error occured sending to "
                          "{0}.".format(self.receiving_number))

Lastly, let’s create a command line interface for our class by using argparse.

# Create a command line interface for our class.
parser = argparse.ArgumentParser(description="Motion Alert - send MMS alerts "
                                             "from Motion events.",
                                 epilog="Powered by Twilio!")

parser.add_argument("-S", "--account_sid", default=None, required=True,
                    help="Use a specific Twilio Account Sid.")
parser.add_argument("-K", "--auth_token", default=None, required=True,
                    help="Use a specific Twilio Auth Token.")
parser.add_argument("-#", "--twilio_number", default=None, required=True,
                    help="Use a specific Twilio phone number "
                         "(e.g. +15556667777).")
parser.add_argument("-s", "--aws_access_key_id", default=None, required=True,
                    help="Use a specific Amazon Web Services Access Key Id.")
parser.add_argument("-k", "--aws_secret_key", default=None, required=True,
                    help="Use a specific Amazon Web Services Secret Key.")
parser.add_argument("-b", "--s3_bucket", default=None, required=True,
                    help="Use a specific Amazon Web Services S3 Bucket.")
parser.add_argument("-t", "--receiving_number", default=None, required=True,
                    help="Number to receive the alerts.")
parser.add_argument("-T", "--timestamp", default=None, required=True,
                    help="Timestamp of event passed from Motion.")
parser.add_argument("-B", "--body", default=None, required=True,
                    help="Body of message you wish to send.")
parser.add_argument("-d", "--motion_target_dir", default=None, required=True,
                    help="Directory where Motion is storing images from "
                         "motion capture.")
parser.add_argument("-i", "--num_of_images", default=None, required=True,
                    help="Number of image to send in an alert.")

# Configure our logging for the CLI output.
logging.basicConfig(level=logging.INFO, format="%(message)s")

# Present that CLI to the user when the Python file is executed.
if __name__ == "__main__":
    args = parser.parse_args()
    motion_alert = MotionAlert(**vars(args))
    motion_alert.send()

With the pieces of functionality in place, let’s put it all together to see what it looks like together in one copy/pasteable snippet.

import os
import glob
import argparse
import logging
from time import sleep

from twilio.rest import TwilioRestClient
from twilio.exceptions import TwilioException

from boto.s3.connection import S3Connection
from boto.s3.key import Key

class MotionAlert(object):
    def __init__(self, account_sid=None, auth_token=None,
                 aws_access_key_id=None, aws_secret_key=None, s3_bucket=None,
                 twilio_number=None, receiving_number=None,
                 motion_target_dir=None, timestamp=None, body=None,
                 num_of_images=None):
        """
        A class that sends Twilio MMS alerts based on input from Motion, a
        software movement detector for Linux.

        Attributes:
            account_sid: Your Twilio Account Sid.
            auth_token: Your Twilio Auth Token.
            aws_access_key_id: Your AWS Access Key Id.
            aws_secret_key: Your AWS Secret Key.
            s3_bucket: The name of the AWS S3 bucket to use for uploading
                       images.
            twilio_number: The Twilio number from which alerts will be sent.
            receiving_number: The number you wish to receive alerts.
            motion_target_dir: Path to where Motion is storing its images.
            timestamp: Current timestamp, generated by the Motion event.
            body: Text of the message you want to send.
            num_of_images: An integer of the number of images you wish 
                           to send in your alert.
            twilio_client: A Twilio client object initialized with your
                           credentials.
            s3_connection: A S3 connection object initialized with your
                           credentials.
        """
        self.account_sid = account_sid
        self.auth_token = auth_token
        self.aws_access_key_id = aws_access_key_id
        self.aws_secret_key = aws_secret_key
        self.s3_bucket = s3_bucket
        self.twilio_number = twilio_number
        self.receiving_number = receiving_number
        self.motion_target_dir = motion_target_dir
        self.timestamp = timestamp
        self.body = body
        self.num_of_images = int(num_of_images)

        # Initialize our two API clients with our credentials.
        self.twilio_client = TwilioRestClient(self.account_sid,
                                              self.auth_token)
        try:
            self.s3_connection = S3Connection(self.aws_access_key_id,
                                         self.aws_secret_key)
        except Exception as e:
            raise MotionAlertError("Error connecting to S3: {0}".format(e))

    def send(self):
        """Send an alert via Twilio MMS from Motion.
        Returns:
            message: a Twilio Message object from a successful request.
        """
        # Let the user know we're working on sending an alert to a phone number.
        logging.info("Sending alert to {0}...".format(self.receiving_number))

        # Get the specified series of images from the camera.
        image_paths = []
        for i in xrange(self.num_of_images):
            image_file_path = \
                self.get_latest_image_from_directory(self.motion_target_dir)
            # Wait 2 seconds to get next image
            image_paths.append(image_file_path)
            if i != self.num_of_images:
                sleep(1)

        # Try to upload that image to S3.
        s3_keys = []
        if image_paths:
            for image_path in reversed(image_paths):
                s3_key = self.upload_image_to_s3(image_path,
                                                 self.s3_bucket)
                s3_keys.append(s3_key)
        else:
            raise MotionAlertError("Could not retrieve an image to send.")

        # Try to send the image uploaded to S3 via Twilio MMS.
        if s3_keys:
            media_urls = []
            for s3_key in s3_keys:
                media_url = "https://s3.amazonaws.com/{0}" \
                            "/{1}".format(self.s3_bucket,
                                          s3_key.key)
                media_urls.append(media_url)
            message = self.send_alert_to_phone_number(from_=self.twilio_number,
                                                      to=self.receiving_number,
                                                      body=self.body,
                                                      media_url=media_urls)
            return message
        else:
            raise MotionAlertError("Could not send image to "
                                   "{0}.".format(self.receiving_number))

        # Confirm to user we are complete sending the alert.
        if message:
            logging.info("Alert sent to {0}.".format(self.receiving_number))
        else:
            logging.error("An unknown error occured sending to "
                          "{0}.".format(self.receiving_number))

    def get_latest_image_from_directory(self, motion_target_dir):
        """Retrieves the most recently created .jpg file from target directory.

        Arguments:
            motion_target_dir: The directory in which Motion stores its images.

        Returns:
            String with path to most recently created image.
        """
        try:
            # Use a glob generator to find the newest image
            return max(glob.iglob('{0}/*.jpg'.format(motion_target_dir)),
                       key=os.path.getctime)
        except ValueError as e:
            # Raise an error if we did not find any images
            raise MotionAlertError("Could not find any images in motion "
                                   "target directory: "
                                   "{0}".format(motion_target_dir))
        except OSError as e:
            # Raise an error if we cannot access the directory.
            raise MotionAlertError("Could not find the motion target dir: "
                                   "{0}".format(e))

    def upload_image_to_s3(self, image_file_path, bucket_name):
        """Uploads images to Amazon's S3 service.

        Arguments:
            image_file_path: Path to image to upload on local machine.
            bucket_name: Name of the S3 bucket where image should be uploaded.
            key_name: Name of the key for the file on S3 (usually the
                      timestamp).
        """
        try:
            # Attempt to get the S3 bucket with our S3 connection.
            bucket = self.s3_connection.get_bucket(bucket_name)
        except Exception as e:
            # Error out if we're unable to locate the S3 bucket.
            raise MotionAlertError("Error connecting to S3 bucket: "
                                   "{0}".format(e))

        try:
            # Create a new key using image_file_path as the key
            key = Key(bucket)
            key.key = image_file_path 
            key.set_contents_from_filename(image_file_path)
            return key
        except Exception as e:
            # Error out if we're unable to upload the image.
            raise MotionAlertError("Error uploading file to S3: {0}".format(e))

    def send_alert_to_phone_number(self, from_=None, to=None, body=None,
                                   media_url=None):
        """Sends a MMS using Twilio.

        Keyword Arguments:
            from_: The Twilio number from which the alert will be sent.
            to: The phone number that will receive the alert.
            body: Text for the alert.
            media_url: The fully qualified path to the image for the alert
                       available on the Internet.
        """
        try:
            # Send the alert using the Twilio Messages resource.
            self.twilio_client.messages.create(from_=from_, to=to,
                                               body=body, media_url=media_url)
        except TwilioException as e:
            # Error out if the request fails.
            raise MotionAlertError("Error sending MMS with Twilio: "
                                   "{0}".format(e))

class MotionAlertError(Exception):
    def __init__(self, message):
        """
        An Exception that handles output of errors to the user.

        Arguments:
            message: The message you want to display to the user for the
            exception.
        """
        logging.error("ERROR: {0}".format(message))
        logging.error("Try running with --help for more information.")

# Create a command line interface for our class.
parser = argparse.ArgumentParser(description="Motion Alert - send MMS alerts "
                                             "from Motion events.",
                                 epilog="Powered by Twilio!")

parser.add_argument("-S", "--account_sid", default=None, required=True,
                    help="Use a specific Twilio Account Sid.")
parser.add_argument("-K", "--auth_token", default=None, required=True,
                    help="Use a specific Twilio Auth Token.")
parser.add_argument("-#", "--twilio_number", default=None, required=True,
                    help="Use a specific Twilio phone number "
                         "(e.g. +15556667777).")
parser.add_argument("-s", "--aws_access_key_id", default=None, required=True,
                    help="Use a specific Amazon Web Services Access Key Id.")
parser.add_argument("-k", "--aws_secret_key", default=None, required=True,
                    help="Use a specific Amazon Web Services Secret Key.")
parser.add_argument("-b", "--s3_bucket", default=None, required=True,
                    help="Use a specific Amazon Web Services S3 Bucket.")
parser.add_argument("-t", "--receiving_number", default=None, required=True,
                    help="Number to receive the alerts.")
parser.add_argument("-T", "--timestamp", default=None, required=True,
                    help="Timestamp of event passed from Motion.")
parser.add_argument("-B", "--body", default=None, required=True,
                    help="Body of message you wish to send.")
parser.add_argument("-d", "--motion_target_dir", default=None, required=True,
                    help="Directory where Motion is storing images from "
                         "motion capture.")
parser.add_argument("-i", "--num_of_images", default=None, required=True,
                    help="Number of image to send in an alert.")

# Configure our logging for the CLI output.
logging.basicConfig(level=logging.INFO, format="%(message)s")

# Present that CLI to the user when the Python file is executed.
if __name__ == "__main__":
    args = parser.parse_args()
    motion_alert = MotionAlert(**vars(args))
    motion_alert.send()

 

Giving It A Try

With our S3 bucket and Motion configured as well as our Python app written, now we get to give it a go and see what happens.

Let’s do that by executing this command using the local.conf file we made towards the beginning of the tutorial.

motion -c local.conf

We should get some output indicating Motion is firing up:

(MotionAlert)rspectre@drgonzo:~/workspace/MotionAlert$ motion -c local.conf 
[0] Processing thread 0 - config file local.conf
[0] Motion 3.2.12 Started
[0] ffmpeg LIBAVCODEC_BUILD 3482112 LIBAVFORMAT_BUILD 3478528
[0] Thread 1 is from local.conf
[1] Thread 1 started
[1] cap.driver: "uvcvideo"
[1] cap.card: "UVC Camera (046d:0821)"
[1] cap.bus_info: "usb-0000:00:1a.7-4"
[1] cap.capabilities=0x84000001
[1] - VIDEO_CAPTURE
[1] - STREAMING
[1] Config palette index 8 (YU12) doesn't work.
[1] Supported palettes:
[1] 0: YUYV (YUV 4:2:2 (YUYV))
[1] 1: MJPG (MJPEG)
[1] Selected palette YUYV
[1] Test palette YUYV (1024x768)
[1] Adjusting resolution from 1024x768 to 960x720.
[1] Using palette YUYV (960x720) bytesperlines 1920 sizeimage 1382400 colorspace 00000008
[1] found control 0x00980900, "Brightness", range 0,255 
[1] 	"Brightness", default 128, current 139
[1] found control 0x00980901, "Contrast", range 0,255 
[1] 	"Contrast", default 32, current 32
[1] found control 0x00980902, "Saturation", range 0,255 
[1] 	"Saturation", default 32, current 32
[1] found control 0x00980913, "Gain", range 0,255 
[1] 	"Gain", default 64, current 64
[1] mmap information:
[1] frames=4
[1] 0 length=1382400
[1] 1 length=1382400
[1] 2 length=1382400
[1] 3 length=1382400
[1] Using V4L2
[1] Resizing pre_capture buffer to 1 items

And if some movement gets generated, we can check our phone for the result.

It seems as though an angry horse-man has intruded into my apartment.

Selection_005

But it appears the beast didn’t care for my interior decorating and departed without touching anything.

Selection_006

Wonder why that guy had such a long face.  BOOM.

Next Steps

Now that we’ve got a rudimentary motion camera sending MMS alerts, we can build on even more functionality in the future.  For example, we could:

  • Send MMS alerts to multiple phones.
  • Spin up and spin down the camera based on an SMS command.
  • Convert recorded video into a single animated GIF instead of still frame pictures.
  • Alert only when there is movement in certain sections of the frame to reduce false positives.
  • Install this rig with a low-power Linux device like a Raspberry Pi.

Playing around with hardware like cameras in creative ways can unlock even more potential for Twilio MMS.  If you’re hacking your cameras in creative ways, I definitely want to hear about it – you can find me on Twitter @dn0t or via email at rob@twilio.com.