DEV Community

Cover image for TeamCity BatSignal - Powered by RaspberryPi
TJ
TJ

Posted on

TeamCity BatSignal - Powered by RaspberryPi

TL;DR: My amazing fiancé brought home a cheap Target find - a two-tone LED light in the shape of a bat, and I turned it into a Bat-Signal for our TeamCity Build server using a Raspberry Pi.

Unaltered Bat Light

The Need/Why:

Our Beta Environment has multiple deploys throughout the day which takes the environment down for approximately 10 minutes or so. I don't always pay attention to the chats or have the TeamCity site open, so I have been wanting to create a visual indicator for the longest time. This cheap little neon-lookalike light was the perfect excuse to make this into a reality.

The Tools:

So, here are the things I used:

Product/Tool Price/Details
Raspberry Pi Zero W Kit by Vilros (Pi Zero W, Headers, Power Supply, Case) $26.99 - Amazon
SanDisk 16GB microSD Card $5.79 - Amazon
LED Bat Light - from Target (Dollar Section - near the front) $5.00
SunFounder 2 Channel DC 5V Relay Module $6.79 - Amazon
Female Jumper Cables (just need 4 of them) $5.99 - Amazon
Soldering Iron the cheapest one would do - get one with a little kit
Helping Hands nice tool to have during soldering

You do not have to use the same parts - just use something comparable.

E.g. for the light, you could follow the same instructions for any light powered by 3 AA batteries. You could use one with 2AA batteries and use the 3.7V output from the Pi instead of the 5V being used here.

The Initial Plan:

I wanted to create a web-hook (API) where TeamCity could post messages to about the build and it would trigger the respective lighting sequence. I figured I'd use ngrok to expose the url since this would be living at work on an internal public WiFi -- this negates the need to set up a Dynamic DNS, set up port-forwarding, etc. However, I came to find out that we didn't have the plug-in for web-hooks on our TeamCity set up. I could set up a Teams web-hook, but then I'd have to parse all the data to figure out what it did in certain situations and I just didn't have the patience to wait. I needed this done that day.

The Final Plan:

My backup plan was the RESTFul API that TeamCity has and to poll that every minute or so. It would only take a few minutes to write up a script and it's only polling the status every minute - so I'd have my desired effect of it being almost realtime and it wouldn't be too detrimental to the server.

Since our WiFi password changes quite often, I wanted a solution where I could easily change the password without messing with it too much.

The How:

Step 1: Prep the OS

Download the BerryLan flavored Raspbian image. BerryLan has an amazing solution where it sets up a Bluetooth Server when your WiFi is unavailable. They have a mobile app for iOS & Android that you can use to connect to your Pi to set up the WiFi - completely headless without a line of code.

If you do not have a need for changing WiFi passwords too often or were planning to use Ethernet, you could skip this step and download plain-Raspbian. If you want a headless WiFi setup without BerryLan, here's a quick guide.

Download Etcher to flash the OS image to the microSD card.

Step 2: Prep the Pi

Solder the 40-Pin Header onto your Pi Zero W. If you need instructions, this site seems to have some good information and infographics on it.

Used a rubber band to hold the pin headers in place while I soldered the four corners

First Row of Pi Pins Soldered

Pi Pins Soldering Complete

Plug your microSD card, your micro-usb power supply (the outer micro-usb port) and wait for the Pi to boot up.

Step 3: Basic Pi Setup

Download and open the BerryLan app, if you went with that flavor. Look for "BT WLAN setup", select your WiFi, enter your password and wait for it to connect. The app will display your IP Address, which we will need to connect in our next steps.

Using PuTTY, Termius or any similar ssh tool, connect to that IP address with pi/raspberry as its credentials.

First things first - change the password. Enter passwd and then follow the instructions to change your password.

Enter sudo raspi-config to change some configuration -

  1. Expand File System
  2. Change Time Zone / Locale

Enter sudo apt-get update followed by sudo apt-get upgrade. This will ensure everything is up-to-date.

Finally, enter sudo shutdown -h now and it will cause your Pi to shutdown safely.

Step 4: Hardware Setup (The LED Light)

Let's pry open the LED Light to see what the innards look like. We will target where the batteries are stored. Use a thin tool to give you some leverage to easily open it up. Inside, it had a very simple circuitry - battery powered the series connection with a switch & resistor in between to toggle the connection.

Battery Base Opening With Tool

LED Light Wires

Use your soldering tool to disconnect the wires from the toggle switch and battery terminals - simply touch the connections for some time and it will loosen up.

Step 5: Hardware Setup (The Pi, Relay & LED Light)

For this, let's start off by looking at the GPIO Pin Layout - https://pinout.xyz/pinout/io_pi_zero

Raspberry Pi Pin Layout

We will be needing a 3.3V/5V to power the Relay, a Ground Pin for the Relay, a 5V to power the LED Lights and a Trigger (GPIO Pin) to turn on/off the LED lights. I went with Pin 1 (3.3V) & Pin 6 (Ground) for the Relay, Pin 4 (5V) to power the LEDs and Pin 33 (BCM 13) to trigger the relay.

  • Pin1 (3.3V) was connected to the Relay at VCC
  • Pin6 (Ground) was connected to the Relay at GND next to IN1
  • Pin 33 (BCM 13) was connected to the Relay at IN1 - we will be controlling the Relay marked as 1.
  • Pin4 (5V) was connected to Relay 1.
  • LED Light - Live Wire was connected to Relay 1.
  • LED Light - Ground Wire was connected to the GND port across from the other GND port on the relay

LED Light Wired with Pi and Relay

Step 6: Quick Test

Now that we have everything connected, let's take a quick look to see if everything works -

We will create a little script to turn on and to turn off your lights.

Enter nano led_turnOn.py (the part before .py is the name of your file - feel free to name it as you wish)

This will open a text editor called nano. Copy or enter the following -

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(13, GPIO.OUT)
print "LED - turned on"
GPIO.output(13, GPIO.LOW)
Enter fullscreen mode Exit fullscreen mode

Save the file by pressing Control + X and then type in Y to confirm.

Now, lets create a similar one to turn off your lights -

Enter nano led_turnOff.py

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(13, GPIO.OUT)
print "LED - turned off"
GPIO.output(13, GPIO.HIGH)
Enter fullscreen mode Exit fullscreen mode

Save the file by pressing Control + X and then type in Y to confirm.

If you notice in the code snippet, we are simply changing the GPIO Output from Low to High to trigger a voltage change that will eventually cause the relay to toggle the lights programatically.

Enter python led_turnOn.py to check if your lights work and then turn them off by entering python led_turnOff.py.

Step 7: Final Script

Now, we need some packages to help us call the API and for the Pi to not error out during SSL handshakes (apparently it was having issues generating random numbers) -

Enter sudo apt-get install python-requests, followed by sudo apt-get install rng-tools.

After they are installed, we will now complete our final script.

I had added support to only check for statuses during weekdays and between 6am and 9pm. Feel free to change those numbers as you desire.

Enter nano buildCheck.py

Copy the following and replace the variables as needed (buildType, apiUrl, username, password):

import requests
import RPi.GPIO as GPIO
import datetime
import time

# Initialize GPIO Pins and Turn LED off
# Setting Layout to GPIO Pin Number not Board Pin Number
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# GPIO 13 is the chosen one
GPIO.setup(13, GPIO.OUT)

# Time Check to only execute this process during the day and on weekdays
now = datetime.datetime.now()
start = datetime.time(6, 00)
end = datetime.time(21)
print('Current DateTime: ' + str(now))

if now.weekday() > 4:
    print('enjoy your weekend')
    GPIO.output(13, GPIO.HIGH)
    quit()
elif now.time() < start:
    print('enjoy your sleep')
    GPIO.output(13, GPIO.HIGH)
    quit()
elif now.time() >= end:
    print('stop working')
    GPIO.output(13, GPIO.HIGH)
    quit()

# Setting UP API Client to call TeamCity Build Server
buildType = 'insertBuildTypeHere'
apiUrl = 'https://insertUrlHere.com'
username = 'insertUsernameHere'
password = 'insertPasswordHere'
payload = {'locator': 'running:true,buildType:' + buildType}
headers = {'Accept': 'application/json'}

print('Calling TeamCity')
try:
    # Get Pending Build Count
    r1 = requests.get(apiUrl + '/app/rest/10.0/builds/', params=payload,
                      auth=(username, password), headers=headers, timeout=10, verify=False)
    if (r1.status_code == requests.codes.ok):
        print('Pending Build Count - Status 200 OK')
        jsonResponse1 = r1.json()
        print('Pending Build Count - ' + str(jsonResponse1['count']))
        if (jsonResponse1['count'] > 0):
            # Pending Build - Turn Light On
            GPIO.output(13, GPIO.LOW)
        else:
            # Check Status of Last Build
            r2 = requests.get(apiUrl + '/app/rest/10.0/builds/buildType:' + buildType,
                              auth=(username, password), headers=headers, timeout=10, verify=False)
            print('TeamCity Response Received')
            # Take Action based on Response Code & Build Status in the Response Message
            if (r2.status_code == requests.codes.ok):
                print('Status 200 OK')
                jsonResponse2 = r2.json()
                print('Build Status: ' + jsonResponse2['status'])
                if (jsonResponse2['status'] == 'SUCCESS'):
                    GPIO.output(13, GPIO.HIGH)
                else:
                    for x in range(0, 10):
                        GPIO.output(13, GPIO.LOW)
                        time.sleep(1)
                        GPIO.output(13, GPIO.HIGH)
                        time.sleep(1)
                    GPIO.output(13, GPIO.LOW)
                    time.sleep(1)
            else:
                # Morse Code for API errors -
                print('ERROR: ' + str(r2.status_code))
                print('Response: ' + r2.text)
                for x in range(0, 3):
                    GPIO.output(13, GPIO.LOW)
                    time.sleep(1)
                    GPIO.output(13, GPIO.HIGH)
                    time.sleep(1)
                for x in range(0, 3):
                    GPIO.output(13, GPIO.LOW)
                    time.sleep(2)
                    GPIO.output(13, GPIO.HIGH)
                    time.sleep(2)
                for x in range(0, 3):
                    GPIO.output(13, GPIO.LOW)
                    time.sleep(1)
                    GPIO.output(13, GPIO.HIGH)
                    time.sleep(1)
except:
    # Unhandled Exception - Just 3 LED Blinks
    print('Unhandled Exception')
    for x in range(0, 3):
        GPIO.output(13, GPIO.LOW)
        time.sleep(1)
        GPIO.output(13, GPIO.HIGH)
        time.sleep(1)
    raise

Enter fullscreen mode Exit fullscreen mode

I'd highly recommend using a set of credentials with minimal access or to safeguard the Pi physically since your password is being stored, unencrypted, on a memory card.

Save the file by pressing Control + X and then type in Y to confirm.

Now, let's create a task to run this every minute -

Enter sudo crontab -e

If you want logging add the following at the end:
*/1 * * * * python /home/pi/buildCheck.py >> /home/pi/buildCheckLog.txt 2>&1

Otherwise, add this: */1 * * * * python /home/pi/buildCheck.py

Step 8: Cleanup

Relay

Feel free to use your creativity to fit the relay and the cables inside the base of your light. I used a drill (did not have a Dremel) to make some holes and then used a set of pliers to shave off the battery holder on the bottom. This gave it quite a bit of space for me to put everything inside of it, but I chose to keep the Pi outside for ease of powering it and to keep it cool.

Finish it off with some velcro or hot-glue and you are all set!

Enjoy the fruits of your labor and watch out for that Bat-Signal!

Completed TeamCity Bat-Signal

Top comments (0)