MapUtopia
World Map (1689 - Amsterdam)

Baseball and Raspberry Pi

tldr; http://mlbmap.red-meteor.com/

(A non-Pi hosted copy.)

We’ll get to talking Baseball and JavaScript in a minute. For now, I need to share a rather odd sight in my basement. It’s not pretty, so I apologize for posting it inline.

Raspberry Pi in Deep Corner of Basement

Within that rather elaborate case is the $35 Raspberry Pi 3. It does not require a mouse, a keyboard, or even a monitor (you can SSH to it remotely). Just load it with a properly configured microSD card (along with your wifi configuration), and it’s good to go. I may not look at it again until I move out.

What does this have to do with anything? Well, that Altoids-sized box is hosting my NodeJS app to the world.

The instructions for self-hosting were fairly straightforward, and I owe a lot to this article by Lauren Orsini at readwrite.com. If you want to host your app so that it is visible to the world, rather than just a local network, I would highly advise following her directions.

Of course, don’t do anything too crazy. Not only will your ISP not appreciate it, it could also have the unintended consequence of slowing down your Netflix stream. I wouldn’t want to be responsible for that.

A Word about Architecture

It wasn’t quite enough to host a Node app on the Pi and call it a day. The goal was to get a Node App running in a Docker container on a Pi. What a cool, original, totally brilliant idea, right!

The humbling thing about the interweb is that however clever you think you are, you’re only a Google search away from realizing that not only is somebody already doing that, they’re crushing it! Enter the folks at Hypriot, who have taken containerization on the Raspberry Pi to a whole new level.

Why even do this? For Hypriot, their focus seems to be on applying container technology to the IOT.
However, containers can also be a great solution for much more mundane day-to-day problems. Even for smaller organizations, there are many advantages to being able to run a variety of applications side-by-side, not worrying about version conflicts or leaving artifacts on your server through the many stages of adding, updating, and removing programs. Once you’ve got a working container, it can be run almost anywhere.

Create a Hypriot Docker microSD Card Image for your Pi


You can get a much more in-depth explanation straight from the source, but to distill it down to the bare minimum:

When you ‘flash’ the image, make sure to pass in your wifi –ssid (network name) and –password. If you don’t, you’ll have to manually configure the file on the Pi itself, which means you’ll need to plug it directly into a monitor and keyboard.

  • Put the Flash Card into your Pi, plug it in. Wait a minute or so.
  • SSH into your Pi.

Because you’re (presumably) accessing the Pi on the same Local Network, you can SSH into it using a local identifier. At your (somewhat remote) command prompt, type in:

ssh [email protected]

The initial password will be ‘hypriot’. (To change your password, see here - It’s easy.)

Steps to Containerize your App and Run in Docker


Presuming you have a working NodeJS app in a Github repository:

  • git clone your repo into a folder on your Pi that you’ll designate as your staging area

  • Write / configure your Dockerfile (pay attention to the –version of Node you are using, see notes at the botton of this post)

If you don’t have any previous experience with Docker, here’s a completely insufficient 2 minute introduction to Dockerfiles:

A Dockerfile (no extension) is a simple text file at the root of your repo which gives Docker instructions on how to build an ‘image’. The Dockerfile for this project is very simple, and will be a good starting point for most any NodeJS Pi App. I recommend keeping it that way (initially), as the iterative, time-consuming debugging process of complex Dockerfiles is one of the most maddening aspects of development that I have personally experienced.

FROM hypriot/rpi-node:4.4.4-wheezy
ADD . .
RUN npm install
EXPOSE 4000
CMD ["node", "app.js"]

From top to bottom:

The FROM statement is the base image you’re using. Please keep in mind that you can’t use just any base image you please, as the Raspberry Pi needs images specifically compiled for its ARM architecture.

The ADD statement ‘adds’ all the files from your repo (including folder/subfolders recursively). Please note the double . .

The RUN command will run npm install and download the proper packages as specified in your package.json file.

If you’re someone who checks in their node_modules folder, then you may be able to skip the RUN npm install command.

EXPOSE 4000 is the port you’re exposing on your Docker container. Think of a Docker container like you would any Linux machine; it has it’s own set of ports that are independent from any other Docker container, as well as being independent from the host machine (your Pi). When you EXPOSE a port, that’s like opening that port up to the outside world. The host machine will then be able to access that port (and map it to one of its own ports, if you tell it to do so.)

  • CMD is referring to the application and file that will run on startup.

Beware: Dockerfiles seem fairly straightfoward from reading the description above, but there are quite a few ‘gotchas’ when you start tackling more advanced configurations. That content is outside the scope of this article, but be advised there’s quite a bit more to learn.

  • Build your container by using docker build -t mlbnew .

The -t parameter denotes a name (or tag) that you can use to refer to your Docker image. An image is like a template that you can use to build a container (or multiple containers).

  • Run your container: docker run -d -p 4000:4000 mlbnew

This command will create a container from your previously created Docker image. The -d parameter tells the container to run in the background, while the -p parameter maps the Docker port (that you EXPOSE‘d in your Dockerfile, to a port on the Pi. We’ve kept it simple here, but the format for -p is host:container)

  • View your active containers: docker ps -a

  • If applicable, check ‘exited’ containers for errors (docker logs containername) docker logs container_name

If your container failed, you’ll either notice it during the build step, or when you check on your container inventory by typing docker ps -a. If the container is running –great– , but make sure you hit your API before you celebrate. Although you should seek to avoid this, sometimes the Node/NPM versions we specify in our Dockerfile are not exactly the same as those we run in our development environments - and this could lead to problems.

  • Try out your application! Go to it’s local IP address and access it on port 4000.

For example, on my local network, my router has assigned my Pi to 192.168.1.8 (this could be different for you). If you’ve also mapped your container port to the Pi’s 4000 port, you’d be able to access your application at 192.168.1.8:4000. I’ve taken it one step further and have opened my Pi to the world (and mapped it to a subdomain I own). Follow these instructions to find out how.

About the App

I was so excited to get your app ‘Dockerized’ that I completely forgot to mention that I made an app of my own!

Have you ever wanted to see the day’s baseball games ON A MAP!!?

No? Well, if you ever change your mind, have I got a map for you!

My goal was to create an API based app that would be served entirely on the ExpressJS framework. I don’t typically espouse using frameworks for what are essentially very tiny projects, but ExpressJS doesn’t have a steep learning curve like so many of the popular client-side frameworks.

The API works as follows:

  • Load all of the MLB schedule data into memory.

This data was not readily available in a convenient format. Each team page had a CSV home schedule published as a service for calendar reminders. (Here’s what they look like). I concatenated these together into a single file.

  • Set up a route to wait for a request to the /game endpoint

  • When the route is hit, figure out what day it is (standardize on Pacific time)

  • Gather the day’s games from the schedule

  • Use a lookup to locate the ballpark coordinates of each home team

The ballpark coordinates are conveniently available through the work of James Fee at https://github.com/cageyjames/GeoJSON-Ballparks. The data includes coordinates not only for MLB stadiums, but for all professional leagues around the world.

  • Send the data back to the requestor as JSON

The Client / Static Files

The client-side portion of the application is served using ExpressJS’s capability to serve static assets.

Just one line:

app.use(express.static('public'));

… and now you’re serving everything from your repo’s ‘public’ folder statically, as if you were using Apache or Nginx.

The client side portion doesn’t contain a lot of surprises; the map is a basic Leaflet map and uses custom markers to display each game.

Rather than creating a unique marker for every team combination, the custom markers are assembled dynamically on the client. This is accomplished by pre-drawing them on a non-visible canvas element, and then saving the assembled image as a URI. The URI addresses are then used in place of an image link when each game marker is created.

Credit goes to this post on Stack Overflow for bringing this technique to my attention.

Without Further Ado

http://pi.red-meteor.com:4000/

and all of the source code:

https://github.com/royhobbstn/GamesMapMLB

MLB Gameday Map Screenshot

Simplistic, but hopefully it’s enough to get you started. Want more? Fork it, and tell me about it!


Issues You’re Likely to Encounter


  • If you’ve elected to not use the Hypriot Flash Image, be aware that the version of Docker included on the other Pi OS flavors (namely, Raspbian) is quite old. You’ll need to upgrade it using these instructions.

  • There’s a major bug with NodeJS in Versions 6.x, and probably in some of the latter 5.x versions as well. You’ll know you’re dealing with it when your app runs fine in development (or anywhere outside of a container, for that matter), but exits immediately if you try to run it within Docker. You’ll likely find in your Docker logs an error to the effect of ‘Cannot find the merge-connectors module’. This ruined my fun for quite a while, and I ended up having to revert to NodeJS version 4. You can track this bug in this Github issue.

  • So you’ve set up everything perfectly, you ‘docker pull’ your favorite image from Dockerhub, and it doesn’t run. What went wrong? Well, remember how I said that once you’ve created a Docker container, it will run almost anywhere? The almost in our case is a system incompatibility. You see, the Pi is built on ARM, whereas your home computer (or cloud server) is probably using x86 - and the two aren’t compatible. The solution is to start with ARM base images, rather than standard Docker base images. You’ll probably have to run the build step on your Pi (as we did), rather Dockerhub-ing it remotely via a git-hook, which may be the workflow that you’re used to if you’ve used Docker previously.