• 1
    Step 1

    Prerequisites

    This tutorial expects you to already have an account with balenaCloud and a Raspberry Pi provisioned. If you haven't, we have an excellent set of getting started guides to get you up and running.

    Software

    Before touching the hardware, let's focus on the software, understand what we want to achieve and the steps we need to take to get there.

    Initially, the code can be separated into two parts, one that fetches the current Bitcoin price and runs the algorithm to decide if the price is higher or lower than the opening price, and another that controls the electronics in the traffic light via the Raspberry Pi GPIO pins.

    To obtain the latest Bitcoin price we'll use the API provided by CoinDesk. The data is provided in JSON format, which you can find more about here.

    Docker Container

    As balenaCloud operates using Docker containers, we must first create a Dockerfile with the container configuration. The container configuration allows us to specify exactly what software components we will need for our application.

    FROM resin/rpi-raspbian:latest
    
    ENV INITSYSTEM on
    
    RUN apt-get update && apt-get install -yq \
      python3-dev \
      python3-pip \
      python3-rpi.gpio \
      vim \
      wget && \
      apt-get clean && rm -rf /var/lib/apt/lists/*
    
    COPY . /usr/src/app
    WORKDIR /usr/src/app
    
    # Finally, start our app
    CMD ["python3", "src/main.py"]
    Python Code

    You can find the entire source code in Python on Github, but let's dive in block-by-block to understand the code.

    import RPi.GPIO as GPIO
    from time import sleep
    import datetime
    import urllib.request
    import json
    
    # Initiate GPIO with breakout pin numbering
    GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) 
    # Setup GPIO Pins
    red = 23
    green = 27
    orange = 22
    
    GPIO.setup(red, GPIO.OUT)
    GPIO.setup(orange, GPIO.OUT)
    GPIO.setup(green, GPIO.OUT)

    We are going to use a library called RPi.GPIO in order to be able to control the GPIO pins of the Raspberry Pi Zero from our code. You can see that library being imported here

    The second part of the code above enables the GPIO pins to be controlled by referencing the numbers in the connector. We decided to use pins 2223 and 27 to set the colors of the Traffic Light, and so we need to set those pins as digital output pins.

    Now we can write the part of the code that reads and parses the information provided by the Coindesk API. The function get_price(url, hdr) reads the price in USD and returns the value with two decimal places. The three set_color_ functions set the color of the traffic light by toggling the appropriate GPIO pins.

    # Bitcoin price-tracking url and header
    url = "https://api.coindesk.com/v1/bpi/currentprice.json"
    hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
           'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
           'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
           'Accept-Encoding': 'none',
           'Accept-Language': 'en-US,en;q=0.8',
           'Connection': 'keep-alive'}
    
    def set_color_red():
        GPIO.output(red, GPIO.HIGH)
        GPIO.output(green, GPIO.LOW)
        GPIO.output(orange, GPIO.LOW)   
    def set_color_green():
        GPIO.output(red, GPIO.LOW)
        GPIO.output(green, GPIO.HIGH)
        GPIO.output(orange, GPIO.LOW)   
    def set_color_orange():
        GPIO.output(red, GPIO.LOW)
        GPIO.output(green, GPIO.LOW)
        GPIO.output(orange, GPIO.HIGH)  
    
    def get_price(url, hdr):
        req = urllib.request.Request(url, headers=hdr)
        r = urllib.request.urlopen(req).read()
        price_usd = float(json.loads(r.decode('utf-8'))['bpi']['USD']['rate'].replace(',',''))
        return "{:.2f}".format(price_usd)

    To make the terminal output a bit more fun, we also decided to change the color or the text displayed in the terminal to green or red.

    # Colorful text
    # https://stackoverflow.com/a/34443116/1412635
    def prRed(prt):
        print("\033[91m {}\033[00m" .format(prt))
    
    def prGreen(prt):
        print("\033[92m {}\033[00m" .format(prt))

    Once we have the initial code ready, it's time to think about the main logic of the program. The opening price is the Bitcoin price at midnight, or at the time the system goes live. After that we need to fetch the current price every minute and perform a comparison to check if it is higher or lower than the opening price. If higher, we print the text in green, otherwise it's printed in red. The same logic goes into the output for the traffic light, if higher we turn the green light on, otherwise red. We've also included a 5 second transition period through the orange light to make it more obvious when the state changes.

    # Initialize Values
    opening_price = get_price(url, hdr)
    max_price = opening_price
    min_price = opening_price
    current_price = opening_price
    current_day = datetime.datetime.today().day
    previous_day = current_day
    previous_state = "green"
    
    while True:
        # Check if new day to reset opening price
        current_day = datetime.datetime.today().day
        if (current_day != previous_day):
            previous_day = current_day
            opening_price = get_price(url, hdr)
    
        # Get current Bitcoin price
        current_price = get_price(url, hdr)
    
        # Set max and min prices of the day
        if(current_price > max_price):
            max_price = current_price
    
        if(current_price < min_price):
            min_price = current_price
    
        # Show prices in terminal
        message = str(current_day) + " - Current Price: $" +  current_price + " - Daily Max: $" + max_price + " - Daily Min: $" + min_price + " - Opening Price: $" + opening_price
    
        if(current_price >= opening_price):
            if previous_state == "red":
                # If previous state was red, we want to make it orange to show transition
                previous_state = "green"
                set_color_orange()
                sleep(5)
    
            prGreen(message)
            set_color_green()
        else:
            if previous_state == "green":
                # If previous state was red, we want to make it orange to show transition
                previous_state = "red"
                set_color_orange()
                sleep(5)
    
            prRed(message)
            set_color_red()
    
        sleep(60)

    If there's anything in the code you don't understand, or would like help modifying it to fit your own project, we're always hanging out in our forums and would be happy to help out with any questions you have.

    Hardware

    With our Raspberry Pi Zero W up and running the container and software from the previous section, now it is time to get our hands dirty and work on the hardware.

    Part list:

    The first thing we need to do is disassemble the lamp and remove the electronics. Looking at the back of the lamp, we can see that it uses 3x AA batteries in series, that means 4.5V.

    With everything working disconnected from the main case, it is time for the most fun part, to reverse engineer the circuit and understand how it works. Surprisingly enough the circuit is very simple, giving us a lot of room to play with it.

    The device name of the microcontroller on the left has faded-away making it hard to know exactly how it works, but by exploring the rest of the circuit, we realized it is a very simple circuit with one BJT NPN transistor to drive each led.

    In the original circuit, everything runs on 4.5V, but with the RPi Zero we only have 5V and 3V3, so what we decided to do here is to control the base of the transistor with 3V3 (which is the voltage of the GPIO pins) and power the led from the 5V, making it slightly brighter than the original.

    From here we connect the 5V and GND pins from the RPi to the circuit, desoldered the microcontroller from the PCB and solder three wires that will go to the pins 2223 and 27, as chosen at the beginning of the project.

    All there is left to do now is to put the circuit back inside the traffic lamp (we used some hot-glue to make sure everything stays in place) and power up the board. Once it connects to balenaCloud and downloads the latest code it will turn on the LEDs.

    And voila! Our Bitcoin traffic light is complete and can keep us up-to-date on the latest price fluctuations. Don't forget to check Github for the full project source code at https://github.com/balena-io-playground/balena-btc-price-tracker.