# Daily UV Index PyPortal Display

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/079/431/medium800/circuitpython_glamor-shot.jpg?1565803450)

## UV Radiation: good and bad

UV (which stands for Ultra Violet) is high energy and short wavelength electro-magnetic radiation just past the violet end of the visible spectrum. UV is what gives you vitamin D and a tan... and a sunburn... and, well, skin cancer.&nbsp; It's a case of a little being ok but too much being pretty bad. Like bacon.

## Keeping your cool

You can do some things to reduce your UV exposure while still enjoying the nice weather (and getting that all-important vitamin D). Here are a few:

- wear long sleeves,
- wear a hat, and
- use adequate strength sunscreen.

You can get an idea of what to do by checking the UV forecast for your area. That will give you an indication of what the relative strength of the UV radiation will be. In the US, the EPA makes this information available online at [https://enviro.epa.gov/enviro/uv\_hourly?zipcode=10014](https://enviro.epa.gov/enviro/uv_hourly?zipcode=10014). Note that you need to change the zipcode argument to reflect where you are.

They also provide that information via an API with multiple response formats, one of which is formatted as JSON data.

In this project we'll use that to fetch current UV index data and display it on a PyPortal screen.

## Parts
### Adafruit PyPortal - CircuitPython Powered Internet Display

[Adafruit PyPortal - CircuitPython Powered Internet Display](https://www.adafruit.com/product/4116)
 **PyPortal** , our easy-to-use IoT device that allows you to create all the things for the “Internet of Things” in minutes. Make custom touch screen interface GUIs, all open-source, and Python-powered using&nbsp;tinyJSON / APIs to get news, stock, weather, cat photos,...

In Stock
[Buy Now](https://www.adafruit.com/product/4116)
[Related Guides to the Product](https://learn.adafruit.com/products/4116/guides)
![Front view of a Adafruit PyPortal - CircuitPython Powered Internet Display with a pyportal logo image on the display. ](https://cdn-shop.adafruit.com/640x480/4116-00.jpeg)

### Adafruit PyPortal Desktop Stand Enclosure Kit

[Adafruit PyPortal Desktop Stand Enclosure Kit](https://www.adafruit.com/product/4146)
PyPortal is&nbsp;our easy-to-use IoT device that allows you to create all the things for the “Internet of Things” in minutes. Create little pocket universes of joy that connect to something good.

And now that you've made a cool internet-connected project...

In Stock
[Buy Now](https://www.adafruit.com/product/4146)
[Related Guides to the Product](https://learn.adafruit.com/products/4146/guides)
![Demo Shot of the Assembled Adafruit PyPortal Desktop Stand Enclosure Kit.](https://cdn-shop.adafruit.com/640x480/4146-03.jpg)

Choose your USB cable color and length

### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
This here is your standard A to micro-B USB cable, for USB 1.1 or 2.0. Perfect for connecting a PC to your Metro, Feather, Raspberry Pi or other dev-board or microcontroller

Approximately 3 feet / 1 meter long

Out of Stock
[Buy Now](https://www.adafruit.com/product/592)
[Related Guides to the Product](https://learn.adafruit.com/products/592/guides)
![USB cable - USB A to Micro-B - 3 foot long](https://cdn-shop.adafruit.com/640x480/592-01.jpg)

### USB A/Micro Cable - 2m

[USB A/Micro Cable - 2m](https://www.adafruit.com/product/2185)
This is your standard USB A-Plug&nbsp;to Micro-USB cable. It's 2 meters long so you'll have plenty of cord to work with for those longer extensions.

Out of Stock
[Buy Now](https://www.adafruit.com/product/2185)
[Related Guides to the Product](https://learn.adafruit.com/products/2185/guides)
![USB Cable with Type A and Micro B ends](https://cdn-shop.adafruit.com/640x480/2185-00.jpg)

# Daily UV Index PyPortal Display

## Setting Up CircuitPython

![](https://cdn-learn.adafruit.com/assets/assets/000/079/307/medium800/circuitpython_blinka-small.png?1565644730)

CircuitPython is a programming language based on Python, one of the fastest growing programming languages in the world. It is specifically designed to simplify experimenting and learning to code on low-cost microcontroller boards. Here is a guide which covers the basics:

- [Welcome to CircuitPython!](https://learn.adafruit.com/welcome-to-circuitpython)

Plug your board into your computer via a USB cable. Please be sure the cable is a good power+data cable so the computer can talk to the board.

A new disk should appear in your computer's file explorer/finder called&nbsp; **CIRCUITPY**. This is the place we'll copy the code and code library. If you can only get a drive named&nbsp; **PORTALBOOT** , load CircuitPython per&nbsp;[the guide mentioned above](https://learn.adafruit.com/pyportal-weather-station/install-circuitpython).

Be sure you have the latest CircuitPython for your board loaded onto your board, as&nbsp;[described here](https://learn.adafruit.com/pyportal-weather-station/install-circuitpython). You will need at least version 4.1. You can tell the version of CircuitPython you have on the board by opening the file&nbsp; **boot\_out.txt** &nbsp;on the&nbsp; **CIRCUITPY** &nbsp;drive which should show up when you plug your PyPortal into your computer USB port.

Primary: 

CircuitPython is easiest to use within the Mu Editor. If you haven't previously used Mu,&nbsp;[this guide will get you started](https://learn.adafruit.com/welcome-to-circuitpython/installing-mu-editor).

## Libraries

Create a new directory on the&nbsp; **CIRCUITPY** &nbsp;drive named&nbsp; **lib**.

Download the latest CircuitPython driver package to your computer using the green button below.&nbsp; **Match the library you get to the version of CircuitPython you are using**. Save to your computer's hard drive where you can find it.

[Go to GitHub to get the latest CircuitPython library bundle](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/)
Download the **adafruit-circuitpython-bundle-4.x-mpy-\*.zip** bundle zip file (replace 4.x with 5.x, etc. for later versions of CircuitPython if necessary), and unzip a folder of the same name. Inside you'll find a **lib** folder. You have two options:

- You can add the **lib** folder to your **CIRCUITPY** drive. This will ensure you have _all the drivers_. But it will take a bunch of space on the 8 MB disk  
- Add each library as you need it, this will reduce the space usage but you'll need to put in a little more effort.

You need the following libraries for the UV Index display. So grab them and copy them into **CIRCUITPY/lib** now. The other libraries required are part of CircuitPython.

- adafruit\_bitmap\_font
- adafruit\_bus\_device
- adafruit\_display\_shapes  
- adafruit\_display\_text  
- adafruit\_esp32spi  
- adafruit\_io
- adafruit\_pyportal.mpy  
- adafruit\_requests.mpy  
- adafruit\_sdcard.mpy  
- adafruit\_touchscreen.mpy  
- neopixel.mpy  
  

Some of these are used directly in our code, but many are used by the PyPortal support library.

Your **CIRCUITPY/lib** directory should look like:

![](https://cdn-learn.adafruit.com/assets/assets/000/079/410/medium800/circuitpython_lib.png?1565743117)

# Daily UV Index PyPortal Display

## Code

![](https://cdn-learn.adafruit.com/assets/assets/000/079/416/medium800/circuitpython_glamor-shot.jpg?1565792785)

The code in its entirety is shown at the end of this page. You should click on the **Download Project Bundle** button in order to get the fonts and libraries used by the code.&nbsp;

Ensure your PyPortal is plugged into your computer via a known good USB cable. The PyPortal should show up as a flash drive called **CIRCUITPY**.

Open the zip file and copy all the files listed below&nbsp;to **CIRCUITPY**.

- **code.py**
- **fonts**

Note: The **boot\_out.txt** file you may see on the&nbsp; **CIRCUITPY** &nbsp;drive is generated by CircuitPython and is not part of the needed files for this project.

## Global Information
Before the functional code, there's some setup to be done. There are constants setting some parameters for the display of the bar graph.

`MAX_BAR_HEIGHT` defines the height of the tallest bar. The highest value bar will always be this tall, and the height of shorter bars are computed relative to this. `MARGIN` is the amount of space to either side of the bars, while `SPACE_BETWEEN_BARS` sets the number of pixels between adjacent bars.

`COLORS` sets the color for the UV values 0-14. 0 is basically night with no UV radiation. And 14... well you remember that comment about crispy bacon? Each bar is colored as determined by its UV Index value. Low values are green, going through yellow and orange as it gets higher, to red and eventually into violet for very high values.

After `COLORS` are definitions of the different fonts used and the URL from which to fetch the US EPA data.

```auto
MAX_BAR_HEIGHT = 160
MARGIN = 10
SPACE_BETWEEN_BARS = 1

COLORS = [0x00FF00, 0x83C602, 0xa2CF02,
          0xF7DE03, 0xF6B502, 0xF78802,
          0xF65201, 0xEA2709,
          0xDA0115, 0xFC019E, 0xB548FF,
          0x988FFE, 0x7EA7FE, 0x66BFFD, 0x4BD9FF]

cwd = ("/"+__file__).rsplit('/', 1)[0]

CAPTION_FONT_FILE = cwd+'/fonts/Helvetica-Bold-16.bdf'
BAR_FONT_FILE = cwd+'/fonts/Arial-Bold-12.bdf'

#pylint:disable=line-too-long
url = f"https://enviro.epa.gov/enviro/efservice/getEnvirofactsUVHOURLY/ZIP/{getenv('zip')}/JSON"
#pylint:enable=line-too-long
```

## Utilities
The JSON returned by the EPA's API has the form:

```auto
[
    {
        "ORDER": 1,
         "ZIP": 20050,
         "DATE_TIME": "AUG/09/2019 07 AM",
         "UV_VALUE": 0
    },
    {
         "ORDER": 2,
         "ZIP": 20050,
         "DATE_TIME": "AUG/09/2019 08 AM",
         "UV_VALUE": 1
    },
    ...
]
```

Each record has an&nbsp;`ORDER`&nbsp;value which seems to be redundant, an echo of the zipcode provided in the query, the data and time for the value, and the UV value. For our purposes we only need the date, time, and value.

The value is just a number which may be used as is. The date and hour need to be separated since we want the date for the screen header and the hour to label each bar.

To extract the hour, the code splits the `DATE_TIME` value at spaces. This gives 3 pieces: the date, the hour, and an 'AM'/'PM' suffix. Any leading '0' is removed and the hour and suffix are joined using a newline so that it will be split over two lines. This is needed due to the limited width below the bars.

```
def extract_hour(date_time):
    """Extract the hour in a format to use for display:
    :param date_time: the timestamp from EPA UV readings
    """
    split_date_time = date_time.split()
    hour = split_date_time[1]
    suffix = split_date_time[2]
    if hour[0] == '0':
        hour = hour[1]
    return '\n'.join([hour, suffix])
```

Getting the date is a little easier. Since we just want the month and day, we can split the `DATE_TIME` value on slashes (I.e. '/') and take the first two items in the resulting array. They can be joined using a space and returned. That's something which can be used in the display header.

```
def extract_date(date_time):
    """Extract the date in a format to use for display:
    :param date_time: the timestamp from EPA UV readings
    """
    return ' '.join(date_time.split('/')[0:2])
```

## Setting up the PyPortal
There's not much to setting up the PyPortal instance. We won't be using the text support, and the caption will be set later. We just need to set the data URL, the background to white, and the caption font. We'll have it use the NeoPixel as a status output as a debugging aid.

```
pyportal = PyPortal(url=url,
                    status_neopixel=board.NEOPIXEL,
                    default_bg=0xFFFFFF,
                    caption_font=CAPTION_FONT_FILE)
```

Next we can set up the canvas for displaying the graph. We also load the font to use for the bar labels.

```auto
canvas = displayio.Group()
pyportal.root_group.append(canvas)
bar_font = bitmap_font.load_font(BAR_FONT_FILE)
```

## Data Processing
In the loop, the code starts by having the PyPortal fetch the data. There's a `try-except` to avoid crashing if there's any problem with the fetch or the parsing of the data returned. It can happen for various reasons, so it should be accounted for. There's nothing to be done other than note it and try again (i.e. restart the loop). After 10 minutes seems fair.

If there is good data, it can be processed into a format more easily used. The list comprehension does this. It filters out any records with a 0 value, and converts the `DATE_TIME` field using the `extract_hour` function above.

Since all the records are for the same day, the day information can be extracted from the first record (using the `extract_date` function above) and build the display header. That then is set as the PyPortal's `caption`.

Then a count of the number of readings that will be displayed. That is used to compute how wide the bars should be.

Finally finding the largest reading value. That's used below to scale the height of the bars.

```
    json_payload = ''
    try:
        json_payload = pyportal.fetch()
        raw_data = json.loads(json_payload)
    except (ValueError, RuntimeError) as ex:
        print('Error: ', ex)
        if type(ex) == ValueError:
            print('JSON:', json_payload)
        print('Retrying in 10 minutes')
        time.sleep(600)
        continue
    data = [{'hour': extract_hour(d['DATE_TIME']), 'value': int(d['UV_VALUE'])}
            for d in raw_data
            if d['UV_VALUE'] &gt; 0]
    the_day = raw_data[0]['DATE_TIME']
    pyportal.set_caption('UV Index for {0}'.format(extract_date(the_day)),
                         (80, 20),
                         0x000000)
    number_of_readings = len(data)
    whitespace = (number_of_readings - 1) * SPACE_BETWEEN_BARS + 2 * MARGIN
    bar_width = (320 - whitespace) // number_of_readings
    max_reading = max([d['value'] for d in data])

```

Finally, is the actual display code.

It starts by removing anything that's already on the canvas.

Then it goes through each reading, computing the height of its bar and its horizontal position. That is used along with the computed bar width and the corresponding color to place a filled rectangle onto the canvas. The reading's processed hour value is placed in a `Label` below the bar and the value, itself, is placed in a `Label` near the top.

## Building the Display
```
    while len(canvas) &gt; 0:
        canvas.pop()

    for i, reading in enumerate(data):
        bar_height = (MAX_BAR_HEIGHT * reading['value']) // max_reading
        x = int(MARGIN + i * (bar_width + SPACE_BETWEEN_BARS))
        canvas.append(Rect(x, 200 - bar_height,
                           bar_width, bar_height,
                           fill=COLORS[reading['value']]))
        canvas.append(Label(bar_font,
                            x=x+3, y=220,
                            text=reading['hour'],
                            color=0x000000,
                            line_spacing=0.6))
        canvas.append(Label(bar_font,
                            x=x+(bar_width//2)-4, y=208-bar_height,
                            text=str(reading['value']),
                            color=0x000000))
```

## The Complete Code

Remember to download the zip so you have the **fonts** directory as well as **code.py**.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/PyPortal/PyPortal_UV_Index/code.py

![CIRCUITPY](https://adafruit.github.io/Adafruit_Learning_System_Guides/PyPortal_PyPortal_UV_Index.png )


## Featured Products

### Adafruit PyPortal - CircuitPython Powered Internet Display

[Adafruit PyPortal - CircuitPython Powered Internet Display](https://www.adafruit.com/product/4116)
 **PyPortal** , our easy-to-use IoT device that allows you to create all the things for the “Internet of Things” in minutes. Make custom touch screen interface GUIs, all open-source, and Python-powered using&nbsp;tinyJSON / APIs to get news, stock, weather, cat photos,...

In Stock
[Buy Now](https://www.adafruit.com/product/4116)
[Related Guides to the Product](https://learn.adafruit.com/products/4116/guides)
### Adafruit PyPortal Desktop Stand Enclosure Kit

[Adafruit PyPortal Desktop Stand Enclosure Kit](https://www.adafruit.com/product/4146)
PyPortal is&nbsp;our easy-to-use IoT device that allows you to create all the things for the “Internet of Things” in minutes. Create little pocket universes of joy that connect to something good.

And now that you've made a cool internet-connected project...

In Stock
[Buy Now](https://www.adafruit.com/product/4146)
[Related Guides to the Product](https://learn.adafruit.com/products/4146/guides)
### USB cable - USB A to Micro-B

[USB cable - USB A to Micro-B](https://www.adafruit.com/product/592)
This here is your standard A to micro-B USB cable, for USB 1.1 or 2.0. Perfect for connecting a PC to your Metro, Feather, Raspberry Pi or other dev-board or microcontroller

Approximately 3 feet / 1 meter long

Out of Stock
[Buy Now](https://www.adafruit.com/product/592)
[Related Guides to the Product](https://learn.adafruit.com/products/592/guides)
### USB A/Micro Cable - 2m

[USB A/Micro Cable - 2m](https://www.adafruit.com/product/2185)
This is your standard USB A-Plug&nbsp;to Micro-USB cable. It's 2 meters long so you'll have plenty of cord to work with for those longer extensions.

Out of Stock
[Buy Now](https://www.adafruit.com/product/2185)
[Related Guides to the Product](https://learn.adafruit.com/products/2185/guides)
### Pink and Purple Braided USB A to Micro B Cable - 2 meter long

[Pink and Purple Braided USB A to Micro B Cable - 2 meter long](https://www.adafruit.com/product/4148)
This cable is&nbsp;super-fashionable&nbsp;with a woven pink and purple Blinka-like pattern!

First let's talk about the cover and over-molding. We got these in custom colors, and if you&nbsp;_have_&nbsp;to have visible cables, then you might as well have the nicest fabric-bound...

No Longer Stocked
[Buy Now](https://www.adafruit.com/product/4148)
[Related Guides to the Product](https://learn.adafruit.com/products/4148/guides)

## Related Guides

- [Adafruit PyPortal - IoT for CircuitPython](https://learn.adafruit.com/adafruit-pyportal.md)
- [League of Legends Level Trophy for PyPortal](https://learn.adafruit.com/league-of-legends-level-trophy-for-pyportal.md)
- [EZ Make Oven](https://learn.adafruit.com/ez-make-oven.md)
- [CircuitPython Minesweeper Game](https://learn.adafruit.com/circuitpython-pyportal-minesweeper-game.md)
- [How to Make Animated Graphics for Hologram Displays](https://learn.adafruit.com/how-to-make-animated-graphics-for-hologram-displays.md)
- [PyPortal Adafruit Quote Book](https://learn.adafruit.com/pyportal-adafruit-quote-board.md)
- [Adafruit IO Basics: AirLift](https://learn.adafruit.com/adafruit-io-basics-airlift.md)
- [CircuitPython Display Support Using displayio](https://learn.adafruit.com/circuitpython-display-support-using-displayio.md)
- [MQTT in CircuitPython](https://learn.adafruit.com/mqtt-in-circuitpython.md)
- [PyPortal Discord Online Counter](https://learn.adafruit.com/pyportal-discord-online-count.md)
- [PyPortal IoT Plant Monitor with AWS IoT and CircuitPython](https://learn.adafruit.com/pyportal-iot-plant-monitor-with-aws-iot-and-circuitpython.md)
- [Cleveland Museum of Art PyPortal Frame](https://learn.adafruit.com/cleveland-museum-of-art-pyportal-frame.md)
- [PyPortal Weekly Countdown Clock](https://learn.adafruit.com/pyportal-countdown-clock.md)
- [Custom Fonts for CircuitPython Displays](https://learn.adafruit.com/custom-fonts-for-pyportal-circuitpython-display.md)
- [A Logger for CircuitPython](https://learn.adafruit.com/a-logger-for-circuitpython.md)
- [PyPortal Case](https://learn.adafruit.com/pyportal-case.md)
