# -*- coding: utf-8 -*- import RPi.GPIO as GPIO import time import threading import numpy as np import datetime from forecastiopy import * ''' File: Weather_Display.py Author: N. Brent Burns Deployed on a Raspberry Pi 3 B+ running Linux raspberrypi 4.19.42-v7+ Using Python 2.7.13 (should be compatible with Python3 as well) Started 6/14/2019 Finished and mounted 6/18/2019 Added rain display and IR receiver, finished 6/20/2019 3.3V, GND, GPIO_14 (pin 8) Added capacitive touch sensor for 4-min coffee timer 12/22/2020 3.3V, GND, GPIO_12 (pin 32): normally LOW, touch --> HIGH, remove --> LOW (doesn't hold or pulse) Added hourglass animation to coffee timer 12/22/2020 for GPIO inputs to the PI, don't use 5V, can only handle 3.3V USEFUL LINKS: https://learn.adafruit.com/connecting-a-16x32-rgb-led-matrix-panel-to-a-raspberry-pi/experimental-python-code https://www.raspberrypi.org/documentation/remote-access/ssh/scp.md sudo python weather_v1.py https://www.bigmessowires.com/2018/05/24/64-x-32-led-matrix-programming/ https://github.com/Detrous/darksky https://darksky.net/dev/docs https://github.com/dvdme/forecastiopy auto reboot: http://www.vk3erw.com/index.php/16-software/58-raspberry-pi-how-to-periodic-reboot-via-cron TO-DO: DONE: Reduce LED current draw by going to sleep during certain hours DONE: Instead have IR control to turn LEDs ON/OFF using the OE_PIN Rain effect, Sun effect (UV) Maybe add sunrise/sunset vertical lines in 24-hour temperature graph Kill or reduce Linux/Pi stuff in the background, free up resources Might solve Pi Zero W issues Add severe weather alerts, maybe just flashing red LEDs or text message R1 (Red 1st bank) G1 (Green 1st bank) B1 (Blue 1st bank) R2 (Red 2nd bank) G2 (Green 2nd bank) B2 (Blue 2nd bank) A, B, C, D (Row address) OE- (neg. Output enable) CLK (Serial clock) STR (Strobe row data, also called LATCH) COLORS 0-7 (only 8 at the moment) 0 = BLACK (OFF) 1 = RED 2 = GREEN 3 = YELLOW 4 = BLUE 5 = MAGENTA 6 = CYAN 7 = WHITE 8+ = OFF/NOTHING ''' ### YOU NEED TO REPLACE THE apikey AND location VARS BELOW WITH ACTUAL VALID NUMBERS ### # these are the only variables you need to change to make this code work # as long as you followed all the steps at my website http://crystal.uta.edu/~burns/ # Dark Sky API variables apikey = '123456abcdef' # darksky code/key, free membership allows 1000 weather pulls a day location = [123.45, 123.45] # [latitude , longitude] of location to gather weather info about # STATES of Program: # 0 = Normal - Display Temperature Line Graph, 3 Temps, UV %, Precip % (LEDs ON) # 1 = Normal - Rain Graph (LEDs ON) # 2 = Paused - Blank Screen (All LEDs OFF) # 3 = Normal - Coffee Timer Countdown (LEDS ON) global_state = 0 # Always start on the temp display global_screen_to_save = 0 # 0 = screen var for temp display, 1 = ... for rain display, 3 = ... for coffee timer display global_screen_to_print = 0 # 0 = screen var for temp display, 1 = ... for rain display, 3 = ... for coffee timer display global_num_bottom_lines = -1 # used in function update_hourglass_sand() GPIO.setwarnings(False) # Get rid of GPIO warning messages, I KIND OF KNOW WHAT I'M DOING! #delay = 0.0000001 #delay = 0.000001 # original delay = 0.00001 # best #delay = 0.0001 # slight brightness flicker #delay = 0.001 # constant flicker #delay = 0.01 # bad flicker and redrawing, too slow, too large a delay #delay = 0.1 # use the relative GPIO# below, not the actual 1-40 pinout numbers GPIO.setmode(GPIO.BCM) red1_pin = 11 green1_pin = 27 blue1_pin = 7 red2_pin = 8 green2_pin = 9 blue2_pin = 10 clock_pin = 17 a_pin = 22 b_pin = 23 c_pin = 24 d_pin = 25 latch_pin = 4 oe_pin = 18 IR_pin = 14 cap_touch_pin = 12 # Make all LED Panel IO pins OUTPUTS (PI --> LED Matrix module) GPIO.setup(red1_pin, GPIO.OUT) GPIO.setup(green1_pin, GPIO.OUT) GPIO.setup(blue1_pin, GPIO.OUT) GPIO.setup(red2_pin, GPIO.OUT) GPIO.setup(green2_pin, GPIO.OUT) GPIO.setup(blue2_pin, GPIO.OUT) GPIO.setup(clock_pin, GPIO.OUT) GPIO.setup(a_pin, GPIO.OUT) GPIO.setup(b_pin, GPIO.OUT) GPIO.setup(c_pin, GPIO.OUT) GPIO.setup(d_pin, GPIO.OUT) GPIO.setup(latch_pin, GPIO.OUT) GPIO.setup(oe_pin, GPIO.OUT) # Two INPUTS (IR Receiver and Capacitive Touch Sensor) GPIO.setup(IR_pin, GPIO.IN) GPIO.setup(cap_touch_pin, GPIO.IN) # Clear all IO pins to ZERO initially GPIO.output(red1_pin, 0) GPIO.output(green1_pin, 0) GPIO.output(blue1_pin, 0) GPIO.output(red2_pin, 0) GPIO.output(green2_pin, 0) GPIO.output(blue2_pin, 0) GPIO.output(clock_pin, 0) GPIO.output(a_pin, 0) GPIO.output(b_pin, 0) GPIO.output(c_pin, 0) GPIO.output(d_pin, 0) GPIO.output(latch_pin, 0) GPIO.output(oe_pin, 0) # Define color numbers BLACK = 0 RED = 1 GREEN = 2 YELLOW = 3 BLUE = 4 MAGENTA = 5 CYAN = 6 WHITE = 7 # numbers 0-9 in order (index == number): 6 wide by 13 tall numbers = [[[1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1]], [[0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 1, 0, 0]], [[1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1]], [[1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1]], [[1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1]], [[1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1]], [[1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1]], [[1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1]], [[1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1]], [[1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 1]]] # numbers 0-9: 3wide by 5tall numbers_small = [[[1, 1, 1], [1, 0, 1], [1, 0, 1], [1, 0, 1], [1, 1, 1]], [[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0]], [[1, 1, 1], [0, 0, 1], [1, 1, 1], [1, 0, 0], [1, 1, 1]], [[1, 1, 1], [0, 0, 1], [1, 1, 1], [0, 0, 1], [1, 1, 1]], [[1, 0, 1], [1, 0, 1], [1, 1, 1], [0, 0, 1], [0, 0, 1]], [[1, 1, 1], [1, 0, 0], [1, 1, 1], [0, 0, 1], [1, 1, 1]], [[1, 1, 1], [1, 0, 0], [1, 1, 1], [1, 0, 1], [1, 1, 1]], [[1, 1, 1], [1, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]], [[1, 1, 1], [1, 0, 1], [1, 1, 1], [1, 0, 1], [1, 1, 1]], [[1, 1, 1], [1, 0, 1], [1, 1, 1], [0, 0, 1], [0, 0, 1]]] # SCREEN '2D list' (top left corner is 0,0) # 64 wide x cols, 32 tall y rows screen0 = [[0 for x in range(64)] for x in range(32)] # for temp display screen1 = [[0 for x in range(64)] for x in range(32)] # for rain display # skipping screen2 (since that is the LEDs OFF state) screen3 = [[0 for x in range(64)] for x in range(32)] # for coffee timer display def clear_screens(): global screen0 global screen1 global screen3 screen0 = [[0 for x in range(64)] for x in range(32)] screen1 = [[0 for x in range(64)] for x in range(32)] screen3 = [[0 for x in range(64)] for x in range(32)] def fill_screens(color): global screen0 global screen1 global screen3 screen0 = [[color for x in range(64)] for x in range(32)] screen1 = [[color for x in range(64)] for x in range(32)] screen3 = [[color for x in range(64)] for x in range(32)] def clock(): GPIO.output(clock_pin, 1) GPIO.output(clock_pin, 0) def latch(): GPIO.output(latch_pin, 1) GPIO.output(latch_pin, 0) def bits_from_int(x): a_bit = x & 1 b_bit = x & 2 c_bit = x & 4 d_bit = x & 8 return (a_bit, b_bit, c_bit, d_bit) def set_row(row): a_bit, b_bit, c_bit, d_bit = bits_from_int(row) GPIO.output(a_pin, a_bit) GPIO.output(b_pin, b_bit) GPIO.output(c_pin, c_bit) GPIO.output(d_pin, d_bit) def set_color_top(color): red, green, blue, _ = bits_from_int(color) GPIO.output(red1_pin, red) GPIO.output(green1_pin, green) GPIO.output(blue1_pin, blue) def set_color_bottom(color): red, green, blue, _ = bits_from_int(color) GPIO.output(red2_pin, red) GPIO.output(green2_pin, green) GPIO.output(blue2_pin, blue) def draw_rectangle_fill(x1, y1, x2, y2, color): for x in range(x1, x2+1): for y in range(y1, y2+1): set_pixel(x, y, color) def draw_rectangle_outline(x1, y1, x2, y2, color): for x in range(x1, x2+1): set_pixel(x, y1, color) # TOP Border set_pixel(x, y2, color) # BOTTOM Border for y in range(y1, y2+1): set_pixel(x1, y, color) # LEFT Border set_pixel(x2, y, color) # RIGHT Border def set_pixel(x, y, color): if(global_screen_to_save == 0): # Temperature Display (screen0) screen0[y][x] = color elif(global_screen_to_save == 1): # Rain Percentage Display (screen1) screen1[y][x] = color elif(global_screen_to_save == 3): # Coffee Timer Countdown Display (screen3) screen3[y][x] = color #--------------------------------------------------------------------------# def draw_hourglass_frame(color): # Draw top border for x in range(33,63): set_pixel(x, 1, color) set_pixel(x, 2, color) # Draw bottom border for x in range(33,63): set_pixel(x, 29, color) set_pixel(x, 30, color) # Draw body frame - left x_list = [35,35,35,35,35,35,36,37,38,39,40,41,42,42,41,40,39,38,37,36,35,35,35,35,35,35] for x, y in zip(x_list, range(3,29)): set_pixel(x, y, color) # Draw body frame - right x_list = [60,60,60,60,60,60,59,58,57,56,55,54,53,53,54,55,56,57,58,59,60,60,60,60,60,60] for x, y in zip(x_list, range(3,29)): set_pixel(x, y, color) def update_hourglass_sand(curr_sec): global global_num_bottom_lines # 12 "lines" of sand for each half of the hourglass # 12 on top and 12 on bottom, but only 12 visible at any one point in time # all start on top first, then disappear and reappear on bottom throughout the 4min countdown # each line represents 20 seconds: 4min timer = 240 seconds / 12 lines = 20 seconds per line top_mask = [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0], [0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0], [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0], [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0], [0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0]] bot_mask = [[0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0], [0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0], [0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0], [0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0], [0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0], [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]] time_elapsed = 240 - curr_sec num_bottom_lines = int(time_elapsed/20) # 0 --> 12 (inclusive), 12 only for the final second # draw vertical 2-pixel wide "pouring sand" line # must do this before drawing sand lines below in for-loops if(num_bottom_lines == 12): # erase, turn BLACK (really just a filled rectangle) draw_rectangle_fill(47, 15, 48, 28, BLACK) # only execute all of this code (costly for loops), if 20seconds have gone by and need to update sand line # don't perform pointless operations that don't change the actual LED Panel, causes LED flickering if(global_num_bottom_lines != num_bottom_lines): global_num_bottom_lines = num_bottom_lines # top-left corner LED Panel offset for each top/bot box top_x_offset = 36 top_y_offset = 3 bot_x_offset = 36 bot_y_offset = 17 for x in range(0, 24): # erase top lines (make black) for y in range(0, num_bottom_lines): set_pixel(top_x_offset+x, top_y_offset+y, BLACK) # erase bottom lines (make black) for y in range(0, 12-num_bottom_lines): set_pixel(bot_x_offset+x, bot_y_offset+y, BLACK) # set yellow top lines for y in range(num_bottom_lines, 12): if(top_mask[y][x] == 1): # valid pixel set_pixel(top_x_offset+x, top_y_offset+y, YELLOW) else: # invalid pixel (out of bounds to the hourglass frame borders) set_pixel(top_x_offset+x, top_y_offset+y, BLACK) # set yellow bottom lines for y in range(12-num_bottom_lines, 12): if(bot_mask[y][x] == 1): # valid pixel set_pixel(bot_x_offset+x, bot_y_offset+y, YELLOW) else: # invalid pixel (out of bounds to the hourglass frame borders) set_pixel(bot_x_offset+x, bot_y_offset+y, BLACK) # draw vertical 2-pixel wide "pouring sand" line # must do this before drawing sand lines below in for-loops if(num_bottom_lines != 12): # draw yellow lines (really just a filled rectangle) draw_rectangle_fill(47, 15, 48, 28, YELLOW) draw_hourglass_frame(MAGENTA) # must do last after YELLOW sand lines and BLACK erasing def draw_custom_checkerboard(dimension, color): # This function draws a special LED pattern/sequence # Checkerboard pattern with "dimension" defining the checkerbox height/width # Just a crude function, could be improved # dimension must be non-zero and positive if(dimension <= 0): dimension = 1 x_max = 64 y_max = 32 x = 0 y = 0 while((x+dimension*2)-1 < x_max): y = 0 while((y+dimension*2)-1 < y_max): draw_rectangle_fill(x, y, x+dimension-1, y+dimension-1,color) draw_rectangle_fill(x+dimension, y+dimension, (x+dimension*2)-1, (y+dimension*2)-1,color) y = y + dimension*2 x = x + dimension*2 def draw_rain(percent): if(percent < 0): percent = 0 elif(percent > 100): percent = 100 offset = int(percent*15.0/100) if(offset <= 5): rain_color = GREEN elif(offset <= 10): rain_color = YELLOW else: # 11-15 rain_color = RED draw_rectangle_fill(1, 31-offset, 5, 30, rain_color) # x1 x2 y2 fixed, offset y1 def draw_uv(percent): if(percent < 0): percent = 0 elif(percent > 100): percent = 100 offset = int(percent*15.0/100) # Normal UV Value Percent offset # 0 0 0 GREEN # 1 9 1 GREEN # 2 18 2 GREEN # 3 27 4 YELLOW # 4 36 5 YELLOW # 5 45 6 YELLOW # 6 55 8 YELLOW # 7 64 9 YELLOW # 8 73 10 RED # 9 82 12 RED # 10 91 13 RED # 11+ 100 15 RED if(offset <= 2): uv_color = GREEN elif(offset <= 9): uv_color = YELLOW else: # 10-15 uv_color = RED draw_rectangle_fill(58, 31-offset, 62, 30, uv_color) # x1 x2 y2 fixed, offset y1 def draw_temperature_numbers(my_type, degrees): # Can handle MAX 3 digits, don't print leading zeros, center whole number # Make sure to pass in degrees as an INT if(degrees < 0): degrees = 0 elif(degrees > 999): degrees = 666 if(my_type == 'low'): color = BLUE main_offset_x = 1 elif(my_type == 'curr'): color = GREEN main_offset_x = 22 elif(my_type == 'high'): color = RED main_offset_x = 43 main_offset_y = 1 num_digits = len(str(degrees)) # digit3 digit2 digit1, d3d2d1, EX: degrees 102 d3=1 d2=0 d1=2 digit3 = int(degrees/100) digit2 = int((degrees%100)/10) digit1 = int(degrees%10) if(num_digits == 1): draw_number(main_offset_x+7, main_offset_y, digit1, color) elif(num_digits == 2): draw_number(main_offset_x+3, main_offset_y, digit2, color) draw_number(main_offset_x+11, main_offset_y, digit1, color) elif(num_digits == 3): draw_number(main_offset_x, main_offset_y, digit3, color) draw_number(main_offset_x+7, main_offset_y, digit2, color) draw_number(main_offset_x+14, main_offset_y, digit1, color) def draw_number(x1, y1, num, color): # x1 y1 are the top left corner of single number character of the 6x13 grid/box # font is 6x13, 6 wide x, 13 tall y for x in range(0, 6): for y in range(0, 13): if(numbers[num][y][x] == 0): set_pixel(x+x1, y+y1, BLACK) # BLACK == LED OFF else: set_pixel(x+x1, y+y1, color) # These two draw_number functions could be combined if you handle different "font" sizes correctly def draw_number_small(x1, y1, num, color): # x1 y1 are the top left corner of single number character of the 3x5 grid/box # font is 3x5, 3 wide x, 5 tall y for x in range(0, 3): for y in range(0, 5): if(numbers_small[num][y][x] == 0): set_pixel(x+x1, y+y1, BLACK) # BLACK == LED OFF else: set_pixel(x+x1, y+y1, color) def draw_temperature_line_graph(temp_list): # tempList contains 48 degree values for 1 day (every 30 minutes) low = min(temp_list) # Lowest temperature of the 24-hour period high = max(temp_list) # Highest temperature of the 24-hour period diff = high-low # Range of temperature values for the day # In case weather data wasn't fetched properly and all templist is 0's, avoid dividing by zero below if(diff <= 0): diff = 1 temp_list = np.array(temp_list, dtype='f') # convert list to numpy array, easier to manipulate temp_list_scaled = ((temp_list - low)/diff)*15 # scale to fit into 48x16 box graph area temp_list_scaled = abs((temp_list_scaled-15)*-1) # - to flip eventual Y values temp_list_scaled = np.round(temp_list_scaled) temp_list_scaled = temp_list_scaled.astype(int) temp_list_scaled = temp_list_scaled.tolist() # convert back to list # Get current time of day to pick (make GREEN) the pixel (i index) curr_time = datetime.datetime.now() elapsed_minutes = (curr_time.hour*60) + curr_time.minute half_hour_index = int(round((float(elapsed_minutes / 1440.0))*47.0)) for i in range(0, len(temp_list_scaled)): time.sleep(0.05) # this delay adds the drawing/animation effect for the temperature line graph, looks cooler if(i == half_hour_index): set_pixel(i+8, temp_list_scaled[i]+16, GREEN) else: set_pixel(i+8, temp_list_scaled[i]+16, MAGENTA) def draw_rain_bar_graph(rain_list): # rainList contains 25 precip % values for 1 day (int 0-100): midnight to midnight rain_list = np.array(rain_list, dtype='f') # convert list to numpy array, easier to manipulate rain_list_scaled = (rain_list/100.0)*24.0 # scale to fit into 0-24 bar graph area rain_list_scaled = np.round(rain_list_scaled) rain_list_scaled = rain_list_scaled.astype(int) rain_list_scaled = rain_list_scaled.tolist() # convert back to list # Get current time of day to pick (make GREEN) the bar graph index (i index) curr_time = datetime.datetime.now() elapsed_minutes = (curr_time.hour*60) + curr_time.minute hour_index = int(round((float(elapsed_minutes / 1440.0))*24.0)) #0-24 (25 len) # Each rectangle bar graph line is 2 pixels wide (x) for i in range(0, len(rain_list_scaled)): time.sleep(0.05) # this delay adds the drawing/animation effect final_X = (i*2)+14 temp_Y = rain_list_scaled[i] # Out of bounds error handling if(temp_Y < 0): temp_Y = 0 elif(temp_Y > 100): temp_Y = 100 final_Y = 29-temp_Y if(i == hour_index): draw_rectangle_outline(final_X, final_Y, final_X+1, 29, GREEN) #curr time (curr hour) else: draw_rectangle_outline(final_X, final_Y, final_X+1, 29, BLUE) def draw_basics(): # draw basic frames/outlines draw_rectangle_outline( 0, 0, 21, 14, MAGENTA) # LEFT box magenta draw_rectangle_outline(21, 0, 42, 14, MAGENTA) # CENTER box magenta draw_rectangle_outline(42, 0, 63, 14, MAGENTA) # RIGHT box magenta draw_rectangle_outline(0, 15, 6, 31, BLUE) # RAIN blue outline box bottom left corner (7x17) draw_rectangle_outline(57, 15, 63, 31, YELLOW) # UV yellow outline box bottom right corner (7x17) #draw_rectangle_outline( 0, 0, 63, 31, MAGENTA) # Entire Screen box magenta def draw_rain_basics(): # draw basic frames/outlines for RAIN screen local_color = MAGENTA draw_rectangle_outline(13, 5, 13, 29, local_color) # Left V line, 29 y tall # Draw/print percentage numbers (0% 25% 50% 75% 100%): 3wide x and 5tall y draw_number_small(6, 27, 0, local_color) # 0 of 0 draw_number_small(2, 21, 2, local_color) # 2 of 25 draw_number_small(6, 21, 5, local_color) # 5 of 25 draw_number_small(2, 15, 5, local_color) # 5 of 50 draw_number_small(6, 15, 0, local_color) # 0 of 50 draw_number_small(2, 9, 7, local_color) # 7 of 75 draw_number_small(6, 9, 5, local_color) # 5 of 75 draw_number_small(0, 3, 1, local_color) # 1 of 100 draw_number_small(2, 3, 0, local_color) # 0 of 100 draw_number_small(6, 3, 0, local_color) # 0 of 100 # Draw left/right percentage lines (5 H lines) (0% 25% 50% 75% 100%) for y in range(0,5): # 0-4 final_Y = (y*6) + 5 draw_rectangle_outline(10, final_Y, 12, final_Y, local_color) # Left H line, 2 x wide def refresh(): # Only refreshes/involves LED graphics stuff, not weather grabs if(global_screen_to_print == 0): screen_final = screen0 elif(global_screen_to_print == 1): screen_final = screen1 elif(global_screen_to_print == 3): screen_final = screen3 for row in range(16): # 0-15 x2 RGB (top/bottom) = 32 rows total GPIO.output(oe_pin, 1) # disables LEDs set_row(row) for col in range(64): # 0-63 cols total set_color_top(screen_final[row][col]) set_color_bottom(screen_final[row+16][col]) clock() latch() GPIO.output(oe_pin, 0) # enables LEDs time.sleep(delay) # delay value is critical and defined at the top of this .py file def get_weather_info(): rain = 0 # 0-100 whole int percent uv = 0 # 0-100 whole int percent (original scale is 0-11) low = 0 # find MIN of tempListHourly 24-hour list in F curr = 0 # find CURRENTLY data for current temperature in F high = 0 # find MAX of tempListHourly 24-hour list in F temp_list = [0 for x in range(48)] # temperature list with every 30 minute temps precip_list_hourly = [0 for x in range(25)] # precip % list for every 1 hour curr_time = str(int(time.time())) # Get the CURRENT NOW time #currTime = str(1560427200) #test june 13, 2019 at noon #currTime = str(1560724224) #test june 16, 2019 # LINK: https://www.epochconverter.com/ try: # In case an internet connection error occurs fio = ForecastIO.ForecastIO(apikey, units=ForecastIO.ForecastIO.UNITS_US, lang=ForecastIO.ForecastIO.LANG_ENGLISH, time=curr_time, latitude=location[0], longitude=location[1]) if fio.has_currently() is True: currently = FIOCurrently.FIOCurrently(fio) curr = int(round(currently.temperature)) rain = int(round(currently.precipProbability*100.0)) uv = ((int(round(currently.uvIndex)))/11.0)*100.0 if fio.has_hourly() is True: hourly = FIOHourly.FIOHourly(fio) temp_list_hourly = [] # sometimes 25 length or 24 length precip_list_hourly = [] # sometimes 25 length or 24 length for hour in range(0, hourly.hours()): temp_list_hourly.append(int(round(hourly.get_hour(hour).get('temperature')))) precip_list_hourly.append(int(round(hourly.get_hour(hour).get('precipProbability')*100.0))) # Special case to handle ONLY getting 24 values (midnight to 11pm) # instead of 25 values (midnight to midnight) # Since I built the code around expecting 25 values # Just copy the 11pm (last value) to the end if(len(temp_list_hourly) == 24): temp_list_hourly.append(temp_list_hourly[-1]) if(len(precip_list_hourly) == 24): precip_list_hourly.append(precip_list_hourly[-1]) # This program ignores negative temperatures (less than zero degrees F) for i in range(0,len(temp_list_hourly)): #0-24 if(temp_list_hourly[i] <= 0): temp_list_hourly[i] = 0 # This program ignores negative precip probabilites for i in range(0,len(precip_list_hourly)): #0-24 if(precip_list_hourly[i] <= 0): precip_list_hourly[i] = 0 high = max(temp_list_hourly) low = min(temp_list_hourly) for i in range(0,len(temp_list_hourly)): #0-24 if(i == len(temp_list_hourly)-1): # special end case temp_list[(i*2)-1] = temp_list_hourly[i] else: temp_list[i*2] = temp_list_hourly[i] # Put average of nextdoor hourly temps into half-hour temp if(i != 0 and i != len(temp_list_hourly)-1): temp_list[(i*2)-1] = (temp_list_hourly[i] + temp_list_hourly[i-1])/2 except Exception as e: print("Exception(s) occured: ", e) return int(rain), int(uv), int(low), int(curr), int(high), temp_list, precip_list_hourly def terminal_input(): global global_state while True: user_input = input("Enter 0 to toggle LEDs ON/OFF: ") if(user_input == 0): if(global_state == 0): global_state = 1 # Pause or Turn OFF the LEDs GPIO.output(oe_pin, 1) # disables LEDs else: global_state = 0 # Resumes normal or Turn ON the LEDs GPIO.output(oe_pin, 0) # enables LEDs else: print("Bitch, that aint right! Hoe.") def timer_display(): # 4:00 display timer for coffee brewing # Special color at 2:30 and 0:00 # numbers 0-9: 6wide by 13tall global global_state global global_screen_to_print global global_screen_to_save global global_num_bottom_lines if(GPIO.input(cap_touch_pin) == 1): # Idles LOW, touch == HIGH global_state = 3 # timer countdown display state global_screen_to_save = 3 # Save the following data to timer display screen3 global_screen_to_print = 3 # Print the following data from timer display screen3 time_in_secs = 240 # 240secs == 4min for curr_sec in range(time_in_secs, -1, -1): # 240 to 0 inclusive # min0 : sec1 sec0 (3:52 == 3=min0, 5=sec1, 2=sec0) min0 = int(curr_sec/60) sec1 = int((curr_sec%60)/10) sec0 = int((curr_sec%60)%10) if(curr_sec > 150): # First 1min30sec (timer hits 2:30) color = BLUE else: # last 2min30sec (after time hits 2:30) color = RED # Draw timer countdown numbers number_x = 5 # top left X coordinate of whole 4:00 countdown number number_y = 9 # top left Y ... draw_number(number_x+0, number_y+0, min0, color) # min0 set_pixel(number_x+7, number_y+3, color) # colon set_pixel(number_x+7, number_y+9, color) # colon draw_number(number_x+9, number_y+0, sec1, color) # sec1 draw_number(number_x+16, number_y+0, sec0, color) # sec0 # Draw hourglass animation (turned out pretty, pretty, pretty cool) update_hourglass_sand(curr_sec) time.sleep(1.0) # seconds time.sleep(3) # Hold the 0:00 and then leave global_num_bottom_lines = -1 # reset hourglass variable global_state = 0 # go back to normal temperature display global_screen_to_print = 0 global_screen_to_save = 0 def IR_decode(): global global_state global global_screen_to_print # STATES of Program: # 0 = Normal - Display Temperature Line Graph (LEDs ON) (INITIAL STATE VALUE) # 1 = Normal - Rain Graph (LEDs ON) # 2 = Paused - Blank Screen (All LEDs OFF) global_screen_to_print = 0 while True: time.sleep(2) while(GPIO.input(IR_pin) == 1): # Wait while idle at 1 (logic HIGH) time.sleep(0.1) # Helps with hiccups of pulsing low time.sleep(1) # wait 1 sec for i in range(0,100): # Used to ignore "regular" IR signals intended for my TV only time.sleep(0.01) if(GPIO.input(IR_pin) == 0): # YES, switch state, long enough remote pulse if(global_state == 0): # currently in temp display, switch to rain global_state = 1 global_screen_to_print = 1 time.sleep(2) # wait 2 sec so screen changes and release remote button break elif(global_state == 1): # currently in rain display, switch to LEDs OFF global_state = 2 time.sleep(2) # wait 2 sec so screen changes and release remote button break elif(global_state == 2): # currently in OFF, switch to temp display global_state = 0 # Resumes normal or Turn ON the LEDs global_screen_to_print = 0 time.sleep(2) # wait 2 sec so screen changes and release remote button break def main_func(): # Weather updates every 4 minutes global global_screen_to_save while True: rain, uv, low, curr, high, temp_list, rain_list_hourly = get_weather_info() clear_screens() # Zeros out screen matrix/2D List global_screen_to_save = 0 # Save the following data to temp display screen0 draw_basics() draw_temperature_numbers('low', low) draw_temperature_numbers('curr', curr) draw_temperature_numbers('high', high) draw_temperature_line_graph(temp_list) # Have traveling animated green dot move to location for temp_rain in range(0,rain+1): # Bar Graph animation effect draw_rain(temp_rain) time.sleep(0.01) for temp_uv in range(0,uv+1): # Bar Graph animation effect draw_uv(temp_uv) time.sleep(0.01) global_screen_to_save = 1 # Save the following data to rain display screen1 draw_rain_basics() draw_rain_bar_graph(rain_list_hourly) # old #time.sleep(240) # seconds (4 minute interval updates) # 1,440 minutes in one day, 1440/4 = 360 weather updates daily # DarkSky API free limit is 1000 per day # new # instead of sleep() above, check for Capacitive Touch input to start 4min countdown # if touch detected, 240sec weather updates are paused until 4min countdown timer is complete for i in range(0,240): time.sleep(1.0) timer_display() #---------------------------Start Threads---------------------------# # Thread to update weather info and screen matrix values threading1 = threading.Thread(target=main_func) threading1.daemon = True threading1.start() # If an IR "pulse" lasts 0.5secs then toggle LEDs (oe_pin) threading2 = threading.Thread(target=IR_decode) threading2.daemon = True threading2.start() # Thread to toggle LEDs ON/OFF based off user keyboard/terminal input #threading3 = threading.Thread(target=terminal_input) #threading3.daemon = True #threading3.start() #--------------------------MAIN ROUTINE (Start)--------------------------# while True: refresh() # this constantly communicates with the LED matrix module, REQUIRED while(global_state == 2): # Paused (LEDs OFF) GPIO.output(oe_pin, 1) # disables LEDs time.sleep(0.1) #---------------------------MAIN ROUTINE (End)---------------------------#