2021-04-26 18:51:21 +00:00
# Copyright (C) 2020 Daniel Richardson richardson.daniel@hotmail.co.uk
#
# This file is part of stockTicker project for justinodunn.
#
# stockTicker can not be copied and/or distributed without the express
# permission of Daniel Richardson
2021-04-28 19:39:30 +00:00
2021-05-01 10:39:20 +00:00
import sys , select
2021-04-26 18:51:21 +00:00
import os
import threading
from PIL import Image , ImageDraw , ImageFont
import time
import csv
2021-04-28 19:39:30 +00:00
import requests
2021-04-26 18:51:21 +00:00
2021-04-27 18:29:14 +00:00
from rgbmatrix import RGBMatrix , RGBMatrixOptions
2021-04-28 19:39:30 +00:00
import finnhub
2021-04-27 18:29:14 +00:00
2021-05-01 10:39:20 +00:00
def getInput ( Block = False ) :
if Block or select . select ( [ sys . stdin ] , [ ] , [ ] , 0 ) == ( [ sys . stdin ] , [ ] , [ ] ) :
msg = sys . stdin . read ( 1 )
#sys.stdin.flush()
else :
msg = ' '
return msg
2021-04-26 19:27:34 +00:00
class StockTicker ( ) :
def __init__ ( self ) :
#Define global resources
self . symbols = [ ]
2021-04-28 19:39:30 +00:00
self . ApiKey = " c24qddqad3ickpckgg80 "
self . SandboxApiKey = " sandbox_c24qddqad3ickpckgg8g "
2021-04-26 19:27:34 +00:00
self . greenORred = ( 255 , 255 , 255 )
2021-05-03 10:39:31 +00:00
#self.blank = Image.open('logos/blank.png')
self . blank = Image . new ( ' RGB ' , ( 0 , 32 ) )
2021-04-26 19:27:34 +00:00
self . running = True
2021-04-27 18:29:14 +00:00
self . brightness = 1.0
2021-05-03 10:39:31 +00:00
self . delay = 0.02
2021-04-27 18:29:14 +00:00
# Configuration for the matrix
options = RGBMatrixOptions ( )
2021-05-04 16:39:16 +00:00
options . rows = 32
2021-04-27 18:29:14 +00:00
options . cols = 64
2021-05-04 20:55:28 +00:00
options . chain_length = 2
2021-04-27 18:29:14 +00:00
options . parallel = 1
options . hardware_mapping = ' adafruit-hat ' # If you have an Adafruit HAT: 'adafruit-hat'
2021-04-28 19:39:30 +00:00
options . gpio_slowdown = 3
2021-04-27 18:29:14 +00:00
self . matrix = RGBMatrix ( options = options )
2021-05-01 11:08:15 +00:00
self . finnhubClient = finnhub . Client ( api_key = self . ApiKey )
2021-04-27 18:29:14 +00:00
def openImage ( self , image_file ) :
image = Image . open ( image_file )
# Make image fit our screen.
#image.thumbnail((self.matrix.width, self.matrix.height), Image.ANTIALIAS)
image = image . convert ( ' RGB ' )
return image
2021-05-03 10:39:31 +00:00
def setImage ( self , image , offset_x = 0 , offset_y = 0 , unsafe = False ) :
2021-04-27 18:29:14 +00:00
if ( image . mode != " RGB " ) :
raise Exception ( " Currently, only RGB mode is supported for SetImage(). Please create images with mode ' RGB ' or convert first with image = image.convert( ' RGB ' ). Pull requests to support more modes natively are also welcome :) " )
if unsafe :
2021-05-01 10:39:20 +00:00
#In unsafe mode we directly acceshow to send commands to running python processs the underlying PIL image array
2021-04-27 18:29:14 +00:00
#in cython, which is considered unsafe pointer accecss,
#however it's super fast and seems to work fine
#https://groups.google.com/forum/#!topic/cython-users/Dc1ft5W6KM4
img_width , img_height = image . size
2021-04-28 19:39:30 +00:00
print ( ' args ' , img_width , img_height , offset_x , offset_y , image )
2021-04-27 18:29:14 +00:00
self . matrix . SetPixelsPillow ( offset_x , offset_y , img_width , img_height , image )
else :
# First implementation of a SetImage(). OPTIMIZE_ME: A more native
# implementation that directly reads the buffer and calls the underlying
# C functions can certainly be faster.
img_width , img_height = image . size
pixels = image . load ( )
for x in range ( max ( 0 , - offset_x ) , min ( img_width , self . matrix . width - offset_x ) ) :
for y in range ( max ( 0 , - offset_y ) , min ( img_height , self . matrix . height - offset_y ) ) :
( r , g , b ) = pixels [ x , y ]
2021-05-01 10:39:20 +00:00
self . matrix . SetPixel ( x + offset_x , y + offset_y , r * self . brightness , g * self . brightness , b * self . brightness )
2021-04-26 19:27:34 +00:00
2021-05-03 10:39:31 +00:00
def scrollImage ( self , image_file , offset_x = 0 , offset_y = 0 ) :
2021-04-27 18:29:14 +00:00
image = self . openImage ( image_file )
2021-04-27 18:59:25 +00:00
img_width , img_height = image . size
2021-04-27 18:29:14 +00:00
2021-04-27 18:59:25 +00:00
print ( offset_x , offset_y )
while offset_x > - img_width :
offset_x - = 1
2021-05-03 10:39:31 +00:00
self . setImage ( image , offset_x = offset_x , offset_y = offset_y )
2021-04-27 18:29:14 +00:00
time . sleep ( delay )
2021-04-27 18:59:25 +00:00
2021-05-03 10:39:31 +00:00
def scrollImageTransition ( self , image_files , offset_x = 0 , offset_y = 0 ) :
2021-04-27 18:59:25 +00:00
# use two image files and switch between them with a seemless transition
current_img = 1
2021-05-01 10:39:20 +00:00
#while True:
2021-04-27 18:59:25 +00:00
2021-05-01 10:39:20 +00:00
if current_img == 1 :
image = self . openImage ( image_files [ 0 ] )
elif current_img == 2 :
image = self . openImage ( image_files [ 1 ] )
img_width , img_height = image . size
2021-04-27 18:59:25 +00:00
2021-05-01 10:39:20 +00:00
while offset_x > - img_width :
offset_x - = 1
2021-05-03 10:39:31 +00:00
self . setImage ( image , offset_x = offset_x , offset_y = offset_y )
time . sleep ( self . delay )
2021-05-01 10:39:20 +00:00
try :
2021-05-03 10:39:31 +00:00
msg = getInput ( )
if msg == ' K ' :
2021-05-01 10:39:20 +00:00
self . resetMatrix ( )
break
2021-05-03 10:39:31 +00:00
elif msg == ' s ' :
2021-05-04 20:55:28 +00:00
stock_ticker . delay = 0.03
2021-05-03 10:39:31 +00:00
elif msg == ' m ' :
stock_ticker . delay = 0.01
2021-05-04 20:55:28 +00:00
elif msg == ' f ' :
stock_ticker . delay = 0.005
2021-05-03 10:39:31 +00:00
elif msg in [ ' 0 ' , ' 1 ' , ' 2 ' , ' 3 ' , ' 4 ' , ' 5 ' , ' 6 ' , ' 7 ' , ' 8 ' , ' 9 ' ] :
self . brightness = min ( 1.0 , float ( msg ) / 10 + 0.1 )
2021-05-01 10:39:20 +00:00
except KeyboardInterrupt :
sys . stdout . flush ( )
pass
2021-04-27 18:59:25 +00:00
2021-05-01 10:39:20 +00:00
if current_img == 1 :
current_img = 2
elif current_img == 2 :
current_img = 1
offset_x = 0
2021-04-27 18:29:14 +00:00
2021-04-26 19:27:34 +00:00
#Get the logo from file that is the same as ticker name
def getLogo ( self , Ticker ) :
2021-05-04 16:39:16 +00:00
logos_path = os . path . join ( os . path . dirname ( os . path . abspath ( __file__ ) ) , ' logos ' )
2021-04-26 19:27:34 +00:00
try :
2021-05-04 16:39:16 +00:00
Logo = Image . open ( os . path . join ( logos_path , Ticker + ' .png ' ) )
2021-04-26 19:27:34 +00:00
except :
print ( ' Could not find logo for: ' + Ticker + ' using default ' )
2021-05-04 16:39:16 +00:00
Logo = Image . open ( os . path . join ( logos_path , ' default.png ' ) )
2021-04-26 19:27:34 +00:00
return Logo
#Using change between min and day price give appropriate arrow
#and set the overall change colour
def getArrow ( self , CHANGE ) :
self . greenORred
2021-05-04 16:39:16 +00:00
logos_path = os . path . join ( os . path . dirname ( os . path . abspath ( __file__ ) ) , ' logos ' )
2021-04-26 19:27:34 +00:00
if ( CHANGE > 0 ) :
2021-05-04 16:39:16 +00:00
Arrow = Image . open ( os . path . join ( logos_path , ' up.png ' ) )
2021-04-26 19:27:34 +00:00
self . greenORred = ( 0 , 255 , 0 )
return Arrow , CHANGE
2021-05-04 16:39:16 +00:00
Arrow = Image . open ( os . path . join ( logos_path , ' down.png ' ) )
2021-04-26 19:27:34 +00:00
self . greenORred = ( 255 , 0 , 0 )
CHANGE = ( CHANGE * - 1 )
2021-04-26 18:51:21 +00:00
return Arrow , CHANGE
2021-04-26 19:27:34 +00:00
def get_text_dimensions ( self , text_string , font ) :
canvas = Image . new ( ' RGB ' , ( 100 , 100 ) )
draw = ImageDraw . Draw ( canvas )
monospace = font
text = text_string
white = ( 255 , 255 , 255 )
draw . text ( ( 10 , 10 ) , text , font = monospace , fill = white )
bbox = canvas . getbbox ( )
width = bbox [ 2 ] - bbox [ 0 ]
height = bbox [ 3 ] - bbox [ 1 ]
return width , height
#Draw Ticker, current and change onto one image
def textToImage ( self , TICKER , CURRENT , CHANGE , ARROW ) :
2021-05-01 11:09:21 +00:00
font = ImageFont . load ( " ./fonts/10x20.pil " )
2021-04-26 19:27:34 +00:00
img = Image . new ( ' RGB ' , ( 150 , 32 ) )
d = ImageDraw . Draw ( img )
2021-04-28 19:39:30 +00:00
d . text ( ( 4 , 0 ) , TICKER , fill = ( 255 , 255 , 255 ) , font = font )
2021-05-03 10:39:31 +00:00
d . text ( ( 4 , 16 ) , CURRENT , fill = self . greenORred , font = font )
2021-04-26 19:27:34 +00:00
text_width_current , text_height = self . get_text_dimensions ( CURRENT , font )
img . paste ( ARROW , ( ( text_width_current + 9 ) , 18 ) )
2021-05-03 10:39:31 +00:00
d . text ( ( ( text_width_current + 29 ) , 16 ) , CHANGE , fill = self . greenORred , font = font )
2021-04-26 19:27:34 +00:00
text_width_change , text_height = self . get_text_dimensions ( CHANGE , font )
newWidth = ( text_width_current + 29 ) + ( text_width_change )
img . crop ( ( 0 , 0 , newWidth , 32 ) )
return img
#Stitch the logo & prices picture into one image
2021-05-03 10:39:31 +00:00
def stitchImage ( self , image_list ) :
widths , heights = zip ( * ( i . size for i in image_list ) )
2021-04-26 19:27:34 +00:00
total_width = sum ( widths )
max_height = max ( heights )
new_im = Image . new ( ' RGB ' , ( total_width , max_height ) )
x_offset = 0
2021-05-03 10:39:31 +00:00
for im in image_list :
2021-04-26 19:27:34 +00:00
new_im . paste ( im , ( x_offset , 0 ) )
x_offset + = im . size [ 0 ]
2021-05-03 10:39:31 +00:00
print ( ' width: ' , im . size [ 0 ] )
2021-04-26 19:27:34 +00:00
return new_im
2021-05-03 10:39:31 +00:00
2021-04-26 19:27:34 +00:00
#Get current prices and day prices for the ticker then format the response into an array
2021-05-03 10:39:31 +00:00
def getStockPrices ( self , symbols ) :
apiCalledError = False
stock_info = [ ]
2021-04-26 19:27:34 +00:00
try :
2021-05-03 10:39:31 +00:00
quotes = [ self . finnhubClient . quote ( symbol ) for symbol in symbols ]
2021-04-28 19:39:30 +00:00
current_prices = [ quote [ ' c ' ] for quote in quotes ]
opening_prices = [ quote [ ' o ' ] for quote in quotes ]
2021-05-03 10:39:31 +00:00
for i , symbol in enumerate ( symbols ) :
stock_info . append ( [ symbol , current_prices [ i ] , opening_prices [ i ] ] )
2021-04-28 19:39:30 +00:00
except Exception as e :
2021-04-26 19:27:34 +00:00
print ( " Could not fetch data - API CALLS REACHED? - Will display old image " )
2021-04-28 19:39:30 +00:00
print ( e )
2021-05-03 10:39:31 +00:00
apiCalledError = True
return stock_info , apiCalledError
2021-05-01 10:39:20 +00:00
def resetMatrix ( self ) :
for x in range ( self . matrix . width ) :
for y in range ( self . matrix . height ) :
self . matrix . SetPixel ( x , y , 0 , 0 , 0 )
2021-04-26 19:27:34 +00:00
#Connect all the pieces togeather creating 1 long final stock image
2021-05-01 11:08:15 +00:00
def getFullStockImage ( self ) :
2021-04-26 19:27:34 +00:00
2021-05-03 10:39:31 +00:00
image_list = [ ]
2021-04-28 19:39:30 +00:00
2021-05-03 10:39:31 +00:00
start = time . time ( )
stock_info , apiCalledError = self . getStockPrices ( self . symbols )
if ( apiCalledError == False ) :
for i in range ( len ( stock_info ) ) :
change = float ( stock_info [ i ] [ 1 ] ) - float ( stock_info [ i ] [ 2 ] ) #TEXT
ticker = stock_info [ i ] [ 0 ] #TEXT
current = ' %.2f ' % float ( stock_info [ i ] [ 1 ] ) #TEXT
logo = self . getLogo ( stock_info [ i ] [ 0 ] )
arrow , change = self . getArrow ( change )
change = ' %.2f ' % change
midFrame = self . textToImage ( ticker , current , change , arrow ) #IMAGE THE TEXT
stitchedStock = self . stitchImage ( [ logo , midFrame ] )
image_list . append ( self . blank )
image_list . append ( stitchedStock )
2021-04-26 19:27:34 +00:00
print ( ' elapsed time loop ended: ' + str ( ( time . time ( ) - start ) ) )
2021-05-03 10:39:31 +00:00
if ( apiCalledError == False ) :
finalDisplayImage = self . stitchImage ( image_list )
finalDisplayImage . save ( ' final.ppm ' )
2021-04-26 19:27:34 +00:00
#Send the final stitched image to the display for set amount of time
def displayMatrix ( self ) :
#os.system("sudo ./demo -D1 final.ppm -t " + str(displayTime) +" -m "+ str(speedDisplay) +" --led-gpio-mapping=adafruit-hat --led-rows=32 --led-cols=256")
2021-04-28 19:39:30 +00:00
#os.system("sudo ./demo -D1 final.ppm -t " + str(self.displayTime) +" -m "+ str(self.speedDisplay) +" --led-gpio-mapping=adafruit-hat --led-rows=64 --led-cols=64 --led-slowdown-gpio=4 ")
2021-05-03 10:39:31 +00:00
self . scrollImageTransition ( [ ' final.ppm ' , ' final.ppm ' ] , offset_x = 0 , offset_y = 0 )
2021-04-26 19:27:34 +00:00
#Retrieve symbols from the CSV file
2021-05-03 10:39:31 +00:00
def getSymbols ( self ) :
2021-04-26 18:51:21 +00:00
2021-04-26 19:27:34 +00:00
self . symbols = [ ]
CSV = csv . reader ( open ( ' csv/tickers.csv ' , ' r ' ) )
2021-05-03 10:39:31 +00:00
2021-04-26 19:27:34 +00:00
for row in CSV :
2021-05-03 10:39:31 +00:00
print ( row )
self . symbols . append ( row [ 0 ] )
2021-04-26 19:27:34 +00:00
print ( self . symbols )
#Main run definition called by server
2021-05-03 10:39:31 +00:00
def runStockTicker ( self , runtime , delay , speedtime ) :
self . getSymbols ( )
2021-04-26 19:27:34 +00:00
self . GetfullStockImage ( )
self . keySwapper + = 1
self . running = True
2021-05-03 10:39:31 +00:00
2021-04-27 18:29:14 +00:00
2021-04-26 19:27:34 +00:00
while ( True ) :
if ( self . running == True ) :
2021-05-03 10:39:31 +00:00
#th = threading.Thread(target=self.displayMatrix)
#th.start()
self . displayMatrix ( )
time . sleep ( ( int ( runtime ) - int ( delay ) ) )
self . getfullStockImage ( )
#th.join()
2021-04-26 18:51:21 +00:00
else :
2021-04-26 19:27:34 +00:00
break ;
#Change running to false stopping refresh at next checkpoint
def stopStockTicker ( self ) :
self . keySwapper = 0
self . running = False
print ( ' MATRIX DISPLAY STOP CALLED ' )
2021-05-01 10:39:20 +00:00
if __name__ == ' __main__ ' :
#print(sys.stdin.readlines())
stock_ticker = StockTicker ( )
2021-05-03 10:39:31 +00:00
stock_ticker . getSymbols ( )
2021-05-04 16:39:16 +00:00
stock_ticker . getFullStockImage ( )
stock_ticker . displayMatrix ( )
2021-05-01 10:39:20 +00:00
while True :
msg = getInput ( )
2021-05-01 11:08:15 +00:00
if msg == ' S ' :
stock_ticker . getFullStockImage ( )
2021-05-01 10:39:20 +00:00
stock_ticker . displayMatrix ( )
2021-05-03 10:39:31 +00:00
elif msg == ' s ' :
2021-05-04 20:55:28 +00:00
stock_ticker . delay = 0.03
2021-05-03 10:39:31 +00:00
elif msg == ' m ' :
stock_ticker . delay = 0.01
2021-05-04 20:55:28 +00:00
elif msg == ' f ' :
stock_ticker . delay = 0.005
2021-05-03 10:39:31 +00:00
elif msg in [ ' 0 ' , ' 1 ' , ' 2 ' , ' 3 ' , ' 4 ' , ' 5 ' , ' 6 ' , ' 7 ' , ' 8 ' , ' 9 ' ] :
stock_ticker . brightness = min ( 1.0 , float ( msg ) / 10 + 0.1 )
2021-05-01 10:39:20 +00:00