Tagcats

The Smart(er) Cat Feeder – Tying it Together With Code

Now that I have a Raspberry Pi that can take pictures and turn electrical sockets on and off, as well as a trained image classifier that knows my cats, it was time to stitch everything together.

The Lola Detector

The following steps were taken to construct a small program that would scan an image on demand and identify Lola (or not):

  1. Install TensorFlow on the Raspberry Pi as a Python library.
  2. Refactor the example image label Python script to start TensorFlow, load the model, and wait.
  3. Use Flask to create a URL that would kick off the image analysis function, and return a JSON object with the Lola/Maddie label probabilities.
  4. Keep this program running.

The code for the Lola Detector can be found in a Gist.

Please note, this is the first Python script I’ve ever written. Feedback (maybe) appreciated.

The Lola Feeder

Rather than use Python for the whole app (like a sane person), I opted for Node to do the rest of the stuff. A big, convoluted Node script does the following:

  1. Takes a picture with raspistill and saves it as rpicam.jpg.
  2. Sends an http request to the Lola Detector service, and waits for a response.
  3. Checks the response for a high Lola probability.
  4. If Lola is NOT found, go back to step 1.
  5. If Lola is found, send a signal via the FM transmitter to power on the cat feeder.
  6. Send a tweet with the recently taken picture to a hidden Twitter account.
  7. Send a message to IFTTT which sends me a push notification.
  8. Wait 60 seconds and send another signal to power off the feeder.
  9. Wait 90 minutes and go back to step 1.

Additionally, I have a small lamp attached to another RF power socket, and have a “cron job” that turns the lamp on for a few hours in the evening, and a few hours in the early morning.

Here’s the code for the RF thing. I also have a white noise machine, another lamp, and some Christmas lights hooked up to other RF outlets. Unfortunately, the Xmas lights outlet stopped working :(

To run and monitor both scripts, I use PM2. It’s awesome. You should use it, too.

The Smart(er) Cat Feeder – Training TensorFlow

How does a computer know the difference between two cats? An image classifier created from a trained neural network, of course! Seriously though, I’ve always figured the realm of machine learning is out of my reach, but some of the tools are becoming more accessible to lay-people. This video was quite reassuring:

Learning About Machine Learning

With a confident attitude I went out searching for some ideas on how to create my own classifier. Luckily I didn’t have to look far. Google’s most basic introductory tutorial happened to fit my problem like a glove. Tensorflow for Poets takes you from nothing to a custom image classifier in less than an hour. It also leaves you with all the tools you need to keep using it with your own images… which is what I did.

Cat Training

The basics of creating the classifier went like this…

I needed pictures of my cats. Lots of them. Like hundreds. So I fired up the picam on the Raspberry Pi to record for a couple minutes while ‘nudging’ each cat in front of the camera in various positions and with different lighting. For this I used the raspivid program that comes with Raspbian. The following command records ten seconds of video:

<

pre>raspivid -o video.h264 -t 10000

For image extraction, I used ffmpeg and the following command to output an image for every second of video:

ffmpeg -i video.h264 -vf fps=1 out%d.png

The ffmpeg docs have lots of useful snippets. Oh, and ffmpeg can be installed as a command line utility via Homebrew on Mac (brew install ffmpeg).

 

The images were all in PNG format, and I needed .jpeg for Tensorflow. Luckily, OS X has a built in batch conversion tool right in the Preview app. See OSXDaily for details. After the conversion, the images were sorted into two folders – one for Lola, the other for Maddie.

For the training, I simply repeated step 4 in the Tensorflow for Poets tutorial, but used ‘lola’ and ‘maddie’ pictures instead of roses and daisies. I used 4000 iterations for extra thorough training. The basic command is below. Of course, instead of flower photos, I obviously had cat photos.

# In Docker
python tensorflow/examples/image_retraining/retrain.py \
--bottleneck_dir=/tf_files/bottlenecks \
--model_dir=/tf_files/inception \
--output_graph=/tf_files/retrained_graph.pb \
--output_labels=/tf_files/retrained_labels.txt \
--image_dir /tf_files/flower_photos

The main output of this training is the model file (retrained_graph.pb), and the accompanying labels file (retrained_labels.txt). These are the files used in the program that will examine new pictures of my cats and determine which one is which (if any cats are present at all).

Finally, I could take the label_image.py script from Step 5 of the tutorial, and use that to analyze new photos of my cats and tell me the confidence of Lola or Maddie being present in the image. All this runs from inside the original Docker image like so:

curl -L https://goo.gl/tx3dqg > $HOME/tf_files/label_image.py
docker run -it -v $HOME/tf_files:/tf_files  gcr.io/tensorflow/tensorflow:latest-devel 
python /tf_files/label_image.py /tf_files/new_lola_pic.jpg

Notice in the last line I used a picture of lola instead of a daisy. The output was something like:

lola (score = 0.99138)
maddie (score = 0.35342)

Even though Maddie was not in the picture, the confidence was pretty high. I’m guessing because Maddie is a nearly solid black cat, any dark mass in the photo may appear to the model as Maddie. Luckily, if Maddie actually is in the photo, the confidence is much higher. Here’s a score with Maddie, but no Lola:

lola (score = 0.01483)
maddie (score = 0.99683)

Good Job, computer!

The Smart(er) Cat Feeder – The Raspberry Pi

This is part 2 of a series describing modifications to an automatic cat feeder used to selectively feed cats. The overview can be found here:
The Smart(er) Cat Feeder Starring TensorFlow and Raspberry Pi

The Raspberry Pi 3

image curtesy raspberrypi.org

image curtesy raspberrypi.org

A Raspberry Pi 3 has just enough horsepower to run pre-trained TensorFlow apps, and a bunch of code for getting up and running on the Pi. There’s also a burgeoning IoT and hacking community, which makes is a great hub for controlling internet connected stuff.

A basic Pi setup needs a bit of hardware:

  • A SD Card. I recommend 32GB – 64GB to have plenty of room for TensorFlow models, pictures, and video.
  • 5V Power. There’s no power cord, so I got a 10ft. micro USB cable and 5V adapter.
  • A RPi case. Plenty of these out there. I got a cheap plastic one for a couple bucks on eBay.

And the basic software setup:

  • Raspbian & Pixel Desktop. I went with the new desktop GUI rather than headless to try out Pixel, and to do development and testing right on the Pi itself without another computer.
  • Add a hidden network to the Wifi list:
    sudo iwlist wlan0 scan essid [yourSSID]
  • Less cruft. Raspbian comes with too much stuff. Easy to remove, though.
  • Node.js 7
  • VNC. In case I did want to log in remotely, I had to fiddle with the video settings in
    /boot/config.txt

    to get a decent sized window. Using hdmi_group=2 and hdmi_mode=27 did the trick.

The Raspberry Pi Camera

Pi Camera

I read a few places that the version 1 (5MP) camera module is better with auto focus than the v2 (8MP) module, so I got a v1 from eBay for about $15. I’m not dissappointed. I ended up using 600×600 pictures in the project anyway, so extra megapixels would have been wasted, and all the features (focus, white balance, filters, rotation, etc…) work great.

There are some clones of the camera module from China for a few dollars less, but why risk it? I did, however, order a protective camera covering from China for $1. I’m still waiting on it as of this writing, though.

Remote Control Sockets and RF Transmitter

outlets

Here’s where the real fun begins. To turn the feeder (and accompanying lamp) on and off, I used remote controlled outlets, but spoofed the remote control frequencies with the Rapsberry Pi and a radio frequency (RF) transmitter attachment. In order to get the RF codes to activate the outlets, a RF receiver attachment is necessary as well. Luckily, the receiver and transmitter are sold in pairs, and are really cheap.

I followed a couple great guides for inspiration and to set up the proper tooling;

The guides above go a few extra steps and set up a web server with PHP, but I skipped that in favor of using Node (more on that in the next article). The basic steps for the setup are as follows:

  1. Install WiringPi and RFSniffer
  2. Plug in the reciever to the Raspberry Pi. Wiring Diagram
  3. Start RFSniffer.
  4. Push all the buttons on the remote control, and write down all the codes. It will look something like
    Received 21811
    Received pulse 192
    Received 21813
    Received pulse 192
    
  5. Plug in the transmitter to the Raspberry Pi.
  6. Test the codes with codesend. Be sure to substitute in your own code and pulse values read from RFSniffer.
    ./codesend 21811 -l 192 -p 0
    

When buying outlets, the popular choice is ETekCity. Amazon was sold out of these when I was looking, so I got another brand. One of the outlets broke a day later, so I’d recommend ETekCity.

Also, the first RF Transmitter I got was busted. I noticed that the circuitboard was slightly different than those pictured in the guides I used. Instead of ‘data’ printed on the transmitter, it said ‘ADAT’. I returned the set, and ordered a receiver/transmitter with ‘DATA’ and everything worked fine.

BAD! Avoid this one!

BAD! Avoid this one!

GOOD! Buy this kind!

GOOD! Buy this kind!

Good to Go!

With the Raspberry Pi set up with a working camera and the ability to turn electronic devices on and off, the stage was set. The rest was just a simple matter of programming…

The Smart(er) Machine-Learned IoT Cat Feeder for Lola the Cat – Overview

tl;dr – I modified my cat feeder to only feed one of my two cats using a Raspberry Pi webcam and image analysis with TensorFlow.

Meet Lola

Lola Resting

My cat for seven years who is healthy and fuzzy and an all-around good cat. However, she is a very picky eater. She takes small bites of food sporadically throughout the day from a food dish just for her on top of a tall dresser.

Meet Madison (Maddie for short)

Hi Maddie

Obese cat that was recently put on a strict diet of canned food twice a day (no carbs!). Due to her obesity, she’s been unable to leap onto the dresser to eat Lola’s food. Until now. Perhaps she’s regaining some athletic ability with the new diet, or her appetite has overwhelmed her fear, but a couple weeks ago she finally made the leap to the bounty of Lola’s food dish. Not good.

The Problem

Lola likes her dry food (a mix of Science Diet and Orijen), and only eats a tiny bit at a time whenever she wants. I’ve made several attempts at getting her on a schedule, but this results in incessant yowling at all hours along with a stubborn refusal to eat during the allotted mealtime. All was well when Lola was the only cat with the leaping ability to reach the skyward food dish. Now there’s no place for cat food to hide that Maddie won’t find.

I needed a way to keep Maddie on her diet, but allow Lola to peck at her food when she decides it’s mealtime. If tiny portions of food could be presented to Lola, she would eat all of it leaving none for Maddie. But how to get Lola (and not Maddie) tiny portions of food when I’m away at work or sleeping?

The Feeder

CSF Super Feeder

I bought an automated cat feeder a few years ago to dispense food to the cats whilst on vacation. It’s sturdy, but very primitive compared to today’s IoT designs. It works by plugging into a wall timer, and dispensing food for a short duration when AC current is supplied, and then resets when the power is cut. The amount of food dispensed is determined by twisting a small potentiometer with a tiny screwdriver (seriously!).

If the Feeder Only Had A Brain

I have a cursory fascination with machine learning, and sometimes read about what’s what in the ML world. TensorFlow has been a hot topic of late, and they have a few dead simple tutorials for lay people like myself. TensorFlow For Poets shows how to retrain an image labelling model to recognize objects of your choosing… like perhaps your own pets.

I thought maybe, just maybe, I could fiddle with the TensorFlow tutorial’s code and cobble together a system that allows the feeder to ‘recognize’ Lola and dispense a tiny meal for her, but refuse service to Maddie. Given the simple nature of the feeder (plug in = food, unplug = reset), some sort of smart-plug for the feeder would work just fine.

The Bright Idea

Grinch Smirk

I have a couple Raspberry Pis lying around (what self-respecting tech nerd doesn’t?), and decided to use one for the guts of the operation. The full solution looked something like this…

Set up the RPi + camera to watch for cats mulling about near the feeder. Periodically snap a photo, and analyze said photo with TensorFlow to find Lola. If Lola is identified in the image, flip the switch on a smart plug or relay connected to feeder with the RPi. Then shut off the power to the feeder and sleep for an hour or two before allowing another feeding. We don’t want it constantly spitting out food while Lola is eating.

The Working Contraption

feeder1

After a few days of tinkering, trial and error, ordering bits and bobs from the internet, and convincing my wife that I’m not crazy, I finally got a working version! As I suspected from the outset, it’s very much cobbled together and has a few quirks, but it does what I set out for it to do – feed one cat, but not the other.

(Quick reminder, I do still feed Maddie, but only twice a day with incredibly expensive cat food that I swear is better than what I eat some days)

The parts list for the working prototype are as follows…

The software involved includes…

  • Raspbian OS
  • TensorFlow
  • Python (and Flask)
  • Node.js
  • ffmpeg
  • IFTTT/Twitter/Twilio account (optional)

The “How it Works”

The RPi is set up near the feeder in a small box. The camera is pointing to the area in front of the feeder. After turning on the feeder, a python script bootstraps TensorFlow which loads the trained model into memory and gets ready to analyze an image. It waits until it is triggered by a GET request set up with Flask. So TensorFlow twiddles its thumbs, waiting patiently for some work to do.

Concurrently, A Node script fires up to do the ‘detection and feeder’ cycle. The script goes a little something like this…

A picture is taken with the camera. After the photo is saved to disk, the Node app calls the python service which analyzes the image and returns some JSON data. The data includes the probabilities of Lola and Maddie being in the image. If the “Lola probability” is greater than 99.3%, the Node script sends an RF signal to turn on the outlet with the connected feeder plug. The feeder gets power and dispenses a tiny bit of food. After a few seconds, the Node script sends an RF signal to turn off the feeder, then sends a message to IFTTT to alert me that Lola got food. It also copies the last image taken into another folder and timestamps the filename. The cycle then times out for 90 minutes, and then resumes taking pictures.

It all works surprisingly well, except at night when the camera sees only blackness. Luckily, the RF controlled outlets come in packs of five, so I hooked up a lamp to another one and have the Node script also turn the light on for a couple hours in the evening, and early in the morning (Lola really likes to eat at 5:30am, and she lets us know it).

The IFTTT notification and copied image are nice-to-haves that let me keep an eye on things until I get a better interface put together. It also lets me check out any false positives that might occur, and give me better ideas for retraining the TensorFlow model.

On the TODO List

  • Definitely a sturdier box and camera mount. Lola has already threatened to knock the whole thing off the desk (as cats do), and barely a tap will knock the camera out of place.
  • A PIR motion detector to trigger the camera instead of running the camera non-stop. Not sure how useful it would be, but I have a PIR sensor, so why not?
  • A database to record data. Collect feeding times and pictures to get a better idea of Lola’s eating habits.
  • A web interface. It would be nice to adjust the timeout between feedings, manually trigger a feeding, control the lamp, and generally see what’s going on via the web.
  • A Maddie scarecrow. Sometimes Lola does’t quite eat all the food, and Maddie jumps in behind her to finish the job. Might be worthwhile to play a loud noise or something if Maddie is detected (but not Lola) soon after a feeding occurs.
  • 100% Python. I used Node because I know Node, but there’s no reason this couldn’t be one Python script instead of two separate programs. Someday I’ll fulfill my dream of learning Python, and may refactor this project accordingly.

Stay Tuned

More posts will follow on the technical details of the implementation. TTFN!

Angular Cats! An AngularJS Adventure Anthology

Over the past couple weeks I’ve been teaching myself about the AngularJS javascript framework. It’s gone pretty well so far, and I think I have a pretty decent handle on the basics. I’ve been able to use the tools provided to put together a small application that uses the Petfinder.com API as a backend. It is a remake of an old Flex application I built in 2009 for the House of Mews pet shelter in Memphis, TN. The application allows the user to browse pictures and information on all the adoptable cats currently at the shelter. See the application in context at www.houseofmews.com (and please excuse the background music). You can also view the app by itself here.

In addition to remaking the old app, I took the remodeling a few steps further in order to try out a few more AngularJS features. The newer version of application stands on its own and has a few added features – most notably, deep linking.

While working on this, I created a series of blog posts to chronicle my progress (and show off). There are five posts in total, with the first three focused on the ‘remake’ app, and the last two posts detailing parts of the ‘remodeled’ app. If you are interested in learning AngularJS for yourself, definitely pay attention to the latter posts. The first three posts are choc full of newbie mistakes and bad practices. They are a great example of how not to build an Angular application.

All of the code is available on GitHub. I’ve already tweaked the code a bit, so it may not match 1:1 with the code pasted into the blog posts, but everything is commented and should be relatively easy to follow. Anyway, thanks for paying attention! Enjoy!

Parts 1 – 3: House of Mews redux (source: ngCatsHOM)

Parts 4 & 5: Angular Cats! (source: ngCats)

Angular Cats! Part 5 – Custom Directive

One more thing…

I took the liberty of making a custom directive out of the Twitter Bootstrap Carousel widget. You’ll see it in partials/catDeatil.html as . I’m not 100% certain I’m doing things the ‘correct angular way’, but it seems to work.

Demo | Code

The catCarousel.html partial:

<!-- The ngCarousel directive gets inserted here to fix a timing error between angularjs and bootstrap -->
<div id="myCarousel" class="carousel slide">
  <!-- Carousel items -->
  <div class="carousel-inner">
    <!-- Give the first item in the collection the 'active' class -->
    <div ng-class="{item:true, active:$first}" ng-repeat="photo in pics">
      <img ng-src="{{photo}}">
    </div>
  </div>
  <!-- Carousel Arrow Buttons -->
  <a class="carousel-control left" href="#myCarousel" data-slide="prev">&lsaquo;</a>
  <a class="carousel-control right" href="#myCarousel" data-slide="next">&rsaquo;</a>
</div>

The directive code:

The templateURL property specifies the HTML to insert into the directive tag. I’ve restricted the type of directive to an element name. It’s just easy that way. One caveat though, it that special considerations need to be made for this to be IE7/8 compatable.

The link function will take the value of the ‘pics’ attribute in the catcarousel HTML tag and assign it to the directive scope. Whenever the ‘cat’ object changes – either by clicking Next/Prev, or selecting a cat from the grid – the directive will update the ‘pics’ and call the carousel() function on the root element of the directive template.

angular.module('myDirectives', [])
.directive('catcarousel', function(){
  return {
    templateUrl: 'partials/catCarousel.html',
    restrict: 'EAC',
    link: function (scope,element,attr) {
      scope.$watch('cat', function () {
        scope.pics = scope.$eval(attr.pics);
        $(element).carousel({
          interval: 0
        });
      });
    }
  }
});

And that just about wraps it up. There’s plenty more code in the cat apps than outlined in these blog posts, so if anyone is interested, please take a look at the project on GitHub. I tried to add as many useful comments as I could. If any Angular experts run across this and are horrified by any of the code, please let me know! Thanks. Bye.

Angular Cats! Part 4 (Refactored) – Deep Linking, More Better Services

Demo | Code

During the creation of Angular Cats!, it didn’t take me long to realize that I was straying from best practices and the intentions of Angular’s creators. I dug myself a bit of technical debt in the name of expediency, and have now come to pay the piper. Over the past few days I’ve been refactoring major portions of the app, and wound up with almost an entirely new codebase. Large sections are still intact (e.g. the translator service), and functionality is basically the same, but a few key differences are present.

The largest change by far is the ability to deep link. Every cat has its own URL, and can be bookmarked.

To get the deep-link ball rolling, the following code was added to app.js:

.config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/cats/:page', {templateUrl: 'partials/catList.html', controller: CatListController});
    $routeProvider.when('/cat/:catId/:page', {templateUrl: 'partials/catDetail.html', controller: CatDetailController});
    $routeProvider.otherwise({redirectTo: '/cats/1'});
}])

The routes allow a user to go to a specific ‘page’ of cats in the cat list, or navigate to the details of any given cat – provided you know its ID. I also store the page number in the URL of the cat detail page so the user can navigate back to the page previously viewed when clicking ‘Back’ on the cat detail page.

In order to navigate around the application, the controllers now use the $location module, and will change the path according to the behavior of the user. The code below shows how the handler for clicking a cat to view details has changed.

Before (use events to change state):

// CatListController
$scope.getDetail = function() {
  eventBroadcast.broadcast('catClicked',{cat:this.cat});
};

//MainController
$scope.$on('catClicked',function() {
  switchView();
});
...
function switchView() {
  $scope.viewList = !$scope.viewList;
  $scope.viewDetail = !$scope.viewDetail;
}

After (use location to change state):

// CatListController
$scope.getDetail = function (catId) {
  $location.path('/cat/' + catId + "/" + $routeParams.page);
};

The CatsService module was completely overhauled to include a getCat() method in addition to getCats(), along with a few other useful methods. Oh, and this version actually has the ability to contact the live Petfinder.com API! Using a config constant, the app can switch between grabbing cat data from a .json file on disk, or reaching out to the live Petfinder API (via a PHP proxy on my own server). While building the app, I usually relied on a static json file for data, but it’s nice to be able to contact a live service to return fresh data.

The returned data is also stored in the CatService module. This data is basically the catCollection property that I previously stuck in $rootScope. Storing application data and state information on $rootScope just didn’t seem right, so I created some properties and accessor methods (get/set) on CatService, and used that instead. Worked out quite nicely.

Below is the new CatService module. Notice that it’s quite a bit larger that the previous module. This time around, the service had a few private variables and functions that the service uses for itself, and the CatService object is returned by the factory and injected into the controllers. The properties of the CatService object are exposed to the controllers and can be used wherever CatService is injected.

The first major feature of this updated service is the remoteSvc resource. Instead of just grabbing a .json file from disk, it will contact the proxy.php service and grab live data returned from the Petfinder API. The query object passed into the $resource function demonstrates how to override default $resource methods. This really isn’t necessary in my case, but I did it just for kicks.

The line var service = (ENDPOINT === "LOCAL") ? localSvc : remoteSvc; will look at the ‘ENDPOINT’ constant set in the application config (see app.js), and determine whether to use the local json file, or server resource to gather the cat data. It’s a quick and dirty approach, and in a larger system it would be better to abstract this into a proper config file, with two separate service modules, but it works pretty well for a small app with one or two services.

The CatService object has some getters and setters for accessing the selected cat, and the selected cat’s index value in the cat collection array. And, of course, there are the getCats and getCat methods, which gather the necessary data for the list and detail views (respectively).

angular.module('catService', ['ngResource','CatServiceHelper'])
.factory('CatsService', function($resource,$filter,$routeParams,CatServiceHelper,ENDPOINT){
    /**
     * Private vars
     */

    // Set the max number of cats to retrieve from the Petfinder API
    var numberOfCatsToGet = 200;

    // Set up the $resource injectable to use the Petfinder API. Some custom options are used in the $resource.query method
    var remoteSvc = $resource('/jsCats/ngCats/proxy.php', {count:numberOfCatsToGet,offset:0},
                { query: {
                      method:'GET',
                      params:{action:'shelter.getPets',count:numberOfCatsToGet, offset:0},
                      isArray:true
                     }
                });

    // Use the cats.json file as the $resource, if necessary
    var localSvc = $resource('cats.json',{});

    // Decide which $resource to use as the actual service
    var service = (ENDPOINT === "LOCAL") ? localSvc : remoteSvc;

    // Private properties & methods used to store data shared between controllers
    var _cats = [];
    var _activeCatIndex = -1;
    var _setActiveCat = function _setActiveCat(catId,callback) {
                          var cat = CatsService.findCatInCollection(catId);
                          if (cat) {
                            _activeCatIndex = _cats.indexOf(cat);
                            callback(cat);
                          }
                        };

    /**
     * CatService Object returned by the factory function.
     * Contains instance methods that can be used by the controllers.
     */

    var CatsService = {
     
      /**
       * ### Accessor Methods
       */
     
     
      // Controllers can read the cat collection, but cannot change it without calling a service method.
      getCatCollection : function getCatCollection() {
        return _cats;
      },

      getActiveCatIndex : function getActiveCatIndex() {
        return _activeCatIndex;
      },

      setActiveCatIndex : function setActiveCatIndex(idx) {
        if (angular.isNumber(idx) && idx <= _cats.length) {
          _activeCatIndex = idx;
        } else {
          _activeCatIndex = -1;
          throw("activeCatIndex must be a number, and cannot exceed the length of the cat collection.");
        }
      },

      /**
       * ### Service Methods
       */


      // This method retrieves all cat data from the service and stores it in _cats
      getCats : function getCats(callback){
        var self = this;
        service.query(function (data) {
          var rawCatData = [];
          if ( data && data.length > 0 && data[1].petfinder.pets.pet && data[1].petfinder.pets.pet.length > 0 ) {
            rawCatData = data[1].petfinder.pets.pet;
          }
          angular.forEach(rawCatData,function(item){
            _cats.push( CatServiceHelper.translateCat(item) );
          });
          if (angular.isFunction(callback)) callback(_cats);
        });
      },

      // This method retrieves data for one cat.  It will also call getCats if the user
      // navigates directly to a catDetail page. This is necessary for the Back and
      // Next/Prev buttons to work correctly.
      getCat : function(catId, callback) {
        var self = this;
        if( _cats.length ) {
          _setActiveCat(catId,callback);
        } else {
          this.getCats(function (results) {
              _setActiveCat(catId,callback);
          });
        }
      },

      // This uses an AngularJS filter function to extract a cat from the cat collection by the cat's id
      findCatInCollection : function findCatInCollection(catId) {
        var cat = $filter('filter')(_cats, function (item) {
                return item.id.toString() === catId;
              });
        return cat[0] || 0;
      },

      // Use the default $resource.query method
      query : service.query,
     
      // Use the default $resource.get method
      get : service.get
   
    };

    // The factory function returns CatsService, which is injected into controllers.
    return CatsService;
});

Angular Cats! Part 3 – Communicating with $broadcast

Continued from: Angular Cats! Part 2
Full Source Code: GitHub: jsCats/ngCatsHOM

In part 2 of this post, the source code in the event handlers within each controller was removed. That’s because there’s some interesting stuff going on that wouldn’t have made sense without first knowing about one last module within the application: eventBroadcast.

To communicate clicks and cats between controllers, I found a dead simple solution that cobbles together an event dispatcher-esque system. When ‘eventBroadcast’ is injected into a controller, it will have access to its member variables – in this case, an event name, and a message. The eventBroadcast object can also broadcast events from the $rootScope, which will then trickle down the scope hierarchy and can be listened for by each controllers $scope. So it goes something like this:

  1. User clicks a cat thumbnail
  2. $scope.getDetail is called.
  3. The clicked cat object is attached to eventBroadcast.message.
  4. An event is broadcast from $rootScope with the name ‘catClicked’
  5. CatDetailController’s $scope responds to ‘catClicked’
  6. eventBroadcast.message is checked for data.

Here is the ngClick handler in CatListController:

function CatListController($scope,$rootScope,$routeParams,CatsService,$location,eventBroadcast) {

  $scope.getDetail = function() {
    // When a thumbnail is clicked, a 'catClicked' event is broadcast,
    // and the data for the clicked cat is attached to eventBroadcaster
    eventBroadcast.broadcast('catClicked',{cat:this.cat});
  };

...
}

Here is the actual eventBroadcast module:

.factory('eventBroadcast', function($rootScope) {
    // eventBroadcaster is the object created by the factory method.
    var eventBroadcaster = {};

    // The message is a string or object to carry data with the event.
    eventBroadcaster.message = '';

    // The event name is a string used to define event types.
    eventBroadcaster.eventName = '';

    // This method is called from within a controller to define an event and attach data to the eventBroadcaster object.
    eventBroadcaster.broadcast = function(evName, msg) {
        this.message = msg;
        this.eventName = evName;
        this.broadcastItem();
    };

    // This method broadcasts an event with the specified name.
    eventBroadcaster.broadcastItem = function() {
        $rootScope.$broadcast(this.eventName);
    };

    return eventBroadcaster; })

And the handler for ‘catClicked’ in CatDetailController:

function CatDetailController($scope,$rootScope,eventBroadcast) {

  // When 'catClicked' is broadcast, we know that new cat data needs to be loaded from eventBroadcast
  $scope.$on('catClicked',function() {
    showCat(eventBroadcast.message.cat);
  });

  ...

  var showCat = function(cat) {
    $scope.cat = cat;
    $scope.catIndex = $rootScope.catCollection.indexOf(cat);
  }

}

And there you have it! A rudamentary technique for communicating between controllers. However, after reading this Google Groups thread, I learned that there are some flaws with this method. Thomas Burleson created a more robust pub/sub module that acts as a message queue, which is more scalable, flexible, and doesn’t rely so much on $rootScope. A demo of the Messaging Service module is on jsFiddle, and the code can be download from his site. I hope to try this out very soon.

Angular Cats! Part 2 – Views (partials) and Controllers

Continued from: Angular Cats! Part 1
Full Source Code: GitHub: jsCats/ngCatsHOM

Gathering a pile of data from an API (or in my case, a raw json file) makes for a pretty poor web application. In order to present the data in a fun and interactive interface, I created a couple HTML templates in the /partials folder, and inserted references to those templates into the MainController section of index.html.

In order to show or hide each partial view, I created two boolean values within the MainController’s scope. The values are named ‘viewList’ and ‘viewDetail’ and setting them to true or false will show or hide each one (respectively). The HTML templates themselves are inserted with the ngInclude directive. Using ngInclude probably isn’t the best option as it can lead to spaghetti code, have adverse affects on page performance, and probably some other stuff I can’t think of. Creating custom AngularJS directives are the best option for reusable components and/or widgets. I’ll eventually get to that, but to keep things simple for now, using ngInclude will work.

<div class="row" ng-controller="MainController">
    <div ng-show="viewList">
      <div ng-include src="'partials/catList.html'"></div>
    </div>
    <div ng-show="viewDetail">
      <div ng-include src="'partials/catDetail.html'"></div>
    </div>
</div>

The MainController has a simple function called switchView that flips the boolean values of viewList and viewDetail. I don’t have a situation where both should be true or false, but I could if I wanted. If you look at the MainController function, there are two event handlers that do nothing but call switchView().

function MainController($scope, $rootScope, eventBroadcast) {

  $scope.$on('catClicked',function() {
    switchView();
  });

  $scope.$on('backClicked',function() {
    switchView();
  });

  function switchView() {
    $scope.viewList = !$scope.viewList;
    $scope.viewDetail = !$scope.viewDetail;
  }

  // Starting values
  $scope.viewList = true;
  $scope.viewDetail = false;
}
// Explicitly inject stuff. This is optional unless you plan on minifying the code.
MainController.$inject = ['$scope','$rootScope','eventBroadcast'];

Upon loading the app, the user is greeted with a nice big grid full of lovely little cat pictures. This grid is actually an unordered list of thumbnails from the cat data. All of the viewable elements on the page are contained within the CatListController section – denoted by the ngController directive. There are a number of directives doing different things here: The ngClick directive handles mouseclicks and calls a click-handler function; The ngMouseover directive works similarly; ngRepeat will iterate through the contents of the ‘cats’ array and create a list-item for each cat; and ngSrc will display the thumbnail in conjunction with the img tag.

<div ng-controller='CatListController'>
  <div class="row span12 catGrid">
    <!-- Grid of cat thumbnails -->
    <ul class="thumbnails">
      <li ng-repeat="cat in cats" ng-click="getDetail(cat.id)" class="span2" ng-mouseover="showName(cat.name)">
        <div class="thumbnail">
          <!-- Remember to use 'ng-src' for images -->
          <img ng-src="{{cat.thumbnail}}">
        </div>
      </li>
    </ul>
  </div>

  <!-- Nav buttons and Cat Name -->
  <div class="row">
    <button class='btn btn-primary span1' ng-click="changePage('prev')">Prev</button>
    <div class="catName span10">{{name}}</div>
    <button class='btn btn-primary pull-right span1' ng-click="changePage('next')">Next</button>
  </div>
</div>

That’s all fine and dandy, but it’s not going to do anything unless we actually have a CatListController. The CatListController will need to define the properties and methods within its $scope, and handle any setup or logic for the listView page. $scope methods will define the behaviors, such as responding to clicks, and will store data used by directives (e.g. catName). Also within CatListController is the call to CatService.getCats(). This retrieves all the translated cat data and stores it on the $rootScope. Storing data on the $rootScope is sort of like creating a global variable – not the best practice, but useful in a pinch. And it’s only ‘global’ within the scope of the AngularJS application, not the global Javascript namespace.

The code below is truncated. For the full source (with comments), take a look on GitHub.

function CatListController($scope,$rootScope,$routeParams,CatsService,$location,eventBroadcast) {

  $scope.getDetail = function() {
    // When a thumbnail is clicked, show the detail
  };

  $scope.goToPage = function goToPage(page) {
    // If there are more cats than can fit in the grid, more pages are needed.  
    // This function go to a specific page.
  };

  $scope.changePage = function(pagingAction) {
   // This handles the Next/Prev buttons to cycle through pages.
  };

  // When a user hovers over a cat, it's name is displayed below the grid.
  $scope.showName = function(catName) {
    $scope.name = catName;
  }

  // If the catCollection is not yet defined, fetch the data, otherwise go to page one.
  if( !$rootScope.catCollection ) {
    // This uses the CatService defined in services.js to retrieve the list of cats.
    CatsService.getCats(function (data) {
      // The cat list returned from the getCats method is loaded onto $rootScope
      // so it can be easily shared between controllers.
      $rootScope.catCollection = data
      $scope.goToPage(1);
    });
  }
CatListController.$inject = ['$scope','$rootScope', '$routeParams', 'CatsService', '$location','eventBroadcast'];

The catDetail template is pretty much the same story. There are a bunch of $scope variables to store data to display within the template, and some click handler functions for buttons. A unique element that appears on this page is ng-carousel, which is a directive that activates Twitter Bootstrap’s carousel gallery component for a series of pictures. Normally, the Bootstrap carousel is activated by the ‘carousel slide’ class. However, after building the view, I noticed that sometimes the slider buttons would just stop working. It was very intermittent, and I think Angular refreshing the DOM on $scope changes would somehow break the carousel. To fix this, I created a directive that would refresh the carousel component every time the $scope.cat variable changed.

<div ng-controller='CatDetailController' class="catDetail">

<!-- Title -->
<div class="row">
    <div class="span1">
      <button class="back btn btn-primary" ng-click="goBack()">Back</button>
    </div>
    <div class="span11">
      <h2 class="pull-right">{{cat.name}}: {{cat.size}} {{cat.age}} {{cat.breed}}</h2>
    </div>
</div>     

<!-- Pics and Description -->
<div class="row catDetailPane">
    <div class="span5">
    <!-- The ngCarousel directive gets inserted here to fix a timing error between angularjs and bootstrap -->
        <div ng-carousel id="myCarousel" class="carousel slide">
          <!-- Carousel items -->
          <div class="carousel-inner">
        <!-- Give the first item in the collection the 'active' class -->
            <div ng-class="{item:true, active:$first}" ng-repeat="photo in cat.pics">
                <img ng-src="{{photo}}">
            </div>
          </div>
          <!-- Carousel Arrow Buttons -->
          <a class="carousel-control left" href="#myCarousel" data-slide="prev">&lsaquo;</a>
          <a class="carousel-control right" href="#myCarousel" data-slide="next">&rsaquo;</a>
        </div>
    </div>

  <!-- Cat Description -->
    <div class="span7">
        <div class="well" ng-bind-html-unsafe="cat.description">
        </div>
    </div>
</div>

<!-- Footer (Nav buttons and addt'l details -->
<div class="row">
  <button ng-click='newCat(catIndex - 1)' class="btn btn-primary span2">Prev Cat</button>
  <div class="span8 catOptions">{{cat.options}}</div>
  <button ng-click='newCat(catIndex + 1)' class="btn btn-primary pull-right span2">Next Cat</button>
</div>

</div>

Oh yeah, did you see that ng-bind-html-unsafe directive? That’s because the description text has a bunch of craptacular MS Word-to-HTML junk sprinkled throughout, and that particular directive handles it quite nicely.

The controller here is pretty straightforward – handle clicks, show a cat.

function CatDetailController($scope,$rootScope,eventBroadcast) {

  $scope.$on('catClicked',function() {
    // This is an event handler for a click on a cat thumbnail.
  });
 
  $scope.goBack = function () {
    // Back button clicked! Handle it!
  };

  // This method is called when 'Next Cat' or 'Prev Cat' is clicked.  
  // It will cycle through each cat in catCollection.
  $scope.newCat = function(idx) {
    // This method is called when 'Next Cat' or 'Prev Cat' is clicked.  
    // It will cycle through each cat in catCollection.
  }

  var showCat = function(cat) {
   // This method takes a cat object as a parameter and sets it as the active cat.
   // It also finds the index of the cat object in catCollection.
  }

}
CatDetailController.$inject = ['$scope','$rootScope','eventBroadcast'];

And here’s the directive to fix the Bootstrap carousel problem:

.directive('ngCarousel', function() {
  return function (scope, elm, attr) {
        scope.$watch('cat', function() {
          $(elm).carousel('pause');
        });
    }
});

Last but not least is a technique to give the controllers the ability to communicate with each other. But that will be a separate post, because this one is too long already.

Angular Cats! Part 1 – The Data Service

View the App | Get the Code

In order to broaden my horizons as a developer of web applications, I’ve been taking a good hard look at some of the popular Javascript MVC frameworks of the modern era.  I started by looking at backbone.js and was horribly confused, and was subsequently delighted when I investigated AngularJS.  The ‘two-way declarative bindings’ remind me a lot of Flex, and it felt very natural jumping into development with Angular.  In order to stretch my brain a bit, and really dig into the framework, I decided to start simple and convert an existing Flex project into an Angular app.  A perfect candidate for this is the House of Mews cat browser.  It’s a simple little widget that pulls pet data from the Petfinder.com API, displays a grid of thumbnails, and allows a user to browse details of adoptable pets.

Try it out!

To get started, I grabbed the angular-seed project from GitHub.  This project includes the latest version of Angular (1.0.2 as of this writing), as well as some tools to run a stand-alone server using node.js, and a unit-test runner.  All I really paid attention to was the contents of the ‘app’ folder. It has a nice directory structure, some starter files, and the library itself.

After clearing out all the cruft, I started with the app.js file in /app/js/. Using a single statement, I defined the main module for my app.

angular.module('catApp', [])

Then it’s a simple as adding the ngApp directive to the root of the index.html document. Oh yeah, I also added Twitter Bootstrap to the project as well. The grid system is just super, and I wanted to use the carousel they provide.

<!doctype html>
<html lang="en" ng-app="catApp">
<head>
  <meta charset="utf-8">
  <title>AngularJS Cats App</title>
  <link rel="stylesheet" href="css/bootstrap.min.css"/>
  <link rel="stylesheet" href="css/app.css"/>
</head>
<body>
  stuff goes here...
</body>
</html>

Before going any further with HTML and partial templates and such, I like to make sure I can grab the data first. I created a service module to gather all the cat data. Because the cat data is only updated once per week, I simply download a data file using the Petfinder API, and load the data directly from my site. This prevents hundereds (thousands?) of unnecessary calls to the Petfinder API, since the data rarely changes. I actually have a PHP script that runs every week to update the local data file.

The angular service module is really a factory that does two things: Uses Angular’s $resource object to load data from cats.json, and create a CatService object to be used throughout the application. For now, this object only has one method: getCats(). Rather than just pass raw data from cats.json around the application, getCats() will first transform all the raw data into a nice collection of sensible Cat objects. To see what I mean, observe the raw data for a single cat below:

"pet": {
    "options": {
        "option": [
            {
                "$t": "altered"
            },
            {
                "$t": "noKids"
            }
        ]
    },
    "breeds": {
        "breed": [
            {
                "$t": "Tortoiseshell"
            },
            {
                "$t": "Domestic Long Hair"
            }
        ]
    },
    "shelterPetId": {},
    "status": {
        "$t": "A"
    },
    "name": {
        "$t": "HARLOW"
    },
    "contact": {
        "email": {},
        "zip": {
            "$t": "38104"
        },
        "city": {},
        "fax": {},
        "address1": {
            "$t": "933 S. Cooper"
        },
        "phone": {},
        "state": {
            "$t": "TN"
        },
        "address2": {
            "$t": "Website:  www.houseofmews.com"
        }
    },
    "description": {
        "$t": "<div>I&#39;m the House of Mews superstar in disguise, Harlow!! As you can see I am a beautiful long-haired, tortoiseshell girl and I know it! I&#39;m actually a little shy, but I know I&#39;m gorgeous, so I just can&#39;t help strutting my stuff everywhere I go. I guess hearing everyone tell me how beautiful I am has even made me a little more outgoing. They don&#39;t even mind my crossed eyes (not that it bothers me). In fact, most people tell me they&#39;re just part of my charm! I like to play with toys on sticks. I need a little patience to get over my shyness, but I&#39;ll be well worth it in the end. Please don&#39;t overlook me just because I am a little shy. Come on, haven&#39;t you ever dreamed of living with a supermodel?? Here&#39;s your chance! Approx. DOB is 5-20-00. </div>"
    },
    "sex": {
        "$t": "F"
    },
    "age": {
        "$t": "Senior"
    },
    "size": {
        "$t": "S"
    },
    "mix": {
        "$t": "yes"
    },
    "shelterId": {
        "$t": "TN198"
    },
    "lastUpdate": {
        "$t": "2011-01-12T20:07:07Z"
    },
    "media": {
        "photos": {
            "photo": [
                {
                    "@size": "x",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-1-x.jpg",
                    "@id": "1"
                },
                {
                    "@size": "fpm",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-1-fpm.jpg",
                    "@id": "1"
                },
                {
                    "@size": "pn",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-1-pn.jpg",
                    "@id": "1"
                },
                {
                    "@size": "pnt",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-1-pnt.jpg",
                    "@id": "1"
                },
                {
                    "@size": "t",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-1-t.jpg",
                    "@id": "1"
                },
                {
                    "@size": "x",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-2-x.jpg",
                    "@id": "2"
                },
                {
                    "@size": "fpm",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-2-fpm.jpg",
                    "@id": "2"
                },
                {
                    "@size": "pn",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-2-pn.jpg",
                    "@id": "2"
                },
                {
                    "@size": "pnt",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-2-pnt.jpg",
                    "@id": "2"
                },
                {
                    "@size": "t",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-2-t.jpg",
                    "@id": "2"
                },
                {
                    "@size": "x",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-3-x.jpg",
                    "@id": "3"
                },
                {
                    "@size": "fpm",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-3-fpm.jpg",
                    "@id": "3"
                },
                {
                    "@size": "pn",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-3-pn.jpg",
                    "@id": "3"
                },
                {
                    "@size": "pnt",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-3-pnt.jpg",
                    "@id": "3"
                },
                {
                    "@size": "t",
                    "$t": "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-3-t.jpg",
                    "@id": "3"
                }
            ]
        }
    },
    "id": {
        "$t": "4169367"
    },
    "animal": {
        "$t": "Cat"
    }
}

Now look at this:

cat: {
age: "Senior",
breed: "Tortoiseshell Domestic Long Hair ",
description: "<div>I&#39;m the House of Mews superstar in disguise, Harlow!! As you can see I am a beautiful long-haired, tortoiseshell girl and I know it! I&#39;m actually a little shy, but I know I&#39;m gorgeous, so I just can&#39;t help strutting my stuff everywhere I go. I guess hearing everyone tell me how beautiful I am has even made me a little more outgoing. They don&#39;t even mind my crossed eyes (not that it bothers me). In fact, most people tell me they&#39;re just part of my charm! I like to play with toys on sticks. I need a little patience to get over my shyness, but I&#39;ll be well worth it in the end. Please don&#39;t overlook me just because I am a little shy. Come on, haven&#39;t you ever dreamed of living with a supermodel?? Here&#39;s your chance! Approx. DOB is 5-20-00. </div>",
id: 4169367,
name: "HARLOW",
options: "Spayed/Neutered, No Kids",
pics: [
    0: "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-1-x.jpg",
    1: "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-2-x.jpg",
    2: "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-3-x.jpg" ]
sex: "Female",
size: "Small",
thumbnail: "http://photos.petfinder.com/photos/US/TN/TN198/4169367/TN198.4169367-1-fpm.jpg" }

Much better, no? It’s a lot easier to work with these tidier objects in the controllers and templates, plus, some validation and manipulation makes it easier to handle cases where data is missing.

Here’s the barebones service module. Too see the full source code (including the ServiceHelper module which does all the data translation), take a look at the project on GitHub.

angular.module('catService', ['ngResource','CatServiceHelper'])
.factory('CatsService', function($resource,$filter,CatServiceHelper){

    // Define the remote service using Angular's $resource module.
    var service = $resource('cats.json',{});

    // CatsService is the object created by the CatsService factory.
    // This is a bit long-winded and could easily be shortened,
    // but I plan on adding some additional methods to CatService in the future.
    var CatsService = {
      // The getCats function calls $resource.query() to retrieve the remote data.
      // The data is then lightly validated and scrubbed using the translateCat()
      // function in the CatServiceHelper module
      getCats : function getCats(callback){
        service.query(function (data) {
          var cats = [];
          var catCollection = [];
          if ( data && data.length > 1 && data[2].pets.pet && data[2].pets.pet.length > 0 ) {
            cats = data[2].pets.pet;
          }
          angular.forEach(cats,function(item){
            catCollection.push( CatServiceHelper.translateCat(item) );
          });
          callback(catCollection);
        });
      }
    };
    return CatsService;
});

So that’s a good start. The application won’t really do anything, but it’s primed and ready to fetch lots of cat data!

© 2017 Eric Terpstra

Theme by Anders NorénUp ↑