# GitHub Actions Status Tower Light

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/112/711/medium800/leds_GitHub_logo_with_mark.png?1656453470)

GitHub is a web service that keeps track of code and files, designed to enable folks to collaborate on projects. [Nearly all of Adafruit's coding projects and hardware files are hosted on GitHub&nbsp; (we have ~1600 repos!)](https://github.com/adafruit). Since Adafruit publishes open source hardware and software, this works great to share the designs and also get feedback and improvements from the community. Community members can even find bugs or add new features, and submit those back to Adafruit so that _everyone_ can benefit from the effort!

Many open source projects (or projects in general!) have standards and formatting styles to which all submitted code must adhere. The process of submitting bug fixes or new features to an existing GitHub repository often involves creating a pull request. Continuous Integration (CI) enables you to verify on GitHub that incoming code follows these standards. One form of CI is called GitHub Actions.

[GitHub Actions is a Continuous Integration / Continuous Delivery (CI/CD) platform](https://github.com/features/actions) that, among many other things, allows you to automate testing and building incoming code. You create workflows that build and test every pull request. When a pull request is submitted, it automatically triggers an Actions run. Actions goes through all of the checks within the workflows. If there are errors, it will fail. If there are no errors, it will pass. Once passing, a maintainer will be able to merge your PR into their project.

As it is, to keep track of the Actions status of a PR, you would need to keep it open in a visible tab in your browser, and check on it periodically to find out where it's at. Not anymore!

You can easily set up a **GitHub Actions Status Tower Light** to place on your desk.&nbsp; **No soldering or wiring is required for this project** [thanks to a handy USB-powered tower light we stock](https://www.adafruit.com/product/5125) that works with any/all computers and operating systems. Simply plug in the tower light, run the code on your computer, and you're ready to go!

- It lights up blinking yellow when the Actions run is queued
- It lights up solid yellow once Actions is running
- When it completes, it will beep.
- If it fails, it will light up red along with the beep.
- If it passes, it will light up green along with the beep.
- If it is cancelled, it will blink red along with the beep.

Of course, all these can be configured to your desire! This guide will show you how to set up the tower light, as well as walk you though the code needed to keep track of your Actions status.&nbsp;

There are a few prerequisites for this guide.

## GitHub Requirements

The repository you intend to track must have some sort of GitHub Actions workflow enabled on it. The program used in this project uses the GitHub API to access workflow statuses. You can update which workflow file will trigger the light, but you must have at least one enabled.

## Required Software

This guide assumes you have Python installed on your computer, including `pip` for installing the required libraries.

It is also assumed that you have a basic understanding of how to run Python programs from your computer.

## Required Hardware
### Tri-Color USB Controlled Tower Light with Buzzer

[Tri-Color USB Controlled Tower Light with Buzzer](https://www.adafruit.com/product/5125)
With this **Tri-Color USB Controlled Tower Light with Buzzer,** you can easily monitor and alert humanoids to the status of a project, machine, or even if the bathroom is occupied!

Unlike our other [tower...](https://www.adafruit.com/?q=tower+light&sort=BestMatch)

In Stock
[Buy Now](https://www.adafruit.com/product/5125)
[Related Guides to the Product](https://learn.adafruit.com/products/5125/guides)
![Video of tall white USB Tower Lamp flashing green, yellow, red LEDs.](https://cdn-shop.adafruit.com/product-videos/640x480/5125-04.jpg)

# GitHub Actions Status Tower Light

## Tower Light Set Up

There are two steps needed to get started with the tower light: installing the USB serial driver, and plugging in the light.

## USB Serial Driver Installation

Inside the tower light is a microcontroller connected over a CH43x USB-to-UART chip. To access the tower light over USB serial, you will need to install the CH43x driver.&nbsp;

The driver is available for Mac and Windows. It is already built into Linux.

Download the driver below for your operating system and install it. If you would like more detail, check out [the guide on installing this driver](https://learn.adafruit.com/how-to-install-drivers-for-wch-usb-to-serial-chips).

[Click here to download the Windows driver](http://www.wch-ic.com/downloads/CH341SER_ZIP.html)
[Click here to download the Mac driver](http://www.wch-ic.com/downloads/CH34XSER_MAC_ZIP.html)
## Plug in the Tower Light

Physical set up of the tower light is incredibly simple. The tower light is controlled over USB via a serial port, directly from your computer.

To get started, simply plug the USB cable attached to the tower light into a USB port on or attached to your computer.

That's it!

# GitHub Actions Status Tower Light

## Code Requirements

There are a couple of things that you need to do before you can run the code for this project. This page covers how to complete the requirements.

# GitHub Personal Access Token

To access the Actions status through the GitHub API, you must have a GitHub Personal Access Token. This section will show you how to generate a new token, and how to make it available to your code.

## Generate Your Personal Access Token

Visit [GitHub](https://github.com/) while logged into your account. Then follow the instructions below.

Info: 

- In the upper right corner, you'll find a **small version of your avatar that is a menu**. Click the dropdown menu.
- Towards the bottom of the menu, you'll find a **Settings** button. Click **Settings**.

![leds_GASL_GitHub_click_Settings.png](https://cdn-learn.adafruit.com/assets/assets/000/112/724/medium640/leds_GASL_GitHub_click_Settings.png?1656462126)

- The left sidebar begins with Public Profile selected. Scroll down the page to the bottom of the sidebar. The last item in the list is **Developer settings**. Click **Developer Settings**.

![leds_GASL_click_Developer_settings.png](https://cdn-learn.adafruit.com/assets/assets/000/112/725/medium640/leds_GASL_click_Developer_settings.png?1656462267)

- The sidebar in Developer Settings is quite short. Click the last item in the sidebar, **Personal access tokens**.

![leds_GASL_click_Personal_access_tokens.png](https://cdn-learn.adafruit.com/assets/assets/000/112/726/medium640/leds_GASL_click_Personal_access_tokens.png?1656462429)

Once you're on the Personal access tokens page, click **Generate new token**.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/727/medium800/leds_GASL_click_Generate_new_token.png?1656462667)

You may be asked to verify your access to this account via password or, if enabled, two-factor authentication. Simply verify to continue.

The New Personal Access Token page contains a couple of items for you to update.

- **Add a Note** - Though not necessary, you can provide a short note as to what the token is for. This helps differentiate it from other tokens. In this case, **GitHub Actions Status Light** would be sufficient. Feel free to change it up to whatever works for you.
- **Verify the Expiration** - The token is set to expire in 30 days by default. Depending on your needs, you can increase or decrease the expiration by clicking the dropdown menu and choosing a different expiration option, or clicking **Custom...** and providing an expiration date.

Warning: 

![](https://cdn-learn.adafruit.com/assets/assets/000/112/729/medium800/leds_GASL_New_token_set_up_options.png?1656463420)

The next section allows you to set specific scopes for your token. In some cases, you will need to allow certain scopes for your token to work with your project. As this project is written, a personal access token with no scope provided will work fine. Skip all the checkboxes.

At the bottom of the page, click the green **Generate token** button.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/731/medium800/leds_GASL_click_Generate_token.png?1656463631)

GitHub will generate your personal access token, return you to the Personal Access Tokens page, and display your new token for you to copy.

Info: 

![](https://cdn-learn.adafruit.com/assets/assets/000/112/759/medium800/leds_GASL_generated_token.png?1656467047)

Danger: 

Now that your token is generated and copied to your clipboard, it's time for the next step.

# Create and Update Your Token Environment Variable

Follow the steps based on which operating system you're using.

## Mac OS / Linux

In the directory from which you intend to run the code, create a file called **.env.sh**. Being in the same directory as the code is not a requirement. It does, however, make it quicker to `source` the file when you're ready to run your code, as you'll already be in the directory containing your code.

Open the **.env.sh** file in an editor of your choice (a text editor will be sufficient), and add the following line to the top of the file.

`export GITHUB_API_TOKEN="ghp_yourtokenvaluehere"`

Replace `ghp_yourtokenvaluehere` with your newly generated personal access token, retaining the quotation marks around it.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/773/medium800/leds_GASL_env_sh_contents.png?1656519247)

Save your changes to the file.

From a command line in a terminal window, run the following:

`source .env.sh`

![](https://cdn-learn.adafruit.com/assets/assets/000/112/772/medium800/leds_GASL_source_env_sh.png?1656518142)

You've now made your personal access token available to the code.

## Windows

Begin typing **environment** into the Windows search. You'll find **Edit the system environment variables**. Click it in the search list, and click **Open** to continue.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/745/medium800/leds_GASL_windows_environment_search.png?1656466411)

Click the **New...** button to open the **New User Variable** window. Fill in the following:

- **Variable name:** = **GITHUB\_API\_TOKEN**
- **Variable value:** = **ghp\_yourtokenvaluehere**

Then click **OK****.**

![](https://cdn-learn.adafruit.com/assets/assets/000/112/753/medium800/leds_GASL_Windows_set_environment_variable.png?1656466551)

You've now made your personal access token available to the code.

You're now ready to run the code and access the GitHub API successfully!

# GitHub Actions Status Tower Light

## Code Configuration

You'll begin by copying the following code to a file on your computer called, for example, **actions\_status.py**. (The filename does not really matter, but this page will assume the file is named **actions\_status.py**.)

Click the **Copy code** link at the top left of the code element below, and paste it into the new file on your computer. For your convenience, this new file should be in the same directory as your **.env.sh** file, if you used that method to create your personal access token environment variable.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/GitHub_Actions_Status_Tower_Light/actions_status.py

# Code Configuration

Early in the code you'll find a series of options for you to configure. This section will walk through each one and provide details about its purpose, and how you can configure it to fit your needs.

## Workflow URL

You can configure the repo workflow URL, which is saved to `REPO_WORKFLOW_URL` in the code. This is the URL to Actions runs for a specific workflow file on a specific repository. It defaults to the Adafruit CircuitPython repository **build.yml** file.

```python
REPO_WORKFLOW_URL = "https://api.github.com/repos/adafruit/circuitpython/actions/workflows/build.yml/runs"
```

There are three elements within the URL that you will likely want to update:

- User
- Repository
- Workflow file

Below is the same line of code with the configurable sections identified.

```python
REPO_WORKFLOW_URL = "https://api.github.com/repos/{USER}/{REPOSITORY}/actions/workflows/{WORKFLOW_FILE}/runs"
```

To follow Actions statuses on a repository on your own account, you want to replace `{USER}` with your GitHub user name, `{REPOSITORY}` with your local repository, and `{WORKFLOW_FILE}` with the specific workflow file you'd like to use to trigger the status light.

Remember, you must have at least one GitHub Actions workflow enabled on the given repo for this project to work. Workflow files can be found within your repo in the **/.github/workflows/** directory.

## API Query Interval Duration

You can choose the interval at which the program will query the GitHub API for updates.&nbsp; `POLL_DELAY` is a duration in seconds. As shown below, it defaults to `60 * 3` seconds, or three minutes.

```python
POLL_DELAY = 60 * 3
```

You can increase or decrease this value to fit the typical duration and frequency of Actions runs on your chosen repository. For example, if you usually have short Actions runs that occur every few minutes, you might want to decrease this to 15 or 30 seconds to ensure you catch the statuses of the runs while they're active. If your Actions runs typically take an hour, and occur a few times per day, you may want to increase this to 20 minutes. You can tweak it to figure out what works best for you.

## Status Light Duration

You can choose the duration the light will remain on after getting a **completed** status response from the GitHub API query. `COMPLETION_LIGHT_TIME` is a duration in seconds, which, as shown below, defaults to `30`.

```python
COMPLETION_LIGHT_TIME = 30
```

When a _completed_ status is returned from the API for the current Actions run, the light will respond by turning on red or green depending on the conclusion. The red or green light will stay on for the duration you choose here.

Increase or decrease this value to fit what works best for you.

## Buzzer Sound Duration

You can choose the duration the buzzer will sound after getting a **completed** status response from the API query. `COMPLETION_BUZZER_TIME` is a duration in seconds, which, as shown below, defaults to `1`.

```python
COMPLETION_BUZZER_TIME = 1
```

Warning: 

When a _completed&nbsp;_status is returned from the API for the current Actions run, the buzzer will sound for both passing or failing conclusions, e.g. the buzzer sounds when the light turns on red or green. The duration of the buzzer is configurable by you.

You can set this variable to 0 to disable the buzzer completely, if you'd rather.

## Enable USB Tower Light

This code is intended to be run with a USB Tower Light attached to your computer. However, it was written to be able to run without the hardware involved. The code defaults to expecting the hardware, with the following variable being set to `True`.

```python
ENABLE_USB_LIGHT_MESSAGES = True
```

If you simply want to test whether your setup is working to enable you access to the GitHub Actions API, you can set `ENABLE_USB_LIGHT_MESSAGES = False` to disable sending the light commands in the code. Statuses will be printed to the serial console if this is disabled.

## Serial Port

When you connect the tower light to your computer via USB (as long as you've installed the drivers!), it will show up on a serial port. You'll want to identify what that port is, and update `serial_port` to match.

### Windows

On Windows, the serial port will be `COM **`, where `** ` is a number. Click [here](https://learn.adafruit.com/welcome-to-circuitpython/advanced-serial-console-on-windows#whats-the-com-2977217) if you need help figuring out where to find the COM port. Once identified, you'll need to do two things in the code. First, **comment out** the Mac/Linux `serial_port =` line by adding a `#` to the beginning of the line. Second, **uncomment** the Windows `serial_port =` line, and update it to match the COM port from your tower light.

Your code would look similar to the following.

```python
serial_port = "COM57"
# Mac/Linux will be a /dev/** path to the serial port. (...)
# serial_port = "/dev/tty.usbserial-144430"
```

### MacOS/Linux

On MacOS and Linux, the serial port will be a **/dev/\*\*** path. If you're struggling to find it, check the contents of **/dev/** with the tower light unplugged, and then check them again with the light plugged in. The new item in **/dev/** will most likely be the tower light serial port. Once Identified, update the serial\_port = line to match the serial port from your tower light.

On MacOS, your code would look similar to the default code.

On Linux, your code may look similar to the following.

```python
serial_port = "/dev/USBserial0"
```

That's it to configure! Now you're ready to run the code.

# GitHub Actions Status Tower Light

## Code Usage

Before continuing, ensure that you completed the [Code Requirements](https://learn.adafruit.com/github-actions-status-tower-light/code-requirements). If you haven't already saved the file, return to the beginning of the [Configuration](https://learn.adafruit.com/github-actions-status-tower-light/code-configuration) page and follow the instructions there.

Open a terminal program on your computer, and navigate to the directory in which you saved **actions\_status.py** during [Code Configuration](https://learn.adafruit.com/github-actions-status-tower-light/code-configuration).

From the command line, run the program. When first running the program, your serial console should look something like the following.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/790/medium800/leds_GASL_beginning_of_actions_status_dot_py.png?1656707461)

# Workflow Run Statuses

There are four possible responses when the workflow run status is fetched:

1. queued
2. in progress
3. completed - success
4. completed - failure
5. completed - cancelled

Each response triggers a different set of actions for the tower light.

## Queued
![](https://cdn-learn.adafruit.com/assets/assets/000/112/845/medium800/leds_GASL_list_Actions_run_queued.png?1657056094)

![](https://cdn-learn.adafruit.com/assets/assets/000/112/846/medium800/leds_GASL_Actions_run_queued.png?1657056103)

When the status is queued, it will print to the serial console.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/847/medium800/leds_GASL_code_Actions_run_queued.png?1657056120)

The light will begin blinking yellow. This will continue as long as the current Actions run remained queued.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/848/medium800thumb/leds_GASL_Light_Actions_queued.jpg?1657056138)

## In Progress
![](https://cdn-learn.adafruit.com/assets/assets/000/112/849/medium800/leds_GASL_list_Actions_run_in_progress.png?1657056158)

![](https://cdn-learn.adafruit.com/assets/assets/000/112/850/medium800/leds_GASL_Actions_run_in_progress.png?1657056167)

When the status is in progress, it will print to the serial console.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/851/medium800/leds_GASL_code_Actions_run_in_progress.png?1657056178)

The light will turn on solid yellow. This will continue as long as the current Actions run remained in progress.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/852/medium800/leds_GASL_light_Actions_in_progress.jpg?1657056187)

## Completed - Success
![](https://cdn-learn.adafruit.com/assets/assets/000/112/853/medium800/leds_GASL_list_Actions_run_success.png?1657056273)

![](https://cdn-learn.adafruit.com/assets/assets/000/112/854/medium800/leds_GASL_Actions_run_success.png?1657056303)

When the status is completed - success, the ID for that Actions run will be added to the already-shown workflow ID list.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/855/medium800/leds_GASL_code_Actions_run_success.png?1657056310)

The light will turn green and the buzzer will sound. The length of time for the light to remain on and the buzzer to sound was determined by you in configuration. After the chosen duration, each will turn off.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/856/medium800/leds_GASL_light_Actions_run_success.jpg?1657056318)

## Completed - Failure
![](https://cdn-learn.adafruit.com/assets/assets/000/112/857/medium800/leds_GASL_list_Actions_run_failure.png?1657056408)

![](https://cdn-learn.adafruit.com/assets/assets/000/112/858/medium800/leds_GASL_Actions_run_failure.png?1657056420)

When the status is completed - failure, the ID for that Actions run will be added to the already-shown workflow ID list.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/859/medium800/leds_GASL_code_Actions_run_failure.png?1657056428)

The light will turn red and the buzzer will sound. The length of time for the light to remain on and the buzzer to sound was determined by you in configuration. After the chosen duration, each will turn off.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/860/medium800/leds_GASL_light_Actions_failure.jpg?1657056444)

## Completed - Cancelled
![](https://cdn-learn.adafruit.com/assets/assets/000/112/844/medium800/leds_GASL_list_Actions_run_cancelled.png?1657055880)

![](https://cdn-learn.adafruit.com/assets/assets/000/112/842/medium800/leds_GASL_Actions_run_cancelled.png?1657055852)

When the status is completed - cancelled, the ID for that Actions run will be added to the already-shown workflow ID list.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/843/medium800/leds_GASL_code_Actions_run_cancelled.png?1657055865)

The light will blink red and the buzzer will sound. The length of time for the light to remain on and the buzzer to sound was determined by you in configuration. After the chosen duration, each will turn off.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/838/medium800thumb/leds_GASL_light_Actions_cancelled.jpg?1657055745)

# Already Followed

If the latest Actions run reached either completed status, and the code fetches again, it will simply print to the serial console, "Already followed the current run." This will continue until a new Actions run is triggered, at which point, it will return one of the four statuses above, and the light (and maybe the buzzer) will turn on as explained.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/836/medium800/leds_GASL_already_followed_run.png?1657055701)

# Exiting the Code

Press CTRL+C to exit the code. You will see the following printed to the serial console.

![](https://cdn-learn.adafruit.com/assets/assets/000/112/837/medium800/leds_GASL_exiting_code.png?1657055717)

# GitHub Actions Status Tower Light

## Code Walkthrough

You've gotten the code up and running. Excellent! However, you may be wondering exactly what's going on with the code. This page will walk you through the code, section by section, and explain what's happening.

# Set Up Code

First, you'll go through the sections of code related to set up, found at the beginning of the file.

## Imports and Configuration

The code begins with importing the necessary libraries and modules.

```python
import json
import time
import serial
import requests
import os
```

Imports are followed by the configuration section explained in the previous page of this guide. You should have already configured this section to meet your needs and your choice of projects.

```python
REPO_WORKFLOW_URL = "https://api.github.com/repos/adafruit/circuitpython/actions/workflows/build.yml/runs"
POLL_DELAY = 60 * 3
COMPLETION_LIGHT_TIME = 30
COMPLETION_BUZZER_TIME = 1
ENABLE_USB_LIGHT_MESSAGES = True

# serial_port = "COM57"
serial_port = "/dev/tty.usbserial-144430"
```

## Tower Light Constants and Baud Rate

The USB tower light is controlled over a serial connection from your computer. To turn on the lights and make sound with the buzzer, you need to send a command using a hex value constant. Assigning these constants to variables makes it much easier to identify which value does what, and makes the code significantly more readable. Therefore, the code includes a list of those constants and their variables.

```python
RED_ON = 0x11
RED_OFF = 0x21
RED_BLINK = 0x41

YELLOW_ON = 0x12
YELLOW_OFF = 0x22
YELLOW_BLINK = 0x42

GREEN_ON = 0x14
GREEN_OFF = 0x24
GREEN_BLINK = 0x44

BUZZER_ON = 0x18
BUZZER_OFF = 0x28
BUZZER_BLINK = 0x48
```

Serial communication requires you to set a baud rate, which is the speed of the serial communication. This is done here.

```python
baud_rate = 9600
```

## Convenience Functions

There are three functions utilised in this example.

The first function enables you to send a command. It requires you to provide a serialport (defined later in this example) and a command.

```python
def send_command(serialport, cmd):
    serialport.write(bytes([cmd]))
```

The second function turns off all of the lights and the buzzer, allowing you to reset the state of the tower light where needed.

```python
def reset_state():
    # Clean up any old state
    send_command(mSerial, BUZZER_OFF)
    send_command(mSerial, RED_OFF)
    send_command(mSerial, YELLOW_OFF)
    send_command(mSerial, GREEN_OFF)
```

The third function includes code to turn the buzzer on for a period of time defined in the configuration section, if the buzzer is not disabled (e.g. `COMPLETION_BUZZER_TIME` set to 0 in configuration). This function is included simply to avoid duplicate code where the buzzer is used.

```python
def buzzer_on_completion():
    if COMPLETION_BUZZER_TIME > 0:
        send_command(mSerial, BUZZER_ON)
        time.sleep(COMPLETION_BUZZER_TIME)
        send_command(mSerial, BUZZER_OFF)
```

## Workflow ID List

The code keeps track of completed workflows by adding the workflow ID number into a list, and querying that list so that it doesn't repeatedly notify you about completed workflows. You must create an empty list before you can add content to it.

```python
already_shown_ids = []
```

## HTTP Header for GitHub API

This code is required to be able to request data from the GitHub API. Your GitHub API Token should have been made available when you configured the environment variable. If you opted not to do this, you can paste it directly into the code, replacing `GITHUB_API_TOKEN` with your personal access token.

Warning: 

```python
headers = {'Accept': "application/vnd.github.v3+json",
           'Authorization': f"token {os.getenv('GITHUB_API_TOKEN')}"}
```

## Serial Connection

As this code can be run without the hardware (if you set `ENABLE_USB_LIGHT_MESSAGES = False` in the configuration stage), you do not necessarily want to create the serial connection if you're not going to be using it. So, to ensure the code runs, the `mSerial` variable is initially created as `None`. Then, if the tower light communication is enabled, it initializes the serial connection as `mSerial` for use later.

```python
mSerial = None
if ENABLE_USB_LIGHT_MESSAGES:
    print("Opening serial port.")
    mSerial = serial.Serial(serial_port, baud_rate)
```

## Starting Status Watcher

Finally, immediately before the main loop, two lines are printed to the serial console, informing you of the status watcher starting, and how to exit the program when you desire. The code will run in an infinite loop until you interrupt it by pressing **CTRL+C** on your keyboard.

```python
print("Starting Github Actions Status Watcher")
print("Press Ctrl-C to Exit")
```

# Main Loop

This section walks through all the code found in the main loop in this example.

Immediately before the main loop begins, you'll find a `try`.

```python
try:
    while True:
```

This is part of a `try`/`except` block surrounding the entire main loop. It will be explained in full at the end of this page.

## Fetch Workflow Data

Before the fetch begins, "Fetching workflow run status." is printed to the serial console, to let you know what comes next.

Then, you perform a GET request to the GitHub API to pull the current Actions workflow data.

This data can be returned in multiple ways. This example will use JSON, so following the request for current data, we access the response as JSON data.

Next, you open a file named&nbsp; **actions\_status\_result.json** to which to write the results, write the results to the newly opened file, and then close the file to complete the file write.

```python
[...]
        print("Fetching workflow run status.")
        response = requests.get(f"{REPO_WORKFLOW_URL}?per_page=1", headers=headers)
        response_json = response.json()
        f = open("action_status_result.json", "w")
        f.write(json.dumps(response_json))
        f.close()
```

## Actions Workflow ID Lookup

The next step is to look up the ID number for the current Actions workflow from the JSON data, and save it to a variable. This is necessary to keep track of workflow IDs later in the code.

```auto
[...]
    	workflow_run_id = response_json['workflow_runs'][0]['id']
```

## Workflow Status and Conclusion

Later in the code, if an Actions run is completed, the ID is added to the `already_shown_ids` list that was created towards the beginning. This is where that list is checked, to determine whether the latest Actions workflow ID is in that list.

If the current workflow ID is _not_ on the already shown list, the program continues on to the rest of the code nested beneath.

The code then looks up the current workflow's `status` and `conclusion`. The status can be **queued** , in **progress** or **completed**. The conclusion applies only to the completed status, and can be either **success** or **failure**. (Conclusion is `None` for queued and in progress.)

It then outputs the status and conclusion by printing to the serial console.

```python
[...]
        if workflow_run_id not in already_shown_ids:
            status = response_json['workflow_runs'][0]['status']
            conclusion = response_json['workflow_runs'][0]['conclusion']
            print(f"Status - Conclusion: {status} - {conclusion}")
```

## Status: Queued

If the status is queued, it prints the active status to the serial console. If the tower light is enabled, it prints the serial command being sent, and sends the serial command `YELLOW_BLINK`. This makes the tower light blink yellow. The yellow blinking will continue as long as the workflow is queued.

```python
[...]
            if status == "queued":
                print("Actions run status: Queued.")
                if ENABLE_USB_LIGHT_MESSAGES:
                    print("Sending serial command 'YELLOW_BLINK'.")
                    send_command(mSerial, YELLOW_BLINK)
```

## Status: In Progress

If the status is in progress, it prints the active status to the serial console. If the tower light is enabled, it prints the serial command being sent, and sends the serial command `YELLOW`. This makes the light turn yellow. The yellow light will continue as long as the workflow is in progress.

```auto
[...]
            if status == "in_progress":
                print("Actions run status: In progress.")
                if ENABLE_USB_LIGHT_MESSAGES:
                    print("Sending serial command 'YELLOW_ON'.")
                    send_command(mSerial, YELLOW_ON)
```

## Status: Completed

If the status is completed, the first thing that happens is printing to the serial console that the workflow ID is being added to the already shown IDs list, and the workflow ID number is added to the list.

```python
[...]
            if status == "completed":
                print(f"Adding {workflow_run_id} to shown workflow IDs.")
                already_shown_ids.append(workflow_run_id)
```

## Conclusion: Success

If the completed status conclusion is success, it prints this info to the serial console. If the tower light is enabled, it first sends the command `YELLOW_OFF`, in the event that the yellow light is still on. Next, it prints to the serial console the next command being sent, and sends the command `GREEN_ON`.

Following that, it runs the `buzzer_on_completion()` function to turn the buzzer on for the duration you previously configured.

Then the code pauses for the duration of the buzzer subtracted from the duration you configured the light to remain on. This ensures that the light will remain on as long as you configured it for, regardless of how long you turn on the buzzer.

Finally, when the completion light time duration is up, it prints to the serial console the command it's sending, and sends the command `GREEN_OFF`.

```python
[...]
                if conclusion == "success":
                    print("Actions run status: Completed - successful.")
                    if ENABLE_USB_LIGHT_MESSAGES:
                        send_command(mSerial, YELLOW_OFF)
                        print("Sending serial command 'GREEN_ON'.")
                        send_command(mSerial, GREEN_ON)
                        buzzer_on_completion()
                    time.sleep(COMPLETION_LIGHT_TIME - COMPLETION_BUZZER_TIME)
                    if ENABLE_USB_LIGHT_MESSAGES:
                        print("Sending serial command 'GREEN_OFF'.")
                        send_command(mSerial, GREEN_OFF)
```

## Conclusion: Failure

If the completed status conclusion is failure, it follows the same set of steps as the success conclusion. The differences are the info printed to the serial console, and the commands being sent to the light. The printed info indicates the failure conclusion. The commands sent to the light are `RED_ON`, and the configured amount of time later, `RED_OFF`.

```python
[...]
                if conclusion == "failure":
                    print("Actions run status: Completed - failed.")
                    if ENABLE_USB_LIGHT_MESSAGES:
                        send_command(mSerial, YELLOW_OFF)
                        print("Sending serial command 'RED_ON'.")
                        send_command(mSerial, RED_ON)
                        buzzer_on_completion()
                    time.sleep(COMPLETION_LIGHT_TIME - COMPLETION_BUZZER_TIME)
                    if ENABLE_USB_LIGHT_MESSAGES:
                        print("Sending serial command 'RED_OFF'.")
                        send_command(mSerial, RED_OFF)
```

## Conclusion: Cancelled

If the completed status conclusion is failure, it follows the same set of steps as the success and failure conclusions. The differences are the info printed to the serial console, and the commands being sent to the light. The printed info indicates the cancelled conclusion. The commands sent to the light are `RED_BLINK`, and the configured amount of time later, `RED_OFF`.

```python
[...]
                if conclusion == "cancelled":
                    print("Actions run status: Completed - cancelled.")
                    if ENABLE_USB_LIGHT_MESSAGES:
                        send_command(mSerial, YELLOW_OFF)
                        print("Sending serial command 'RED_BLINK'.")
                        send_command(mSerial, RED_BLINK)
                        buzzer_on_completion()
                    time.sleep(COMPLETION_LIGHT_TIME - COMPLETION_BUZZER_TIME)
                    if ENABLE_USB_LIGHT_MESSAGES:
                        print("Sending serial command 'RED_OFF'.")
                        send_command(mSerial, RED_OFF)
```

## Already Followed and Query Delay

The code ends with an `else` block that is matched up with the `if workflow_run_id not in already_shown_ids:` line from the Workflow Status and Conclusion section above. If the `workflow_run_id` _is_ in the `already_shown_ids` list, then the code in the `else` block is run. It simply prints to the serial console, "Already followed the current run." to let you know that no new Actions run has occurred since the completion of the previous one.

Then, no matter what, the code sleeps for the `POLL_DELAY` duration. This is the length of time between queries to the GitHub API, configured in the Configuration section of this guide.

```python
[...]
        else:
            print("Already followed the current run.")
        time.sleep(POLL_DELAY)
```

## `try` / `except` Block

As mentioned at the beginning of the Main Loop section, the entire main loop is wrapped in a `try` / `except`. If you recall, you must press CTRL+C to exit the program. This causes a `KeyboardInterrupt`, which can be caught by an `except`, enabling you to run code when exiting the program.

This block tries to run the main loop, and if a `KeyboardInterrupt` is detected, it instead runs the code within the `except`. This code prints the exiting status to the serial console, and runs the `reset_state()` function to turn off all the possible tower light actions (i.e. the LEDs and the buzzer).

```python
try:
[...]  # Main loop.
except KeyboardInterrupt:
    print("\nExiting Github Actions Status Watcher.")
    reset_state()
```

In this case, the problem was that without this block, if you exited the program while the tower light was doing something (e.g. the yellow light blinking, or green light and buzzer going off), the light would continue its current state after the program had exited. To terminate the continued tower light state, you would be required to unplug the light from your computer (to power it off) and plug it back in to be ready for the next time you run the code.

This solves that problem by using the `reset_state()` function to send the `*_OFF` commands for all possible light functionality. When you use CTRL+C to exit the program, the tower light will stop any current actions as well.

# GitHub Actions Status Tower Light

## 3D Printed Stand

You may have noticed that the tower light base doesn't have clearance for the USB cable to come out of the side of it, causing it to sit on an angle if placed on your desk or other flat surface. There are many ways to resolve this problem (including cardboard!). As a bonus, a 3D printed bolt-on stand is included here as an option.

Thank you to [Rose Hooper](https://github.com/rhooper/3d-models/tree/main/usb_tower_light) for designing this 3D printed stand!

The STL file can be downloaded by clicking the button below.

[USB Tower Light Stand](https://cdn-learn.adafruit.com/assets/assets/000/112/867/original/USB_Tower_Light_Stand.stl?1657056989)
## Suggested Hardware

- M4 x 8mm nuts x 4
- M4 bolts x 4

You can use any size fasteners that work for you. These are what were used in the assembled images below.

The top of the stand prints with a track for the USB cable to fit underneath the tower light base, and the tops of four holes through which to fit fasteners to attach it to the base of the light.

![leds_GASL_3D_stand_top.jpg](https://cdn-learn.adafruit.com/assets/assets/000/112/861/medium640/leds_GASL_3D_stand_top.jpg?1657056491)

The bottom of the stand prints with four recessed holes to allow for fasteners to be attached without sticking out below the stand.

![leds_GASL_3D_stand_bottom.jpg](https://cdn-learn.adafruit.com/assets/assets/000/112/863/medium640/leds_GASL_3D_stand_bottom.jpg?1657056669)

In this image, M4 x 8mm nuts were used, as they fit perfectly without sticking out the top of the M4 nuts. You can use any size that works for you.

![leds_GASL_stand_installed_bolt.jpg](https://cdn-learn.adafruit.com/assets/assets/000/112/865/medium640/leds_GASL_stand_installed_bolt.jpg?1657056781)

Once assembled, the USB cable will sit in the stand track and no longer interfere with stability of the tower light!

![leds_GASL_stand_installed_USB_side.jpg](https://cdn-learn.adafruit.com/assets/assets/000/112/864/medium640/leds_GASL_stand_installed_USB_side.jpg?1657056739)


## Featured Products

### Tri-Color USB Controlled Tower Light with Buzzer

[Tri-Color USB Controlled Tower Light with Buzzer](https://www.adafruit.com/product/5125)
With this **Tri-Color USB Controlled Tower Light with Buzzer,** you can easily monitor and alert humanoids to the status of a project, machine, or even if the bathroom is occupied!

Unlike our other [tower...](https://www.adafruit.com/?q=tower+light&sort=BestMatch)

In Stock
[Buy Now](https://www.adafruit.com/product/5125)
[Related Guides to the Product](https://learn.adafruit.com/products/5125/guides)
### Etched Glow-Through Keycap with GitHub Octocat Logo

[Etched Glow-Through Keycap with GitHub Octocat Logo](https://www.adafruit.com/product/5192)
Show off your social-coding flair with a super-classy, custom-made and officially licensed **Octocat Etched Keycap** featuring the GitHub logo.

It's made with a translucent/opaque plastic that is etched to create an elegant glow-through effect. Perfect to show your coder...

In Stock
[Buy Now](https://www.adafruit.com/product/5192)
[Related Guides to the Product](https://learn.adafruit.com/products/5192/guides)

## Related Guides

- [LED Rocket Lamp](https://learn.adafruit.com/led-rocket-lamp.md)
- [Adafruit QT Py Activity Timer and Hydration Reminder](https://learn.adafruit.com/qt-py-activity-timer-and-hydration-reminder.md)
- [Solder Dispenser Adabot Head](https://learn.adafruit.com/solder-dispenser-adabot-head.md)
- [Introducing Adafruit CLUE](https://learn.adafruit.com/adafruit-clue.md)
- [Square NeoPixel Display with Black LED Acrylic](https://learn.adafruit.com/sqaure-neopixel-display-with-black-led-acrylic.md)
- [Numpad 4000 Mechanical Keyswitch Data Entry Device](https://learn.adafruit.com/numpad-4000-mechanical-keyswitch-data-entry-device.md)
- [CircuitPython Animated Holiday Wreath Lights](https://learn.adafruit.com/circuitpython-animated-holiday-wreath-lights.md)
- [SNES Mouse to USB HID with CircuitPython](https://learn.adafruit.com/snes-mouse-to-usb-hid-with-circuitpython.md)
- [3D Printed Glowing Mace Prop](https://learn.adafruit.com/3d-printed-glowing-mace-prop.md)
- [Hexpad](https://learn.adafruit.com/hexpad.md)
- [Adafruit PCA9546 4-Channel I2C Multiplexer](https://learn.adafruit.com/adafruit-pca9546-4-channel-i2c-multiplexer.md)
- [Split Ortho Keyboard with TCA8418 Matrix Expanders](https://learn.adafruit.com/split-ortho-keyboard.md)
- [Mad Science Test Tube Rack](https://learn.adafruit.com/mad-science-test-tube-rack.md)
- [Adabot Toy Robot Friend](https://learn.adafruit.com/adabot-rp2040.md)
- [Touch Deck: DIY Customizable TFT Control Pad](https://learn.adafruit.com/touch-deck-diy-tft-customized-control-pad.md)
