├── README.md ├── scan.sh ├── MPi3_can.py └── MPi3.py /README.md: -------------------------------------------------------------------------------- 1 | # MPi3 2 | CAN-BUS enabled MP3 Player in Python (Running on Raspberry Pi) 3 | 4 | Plays music stored in sqlite-DB based on the amount of before plays with possibility to delete song from playmode. 5 | Controlls via Opel Astra H steering wheel buttons (UP+DOWN). 6 | Writes data to Opel Astra H Graphic or Color display. 7 | 8 | Uses MCP2515 and MCP2551 CAN-Transciever for Communication with vehicles midspeed-CAN-bus. 9 | A working CAN-Setup is required. 10 | -------------------------------------------------------------------------------- /scan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MUSIC_DIR=/data/musik 4 | DB=/data/mpi3/mpi3.db 5 | SQLFILE=/tmp/mpi3._update.sql 6 | 7 | IFS=" 8 | " 9 | 10 | for f in `find ${MUSIC_DIR} -type d|tac`; do 11 | n=`echo "${f}" | sed -e 's/[^A-Za-z0-9._-/]/_/g'` 12 | if [[ "${f}" != "${n}" ]]; then 13 | if [[ ${1} == "-v" ]]; then echo $n; fi 14 | mv "$f" "$n" 15 | fi 16 | done 17 | 18 | echo "UPDATE songs SET found=0;" >${SQLFILE} 19 | for f in `find ${MUSIC_DIR} -type f -name "*.mp3" -o -name "*.flac"`; do 20 | n=`echo "${f}" | sed -e 's/[^A-Za-z0-9._-/]/_/g'` 21 | if [[ "${f}" != "${n}" ]]; then 22 | if [[ ${1} == "-v" ]]; then echo $n; fi 23 | mv "$f" "$n" 24 | fi 25 | #echo "INSERT INTO songs (filename,found) VALUES('${n}',1) ON DUPLICATE KEY UPDATE found=1;" >> ${SQLFILE} 26 | echo "INSERT OR IGNORE INTO songs (filename,found) VALUES('${n}',1);" >> ${SQLFILE} 27 | echo "UPDATE SONGS SET found=1 WHERE filename LIKE '${n}';" >> ${SQLFILE} 28 | 29 | done 30 | 31 | echo "DELETE FROM disabled_songs WHERE sid IN (SELECT sid FROM songs WHERE found=0);" >> ${SQLFILE} 32 | echo "DELETE FROM songs WHERE found=0;" >> ${SQLFILE} 33 | sqlite3 ${DB} <${SQLFILE} 34 | rm ${SQLFILE} 35 | -------------------------------------------------------------------------------- /MPi3_can.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | def pack_multi_message(data): 4 | num=0 5 | package=[] 6 | line=bytearray() 7 | line.append(int((len(data) & 0xff00)/256+16)) # 0x10 + first byte of length 8 | line.append(len(data) & 0xff) # last byte of length 9 | data.append(1) # 0x01 not counted in lengt 10 | pos=1 11 | for i in range(0,len(data)): # Loop through data 12 | if(pos==7): # when package full 13 | pos=0 14 | num=(num+1) 15 | if(num==16): 16 | num=0 17 | package.append(line) # Append package to array 18 | line=[] # clear line 19 | line.append(num+32) # Set identifier 20 | line.append(data[i]) # Insert current data byte 21 | pos=(pos+1) 22 | for i in range(pos,7): 23 | line.append(0) # Fill last package with zero 24 | package.append(line) # Insert last package 25 | return(package) 26 | 27 | def generate_string(id,text,number=0): # String-ID,Message,circled number as prefix(optional) 28 | line=bytearray() # Create empty bytearray 29 | line.append(id) # Write String-ID 30 | if(number>0): # If with number 31 | line.append(len(text)+2) # Write length incl number+whitespace 32 | line.append(0x27) # First byte of circle 33 | line.append(127+number) # Second byte of circle 34 | line.append(0) # Fake UTF-16 35 | line.append(0x20) # Whitespace after number 36 | else: 37 | line.append(len(text)) # Write length (num of chars) 38 | for c in text: 39 | line.append(0) # Fake UTF-16 40 | if(ord(c)<256): 41 | line.append(ord(c)) 42 | else: 43 | line.append(0x20) 44 | return(line) 45 | 46 | def generate_aux_message(title,album,artist,mode): 47 | payload=bytearray([0xC0,0x00]) # Command for DIS 48 | data=bytearray([0x03]) # Datatype 49 | data=(data+generate_string(0x02,"Aux")) # Channel-Name 50 | data=(data+generate_string(0x01," ")) # ??? 51 | data=(data+generate_string(0x10,title)) # Title (Middle) 52 | data=(data+generate_string(0x11,artist)) # Artist (Top) 53 | data=(data+generate_string(0x12,album,mode)) # Album (Bottom) 54 | try: 55 | payload.append(len(data)) # TODO: Crashes if >256 56 | except ValueError: 57 | print("Message_Too_Long") 58 | payload.append(255) # TODO: extremly bad workaround 59 | 60 | return(pack_multi_message(payload+data)) 61 | -------------------------------------------------------------------------------- /MPi3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | ### 4 | # Configuration 5 | ### 6 | 7 | maxmode=3 # Number of modes 8 | modefile="lastmode.stat" # Lastmode file 9 | basedir="/data/mpi3" 10 | dbfile=basedir+"/mpi3.db" # SQLite database 11 | 12 | channel="can0" # CAN device 13 | interface="socketcan" # CAN inteface type 14 | can_filters = [] # CAN-BUS Adress filters 15 | can_filters.append({"can_id": int("201", base=16), "can_mask": int("FFF", base=16)}) # Messages from radio buttons 16 | can_filters.append({"can_id": int("206", base=16), "can_mask": int("FFF", base=16)}) # Messages from steering wheel 17 | can_filters.append({"can_id": int("188", base=16), "can_mask": int("FFF", base=16)}) # Messags from ECU with ignition state 18 | can_filters.append({"can_id": int("6C1", base=16), "can_mask": int("FFF", base=16)}) # Messags from EHU to DIS 19 | can_filters.append({"can_id": int("548", base=16), "can_mask": int("FFF", base=16)}) # Diagnostical reply from ECC 20 | 21 | v_data = {} # Array for vehicle data 22 | v_data['t_out']=0 # Declare all data 23 | v_data['t_eng']=0 24 | v_data['t_ac1']=0 25 | v_data['t_ac2']=0 26 | v_data['speed']=0 27 | v_data['rpm']=0 28 | v_data['volt']=0 29 | 30 | 31 | ### 32 | # Variable declaration 33 | ### 34 | 35 | canupcnt=0 36 | candncnt=0 37 | can_ign=0 38 | ignition=0 39 | offlineSongs=[] 40 | offlinePos=0 41 | mode=1 # Start mode - overwritten by modefile 42 | d_mode=1 # Display mode 43 | aux=0 44 | sid=0 45 | t_check_player=90 46 | t_check_ignition=100 47 | t_update_display=1000 48 | title="" 49 | artist="" 50 | album="" 51 | last_aux_message=0 52 | listenning=0 53 | 54 | 55 | ### 56 | # Modules 57 | ### 58 | 59 | import time # Time functions for sleep 60 | import pygame # Pygame for music 61 | import sqlite3 # sqlite3 for SQLite database 62 | import stagger # stagger for ID3 tags 63 | import math # Math functions maybe needless? 64 | import sys # Sys used for exit 65 | import os # OS to execute commands 66 | import can # Python-CAN for CAN-BUS 67 | import subprocess 68 | import MPi3_can 69 | 70 | 71 | ### 72 | # Classes 73 | ### 74 | 75 | class checkMessage(): # Notifier class for CAN 76 | def __call__(self,m): # Call function 77 | global canupcnt,candncnt,can_ign,last_aux_message,v_data,d_mode 78 | if(m.arbitration_id==0x0201): # If from radio butons 79 | if(m.data[0]==0x01 and m.data[1] > 0x30 and m.data[1] < 0x3A and m.data[2]==0x00): # Button pressed 80 | d_mode=(m.data[1]-0x30) # map button to mode 81 | if(m.arbitration_id==0x0206): # If from steeringwheel 82 | if(m.data[0]==0x01): # Check first data byte 83 | if(m.data[1]==0x91): # 2nd byte is BTN UP 84 | if(m.data[2]<=0x0A): # Pressed for less than 11 cycles 85 | if(m.data==bytearray([0x01,0x91,0x0A])): # Pressed exactly 11 cycles long 86 | canupcnt=300 # Request delete 87 | else: # Pressed for less than 10 cycles 88 | canupcnt=150 # Set counter to wait for more messages 89 | if(m.data[1]==0x92): # 2nd byte is BTN DOWN 90 | if(m.data[2]<=0x0A): # Pressed for less than 11 cycles 91 | if(m.data==bytearray([0x01,0x92,0x0A])): # Pressed exactly 11 cycles long 92 | candncnt=300 # Request delete 93 | else: # Pressed for less than 10 cycles 94 | candncnt=150 # Set counter to wait for more messages 95 | if(m.arbitration_id==0x06C1): # If from EHU 96 | if(m.data==bytearray([0x10,0x2E,0xC0,0x00,0x2B,0x03,0x01,0x01])) \ 97 | or(m.data==bytearray([0x10,0x36,0xC0,0x00,0x33,0x03,0x01,0x05])) \ 98 | or(m.data==bytearray([0x10,0x32,0x40,0x00,0x2F,0x03,0x01,0x03])): # If EHU in AUX-Mode 99 | corrupt_message() # Corrupt the Aux-Message 100 | last_aux_message=10000 # Delay to keep aux-state 101 | if(m.arbitration_id==0x0188): # If from ECU 102 | if(m.data==bytearray([0x46,0x0A,0x00,0x00,0x00,0x00])): # If ignition is on 103 | can_ign=1 # Set ignition 104 | if(m.data==bytearray([0x46,0x00,0x00,0x00,0x00,0x00])): # Ignition is off 105 | can_ign=0 # Set ignition 106 | if(m.arbitration_id==0x0548): # If from ECC 107 | if(m.data[0]==0x06): 108 | v_data['t_ac1']=((((m.data[1]*256)+m.data[2])/10)-10) # AC outlet 1 temp 109 | v_data['t_ac2']=((((m.data[5]*256)+m.data[6])/10)-10) # AC outlet 2 temp 110 | print("AC1: "+str(v_data['t_ac1'])) 111 | print("AC2: "+str(v_data['t_ac2'])) 112 | if(m.data[0]==0x07): 113 | v_data['volt']=(m.data[2]/10) # Battery voltage 114 | print("Voltage: "+str(v_data['volt'])) 115 | if(m.data[0]==0x10): 116 | v_data['t_eng']=(((m.data[3]*256)+m.data[4])/10) # Engine Temp 117 | v_data['t_out']=(((m.data[1]*256)+m.data[2])/10) # Outdoor Temp 118 | if(m.data[0]==0x10): 119 | v_data['t_eng']=(((m.data[3]*256)+m.data[4])/10) # Engine Temp 120 | v_data['t_out']=(((m.data[1]*256)+m.data[2])/10) # Outdoor Temp 121 | print("ENGINE: "+str(v_data['t_eng'])) 122 | print("OUT: "+str(v_data['t_out'])) 123 | if(m.data[0]==0x11): 124 | v_data['speed']=m.data[4] # Speed 125 | v_data['rpm']=((m.data[1]*256)+m.data[2]) # RPM 126 | print("SPEED: "+str(v_data['speed'])) 127 | print("RPM: "+str(v_data['rpm'])) 128 | 129 | 130 | ### 131 | # Functions 132 | ### 133 | 134 | def check_player(): 135 | global aux 136 | if(aux==0): # Only if not on AUX 137 | if not pygame.mixer.music.get_busy(): # If not playing 138 | time.sleep(0.1) 139 | if not pygame.mixer.music.get_busy(): # If still not playing 140 | print("Song ended") 141 | song_ended() # Go to next song 142 | 143 | def get_next_offline_song(): 144 | global offlineSongs, offlinePos 145 | offlinePos += 1 146 | if(offlinePos >= len(offlineSongs)): 147 | offlinePos=0 148 | return(offlineSongs[offlinePos]) 149 | 150 | def next_song(): 151 | global sid,ignition,playround,dbfile,aux,title,album,artist 152 | if(aux==1): 153 | return() 154 | if(ignition == 2): 155 | filename=get_next_offline_song() 156 | else: 157 | check_playround() # Check if playround ends 158 | db = sqlite3.connect(dbfile) # Load database 159 | cursor=db.cursor() 160 | cursor.execute("SELECT sid,filename FROM `songs` \ 161 | LEFT JOIN (SELECT * FROM disabled_songs WHERE mode="+str(mode)+") AS d USING(sid) \ 162 | WHERE mode is null \ 163 | AND playround < "+str(playround)+" \ 164 | ORDER BY listened,skipped,RANDOM() \ 165 | LIMIT 1;") 166 | try: 167 | song=cursor.fetchone() 168 | sid=song[0] 169 | except TypeError: 170 | print("Error while fetching song from DB") 171 | skip_song() 172 | return() 173 | cursor.execute("UPDATE songs SET playround="+str(playround)+" WHERE sid="+str(sid)+";") 174 | db.commit() 175 | filename=song[1] 176 | cursor.close(); 177 | db.close() # Close DB 178 | print("Song: "+filename+"(SID:"+str(sid)+")") 179 | if not os.path.isfile(filename): 180 | print("File not found!") 181 | skip_song() 182 | pygame.mixer.music.set_volume(1) 183 | try: 184 | pygame.mixer.music.load(filename) 185 | except: 186 | print("Unable to play "+filename) 187 | time.sleep(0.1) 188 | return() 189 | pygame.mixer.music.play() 190 | try: 191 | id3=stagger.read_tag(filename) 192 | title=id3.title 193 | album=id3.album 194 | artist=id3.artist 195 | except: 196 | title=os.path.basename(filename) 197 | artist="" 198 | album="" 199 | print("title: "+title) 200 | print("album: "+album) 201 | print("artist: "+artist) 202 | print("playing:"+filename) 203 | update_display() 204 | 205 | def skip_song(): 206 | global sid,ignition,dbfile 207 | print("skipped") 208 | pygame.mixer.music.fadeout(500) 209 | if(ignition != 2): 210 | db = sqlite3.connect(dbfile) # Load database 211 | cursor=db.cursor() 212 | cursor.execute("UPDATE songs SET skipped=skipped+1 WHERE sid="+str(sid)+";") 213 | db.commit() 214 | cursor.close(); 215 | db.close() # Close DB 216 | next_song() 217 | 218 | def song_ended(): 219 | global sid,ignition,dbfile 220 | print("ended") 221 | if(listenning==1): # Only continnue if someone listens 222 | if(ignition != 2): 223 | db = sqlite3.connect(dbfile) # Load database 224 | cursor=db.cursor() 225 | cursor.execute("UPDATE songs SET listened=listened+1 WHERE sid="+str(sid)+";") 226 | db.commit() 227 | cursor.close(); 228 | db.close() # Close DB 229 | next_song() 230 | 231 | def disable_song(): 232 | global mode,sid,ignition,songcnt,dbfile,aux 233 | if(aux==1): 234 | return() 235 | print("deleting song") 236 | if(ignition != 2): 237 | db = sqlite3.connect(dbfile) # Load database 238 | cursor=db.cursor() 239 | cursor.execute("INSERT INTO disabled_songs (sid,mode) VALUES("+str(sid)+","+str(mode)+");") 240 | cursor.close(); 241 | db.commit() 242 | db.close() # Close DB 243 | if(os.path.isfile(basedir+"/MPi3/del.mp3")): 244 | pygame.mixer.music.load(basedir+"/MPi3/del.mp3") 245 | pygame.mixer.music.play() 246 | songcnt-=1 247 | else: 248 | next_song() 249 | 250 | def switch_mode(): 251 | global mode,maxmode,aux 252 | if(aux==1): 253 | toggle_aux() 254 | return() 255 | mode += 1 256 | if(mode > maxmode): 257 | mode=1 258 | print("mode"+str(mode)) 259 | if(os.path.isfile(basedir+"/MPi3/m"+str(mode)+".mp3")): # Check if mode-file exists 260 | pygame.mixer.music.load(basedir+"/MPi3/m"+str(mode)+".mp3") # Play modefile 261 | pygame.mixer.music.play() 262 | file=open(modefile,"w") 263 | file.write(str(mode)) 264 | file.close() 265 | check_songcnt() 266 | 267 | def check_songcnt(): 268 | global songcnt,mode,dbfile 269 | db = sqlite3.connect(dbfile) # Load database 270 | cursor=db.cursor() 271 | cursor.execute("SELECT count(s.sid) FROM `songs` AS s \ 272 | LEFT JOIN (SELECT * FROM disabled_songs WHERE mode="+str(mode)+") AS d USING(sid) \ 273 | WHERE mode is null;") 274 | result=cursor.fetchone() 275 | cursor.close(); 276 | db.close() # Close DB 277 | songcnt=result[0] 278 | 279 | def check_playround(): 280 | global playround,dbfile 281 | db = sqlite3.connect(dbfile) # Load database 282 | cursor=db.cursor() 283 | cursor.execute("SELECT count(s.sid) FROM `songs` AS s \ 284 | LEFT JOIN (SELECT * FROM disabled_songs WHERE mode="+str(mode)+") AS d USING(sid) \ 285 | WHERE mode is null \ 286 | AND playround="+str(playround)+";") 287 | result=cursor.fetchone() 288 | cursor.close(); 289 | db.close() # Close DB 290 | print("playround: "+str(playround)) 291 | print("played: "+str(result[0])) 292 | print("cnt: "+str(songcnt)) 293 | if((result[0]/songcnt) > .9): 294 | playround=(playround+1) 295 | print("NEXT ROUND") 296 | 297 | def init_playround(): 298 | global playround, songcnt,dbfile 299 | db = sqlite3.connect(dbfile) # Load database 300 | cursor=db.cursor() # Open Database session 301 | cursor.execute("SELECT max(playround) FROM `songs`;") # Query playround 302 | result=cursor.fetchone() # Get results 303 | playround=result[0] # Write results to variable 304 | cursor.close() # Close session 305 | db.close() # Close DB 306 | check_songcnt() # Get amount of songs in db 307 | check_playround() # Check if playround ends 308 | 309 | def check_btn(): 310 | global canupcnt,candncnt,listenning,last_aux_message 311 | 312 | if(canupcnt>0): # If UP pressed 313 | if(canupcnt>1): # In wait cycle 314 | if(canupcnt>200): # Long press 315 | disable_song() # Disable song 316 | canupcnt=0 # Reset counter 317 | else: # Short press 318 | canupcnt=(canupcnt-1) # Decrement wait counter 319 | else: # wait cycle ended 320 | next_song() # Skip song 321 | canupcnt=0 # Reset counter 322 | 323 | if(candncnt>0): # If DOWN pressed 324 | if(candncnt>1): # In wait cycle 325 | if(candncnt>200): # Long press 326 | candncnt=0 # Reset counter 327 | toggle_aux() # ToggleAUX 328 | else: # Short press 329 | candncnt=(candncnt-1) # Decrement wait counter 330 | else: # wait cycle ended 331 | candncnt=0 # Reset counter 332 | switch_mode() # Switch mode 333 | 334 | if(last_aux_message>0): # If in AUX Mode 335 | if(listenning==0): 336 | listenning=1 # Player active 337 | print("Listener started") 338 | last_aux_message=(last_aux_message-1) # Decrement counter 339 | else: 340 | if(listenning==1): 341 | listenning=0 # No one listenns 342 | print("No listeners") 343 | 344 | def toggle_aux(): 345 | global aux,artist,album,mode,title 346 | if(aux==1): 347 | aux=0 348 | print("Stopping aux-loop") 349 | os.system("pkill alsaloop") 350 | else: 351 | aux=1 352 | print("Starting aux-loop") 353 | pygame.mixer.music.fadeout(500) 354 | artist="" 355 | title="TV-Mode" 356 | album="" 357 | subprocess.Popen(["/usr/bin/alsaloop","-C","hw:1,0","-P","default","-c","1","-S","5","-t","200000"]) 358 | 359 | 360 | def makeRO(): 361 | global offlineSongs, offlinePos 362 | print("Make Readonly") 363 | db = sqlite3.connect(dbfile) # Load database 364 | cursor=db.cursor() # Open Database session 365 | cursor.execute("SELECT sid,filename FROM `songs` \ 366 | LEFT JOIN (SELECT * FROM disabled_songs WHERE mode="+str(mode)+") AS d USING(sid) \ 367 | WHERE mode is null \ 368 | AND playround < "+str(playround)+" \ 369 | AND skipped <= (SELECT MIN(skipped) FROM `songs`) \ 370 | AND listened <= (SELECT MIN(listened) FROM `songs`) \ 371 | ORDER BY RANDOM() \ 372 | LIMIT 30;") 373 | songs=cursor.fetchall() # Get results 374 | cursor.close(); # Close session 375 | for song in songs: # Loop through songs 376 | offlineSongs.append(song[1]) # Write songs to array 377 | offlinePos=0 # Start list from beginning 378 | db.close() # Close database 379 | print("Done.syncing.") 380 | os.system("/bin/sync") # Sync filesystem 381 | 382 | def check_ignition(): 383 | global ignition,can_ign # 0: was never on 384 | if(can_ign==1): # 1: Currently on 385 | if ignition != 1: # 2: was on 386 | ignition=1 387 | else: 388 | if ignition == 1: # if was on before 389 | makeRO() # Switch to safe mode 390 | ignition=2 391 | 392 | def update_display(): 393 | global bus,artist,album,mode,title,listenning,d_mode,v_data,playround,ignition 394 | thirdrow="" # Variable for third row 395 | if(listenning==1): 396 | if(d_mode==1): 397 | thirdrow=album 398 | if(d_mode==2): 399 | thirdrow="Engine temp: "+str(v_data['t_eng'])+" °C" 400 | if(d_mode==3): 401 | thirdrow="Outdoor temp: "+str(v_data['t_out'])+" C" 402 | if(d_mode==4): 403 | thirdrow="Speed: "+str(v_data['speed'])+" km/h" 404 | if(d_mode==5): 405 | thirdrow="RPM: "+str(v_data['rpm'])+" rpm" 406 | if(d_mode==6): 407 | thirdrow="Playround: "+str(playround) 408 | if(d_mode==7): 409 | if(ignition==1): 410 | thirdrow="Ignition ON" 411 | else: 412 | thirdrow="Ignition OFF" 413 | if(d_mode==8): 414 | thirdrow="Voltage: "+str(v_data['volt'])+" V" 415 | if(d_mode>8): 416 | thirdrow="UNUSED" 417 | print("Sending..") 418 | for package in MPi3_can.generate_aux_message(title,thirdrow,artist,mode): 419 | msg = can.Message(arbitration_id=0x06C1,data=package,extended_id=False) 420 | bus.send(msg) 421 | time.sleep(0.001) # 1ms pause 422 | print("ok") 423 | 424 | def corrupt_message(): 425 | global bus 426 | time.sleep(0.001) 427 | msg = can.Message(arbitration_id=0x06C1,data=[0x10,0x2E,0xC0,0x00,0x2B,0x03,0x01,0x01],extended_id=False) 428 | bus.send(msg) 429 | 430 | 431 | ### 432 | # Initialization 433 | ### 434 | 435 | if os.path.isfile(modefile): # Check if modfile exists 436 | file=open(modefile,"r") # Open modefile 437 | mode=int(file.read()) # Read modefile 438 | file.close() # Close modefile 439 | print("Reading mode "+str(mode)+" from file") 440 | 441 | pygame.init() # Initialize pygame framework for music 442 | 443 | db = sqlite3.connect(dbfile) # Load database 444 | 445 | bus = can.interface.Bus(channel, bustype=interface, can_filters=can_filters) # Initialize CAN-BUS 446 | notifier = can.Notifier(bus, [checkMessage()]) # Register notifier class 447 | 448 | init_playround() # Get actual playround from db 449 | next_song() # Begin play 450 | 451 | 452 | ### 453 | # Loop 454 | ### 455 | 456 | try: 457 | while True: # Mainloop 458 | time.sleep(0.001) # 1ms pause 459 | 460 | t_check_ignition=(t_check_ignition-1) # decrement timer 461 | if(t_check_ignition==0): # on timer hit 462 | t_check_ignition=10 # reset timer 463 | check_ignition() # run ignition check 464 | 465 | check_btn() # run button check 466 | 467 | t_check_player=(t_check_player-1) # decrement timer 468 | if(t_check_player==0): # on timer hit 469 | t_check_player=10 # reset timer 470 | check_player() # Check for running songs 471 | 472 | t_update_display=(t_update_display-1) # decrement timer 473 | if(t_update_display==0): # on timer hit 474 | t_update_display=1000 # reset timer 475 | update_display() # Update Display 476 | 477 | 478 | except KeyboardInterrupt: 479 | bus.shutdown() # Free CAN-BUS 480 | --------------------------------------------------------------------------------