Category: Development

How to Paginate Query Results in React Native with Realm JS (MongoDB)

How to Paginate Query Results in React Native with Realm JS (MongoDB)

While building my app ThinkBack I decided to use Realm as my underlying data-store. ThinkBack’s home screen contains a list of notes which need pagination to avoid loading everything unnecessarily.

From the Realm docs:

Pagination & Limits

Some queries only need to access a subset of all objects that match the query. Realm’s lazy-loaded collections only fetch objects when you actually access them, so you do not need any special mechanism to limit query results.

For example, if you only want to find 10 matching objects at a time (such as in a paged product catalog) you can just access ten elements of the results collection. To advance to the next page, access the next ten elements of the results collection starting at the index immediately following the last element of the previous page.

Lazy-loading removes the need to build paging logic into the query itself and instead defers pagination or limiting results to when you interact with the result set. In other words, paging is handled at the time you want to access/materialize the objects into memory rather than the time you issue a query to Realm.

This post aims to demonstrate this concept through a simplified implementation.

End to End Paging Implementation

The following implementation will load a series of “notes” from Realm, apply pagination logic and present the results in a FlatList to the user. I tend to prefer a layer of abstraction between the UI and the underlying data store, this keeps a clean separation between the front-end and any business logic that may exist.

To support this we’ll create the following:

  • RealmStore.ts, which creates an instance of the Realm datastore.
  • Note.ts, which defines the schema of the Note table in Realm.
  • NoteService.ts, which will contain logic for querying Realm and paginating the results.
  • App.tsx, which will store paging “state” (i.e. what page we’re on, page size), interact with the NoteService class to load paged notes and present them in a FlatList.

The structure of these files in the project is as follows:

root/
├─ realm/
│ ├─ Note.ts
│ ├─ NoteService.ts
│ ├─ RealmStore.ts
├─ App.tsx

To follow along locally, initialize a new React native app and install Realm JS.

Note.ts

This class defines the schema of a Realm object and will ultimately map to a table in Realm.

import uuid from 'react-native-uuid';
import Realm from 'realm';

export interface NoteProps {
  id?: string;
  content: string;
  createdOn?: Date;
  modifiedOn?: Date;
}

export default class Note {
  public id: string;
  public content: string;
  public createdOn?: Date;
  public modifiedOn?: Date;

  constructor({
    id = uuid.v4().toString(),
    content,
    createdOn,
    modifiedOn,
  }: NoteProps) {
    const now = new Date();

    this.id = id;
    this.content = content;
    this.createdOn = createdOn ?? now;
    this.modifiedOn = modifiedOn ?? now;
  }

  static schema: Realm.ObjectSchema = {
    name: 'Note',
    properties: {
      id: 'string',
      content: 'string',
      createdOn: 'date',
      modifiedOn: 'date',
    },
    primaryKey: 'id',
  };
}

RealmStore.ts

This class creates a new instance of the Realm which we’ll use in NoteService.ts to interact with the datastore.

import Note from './Note';
import {Realm} from '@realm/react';

export default new Realm({
  path: 'default',
  schema: [Note.schema],
  deleteRealmIfMigrationNeeded: __DEV__,
});

NoteService.ts

This class is responsible for loading data from the datastore and returning a paged result based on the paging properties.

Line 20 obtains a reference to the Notes Realm object but doesn’t actually issue any queries yet thanks to lazy loading which defers queries to only when you need them (accessing an object). This is effectively saying “give me access to all Notes but don’t load them into memory yet until I figure out which ones I want”.

Line 25 calls getPagedCollection which uses the provided paging options (e.g. page size, current page) to access a subset of the Realm Notes. Calling slice on the Realm Notes array is a form of access as we’re now directly interacting with individual notes. This issues a query against the Realm store and loads the subset of notes being accessed. For example, if 100 notes existed in array then array.slice(0, 10) would cause the first 10 notes to be loaded.

import {RealmObject} from 'realm/dist/public-types/Object';
import Note from './Note';
import realm from './RealmStore';

export interface PagingOptions {
  page: number;
  pageSize: number;
  hasNext: boolean;
}

export interface PagedCollection {
  result: Array;
  pagingOptions: PagingOptions;
}

export class NoteService {
  getPagedNotes(pagingOptions: PagingOptions): PagedCollection {
    // Lazy load the notes. These aren't materialized yet so no paging needs to occur
    // at the "realm" level.
    let notes = realm.objects('Note');
    notes = notes.sorted([['createdOn', false]]);

    // Access a subset of the notes based on the paging options (e.g. page, page size). This will
    // cause actual loading/materialization of the matching object(s) from the datastore.
    const pagedResult = this.getPagedCollection(notes, pagingOptions);
    console.log(
      `Paged Notes Count: ${
        pagedResult.result.length
      }, Paging options: ${JSON.stringify(pagedResult.pagingOptions)}`,
    );

    return pagedResult;
  }

  getPagedCollection(
    array: Realm.Results<RealmObject<T, never> & T>,
    pagingOptions: PagingOptions,
  ): PagedCollection {
    const {pageSize} = pagingOptions;
    let currentPage = pagingOptions.page;
    const totalPages = Math.ceil(array.length / pageSize);

    if (currentPage < 1) {
      currentPage = 1;
    }

    if (currentPage > totalPages) {
      currentPage = totalPages;
    }

    const start = (currentPage - 1) * pageSize;
    let end = start + pageSize;
    if (end > array.length) {
      end = array.length;
    }

    return {
      result: array.slice(start, end),
      pagingOptions: {
        ...pagingOptions,
        hasNext: end !== array.length,
      },
    };
  }
}

// singleton instance for convenience
export const noteService = new NoteService();

App.tsx

This component is responsible for maintaining paging state as the user interacts with the list. As the user scrolls and approaches the end of the list the next page is loaded.

Line 51 leverages the FlatList onEndReached callback which fires as the list breaches the threshold set by onEndReachedThreshold. The handler for this callback increments the current page state by one.

Line 35 of the useEffect hook declares pagingOptions.page as a dependency which means the function will be invoked any time the “page” value is modified.

Line 27 passes the current paging options state to the noteService.getPagedNotes function which returns a paged collection of notes from Realm. The loaded notes are then merged with any existing notes state which causes the component to rerender and the latest set of notes to be shown.

import React, {useState} from 'react';
import {FlatList, SafeAreaView, StatusBar, Text} from 'react-native';
import Note from './realm/Note';
import {noteService} from './realm/NoteService';

const itemStyle = {
  padding: 20,
  margin: 5,
  borderWidth: 1,
  borderColor: 'red',
};

function App(): React.JSX.Element {
  const [pagingOptions, setPagingOptions] = useState({
    page: 1,
    pageSize: 10,
    hasNext: true,
  });
  const [notes, setNotes] = useState<Note[]>([]);

  React.useEffect(() => {
    if (!pagingOptions.hasNext) {
      // no more pages remain so don't attempt to load anything.
      return;
    }

    const pagedResult = noteService.getPagedNotes(pagingOptions);
    // merge the new notes with the existing note state.
    const newNotes = [...notes, ...pagedResult.result];
    console.log(`Total notes loaded: ${newNotes.length}`);
    setPagingOptions(pagedResult.pagingOptions);
    setNotes(newNotes);

    return () => {};
  }, [pagingOptions.page]);

  return (
    <SafeAreaView>
      <StatusBar />
      <FlatList
        contentInsetAdjustmentBehavior="automatic"
        data={notes}
        extraData={notes.length}
        renderItem={item => {
          return (
            <Text key={item.item.id} style={itemStyle}>
              {item.item.content}
            </Text>
          );
        }}
        onEndReached={() => {
          if (pagingOptions.hasNext) {
            // breached the "end reached" threshold in the list and there's more notes to load,
            // increment the current page by one.
            setPagingOptions({
              ...pagingOptions,
              page: pagingOptions.page + 1,
            });
          }
        }}
        onEndReachedThreshold={0.5}
      />
    </SafeAreaView>
  );
}

export default App;

Additional Content

For convenience sake I used the below createNotes helper function to generate some notes for testing purposes in the Realm database. You can add a log statement to see the absolute path where your Realm database lives:

console.log(`Realm path: ${realm.path}`);
The following function was added to the NoteService.
createNotes() {
  for (let i = 0; i < 30; i++) {
    const createdOn = new Date();
    const note: Note = {
      id: `${i}`,
      content: `[${
        i + 1
      }] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Habitant morbi tristique senectus et netus et malesuada fames. Etiam non quam lacus suspendisse faucibus interdum. Faucibus et molestie ac feugiat sed. Feugiat in ante metus dictum at. Morbi blandit cursus risus at ultrices mi tempus.`,
      createdOn,
      modifiedOn: createdOn,
    };
    realm.write(() => {
      realm.create<Note>('Note', note);
    });
  }
}

I added a button above the FlatList to invoke the helper function and generate the notes:

<Button
  title="Create sample notes"
  onPress={() => noteService.createNotes()}
/>

Xamarin iOS Detect if Ringer Is On Silent or Muted

Xamarin iOS Detect if Ringer Is On Silent or Muted

There’s currently no native support for detecting whether or not the device ringer is set to silent. This poses an issue when you want to leverage the state of the ringer to trigger some action within the app, such as notifying the user.

Fortunately there’s a fairly straightforward way to detect this on your own using a system sound.

Detecting Ringer State by Playing a System Sound

When the ringer is on silent it prevents any system sounds you attempt to play from actually playing. Conversely when the ringer is not on silent the sound will play as expected. We can leverage this behavior to confidently determine whether or not the device is on silent based on how quickly our system sound finishes playing.

Add Muted Sound File to Resources

The first thing we need to do is obtain a copy of a sound file that doesn’t actually make any noise. Here’s one for convenience. After downloading the file add it to the Resources folder in your iOS project.

I added mine under Resources > Audio:

Audio Resources Folder

Write Code to Determine Playback Time of System Sound

Now that we have the file in place we need to create a System Sound and determine how fast the sound played.

The SystemSound object has an observer method called AddSystemSoundCompletion, which as the name suggests lets us know when the sound finishes playing. The idea here is simple: find the time delta between when the sound started playing and when it finished. If the difference in time is negligible than we can safely assume the ringer is on silent. However, if the time delta is closer to how long the sound file would typically take to play, it’s likely the ringer is not on silent.

The following method can be added to your view controller or other class that needs to detect the state of the ringer and take action:

public void IsMuted(Action mutedCheckComplete) {
    // create an instance of the SystemSound object, pointing to your "mute" sound resource
    var soundFilePath = NSBundle.MainBundle.PathForResource("Audio/mute", "caf");
    var mutedSound = new SystemSound(new NSUrl(soundFilePath, false));

    // capture the start time of the sound
    DateTime startTime = DateTime.Now;

    mutedSound.AddSystemSoundCompletion(() => {
        // find the delta between start and end times to determine if the sound played or was cut short.
        var endTime = DateTime.Now;
        var timeDelta = (endTime - startTime).Milliseconds;
        var muted = timeDelta < 100;

        // perform the callback to the invoker of this method, letting them know we have an answer.
        mutedCheckComplete(muted);
    });

    mutedSound.PlaySystemSound();
}

In my tests the time delta was very close to zero when the ringer was on silent, and closer to ~300ms when not on silent. In the above snippet I consider anything less than 100ms to be considered muted.

Now that the method is created we can invoke it whenever we need to check the status of the ringer. In my case, I use this in the ViewWillAppear method of my view controller. To avoid any unexpected lag between loading the UI and performing this check, I added the method call to the dispatch queue as follows:

DispatchQueue.MainQueue.DispatchAfter(new DispatchTime(DispatchTime.Now, TimeSpan.FromMilliseconds(100)), () => {
    IsMuted((muted) => {
        Debug.WriteLine($"Is Muted: {muted}");
    });
});

There we have it, a fairly trivial approach at determining the state of the ringer.

Xamarin iOS UITableView GetCell Method Called for Invisible Cells

Xamarin iOS UITableView GetCell Method Called for Invisible Cells

In my Xamarin iOS application I’m leveraging the UITableView control to page through a collection of items in a performant manner. The UITableView uses the GetCell method of the UITableViewDataSource delegate to create or reuse instances of cells as they become visible on screen.

Great, that sounds efficient, and it is! However, in my application the GetCell method was being called once for every cell when the UITableView was initialized, including the invisible ones.

What gives?

This post spawned from a stackoverflow question I posted, then immediately found the answer to.

Replicating the Behavior By Example

The UITableView in question was added to a ViewController in the StoryBoard. Within the ViewController’s ViewDidLoad method, I have the following setup:

public override void ViewDidLoad() {
    base.ViewDidLoad();

    var model = new UIColor[] {
        UIColor.Green,
        UIColor.Red,
        UIColor.Magenta,
        UIColor.Cyan,
        UIColor.Blue,
        UIColor.Purple
    };

    SampleTableView.RowHeight = SampleTableView.Frame.Height;
    SampleTableView.Setup(model);
}

In short, a model consisting of a collection of colors is created and passed through to a Setup method on my custom UITableView, SampleTableView.
In addition to the setup, I already know the height of each row ahead of time, so the RowHeight property of the UITableView is set. As you can see, each row will take the full height of the UITableView. Swiping up or down will then reveal the next page.

Here’s what the SampleTableView and related dependencies look like:

public class SampleCell : UITableViewCell {
    public void Setup(UIColor color) {
        BackgroundColor = color;
    }
}

public class SampleTableViewDataSourceDelegate : UITableViewDataSource {
    public SampleTableViewDataSourceDelegate(UIColor[] model) {
        this.model = model;
    }

    private readonly UIColor[] model;

    public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) {
        Debug.WriteLine($"GetCell called. Row {indexPath.Row}");
        var reuseIdentifier = "SampleCell";

        var cell = tableView.DequeueReusableCell(reuseIdentifier) as SampleCell;

        cell = (cell ?? (cell = new SampleCell()));

        cell.Setup(model[indexPath.Row]);

        return cell;
    }

    public override nint NumberOfSections(UITableView tableView) {
        return 1;
    }

    public override nint RowsInSection(UITableView tableView, nint section) {
        return model.Length;
    }
}

public partial class SampleTableView : UITableView {
    public SampleTableView(IntPtr handle) : base(handle) {
        PagingEnabled = true;
    }

    private UIColor[] model;

    public void Setup(UIColor[] model) {
        this.model = model;

        DataSource = new SampleTableViewDataSourceDelegate(model);
    }
}

SampleCell
A custom cell that simply sets the background color to the one provided by the model.

SampleTableViewDataSourceDelegate
A derived class of UITableViewDataSource, which handles implementing the GetCell method. This class is responsible creating and reusing cells as they become visible.

SampleTableView
A derived class of UITableView, which handles initial setup of the UITableView via the Setup method, invoked by the containing ViewController.

We now have a fairly slimmed down example of a UITableView implementation. In the GetCell delegate method there’s a line of code that outputs a message to the log when it’s called. After running the application with Visual Studio in a simulator, the Visual Studio Output console shows the following:

[0:] GetCell called. Row 0
[0:] GetCell called. Row 1
[0:] GetCell called. Row 2
[0:] GetCell called. Row 3
[0:] GetCell called. Row 4
[0:] GetCell called. Row 5

Therein lies the issue; the GetCell method is called once for each cell in our model. Why? Didn’t I specify the RowHeight to be the height of the UITableView? If that’s true, and we know that GetCell should only be called when cells become visible, then why is it being called Model.Length number of times?

UITableView EstimatedRowHeight

Our RowHeight property is set to take up the entire UITableView. When the UITableView data source is initialized, however, the UITableView attempts to estimate the required row height for us on-the-fly via the EstimatedRowHeight property. The EstimatedRowHeight property was not set, effectively relying on the table view to calculate it for us. The solution to this particular problem then is to set the EstimateRowHeight property in addition to the RowHeight property.

Back in the ViewDidLoad method of the ViewController, adding the following line of code above the RowHeight setter did the trick:

 SampleTableView.EstimatedRowHeight = SampleTableView.Frame.Height; 

The Output log now shows what we’d expect, a single call to GetCell on initialize:

[0:] GetCell called. Row 0

Download File with jQuery and Web API 2.0 IHttpActionResult

Download File with jQuery and Web API 2.0 IHttpActionResult

I came across a need to download a file through an AJAX request to a Web API 2.0 endpoint. A quick search revealed a fairly straightforward way to tackle this problem, which is to implement a custom IHttpActionResult (available in Web API 2.0) that sets up the HttpResponse in a way that can be digested by the client.

Create a Custom Web API 2.0 IHttpActionResult

The first step was to implement the IHttpActionResult as outlined below.

    public class ApiFileResult : IHttpActionResult {
        public ApiFileResult(string filePath, string contentType, string fileName = null) {
            if (string.IsNullOrWhiteSpace(filePath)) {
                throw new ArgumentNullException(nameof(filePath));
            }

            this.contentType = contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(filePath));
            var fileStream = File.OpenRead(filePath);
            var fileBytes = new byte[fileStream.Length];
            fileStream.Read(fileBytes, 0, fileBytes.Length);
            stream = new MemoryStream(fileBytes);

            this.fileName = fileName ?? Path.GetFileName(filePath);
        }

        public ApiFileResult(MemoryStream stream, string contentType, string fileName) {
            if (stream == null) {
                throw new ArgumentNullException(nameof(stream));
            }
            if (contentType == null) {
                throw new ArgumentNullException(nameof(contentType));
            }
            
            this.stream = stream;
            this.contentType = contentType;
            this.fileName = fileName;
        }

        private readonly string contentType;
        private readonly string fileName;
        private readonly MemoryStream stream;

        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) {
            var bytes = stream.ToArray();
            var memoryStream = new MemoryStream(bytes);

            var response = new HttpResponseMessage(HttpStatusCode.OK) {
                Content = new StreamContent(memoryStream)
            };

            response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
            response.Content.Headers.ContentLength = memoryStream.Length;
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
                FileName = fileName
            };

            return Task.FromResult(response);
        }
    }

Once that’s in place we can use the new IHttpActionResult as our API’s return.

Create an ApiController Action That Returns Custom IHttpActionResult

Next up is creating an instance of this new custom Web API 2.0 IHttpActionResult class and using it as our API’s return type. Below is a fairly lightweight ApiController to demonstrate just that. The injected IBillingExportService class is used to generate a memory stream containing the contents of the file. We then new-up an instance of our custom ApiFileResult object, passing the file stream, file type and file name through.

    [RoutePrefix("api/v1/Billing")]
    public class BillingApiController : ApiController {
        public BillingApiController(IBillingExportService service) {
            this.service = service;
        }

        private readonly IBillingExportService service;

        [HttpPost, Route("Export", Name = "BillingExport")]
        public async Task<IHttpActionResult> GetBillingExport(int id) {
            var exportStream = await service.GetExportFile(id);
            return new ApiFileResult(exportStream, 
                                     "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 
                                     $"Billing Export {DateTime.Today:yyyy-MM-dd}{ExcelExport.WorkbookBuilder.ExcelFileExtension}");
        }
    }

Now that we have the API endpoint in place we can make a jQuery AJAX request to it, as follows:

    $.ajax({
            type: 'POST',
            cache: false,
            url: exportScheduledBillingUrl,
            data: serializeScheduledBillingGrid(),
            xhrFields: {
                    // make sure the response knows we're expecting a binary type in return.
                    // this is important, without it the excel file is marked corrupted.
                    responseType: 'arraybuffer'
                }
        })
    .done(function (data, status, xmlHeaderRequest) {
        var downloadLink = document.createElement('a');
        var blob = new Blob([data],
            {
                type: xmlHeaderRequest.getResponseHeader('Content-Type')
            });
        var url = window.URL || window.webkitURL;
        var downloadUrl = url.createObjectURL(blob);
        var fileName = '';

        // get the file name from the content disposition
        var disposition = xmlHttpRequest.getResponseHeader('Content-Disposition');
        if (disposition && disposition.indexOf('attachment') !== -1) {
            var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
            var matches = filenameRegex.exec(disposition);
            if (matches != null && matches[1]) {
                fileName = matches[1].replace(/['"]/g, '');
            }
        }

        // Blob download logic taken from: https://stackoverflow.com/questions/16086162/handle-file-download-from-ajax-post
        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007" and "Access Denied" error.
            window.navigator.msSaveBlob(blob, fileName);
        } else {
            if (fileName) {
                if (typeof downloadLink.download === 'undefined') {
                    window.location = downloadUrl;
                } else {
                    downloadLink.href = downloadUrl;
                    downloadLink.download = fileName;
                    document.body.appendChild(downloadLink);
                    downloadLink.click();
                }
            } else {
                window.location = downloadUrl;
            }

            setTimeout(function () {
                    url.revokeObjectURL(downloadUrl);
                },
                100);
        }
    });

When the above Ajax call is triggered (i.e. a user clicks a button on your page) it’ll return the new custom IHttpActionResult you created. We then use the information provided by that result to build a Blob object. This Blob object enables us to generate an object URL which can be used to download the contents of our file.

Xamarin iOS MT1007 Failed to Launch the Application

Xamarin iOS MT1007 Failed to Launch the Application

After going through the free provisioning profile article I’ve been able to debug my Xamarin iOS app on a physical device. I wanted to verify what happens when the application gets installed for the first time, so I deleted the app from my phone. After running the Visual Studio debugger, the application was reinstalled successfully on my device, but failed to launch the debugger with the following message in the output log:

The app could not be launched on ‘iPhone’. Error: error MT1007: Failed to launch the application ‘/path/to/my/iOS.app’ on the device ‘iPhone’: Failed to launch the application ‘My.App.Namespace’ on the device ‘iPhone’: ESecurity. You can still launch the application manually by tapping on it.

Resolve the Xamarin iOS MT1007 Failed to Launch Error

The message sent to the Visual Studio output log was fairly vague. A quick Google search brought me to several different posts suggesting things like restarting my phone, updating Xcode and so on. Restarting wasn’t the solution and Xcode was already up-to-date. 

Enabling the Device Log prior to debugging gave me a bit more insight: after the error was raised, the following was output to the device log:

The request was denied by service delegate (SBMainWorkspace) for reason: Security (“Unable to launch my.application.namespace because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user”).

The line “its profile has not been explicitly trusted by the user” caught my interest, and made me believe something relate to my provisioning profile was undone. I plugged my phone into the Mac and opened my provisioning profile Xcode project. I then selected my device as the target and ran the application, which greeted me with an error message stating that my device does not trust the developer profile associated with the app.

You Just Need a Little Trust

While the Xamarin iOS MT1007 error can mean many things, in this case it boils down to trust. The solution to this trust issue is the following:

  1. On your iPhone, navigate to Settings -> General -> Device Management.
  2. Within Device Management, you should see your developer provisioning profile, click it.
  3. Trust the developer profile.

After trusting the profile you’ll see a callout stating:

Apps from developer “Your Profile” are trusted on this iPhone and will be trusted until all apps from the developer are deleted.”

Those last few words sum it all up. The problem was introduced the moment I uninstalled the app from my device, and rightfully so according to the relationship my device has with the developer profile.

Automatic Cat Laser Pointer Toy Using the Raspberry Pi

Automatic Cat Laser Pointer Toy Using the Raspberry Pi

Living in the city has shown me just how hard it can be to find an affordable apartment with a reasonable amount of space. Having two cats in such a small place doesn’t really give them much room to roam or pseudo hunt. Inspired by this cat tower, I decided to build an automatic cat laser pointer toy using my Raspberry Pi. The cats had one condition though, that it be a shark with a frikin’ laser beam attached to its heads (sound warning!).

If you’ve come across my automatic cat feeder post you might be thinking I’m automating myself out of the equation. Fear not, for we are still their primary source of entertainment, as they are ours.

Goal

The goal may be obvious at this point, but I like being explicit: to build an automatic cat laser pointer toy that is configurable, and can be triggered by a button.

Bonus Goal: configure and trigger this remotely. We’ll focus on the simpler solution first, and optionally over-engineer the project later on.

Prerequisites

You should have a configured Raspberry Pi ready to go. I used my Raspberry Pi 3B (40 GPIO pins), a 32gb MicroSD card (you could certainly go smaller) and the Raspbian distro (highly recommended). This guide was pretty straightforward and got me up and running in no time. 

The Parts

As with my automatic cat feeder post, I decided to break this down into four distinct parts:

  • Here in part one we’re introducing the project and what you’ll need to get it done.
  • In part two we’ll assemble all the pieces of the cat laser pointer toy.
  • Part three will focus on writing the python code, which will be responsible for moving the servo motors and turning on the laser beam at the push of a button.
  • Part four will cover the bonus goal of configuring and triggering remotely [optional].

Things You’ll Need for the Automatic Cat Laser Pointer Toy

  • Pan/tilt camera mount with 2x servos for controlling the movement of the laser.
  • 1x Laser diode to take over the world with.
  • 6x male-to-female, 6x any-to-female jumper wires to connect everything to the GPIO pins. What I mean by “any” here is that “it doesn’t matter“, because we’ll be cutting off that end of the wire and soldering it anyway.
  • 1x NPN Transistor to logically control the Pi’s 3v3 power rail through a GPIO pin, and toggle the laser on/ off. You only need one of these. eBay may have better options in terms of quantity, or hey, even stop at a physical store like Radioshack!
  • SHARKS! because it really is hilarious.
  • 5v 2.5A power supply to power the Pi, servos and lasers. The servos and laser draw a good amount of current, so we need to make sure the power supply is capable of supporting it.
  • Raspberry Pi Case to house the Pi and wiring. This isn’t strictly necessary, but it made for a convenient way to mount the laser and control the wiring.
  • 1x Push Button to manually engage the laser. I got mine in a starter kit, but you can buy them separately if you’d like.
  • Velcro to mount the shark (or laser if you opted out of adding a shark, which saddens me) to the pan/tilt mount. I used velcro here so I can conveniently point the laser in the opposite direction, which provides an entirely different available range for the pan/tilt mount.
  • Electrical tape to wrap the soldered wires.

The Tools

  • [Optional] Power drill to make holes in the Raspberry Pi case to fasten the pan/tilt mount to it. If I did this project over, I would have used velcro as my fastener as it’s much simpler and less intrusive. Your call.
  • Hot glue gun to glue the laser to the shark. Again, any other adhesive would suffice if you don’t already have a hot glue gun.
  • Soldering iron to solder some of the wiring together.
  • Wire stripper for when we get to stripping and soldering a few of the wires.
  • Tiny philips screwdriver to assemble the pan/tilt mount.

Conclusion and Next Steps

At this point we covered the basics of what it is we’re trying to build here in part one, and what you’ll need to accomplish it. Head over to part two where we’ll be focusing on assembling all the pieces.

Ahead of the game? Nice! Part three has the Python code, and part four sets up a way to remotely control the whole thing.

Assemble the Raspberry Pi Cat Laser Pointer Toy

Assemble the Raspberry Pi Cat Laser Pointer Toy

In part one we discussed what exactly we’re trying to build, and what’s needed to begin assembling our cat laser pointer toy. Here in part two we’re going to take all the pieces we’ve gathered and assemble them into one beautiful creation.

The goal of this part?

  • Assemble the pan/tilt mount and servo motors.
  • Solder the laser diode to the NPN transistor, and attach it to the shark(!!!).
  • Solder the physical push button.
  • Connect all the pieces together

If words like solder and transistor are a bit intimidating, don’t let them be! It’s really not that difficult, promise.

Later on in part three we’ll cover how to write code to fire the laser in a pseudo-random fashion at the touch of a button. As a bonus, part four will take this one more over-engineered step and demonstrate how to trigger the laser remotely as well as on a schedule, should you be so inclined.

Assemble the Pan Tilt Mount and Servo Motors

The pan/tilt mount provides a convenient way for us to use our servo motors as a means to navigate the cartesian plane. We will be using one of the servos as the x-axis, and the other as the y-axis.

Our servo motors can rotate from 0 to 180 degrees, no more no less. In order to provide a full range of motion for our laser contraption after we assemble all the pieces, we should first ensure the starting point of each servo is where we want it.

The servo traveling along the x axis will be responsible for moving the laser left and right. In order to accomplish this we should have a center position once mounted of 90 degrees, which allows it to turn left to 0 degrees, and right to 180 degrees.

The servo traveling along the y axis will handle moving the laser up and down. For this, we’ll want the starting position to be 0 degrees, which will aim the laser straight out once mounted, while 90 degrees will aim straight down and 180 degrees will, well, aim the laser backwards. We won’t have much use for anything over 90 degrees on the y axis, but it’s good to note.

Calibrating the X and Y Servos

The first step to calibrating our servos is to designate one as the x axis and the other as the y axis. I wrapped a piece of electrical tape around the x axis wires for quick identification, which helped avoid confusing the two later on.

Now that we’ve identified the servos, it’s time to write some code to set them in the correct starting positions before we actually attach them to the pan/tilt mount. If you’re using the same servos as me, they have powerground and control wires.

Connect three of your male-to-female jumper cables to your servo pins. Now, using the GPIO diagram below as reference, connect the female connectors to the Pi’s GPIO headers as follows:

GPIO Pinout Diagram

X Servo Y Servo
  • power connects to header pin 2 (5v power)
  • ground connects to header pin 6 (Ground)
  • control connects to header pin 7 (GPIO4)
  • power connects to header pin 4 (5v power)
  • ground connects to header pin 9 (Ground)
  • control connects to header pin 11 (GPIO17)

The servos are now connected and ready to be calibrated. Nice work!

Writing Code to Calibrate the Servos

Before getting started, let’s update our Pi to the latest packages and install the GPIO library. Run the below commands in the terminal:

Note: if this is the first time you’re running these commands it may take a bit to complete, so hang tight.

sudo apt-get update 
sudo apt-get upgrade 
sudo apt-get install rpi.gpio

We’re all set, it’s time to create the calibration script. Create a new file called Calibrate.py with the following code in it:

#!/usr/bin/env python

import RPi.GPIO as GPIO
import time

GPIO_X_SERVO = 4
GPIO_Y_SERVO = 17

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    
    GPIO.setup(GPIO_X_SERVO, GPIO.OUT)
    GPIO.setup(GPIO_Y_SERVO, GPIO.OUT)

    print "calibrating..."
    try:
        x_servo = GPIO.PWM(GPIO_X_SERVO, 50)
        y_servo = GPIO.PWM(GPIO_Y_SERVO, 50)
        
        x_servo.start(7.5)  # X Servo: 7.5 is 90 degrees
        y_servo.start(2.5)  # Y Servo: 2.5 is 0 degrees

        time.sleep(1) # give the servos a chance to move
        
    finally:
        GPIO.cleanup()

The above script is is pretty minimal: each time it runs the x servo will be set to its center at 90 degrees, while the y servo gets set to its starting point of 0 degrees.

You need only run this once.

In the terminal, navigate to the directory that has this newly created python script in it. Once there, type chmod +x Calibrate.py and press enter to make the script an executable, then type ./Calibrate.py. If there were no issues in your script, you should have heard the motors spin to the desired positions.

Excellent! We now have two servos pointing in the correct starting positions. You can unplug the servo wires from the GPIO pins, it’s time to attach them to the pan/tilt mount.

Attach the Servos to the Pan Tilt Mount

Attaching the servos to the pan/tilt mount went fairly smooth with the exception of one rather important part: the two white plastic servo attachments, despite coming with the pan/tilt mount, didn’t fit perfectly in their designated spots. You may not have this issue, but I ended up using scissors and a box cutter to pare them back a bit where necessary, which is apparent in the below image:

Pan Tilt and Servo Motor Pieces Ready to Assemble

When piecing this together it’s important to keep in mind that the servos are calibrated right now to their starting positions, but we haven’t attached the white plastic arms yet. When it comes time to attach them, the servo should be positioned in a way that when the plastic arm is attached, it’s aligned as follows, without the need to manually rotate the motor:

I demonstrate this a bit further a couple images down. As for the assembly itself, just in case you’re having trouble piecing it together here’s a few screenshots of the progress as I went.

X and Y Servos Attached
X (bottom) and Y (top) servo’s connected to their respective pieces

Y Servo mounted and X Servo in Place
Final wall set in place, and the Y servo’s plastic arm attached and screwed in.

The previous image showcases a couple of important points:

  • I consider the front of this pan/tilt mount to be the side where the wires leave the servo motors. This allows sufficient movement when the mount is on a flat surface. Alternatively, the back in this case (opposite side) could be used when your intention is to have the mount on its side (e.g. on a wall, or fridge).
  • The top of the mount where the y axis servo is attached was positioned as flat as can be before screwing in the servo arm. If you recall, we want the y axis to have a starting position of 0 degrees. If you can imagine a laser sitting on top of the mount in this position, aiming the direction of where the wires are coming out, it would be shooting straight. Tilting the mount to 90 degrees would cause the laser to aim straight down, and so on. 

Pan Tilt Mount Assembled
X servo’s plastic arm connected to the base plate, and jumper cables set to extend the servo wires.

I decided to use similarly colored jumper cables as the built-in servo wires to help reduce the amount of confusion while connecting them later. Red is power, brown is ground and orange is control.

Now that we have motors calibrated and attached to the mount, it’s time to setup the laser.

Prepare the Laser Diode

The laser diode has two wires: power and ground. It does not have a third wire for control. The GPIO pins on your Raspberry Pi output 3.3v when turned on but don’t provide enough current to make the laser glow to a brightness worth chasing. The 3V3 regulated pin (e.g. pin 1 on the Pi) provides the necessary current, but cannot be logically toggled on and off by our code. On one hand we have control but insufficient current, and on the other hand we have sufficient current without control. How do we get around this?

Enter the NPN transistor. We’ll leverage this little device as a way to introduce control into the equation. The NPN transistor has three connections: the base, which is what our control wire will connect to, the collector and the emitter. The transistor is used as an electronic switch, which logically connects the collector with the emitter once electricity hits the base.

With that said, we’re going to attach the laser diode to the shark (please tell me you bought the shark!), then we’ll solder the laser and some additional wiring to the NPN transistor.

Using a hot glue gun (or some other form of adhesive), attach the laser diode to the sharks head, like so:

Once the glue dries, take three (3) wires that have at least one female connector and snip off the opposite end of the female connector. Then, strip about an inch of this newly snipped end from its casing to expose the wiring inside. You should be left with three wires having one female end and one stripped end. These three wires will be used to connect the laser/ transistor to the Raspberry Pi’s GPIO pins.

Take one more wire, snip off both ends and strip about an inch of casing from either side. This wire will be used as an extension between the laser diode and the transistor to provide some needed slack.

The screenshot below demonstrates the three female wires and one extension wire soldered to the transistor as follows:

  • The green wire is soldered to the transistor’s emitter, and will eventually connect to the Pi’s ground GPIO.
  • The purple wire is soldered directly to the laser diode’s power wire, and will provide power to the laser. 
  • The blue wire (not the one connected to the laser diode) is connected to the transistors base (middle connection), and will be used as our control.
  •  The laser diode’s ground wire is connected to an extension wire (yellow below), which is then connected to the transistor. Again, this is to provide additional slack so the wire can reach the GPIO pins once mounted.

I wrapped the soldered parts with electrical tape to give them a bit of protection.

Now that everything is soldered together, let’s give it a test drive! Using the above image, wire coloring and previously shown GPIO diagram as reference, connect the laser to the Raspberry Pi as follows:

  • Purple connects pin 1 (3V3 Power)
  • Blue connects to pin 13 (GPIO27)
  • Green connects to pin 25 (Ground)

With the connections in place, create a new file called LaserTestDrive.py and paste the following code snippet inside:

#!/usr/bin/env python

import RPi.GPIO as GPIO
import time

GPIO_LASER = 27

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(GPIO_LASER, GPIO.OUT)

    print 'blinking laser...'
    try:  
        # Test turning the laser on and off
        time.sleep(1)
        GPIO.output(GPIO_LASER, 1)
        time.sleep(1)
        GPIO.output(GPIO_LASER, 0)
        time.sleep(1)
        GPIO.output(GPIO_LASER, 1)
        time.sleep(1)
        GPIO.output(GPIO_LASER, 0)

    finally:
        GPIO.output(GPIO_LASER, 0)
        GPIO.cleanup()

In the terminal, navigate to the folder that contains the above script and type chmod +x LaserTestDrive.py to make it an executable. Now, type ./LaserTestDrive.py to kick off the script. If done correctly, you should see the laser flash multiple times. Boom baby!

We now have a shark with a freakin’ laser beam attached to it’s head. Dr. Evil would be proud. It’s time to set ourselves up with a physical push button so, later on, we can actually engage the cat laser pointer toy at the press of this button.

Setup a Physical Push Button

The physical push button I used came as part of a starter kit, but you can purchase just the buttons if the kit isn’t your thing. Take two (2) female-to-any wires from your stash, snip off the non-female end, then strip about an inch of casing to expose the wires. If you’re using the same button as me, flip it over and you’ll see two parallel plastic lines running the length of the button, inline with the metal connectors. Twist and solder your wires to the buttons metal connectors, one wire per plastic line. Here’s how mine came out:

With the soldering in place, use the previously mentioned GPIO diagram as a reference and connect the wires as follows:

  • Black connects to pin 39 (Ground)
  • White connects to pin 37 (GPIO26)

Now that everything’s wired in, let’s test our little button out. Create a new file called Button.py with the following code in it:

#!/usr/bin/env python

import RPi.GPIO as GPIO
import time

GPIO_BUTTON = 26
    
if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(GPIO_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    print 'Press the button 3 times to exit...'

    press_count = 0
    while press_count < 3:
        # Test pressing of the button!
        button_pressed = GPIO.input(GPIO_BUTTON) == False
        if(button_pressed):
            print 'Button Pressed!'
            press_count += 1

            # pause to avoid the button press being picked up multiple times
            time.sleep(0.5)
    
    GPIO.cleanup()

In the terminal, navigate to the directory where this new script lives and type chmod +x Button.py to make it executable. Afterwards, run the script by typing ./Button.py. Once running, pressing the button should print Button Pressed! in the terminal. Press the button three total times to have the script exit.

Fantastic work! You’ve accomplished a lot so far. Now it’s time to put all those disparate pieces together.

Connecting All the Pieces

At this point we prepared all the individual pieces and ran some tests to ensure they’re functioning as we expect. Now it’s time to put it all together. If you’re using the same Pi case as me, open it up and place the Pi inside, but leave the top piece detached. The top of the case has a rectangular opening which exposes the GPIO section of the Pi, we’ll use this to fish some wires through. With the Pi in place, let’s get to assembling:

We’ll start off by wiring up the pan/tilt mount. Fish the servo wires through the rectangular opening in the top of the Pi case, then connect them to the GPIO’s as follows:

X Servo Y Servo
  • power connects to header pin 2 (5v power)
  • ground connects to header pin 6 (Ground)
  • control connects to header pin 7 (GPIO4)
  • power connects to header pin 4 (5v power)
  • ground connects to header pin 9 (Ground)
  • control connects to header pin 11 (GPIO17)

 

Servos Connected to Pi

Next up is attaching the shark and laser to the pan/tilt mount. I used velcro which allowed for a less permanent solution should I decide I want to turn the shark around. As was mentioned before, the servos have a finite range of motion. Right now we’re treating the front of our mount as the side where the wires exit the servos. Deciding to treat that as the back of our mount would allow for an entirely different range of motion.

I used a hacksaw to trim away some of the plastic on top of the pan/tilt mount prior to mounting the shark. This allowed for less interference and better positioning, but is not strictly necessary.

Shark Laser Mounted

Once the shark is mounted, fish the wires and NPN transistor through that same rectangular opening in the Pi case, then connect them as follows:

  • Purple connects pin 1 (3V3 Power)
  • Blue connects to pin 13 (GPIO27)
  • Green connects to pin 25 (Ground)

The mount and shark laser are primed and ready, time for the push button. I fished these wires through a different hole on the case to avoid overcrowding the GPIO opening. Fish them through wherever works, then connect as follows:

  • Black connects to pin 39 (Ground)
  • White connects to pin 37 (GPIO26)

Push Button Attached

I didn’t use any adhesive or other trickery to keep the button in place. The wiring and positioning of the button actually made it naturally stationary, enough for my needs anyway.

Finally, it’s time to attach the pan/tilt mount to the case itself. For this I went the overly complicated route of drilling out holes in the case to screw the base of the mount into. A much simpler and less permanent solution would be to use velcro. Velcro is plenty strong enough to support the weight of our little contraption.

Cat Toy Laser Assembly Complete

Congratulations, you now have a super evil cat laser pointer toy assembled and ready for action. Now we just need to give it life in the form of python code.

Conclusion and Next Steps

Part one set the goals and outline. Now with part two wrapped up we have a fully assembled cat toy laser. I’m heading to part three where we’ll write the python code to make all these parts function together at the push of a button.

Later in Part Four I’ll be covering how to trigger our cat laser pointer toy remotely as well as on a schedule. First, let’s figure out how to trigger it by pushing that button we just installed.

 

Trigger the Automatic Cat Laser Pointer Toy Remotely

Trigger the Automatic Cat Laser Pointer Toy Remotely

You made it here to part four because pushing that button isn’t enough. No, you want to trigger this cat laser pointer toy remotely, or even on a schedule. Well you’re in luck, because I too wanted more ways to do the same thing.

Part one got us on our way, part two was all about building the laser contraption, and part three brought it life. That means you have a fully functional cat laser pointer toy capable of being triggered at the push of a button.

What are we going to be doing here exactly?

  • Set up your Gmail account with 2-step authentication for a secure way to interact with it through code.
  • Introduce a modified version of the GmailWrapper.py script from my automatic cat feeder series to scan for an email with a given subject and, when found, trigger the cat laser pointer toy.
  • Use IFTTT to provide support for remote triggering and scheduling.

Enough said. It’s go time.

Preparing Your Gmail Account with 2-step Authentication

We’re going to be using email as the trigger to engage the cat laser pointer toy. Before that’s possible we’ll need a way to securely log into our Gmail account from code. You could store your credentials in plain text within the code we write, but I strongly discourage that. Instead, make use of Gmail’s two-step authentication and app passwords

I created a new Gmail account for this purpose.

  1. Log into your Gmail account
  2. Navigate to the Sign-in and security page
  3. Under the Signing in to Google section, click the 2-Step Verification menu, then follow the instructions to enable 2-Step Verification
  4. Back in the Sign-in and security page right under the 2-Step Verification button you’ll see App passwords
  5. Generate a password
    1. Note: this password will be 16 digits with spaces separating every 4 digits (e.g. xxxx xxxx xxxx xxxx). Please be sure to include the spaces!
  6. You can only view a generated password once, so copy it to the side for now.

The password you generated can be used to log into your Gmail account. We don’t need it right this second, but we’ll be using it to scan for an email that demands we initiate the laser sequence!

Writing Code to Read A Gmail Account

Now that we have a Gmail account ready to rock, let’s write some code to interrogate it. We’re going to be using the IMAPClient python library for this purpose, and we’ll wrap the calls to this client in our own Python class.

Install IMAPClient now from the terminal: sudo pip install imapclient

Create the GmailWrapper.py Script

Now let’s create our Gmail wrapper class: create a new file called GmailWrapper.py with the following code:

#!/usr/bin/env python
 
from imapclient import IMAPClient, SEEN
 
SEEN_FLAG = 'SEEN'
UNSEEN_FLAG = 'UNSEEN'
 
class GmailWrapper:
    def __init__(self, host, userName, password):
        #   force the user to pass along username and password to log in as 
        self.host = host
        self.userName = userName
        self.password = password
        
        self.login()
 
    def login(self):
        print('Logging in as ' + self.userName)
        server = IMAPClient(self.host, use_uid=True, ssl=True)
        server.login(self.userName, self.password)
        self.server = server
 
    #   The IMAPClient search returns a list of Id's that match the given criteria.
    #   An Id in this case identifies a specific email
    def getIdsBySubject(self, subject, unreadOnly=True, folder='INBOX'):
        #   search within the specified folder, e.g. Inbox
        self.setFolder(folder)  
 
        #   build the search criteria (e.g. unread emails with the given subject)
        self.searchCriteria = [UNSEEN_FLAG, 'SUBJECT', subject]
 
        if(unreadOnly == False):
            #   force the search to include "read" emails too
            self.searchCriteria.append(SEEN_FLAG)
 
        #   conduct the search and return the resulting Ids
        return self.server.search(self.searchCriteria)

    def getIdsByGmailSearch(self, search, folder='INBOX'):
        # powerful search enabled by Gmail. Examples: `in:unread, subject: <subject>`
        self.setFolder(folder)
        return self.server.gmail_search(search)
    
    def getFirstSubject(self, mailIds, folder='INBOX'):
        self.setFolder(folder)
        
        data = self.server.fetch(mailIds, ['ENVELOPE'])
        for msgId, data in data.items():
            envelope = data[b'ENVELOPE']
            return envelope.subject
            
        return None
 
    def markAsRead(self, mailIds, folder='INBOX'):
        self.setFolder(folder)
        self.server.set_flags(mailIds, [SEEN])
 
    def setFolder(self, folder):
        self.server.select_folder(folder)

Verifying the GmailWrapper.py Class Works

Let’s do a test: our script is going to log into the Gmail account, search for email with a specific subject, retrieve that subject, then mark the email as read. Before running the code, send yourself an email with the subject begin laser ignition (the search is case-insensitive, by the way).

We’re going to use the Python interpreter to run our tests. In your terminal make sure you’re in the same directory as the GmailWrapper.py script we just created, then:

# press enter after each line for the interpreter to engage
 
# invoke the interpreter
python
 
# import our wrapper class for reference
from GmailWrapper import GmailWrapper
 
# create an instance of the class, which will also log us in
# the <password> should be the 2-step auth App Password, or your regular password
gmailWrapper = GmailWrapper('imap.gmail.com', '<your gmail username>', '<password>')
 
# search for any unread emails with the subject 'begin laser ignition', and return their Ids
ids = gmailWrapper.getIdsByGmailSearch('begin laser ignition')
 
# have the interpreter print the ids variable so you know you've got something
ids

# grab the full subject of the first id returned
subject = gmailWrapper.getFirstSubject(ids)

# have the interpreter print the subject variable to see what you've got
subject
 
# we successfully found and read our email subject, now lets mark the email as read
gmailWrapper.markAsRead(ids)
 
# exit the interpreter
quit()

If everything went as planned your email should now be marked as read. Pretty neat! 

LaserWrapper, Meet GmailWrapper: Putting the Two Together

As we know by now, the LaserWrapper.py script is constantly watching the physical button and waiting for it to be pressed. We’re going to modify it a bit so it also watches the Gmail account and waits for an email with the correct subject to come through. In the event it finds this email we want it to trigger the cat laser pointer toy.

Alright, time for some modifications. Open your LaserWrapper.py script and replace its contents with the following:

#!/usr/bin/env python
 
from Laser import Laser
from GmailWatcher import GmailWrapper
import json
import datetime
import RPi.GPIO as GPIO
import time
from imaplib import IMAP4

HOSTNAME = 'imap.gmail.com'
USERNAME = 'your username'
PASSWORD = 'your password'
 
# seconds to wait before searching Gmail
CHECK_DELAY = 30
 
# seconds to wait before logging into gmail. if we don't wait, we run the risk of trying to 
# log in before the Pi had a chance to connect to wifi.
GMAIL_CONNECT_DELAY = 20

# minutes to wait before reconnecting our Gmail instance.
GMAIL_RECONNECT_DELAY = 60
GPIO_BUTTON = 26
 
FIRE_LASER_SUBJECT = 'begin laser ignition'
DISMANTLE_LASER_SUBJECT = 'dismantle laser'
 
engage = False
gmailWrapper = None
laser = Laser()
 
start_time = datetime.datetime.now()
run_time = 0
last_gmail_check_time = datetime.datetime.now()
last_gmail_connect_attempt = datetime.datetime.now()
last_reconnect_attempt = datetime.datetime.now()
 
default_configuration = '{"run_time": 30, "min_movement": 12, "x_min": 0, "x_max": 90, "y_min": 0, "y_max": 22}'
 
def initiateLaserSequence():
    global gmailWrapper
    global engage
    
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(GPIO_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 
    stop = False
    run = True
    
    # wire up a button press event handler to avoid missing a button click while the loop below
    # is busy processing something else.
    GPIO.add_event_detect(GPIO_BUTTON, GPIO.FALLING, callback=__button_handler, bouncetime=200)
    
    last_gmail_check = datetime.datetime.now().time()
    print 'running, press CTRL+C to exit...'
    try:
        while run:
            try:
                __check_gmail_connection()
                
                # avoid pinging gmail too frequently so they don't lock us out.
                if(__should_check_gmail(CHECK_DELAY)):
                    print 'Checking Gmail!'
                    stop = __should_stop_firing()
                    
                    ids = __get_email_ids_with_subject(FIRE_LASER_SUBJECT)
                    if(len(ids) > 0):
                        print 'Email found, initiating laser sequence!'
                        engage = True
            
                        laser.stop()
            
                        # grab any config options from the email and begin
                        __calibrate_laser(__get_configuration(ids))
                        gmailWrapper.markAsRead(ids)
                
                if(stop):
                    engage = False
                    stop = False
                    laser.stop()
                
                if(engage):
                    if(__run_time_elapsed()):
                        stop = True
                    else:
                        laser.fire()
                else:
                    # sleep here to lessen the CPU impact of our infinite loop
                    # while we're not busy shooting the laser. Without this, the CPU would
                    # be eaten up and potentially lock the Pi.
                    time.sleep(1)
                
            except IMAP4.abort, e:
                # Gmail will expire your session after a while, so when that happens we
                # need to reconect. Setting None here will trigger reconnect on the
                # next loop.
                gmailWrapper = None
                print 'IMAP4.abort exception: {0}'.format(str(e))
            except Exception, e:
                # swallowing exceptions isn't cool, but here we provide an opportunity to
                # print the exception to an output log, should crontab be configured this way
                # for debugging.
                print 'Unhandled exception: {0}'.format(str(e))
            except KeyboardInterrupt:
                run = False
                print 'KeyboardInterrupt: user quit the script.'
                break
    finally:
        print 'Exiting program'
        laser.stop()
        GPIO.cleanup()
        
def __button_handler(channel):
    global engage
    
    print 'Button pressed! '.format(str(channel))
    
    if(engage):
        print 'Already firing the laser, button press ignored'
    else:
        print 'Initiating Firing Sequence!'
        # only start a new firing sequence if we're not already in the middle of one.
        engage = True
        __calibrate_laser(None)
 
def __check_gmail_connection():
    if(gmailWrapper is None):
        __connect_gmail()
    
def __connect_gmail():
    global gmailWrapper
    global last_gmail_connect_attempt
    
    now = datetime.datetime.now()
    next_connect_time = (last_gmail_connect_attempt + datetime.timedelta(seconds=GMAIL_CONNECT_DELAY)).time()
    if(now.time() > next_connect_time):
        print '__connect_gmail: Attempting to login to Gmail'
        try:
            last_gmail_connect_attempt = now
            gmailWrapper = GmailWrapper(HOSTNAME, USERNAME, PASSWORD)
        except Exception, e:
            print '__connect_gmail: Gmail failed during login, will retry automatically.'.format(str(e))

def __should_check_gmail(delay):
    global last_gmail_check_time
    
    if(gmailWrapper is None):
        # we haven't yet successfully connected to Gmail, so exit
        return
    
    now = datetime.datetime.now()
    next_check_time = (last_gmail_check_time + datetime.timedelta(seconds=delay)).time()
    
    if(now.time() > next_check_time):
        last_gmail_check_time = now
        return True
    
    return False
 
def __run_time_elapsed():
    now = datetime.datetime.now()
    end_time = (start_time + datetime.timedelta(seconds=run_time)).time()
    
    if(now.time() > end_time):
        return True
    
    return False
 
def __calibrate_laser(configuration):
    global start_time
    global run_time
    
    if(configuration is None):
        # no user defined config, so we'll go with the defaults
        configuration = json.loads(default_configuration)
    
    print "starting laser with config: {0}".format(configuration)
    
    start_time = datetime.datetime.now()
    
    run_time = configuration.get('run_time')
    min_movement = configuration.get('min_movement')
    x_max = configuration.get('x_max')
    x_min = configuration.get('x_min')
    y_max = configuration.get('y_max')
    y_min = configuration.get('y_min')
 
    laser.calibrate_laser(min_movement, x_min, x_max, y_min, y_max)
        
def __get_email_ids_with_subject(subject):
    return gmailWrapper.getIdsByGmailSearch('in:unread subject:{0}'.format(subject))
    
def __get_configuration(emailIds):
    subject = gmailWrapper.getFirstSubject(emailIds)
    config_start_index = subject.find('{')
    
    # no config found in subject, so return nothing
    if(config_start_index == -1):
        return None
    
    # grab the substring from opening { to the end
    return json.loads(subject[config_start_index:None])
 
def __should_stop_firing():
    ids = __get_email_ids_with_subject(DISMANTLE_LASER_SUBJECT)
    return len(ids) > 0

if __name__ == '__main__':
    initiateLaserSequence()

The above script is almost ready for use, we just need to make a few tweaks:

  • Replace lines 12 and 13 with your Gmail username (that’s your email without @gmail), and the app password you generated.
  • Replace lines 26 and 27 with the email subjects that you want to use to start or stop the laser sequence. That’s right, the ability to remotely stop the laser is baked in as well, just in case it’s running a bit longer than desired.

Once these changes are in place, give it a test drive. Run the script and press the physical button to ensure we didn’t break anything there. If all looks good, send yourself an email with the subject you chose (I used begin laser ignition). After about 30 seconds the laser should have began firing.

Subject Matters: Override the Default Configuration

At this point we’re able to trigger the cat laser pointer toy remotely by sending an email. This is pretty powerful stuff. If you recall from the previous posts, we built in the ability to configure the cat laser pointer toy in the form of a JSON string. What if we want to set the run time, or the minimum amount of movement, remotely?

You’re in luck! The script we just wrote above is smart enough to do that. If you send an email with just the trigger key in the subject (e.g. begin laser ignition), then it will use the default_configuration variable on line 39 of the script. However, you can override that by adding your own configuration into the subject itself.

Here’s a sample email subject to showcase what I mean:

begin laser ignition {"run_time": 30, "min_movement": 12, "x_min": 0, "x_max": 90, "y_min": 0, "y_max": 22}

The script we wrote will parse this more complex subject and use the configuration settings you provide here over the default ones.

Scheduling an Email to be Sent Regularly

We have the code ready to rock. Sending an email on demand will cause the cat laser pointer toy to start firing, but what about firing it on a schedule? That’s where we’ll make use of IFTTT. IFTTT stands for if this then that and allows you to connect two disparate systems based on what they call a “recipe” (trigger and action). For our purpose, we need the clock to trigger an email to be sent (action).

Here’s what to do:

  1. Setup an account if you haven’t already (free, free, free).
  2. Use IFTTT website or download the app to your phone and log in.
  3. In the My Applets section, add a new applet.
  4. You’ll see a screen saying if +this then that: click the +this button, then select the Date & Time service.
  5. Select the Every day at trigger, and select the time you’d like the cat laser pointer toy to activate, then hit next
  6. Now you’ll see +that, click it and find the Gmail service. You’ll need to connect the service to your Gmail account. Once finished, set the action to Send yourself an email with the subject  Begin Laser Ignition.
    1. Don’t forget, you have the option of adding configuration to the subject, too.
  7. Hit next, then Finish

There you have it, every day at the time you specified an email will be sent to your account. If you had any issues setting up the IFTTT recipe, check out this post for a really nice and in-depth walk-through.

Having fun? Here’s Other Ways to Trigger

Alexa

Alexa (and the Echo Dot) integrates nicely with IFTTT. In the IFTTT app, create a new recipe with the trigger (+this) connecting to Alexa. You’ll need to connect the service like you did for Gmail. Once connected, select the option to Say a specific phrase and enter a phrase like cat laser. Once the Alexa side is setup, set the action (+that) to send an email, like we did in the previous section.

Hands free laser initiation at the ready, just say: Alexa, trigger the cat laser.

The DO Button App

Created by the IFTTT team, the DO Button app and accompanying widget provides a straightforward way to trigger the action. You! You’re the trigger. You setup a recipe, same as before, except you’ll notice there’s no +this. You are +this. You open the app and click the button, it then triggers an email which triggers the cat laser. This app can also be configured to show on your iPhone or Androids home screen, so triggering the laser is even easier.

Conclusion

I hope you enjoyed this project as much as I did. It was a blast seeing a shark with a laser attached to its head causing mayhem throughout my apartment. I think the cats enjoy it too, almost as much as I do.

As always, a little shoutout to the previous sections in case you need to get there:

  • Part one where we found out what we were building and what it would take.
  • Part two which helped guide us to creating a machine of chaos.
  • Part three where our inner mad scientist gave life to this machine in the form of code.
  • And of course, the bonus part, part four where we couldn’t live without a remote way to control our cat laser pointer toy.

I encourage you to leave feedback in the comments below. If you’re stuck, reach out! And of course, thank you for reading.

Write Code to Control the Raspberry Pi Cat Laser Pointer Toy

Write Code to Control the Raspberry Pi Cat Laser Pointer Toy

At this point you’ve covered part one and part two (nice job!), or you didn’t and just happen to stumble upon part three of this guide (it’s good to have you). Here in part three we’ll be focusing primarily on writing the necessary python code to make our super evil laser contraption fully functional at the push of a button. This could technically be your last part of the series (it’s sad, but it had to end sometime). If you’re hankering for more like I was, part four will cover the bonus feature of triggering the cat laser pointer toy remotely and on a schedule.

With that said, let’s dig in!

Create the Scripts to Automate the Cat Laser Pointer Toy

We’re going to create two scripts:

  • Laser.py, which will contain the code responsible for configuring our laser and servo motors, and moving the laser from one position to another.
  • LaserWrapper.py, which as the name suggests will wrap the Laser.py functionality, and will primarily be responsible for deciding when the cat laser pointer toy should be enabled as well as which settings it should use during runtime.

Create a new file called Laser.py with the following code:

#!/usr/bin/env python

import time
import RPi.GPIO as GPIO
import random

DEFAULT_RUN_TIME = 90
DEFAULT_MIN_MOVEMENT = 10
DEFAULT_X_MIN_POSITION = 40
DEFAULT_X_MAX_POSITION = 120
DEFAULT_Y_MIN_POSITION = 20
DEFAULT_Y_MAX_POSITION = 60

# define which GPIO pins to use for the servos and laser
GPIO_X_SERVO = 4
GPIO_Y_SERVO = 17
GPIO_LASER = 27

class Laser:
    def __init__(self):
        GPIO.setmode(GPIO.BCM)
        
        self.x_servo = None
        self.y_servo = None

        GPIO.setup(GPIO_X_SERVO, GPIO.OUT)
        GPIO.setup(GPIO_Y_SERVO, GPIO.OUT)
        GPIO.setup(GPIO_LASER, GPIO.OUT)

    def calibrate_laser(self, min_movement, x_min, x_max, y_min, y_max):
        # set config variables, using the defaults if one wasn't provided
        self.min_movement = DEFAULT_MIN_MOVEMENT if min_movement is None else min_movement
        self.x_min = DEFAULT_X_MIN_POSITION if x_min is None else x_min
        self.x_max = DEFAULT_X_MAX_POSITION if x_max is None else x_max
        self.y_min = DEFAULT_Y_MIN_POSITION if y_min is None else y_min
        self.y_max = DEFAULT_Y_MAX_POSITION if y_max is None else y_max
        
        # start at the center of our square/ rectangle.
        self.x_position = x_min + (x_max - x_min) / 2
        self.y_position = y_min + (y_max - y_min) / 2
        
        # turn on the laser and configure the servos
        GPIO.output(GPIO_LASER, 1)
        self.x_servo = GPIO.PWM(GPIO_X_SERVO, 50)
        self.y_servo = GPIO.PWM(GPIO_Y_SERVO, 50)
        
        # start the servo which initializes it, and positions them center on the cartesian plane
        self.x_servo.start(self.__get_position(self.x_position))
        self.y_servo.start(self.__get_position(self.y_position))

        # give the servo a chance to position itself
        time.sleep(1)

    def fire(self):
        self.movement_time = self.__get_movement_time()
        print "Movement time: {0}".format(self.movement_time)
        print "Current position: X: {0}, Y: {1}".format(self.x_position, self.y_position)

        # how many steps (how long) should we take to get from old to new position
        self.x_incrementer = self.__get_position_incrementer(self.x_position, self.x_min, self.x_max)
        self.y_incrementer = self.__get_position_incrementer(self.y_position, self.y_min, self.y_max)

        for index in range(self.movement_time):
            print "In For, X Position: {0}, Y Position: {1}".format(self.x_position, self.y_position)
            self.x_position += self.x_incrementer
            self.y_position += self.y_incrementer

            self.__set_servo_position(self.x_servo, self.x_position)
            self.__set_servo_position(self.y_servo, self.y_position)

            time.sleep(0.02)

        # leave the laser still so the cat has a chance to catch up
        time.sleep(self.__get_movement_delay())

    def stop(self):
        # always cleanup after ourselves
        print ("\nTidying up")
        if(self.x_servo is not None):
            self.x_servo.stop()
        
        if(self.y_servo is not None):
            self.y_servo.stop()
            
        GPIO.output(GPIO_LASER, 0)
        
    def __set_servo_position(self, servo, position):
        servo.ChangeDutyCycle(self.__get_position(position))

    def __get_position(self, angle):
        return (angle / 18.0) + 2.5

    def __get_position_incrementer(self, position, min, max):
        # randomly pick new position, leaving a buffer +- the min values for adjustment later
        newPosition = random.randint(min + self.min_movement, max - self.min_movement)
        print "New position: {0}".format(newPosition)

        # bump up the new position if we didn't move more than our minimum requirement
        if((newPosition > position) and (abs(newPosition - position) < self.min_movement)):
            newPosition += self.min_movement
        elif((newPosition < position) and (abs(newPosition - position) < self.min_movement)):
            newPosition -= self.min_movement

        # return the number of steps, or incrementer, we should take to get to the new position
        # this is a convenient way to slow the movement down, rather than seeing very rapid movements
        # from point A to point B
        return float((newPosition - position) / self.movement_time)

    def __get_movement_delay(self):
        return random.uniform(0, 1)

    def __get_movement_time(self):
        return random.randint(10, 40)

This code is pretty self contained. The only reason you’d need to adjust anything above is if you decided to use different GPIO pins than I described in the previous sections. If so, you’ll need to set them accordingly (lines 15 – 17).

One script down, that was easy! Create another file called LaserWrapper.py with the following code:

#!/usr/bin/env python

from Laser import Laser
import json
import datetime
import RPi.GPIO as GPIO
import time

GPIO_BUTTON = 26

laser = Laser()
start_time = datetime.datetime.now()
run_time = 0
engage = False

default_configuration = '{"run_time": 30, "min_movement": 12, "x_min": 0, "x_max": 90, "y_min": 0, "y_max": 22}'

def initiateLaserSequence():
    global engage
    # setup the push button GPIO pins
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(GPIO_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    run = True
    
    # wire up a button press event handler to avoid missing a button click while the loop below
    # is busy processing something else.
    GPIO.add_event_detect(GPIO_BUTTON, GPIO.FALLING, callback=__button_handler, bouncetime=200)
    
    print "running, press CTRL+C to exit..."
    try:
        while run:
            try:
                if(engage):
                    if(__run_time_elapsed()):
                        # we ran out of time for this run, shutdown the laser
                        engage = False
                        laser.stop()
                    else:
                        laser.fire()
                else:
                    # sleep here to lessen the CPU impact of our infinite loop
                    # while we're not busy shooting the laser. Without this, the CPU would
                    # be eaten up and potentially lock the Pi.
                    time.sleep(1)
                        
            except Exception, e:
                # swallowing exceptions isn't cool, but here we provide an opportunity to
                # print the exception to an output log, should crontab be configured this way
                # for debugging.
                print 'Unhandled exception: {0}'.format(str(e))
            except KeyboardInterrupt:
                run = False
                print 'KeyboardInterrupt: user quit the script.'
                break
    finally:
        print 'Exiting program'
        laser.stop()
        GPIO.cleanup()

def __button_handler(channel):
    global engage
    
    print 'Button pressed! '.format(str(channel))
    
    if(engage):
        print 'Already firing the laser, button press ignored'
    else:
        print 'Initiating Firing Sequence!'
        # only start a new firing sequence if we're not already in the middle of one.
        engage = True
        __calibrate_laser(None)

def __run_time_elapsed():
    # figure out if the laser has ran its course, and should be stopped.
    now = datetime.datetime.now()
    end_time = (start_time + datetime.timedelta(seconds=run_time)).time()
    
    if(now.time() > end_time):
        return True
    
    return False

def __calibrate_laser(configuration):
    global start_time
    global run_time
    
    if(configuration is None):
        # no user defined config, so we'll go with the defaults
        configuration = json.loads(default_configuration)
    
    print "starting laser with config: {0}".format(configuration)
    
    start_time = datetime.datetime.now()
    
    run_time = configuration.get('run_time')
    min_movement = configuration.get('min_movement')
    x_max = configuration.get('x_max')
    x_min = configuration.get('x_min')
    y_max = configuration.get('y_max')
    y_min = configuration.get('y_min')
 
    laser.calibrate_laser(min_movement, x_min, x_max, y_min, y_max)

if __name__ == '__main__':
    initiateLaserSequence()

As mentioned previously, this script defines a few settings which tell the Laser.py script information about how it should move. This script is also responsible for triggering the movement, as it’s continuously looking for the press of that physical button we wired up. 

The default settings are defined as a JSON string on line 16, and are stored in the default_configuration variable. I chose to use JSON instead of individual variables to prepare the script for future changes introduced in part four, where we trigger the laser remotely. 

Here’s a bit of documentation around what exactly these configuration variables mean:

PropertyDescription
run_timeHow long, in seconds, the cat laser pointer toy should operate each time the button is pressed.
min_movementThe minimum amount of movement that needs to occur when the laser moves from point A to point B on both the x and y axis.
x_minThe minimum position, in degrees, the laser is allowed to move to along the x axis.
Must be greater than or equal to 0 degrees.
x_maxThe maximum position, in degrees, the laser is allowed to move to along the x axis.
Must be less than or equal to 180 degrees. 
y_minThe minimum position, in degrees, the laser is allowed to move to along the y axis
Must be greater than or equal to 0 degrees.
y_maxThe maximum position, in degrees, the laser is allowed to move to along the y axis.
Must be less than or equal to 180 degrees. 

 

To really drive home the point, let’s dissect the default_configuration variable in the above script:

default_configuration = '{"run_time": 30, "min_movement": 12, "x_min": 0, "x_max": 90, "y_min": 0, "y_max": 22}'

With these settings, the cat laser pointer toy will:

  • Run for 30 seconds when the physical button is pressed, stopping after that time has elapsed.
  • Move at least 12 degrees along both the x axis (left/ right) and y axis (up/ down).
  • Move within the bounds of 0 degrees (minimum) and 90 degrees (maximum) along the x axis. Remember, our servo’s can move between 0 and 180 degrees if we wanted. Here we can restrict that range to our needs.
  • Move within the bounds of 0 degrees (minimum) and 22 degrees (maximum) along the y axis.

The x and y min/ max variables are nothing more than a way for us to define a square or rectangular shape for the laser to move within. I adjusted these settings until they fit the space where I have the cat laser pointer toy set up. You’ll likely need to tailor them to your own play space.

Now that we have all the code in place we can finally test this puppy out, hell yeah!

Begin Laser Ignition!

Frau Farbissina said it best, it’s time we begin laser ignition. Open up the terminal and navigate to the directory where you created the Laser.py and LaserWrapper.py scripts, then type the following commands:

chmod +x Laser.py
chmod +x LaserWrapper.py

Our scripts are now executable. The LaserWrapper.py script is the one we want to kick off, as it’s responsible for configuring and running Laser.py internally. Run the script by typing ./LaserWrapper.py. If all went well you should see an error-free terminal, which means the script is running and awaiting your command.

Press the button already!

If everything’s wired up correctly and the code is in place, the laser should have started moving. Congratulations, you’ve built a fully functional world dominating cat laser pointer toy!

If nothing happened or you had errors while executing the script: take a step back and double-check all your GPIO connections from the previous section. Did you use the same GPIO’s as me? If you did, great, that’s probably not the problem, but if you didn’t then you’ll need to adjust the GPIO variables in the scripts to point to the ones you chose. If the pins are in place and still no luck, don’t be discouraged! Reach out in the comments or contact me directly and we can take a crack at it together.

Using Cron to Automatically Run our Script

Our cat laser pointer toy has a push button to trigger the movement of the laser, but that button is no good to us if our LaserWrapper.py script isn’t constantly running. We need this script to run on a regular basis, which means starting it when the Raspberry Pi boots up. For that, we can use cron.

Open a terminal and type crontab -e to open the cron job editor in nano. Add the following line to the end of this file:

@reboot sudo python /path/to/your/script/LaserWrapper.py

It may seem obvious, but make sure you replace /path/to/your/script above with the actual path to your script. Move your cursor to the end of the newly added line (after .../LaserWrapper.py) and press Enter to create a new line under it. A known quirk with cron is that it requires the command to be followed by a new line, else it won’t run our script.

Since we’re in nano, save and exit by pressing CTRL+X, then Y, then Enter. This script will now be executed anytime the Pi reboots. Give it a go: reboot your Pi and give it time to load up. Once loaded, try triggering the cat laser pointer toy without manually running the script first.  

Conclusion and Next Steps

You did it! We now have the awesome power of an automatic cat laser pointer toy. I hope you have a cat to use it on (I won’t judge if you don’t).

Part one got us started, while part two really made the project interesting once we had a fully assembled contraption. Here in part three we were able to bring life to our laser with a little help from our friend python. 

Not satisfied with stopping here? Too lazy to physically push the button on the laser? Me too. If you haven’t had enough yet I’ll be over in part four over-engineering this project with some remote capabilities.

I want to reiterate that if you’re stuck, that’s ok! Don’t be discouraged. This project caught me up a few times, it’s all part of the process. Just reach out!

If you won’t be joining me for part four, then I want to thank you for taking the time to read through this series; I hope you found it most excellent. If you have any feedback, good or bad, I implore you to reach out in the comments.

A Git Cheat Sheet For That Quick Reminder

A Git Cheat Sheet For That Quick Reminder

Switching from subversion to Git was a paradigm shift that took some getting use to, but looking back now, the switch was worth it. Rather than getting tied to any one Git UI, I wanted to get a grasp of plain old Git and the commands it offers.

With that, I’ve created this Git Cheat Sheet to help along the way.

Cloning a Repository

NotesCommands
clone an existing repositorygit clone "repository-path"
create a new local repositorygit init

 

Branch Management

NotesCommands
list local branchesgit branch
list local and remote branchesgit branch -av
create local branchgit branch "branch-name"
create and checkout local branchgit checkout -b "branch-name"
delete local branch that has had its changes pushed to a remote branch (cannot currently be checked out)git branch -d "branch-name"
delete local branch, ignoring whether or not its changes were pushed to a remote branchgit branch -D "branch-name"
delete local and remote branchgit push origin --delete "branch-name"
checkout branchgit checkout "branch-name"
rename currently checked out local branchgit branch -m "new-branch-name"

 

Handling Changes

NotesCommands
see changes made to local branchgit status
track and stage local changesgit add .
commit staged only with a commit messagegit commit -m "commit-message"
commit unstaged and staged with a commit messagegit commit -a -m "commit-message"
amend changes to previous commit (before pushing)git commit --amend
temporarily stash local changes (resets local branch but preserves changes)git stash
apply previously stashed changes to the current branchgit stash pop

 

Undoing (oops)

NotesCommands
discard all changes in working directorygit reset --hard
reset HEAD to a specific commit and discard local changesgit reset --hard
reset HEAD to a specific commit but preserve local changesgit reset "commit-hash"
discard all changes to specific filegit checkout HEAD
revert commit by applying previous commit changes to HEAD then committinggit revert

 

Tracking History

NotesCommands
show all commits for current HEADgit log
show all commits for current HEAD for given filegit log -p "file"
show all commits that HEAD has pointed togit reflog

 

Fetch, Pull & Push

NotesCommands
grab changes from remote, but don't merge into current HEADgit fetch
grab changes from remote, don't merge, and clean-up remote references (known as pruning, prune)git fetch -p
grab changes and merge into current HEADgit pull
create and track remote branch, push all local changes to itgit push -u "remote" "branch-name"
push all local changes to tracked remote branch for current HEADgit push

 

Merge & Rebase

NotesCommands
merge commits from branch into current HEADgit merge "source-branch-name"
merge commits from source branch into target branchgit merge "source-branch-name" "target-branch-name"
rebase current HEAD onto branch (e.g. make that branch the new base of your changes)git rebase "branch"