Last active
March 6, 2021 16:53
-
-
Save MCJack123/5c10789fd19d9a04c0371a63c715275e to your computer and use it in GitHub Desktop.
iPod-style Spotify track info display (or clock when no track is playing) using 16x2 I2C LCD (Python 2 only)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Requires smbus: sudo apt install python-smbus | |
import I2C_LCD_driver | |
import time | |
import json | |
import httplib | |
import io | |
import os | |
from sys import exit | |
# Add the refresh key here (https://bit.ly/2DM1oRZ) | |
refresh_key = "" | |
# Specifies the number of minutes the clock will appear after (0 to turn off) | |
clock_appear_frequency = 3 | |
lcd = I2C_LCD_driver.lcd(0x27) | |
top_offset = 0 | |
bottom_offset = 0 | |
last_track = None | |
authkey = "" | |
expire_time = 0 | |
need_clock = False | |
timeout = 0 | |
if refresh_key == "": | |
lcd.lcd_display_string(" Please add key ", 1) | |
lcd.lcd_display_string(" to script file ", 2) | |
time.sleep(3) | |
lcd.lcd_display_string("https://bit.ly/ ", 1) | |
lcd.lcd_display_string("2DM1oRZ ", 2) | |
while True: | |
time.sleep(100) | |
def getAuthKey(): | |
global authkey | |
global expire_time | |
tokreq = httplib.HTTPConnection("cppconsole.bruienne.com", timeout=1) | |
tokres = None | |
try: | |
tokreq.request("GET", "/spotipi/get_token.php?token=" + refresh_key) | |
tokres = tokreq.getresponse() | |
except (socket.timeout, ssl.SSLError): | |
lcd.lcd_display_string(" Failed to get ", 1) | |
lcd.lcd_display_string(" access key ", 2) | |
print("Timeout") | |
time.sleep(5) | |
authkey = "" | |
return | |
if tokres.status != 200: | |
lcd.lcd_display_string(" Failed to get ", 1) | |
lcd.lcd_display_string(" access key ", 2) | |
print("HTTP " + str(tokres.status)) | |
time.sleep(5) | |
authkey = "" | |
return | |
tokresbody = tokres.read() | |
if tokresbody == "": | |
lcd.lcd_display_string(" Failed to get ", 1) | |
lcd.lcd_display_string(" access key ", 2) | |
print("Empty body") | |
time.sleep(5) | |
authkey = "" | |
return | |
tokbody = json.loads(tokresbody) | |
authkey = tokbody["token_type"] + " " + tokbody["access_token"] | |
expire_time = time.time() + tokbody["expires_in"] | |
def addWithMax(add1, add2, max, min = 0): | |
if add1 + add2 >= max: | |
return ((add1 + add2) - max * int((add1 + add2) / max)) + min | |
else: | |
return add1 + add2 | |
def substrWrap(string, start, length): | |
retval = "" | |
for i in range(0, length): | |
retval += string[addWithMax(start, i, len(string))] | |
return retval | |
def getTrackData(): | |
req = httplib.HTTPSConnection("api.spotify.com") | |
req.request("GET", "/v1/me/player/currently-playing", None, {"Authorization": authkey}) | |
res = req.getresponse() | |
if res.status != 200: | |
print("Got code " + str(res.status)) | |
return None | |
body = json.loads(res.read()) | |
if body["currently_playing_type"] == "ad": | |
return {"album": "", "artist": "", "track": "Advertisement"} | |
if not body["is_playing"] or body["item"] == None: | |
return None | |
artists = "" | |
for a in body["item"]["artists"]: | |
if artists != "": | |
artists += ", " | |
artists += a["name"] | |
return { | |
"album": body["item"]["album"]["name"], | |
"artist": artists, | |
"track": body["item"]["name"] | |
} | |
print("Getting key...") | |
getAuthKey() | |
lcd.lcd_clear() | |
while True: | |
if expire_time <= time.time() or authkey == "": | |
end_timee = time.time() + 5 | |
while end_timee > time.time(): | |
lcd.lcd_display_string(" %s " %time.strftime("%H:%M:%S"), 1) | |
lcd.lcd_display_string(" %s " %time.strftime("%m/%d/%Y"), 2) | |
time.sleep(.05) | |
top_offset = -4 | |
bottom_offset = -4 | |
getAuthKey() | |
elif int(time.time() % (clock_appear_frequency * 60)) == 0 and clock_appear_frequency > 0: | |
need_clock = True | |
if need_clock and (top_offset == 0 or bottom_offset == 0): | |
end_time = time.time() + 5 | |
lcd.lcd_clear() | |
while end_time > time.time(): | |
lcd.lcd_display_string(" %s " %time.strftime("%H:%M:%S"), 1) | |
lcd.lcd_display_string(" %s " %time.strftime("%m/%d/%Y"), 2) | |
time.sleep(.05) | |
top_offset = -4 | |
bottom_offset = -4 | |
lcd.lcd_clear() | |
need_clock = False | |
track = getTrackData() | |
if track == {}: | |
track = last_track | |
print(track) | |
if track == None: | |
lcd.lcd_display_string(" %s " %time.strftime("%H:%M:%S"), 1) | |
lcd.lcd_display_string(" %s " %time.strftime("%m/%d/%Y"), 2) | |
top_offset = -4 | |
bottom_offset = -4 | |
need_clock = False | |
last_track = None | |
time.sleep(0.25) | |
continue | |
if last_track != track: | |
top_offset = -4 | |
bottom_offset = -4 | |
top_line = track["track"] | |
bottom_line = track["artist"] + " -- " + track["album"] | |
if len(top_line) > 16 and len(bottom_line) > 16: | |
if len(top_line) > len(bottom_line): | |
bottom_line = bottom_line.ljust(len(top_line)) | |
elif len(bottom_line) > len(top_line): | |
top_line = top_line.ljust(len(bottom_line)) | |
top_line_orig = top_line | |
bottom_line_orig = bottom_line | |
top_pos = top_offset | |
if top_pos < 0: | |
top_pos = 0 | |
bottom_pos = bottom_offset | |
if bottom_pos < 0: | |
bottom_pos = 0 | |
if len(top_line) < 16: | |
top_line = top_line.center(16) | |
elif len(top_line) > 16: | |
top_line = substrWrap(top_line + " ", top_pos, 16) | |
if len(bottom_line) < 16: | |
bottom_line = bottom_line.center(16) | |
elif len(bottom_line) > 16: | |
bottom_line = substrWrap(bottom_line + " ", bottom_pos, 16) | |
lcd.lcd_display_string(top_line, 1) | |
lcd.lcd_display_string(bottom_line, 2) | |
top_offset = addWithMax(top_offset, 1, len(top_line_orig) + 4, -4) | |
bottom_offset = addWithMax(bottom_offset, 1, len(bottom_line_orig) + 4, -4) | |
time.sleep(.25) | |
last_track = track |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
# Original code found at: | |
# https://gist.github.com/DenisFromHR/cc863375a6e19dce359d | |
""" | |
Compiled, mashed and generally mutilated 2014-2015 by Denis Pleic | |
Made available under GNU GENERAL PUBLIC LICENSE | |
# Modified Python I2C library for Raspberry Pi | |
# as found on http://www.recantha.co.uk/blog/?p=4849 | |
# Joined existing 'i2c_lib.py' and 'lcddriver.py' into a single library | |
# added bits and pieces from various sources | |
# By DenisFromHR (Denis Pleic) | |
# 2015-02-10, ver 0.1 | |
""" | |
# i2c bus (0 -- original Pi, 1 -- Rev 2 Pi) | |
I2CBUS = 1 | |
import smbus | |
from time import sleep | |
class i2c_device: | |
def __init__(self, addr, port=I2CBUS): | |
self.addr = addr | |
self.bus = smbus.SMBus(port) | |
# Write a single command | |
def write_cmd(self, cmd): | |
self.bus.write_byte(self.addr, cmd) | |
sleep(0.0001) | |
# Write a command and argument | |
def write_cmd_arg(self, cmd, data): | |
self.bus.write_byte_data(self.addr, cmd, data) | |
sleep(0.0001) | |
# Write a block of data | |
def write_block_data(self, cmd, data): | |
self.bus.write_block_data(self.addr, cmd, data) | |
sleep(0.0001) | |
# Read a single byte | |
def read(self): | |
return self.bus.read_byte(self.addr) | |
# Read | |
def read_data(self, cmd): | |
return self.bus.read_byte_data(self.addr, cmd) | |
# Read a block of data | |
def read_block_data(self, cmd): | |
return self.bus.read_block_data(self.addr, cmd) | |
# commands | |
LCD_CLEARDISPLAY = 0x01 | |
LCD_RETURNHOME = 0x02 | |
LCD_ENTRYMODESET = 0x04 | |
LCD_DISPLAYCONTROL = 0x08 | |
LCD_CURSORSHIFT = 0x10 | |
LCD_FUNCTIONSET = 0x20 | |
LCD_SETCGRAMADDR = 0x40 | |
LCD_SETDDRAMADDR = 0x80 | |
# flags for display entry mode | |
LCD_ENTRYRIGHT = 0x00 | |
LCD_ENTRYLEFT = 0x02 | |
LCD_ENTRYSHIFTINCREMENT = 0x01 | |
LCD_ENTRYSHIFTDECREMENT = 0x00 | |
# flags for display on/off control | |
LCD_DISPLAYON = 0x04 | |
LCD_DISPLAYOFF = 0x00 | |
LCD_CURSORON = 0x02 | |
LCD_CURSOROFF = 0x00 | |
LCD_BLINKON = 0x01 | |
LCD_BLINKOFF = 0x00 | |
# flags for display/cursor shift | |
LCD_DISPLAYMOVE = 0x08 | |
LCD_CURSORMOVE = 0x00 | |
LCD_MOVERIGHT = 0x04 | |
LCD_MOVELEFT = 0x00 | |
# flags for function set | |
LCD_8BITMODE = 0x10 | |
LCD_4BITMODE = 0x00 | |
LCD_2LINE = 0x08 | |
LCD_1LINE = 0x00 | |
LCD_5x10DOTS = 0x04 | |
LCD_5x8DOTS = 0x00 | |
# flags for backlight control | |
LCD_BACKLIGHT = 0x08 | |
LCD_NOBACKLIGHT = 0x00 | |
En = 0b00000100 # Enable bit | |
Rw = 0b00000010 # Read/Write bit | |
Rs = 0b00000001 # Register select bit | |
class lcd: | |
#initializes objects and lcd | |
def __init__(self, addr): | |
self.lcd_device = i2c_device(addr) | |
self.lcd_write(0x03) | |
self.lcd_write(0x03) | |
self.lcd_write(0x03) | |
self.lcd_write(0x02) | |
self.lcd_write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE) | |
self.lcd_write(LCD_DISPLAYCONTROL | LCD_DISPLAYON) | |
self.lcd_write(LCD_CLEARDISPLAY) | |
self.lcd_write(LCD_ENTRYMODESET | LCD_ENTRYLEFT) | |
sleep(0.2) | |
# clocks EN to latch command | |
def lcd_strobe(self, data): | |
self.lcd_device.write_cmd(data | En | LCD_BACKLIGHT) | |
sleep(.0005) | |
self.lcd_device.write_cmd(((data & ~En) | LCD_BACKLIGHT)) | |
sleep(.0001) | |
def lcd_write_four_bits(self, data): | |
self.lcd_device.write_cmd(data | LCD_BACKLIGHT) | |
self.lcd_strobe(data) | |
# write a command to lcd | |
def lcd_write(self, cmd, mode=0): | |
self.lcd_write_four_bits(mode | (cmd & 0xF0)) | |
self.lcd_write_four_bits(mode | ((cmd << 4) & 0xF0)) | |
# write a character to lcd (or character rom) 0x09: backlight | RS=DR< | |
# works! | |
def lcd_write_char(self, charvalue, mode=1): | |
self.lcd_write_four_bits(mode | (charvalue & 0xF0)) | |
self.lcd_write_four_bits(mode | ((charvalue << 4) & 0xF0)) | |
# put string function with optional char positioning | |
def lcd_display_string(self, string, line=1, pos=0): | |
if line == 1: | |
pos_new = pos | |
elif line == 2: | |
pos_new = 0x40 + pos | |
elif line == 3: | |
pos_new = 0x14 + pos | |
elif line == 4: | |
pos_new = 0x54 + pos | |
self.lcd_write(0x80 + pos_new) | |
for char in string: | |
self.lcd_write(ord(char), Rs) | |
# clear lcd and set to home | |
def lcd_clear(self): | |
self.lcd_write(LCD_CLEARDISPLAY) | |
self.lcd_write(LCD_RETURNHOME) | |
# define backlight on/off (lcd.backlight(1); off= lcd.backlight(0) | |
def backlight(self, state): # for state, 1 = on, 0 = off | |
if state == 1: | |
self.lcd_device.write_cmd(LCD_BACKLIGHT) | |
elif state == 0: | |
self.lcd_device.write_cmd(LCD_NOBACKLIGHT) | |
# add custom characters (0 - 7) | |
def lcd_load_custom_chars(self, fontdata): | |
self.lcd_write(0x40); | |
for char in fontdata: | |
for line in char: | |
self.lcd_write_char(line) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
i implement de line, it runs nicely. i am verity impressed by your programming skills.
In past i used jSon lib, on Arduino IDE for a meteo station, but was a liitle harder on a microcontroller like (esp 32). On raspberry Pi is more easy.
thanks for you fast reply.
my respects!