Chicago “L” Map - Part 2

Posted on Wed, Mar 8, 2023 project

In part 1 i talked all about the software to figure out where trains are in chicago.

In this part I’ll quickly show how I put together the map.

First, I built a frame out of some 1x3’s and particle board. The process is fairly straightforward, and can basically be explained by this image:

As for the computer bits, the setup is here

Some special leds were used that are addressable — meaning you can program the color & brightness of each one individually.

A power supply was added, since the lights use power.

Raspberry pi zero w is the computer. This is definitely overkill, but just what I had laying around.

Now, putting it together involves drilling holes in the map and gluing the lights in place.

The wiring between the lights was quite short.

Basically, this means the lights needed to zig-zag around all out of order to fit them in.

Heres a little diagram that help me keep track of where they are going:

light number diagram

The order doesn’t really matter in the code, since its just a number after all.

Each light got it’s number

But the lights are too fat. Got to grind them down.

Finally, I just hot glued them in place. I’m sure theres a better way, but it worked.

Still glueing…

This took forever, since you have to hold the lights in place as the glue cools. Got some good podcasts in.

Now with the map together, and turning on, getting the software running was simple enough

There is a library called ‘’neopixel’’ made just for controlling these led lights.

After mapping the number of the physical lights to the station & color, it was easy to light them up correctly.

If you want to see the code, check the bottom of this post.

You can do some cool animations as well:

I also added a button on the side to switch between the different modes.

The most interesting case is of course the point of this project: Using the server from part 1, I can get a list of where the trains are right now, and can turn on the correct lights:

It updates itself every 30 seconds or so.

Not sure what to do with it now, but hey, it exists.

If anyone at cta wants these in their stations, hit me up.

-Hew

— Bonus Code Stuff —

Check out link to see the python used on the pi:

Github project

The ‘controller.py’ runs on the PI, allowing for different scripts to be switched between by the physical button.

from gpiozero import Button
from subprocess import Popen
from subprocess import call
from time import sleep

button = Button(2)
index = 0
commands = [
	"poll.py",
	"move.py",
	"lines.py",
	"rainbow.py",
	"off.py"
]

call(["python3", "startup.py"])
p = Popen(["python3", commands[index]])

while True:
	try:
		button.wait_for_press()
		p.terminate()
		index = index + 1
		if index >= len(commands):
			index = 0
		print('Switching to ' + commands[index])
		p = Popen(["python3", commands[index]])
		sleep(0.5)

Color Map

fyi: ‘map’ in programming is a way to keep track of how data relates to each other. ‘map’ for most of this post has been a physical thing with lights on it that shows where trains are in chicago. I know, confusing.

colorOrder is used to decide which color each light is by going in order (remember the wacky wiring)

colorOrder = ['r','r','r','r','r','g','g','o','o','o','o','o','o','o','r','r','r','r','g','g','g','g','g','g','g','g','g','o','g','r','r','b','b','b','pk','pk','pk','pk','pk','pk','pk','pk','pk','pk','pk','b','b','b','b','b','b','b','b','b','pk','pk','pk','g','g','g','g','g','g','g','g','g','g','g','g','g','g','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','b','br','br','br','br','br','br','br','br','br','br','br','br','br','br','br','br','r','br','p','r','r','r','br','p','p','p','r','p','p','p','r','r','r','r','p','r','r','r','r','r','r','r','r','s','x','s','s','s','s','r','r','p','y','y','y','p','p','p','p','p','p','p','p','br','p','o','pk','r','b','b','br','p','o','pk','br','p','o','pk','b','pk','o','p','br','b','pk','g','o','p','br','p','br','br','p','o','g','pk','b','r','br','p','o','g','pk','r','pk','g','o','p','br','x','x','x','x']
# (g,r,b)
colorMap = {
	's': (80, 80, 80),
	'x': (0, 0, 0),
  'r': (0, 255, 0),
	'b': (100, 0, 255),
	'g': (255, 0, 0),
	'pk': (40, 255, 70),
	'o': (50, 255, 0),
	'br': (180, 180, 180),
	'p': (0, 60, 255),
	'y': (150, 255, 0)
}

combined with a colormap, it makes it easy to know which light is what. If you need to light up #117 for example, this will tell you the color.

Example 1 (train lines)

Finally lets look at a couple of scripts for the lights.

Starting simple, to display all of the lights in the correct station color, we just load in the colormap, and turn them all on:

for i in range(length):
	pixels[i] = colorMap[colorOrder[i]]

result:

Example 2 (moving trains)

To make the animated lines move like a train, just write down the numbers of the lights you would like to go to in order according to the light number diagram above.

Here’s the example of the different lines:

orders = {
	'order': ['r', 'b', 'g', 'o', 'pk', 'br', 'p'],
	'r': [1,2,3,4,5,18,17,16,15,30,31,155,191,185,107,108,109,104,114,118,119,120,121,123,124,125,126,127,128,129,130,137,138],
	'b': [46,47,48,49,50,51,52,53,54,34,33,32,157,156,166,184,72,73,74,75,76,77,78,79,80,81,82,83,87,84,86,85],
	'g': [7,6,20,19,21,22,23,24,25,26,27,29,193,189,182,173,58,59,60,71,61,70,62,69,63,68,64,67,65,66],
	'o': [8,9,10,11,12,13,14,28,153,160,164,168,174,181,188,194,28,14,13,12,11,10,9,8],
	'pk': [45,44,43,42,41,40,39,38,37,36,35,55,56,57,172,183,190,192,154,161,165,167,57,56,55,35,36,37,38,39,40,41,42,43,44,45],
	'br': [88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,110,105,176,170,162,158,151,196,186,179,178,176,105,110,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88],
	'p': [150,149,148,147,146,143,145,144,139,122,117,116,115,113,112,111,106,177,175,180,187,195,152,159,163,169,177,106,111,112,113,115,116,117,122,139,144,145,143,146,147,148,149,150],
	'y': [140,141,142],
	's': [131,133,134,135,136]
}

Then just cycle through them, turning them off and on according to the colors in the color map.

while True:
	pixels.fill((0,0,0))
	on = []
	i = 0
	for color in orders['order']:
		for light in orders[color]:
			pixels[light - 1] = colorMap[colorOrder[light - 1]]
			on.append(light)
			i = i + 1

			if i > size - 1:
				pixels[on[i-size] - 1] = (0,0,0)
			
			sleep(delay)

The ‘size’ is set to 8, so the length of the moving trains is 8 lights long.

result:

Example 3 (Live map)

Finally, the most interesting example is getting the data from the server and showing live positions of trains. Server code in part 1.

1) get data from server.

while True:
  res = requests.get(API_ENDPOINT);
  trains = res.json()

2) Filter duplicate trains (caused some problems)

lights = []
for i in range( len(trains) ):
  if 'number' in trains[i]: # check to remove duplicates
    if trains[i]['number'] not in lights: # check to remove duplicates
      lights.append( trains[i]['number'] )

3) Determine which lights to turn off using ‘old’ — which is the positions of the trains from the last poll

off = []
for i in range( len(old) ):
  if old[i] not in lights:
    off.append(old[i])

4) Turn those lights off

for i in range( len(off) ):
    pixels[off[i] - 1] = (0, 0, 0)

5) Turn the new lights on

for i in range( len(lights) ):
    pixels[lights[i] - 1] = colorMap[colorOrder[lights[i] - 1]]

6) Save current position of lights for next time, do that again in 30 seconds (”interval”)

old = lights
j = j + 1
sleep(interval)

And there it is 👏

Theres a few more scripts I use on there, like a cool rainbow, or just setting the lights to orange to use as a lamp.

-Hew