Volumio in raspberry pi zero w with ili9341 display and PCM5102

Пришла мне идея добавить к моему Volumio дисплей.
И при этом попутно собрать все в маленький бокс что бы выглядело как готовое устройство.
Уж очень мне понравилось использование Volumio.
Купил я новый rasperry pi zero w еще один ЦАП pcm5102 и дисплей ili9341 с SPI интерфейсом.
Хотелось вывести на него лишь полезную информацию, весь рабочий стол мне не нужен.
Скрипт сейчас выглядит сыро, накопирован из разных источников и частично допилен мною.
Желающим присоединится к его доработке, всегда пожалуйста пишите доработаем.
В общем процесс сборки всей этой кухни читаем ниже.

Качаем образ Volumio для Raspberry Pi https://volumio.org/get-started/
Качаем Win32 Disk Imager https://sourceforge.net/projects/win32diskimager/

Пины подключения дисплея к raspberry:

GPIO 08 - CS
GPIO 09 - MISO
GPIO 10 - MOSI
GPIO 11 - CLK
GPIO 12 - LED
GPIO 24 - DC/RS
GPIO 23 - RST
PIN 17  - VCC
PIN 20  - GND

Пины подключения PCM5102 к raspberry:

Not connected - SCK (В этом режиме у PCM5102 перемычка под SCK должна быть запаяна.)
GPIO 18 - BCK
GPIO 21 - DIN
GPIO 19 - LCK
PIN 39 - GND
PIN 01(02) - VIN
  1. Записываем образ Volumio на MicroSD карту.
  2. Включаем ждем загрузки.
  3. Берем свой мобильный телефон и находим WiFi сеть Volumio подключаемся, пароль: volumio2
  4. При удачном подключении должен открытся визард установки.
  5. Выбираем язык.
  6. Название устройства.
  7. Наличие ЦАП, для PCM5102 — Generic I2S DAC
  8. Подключаемся к своей WiFi сети
  9. Если нужно добавляем USB диск с музыкой.
  10. Если есть желание и возможность донатим на развитие плеера.
  11. Перезагружаемся.

После загрузки находим на своем роутере или используя https://www.advanced-ip-scanner.com/ru/ сканер сети новое устройство с именем как вы указали при настройке в визарде.
Нашли заходим: у вас это может выглядеть примерно вот так http://192.168.0.15 нам нужно зайти в режим включения опций разработки заходим сюда http://192.168.0.15/dev
Тапаем на SSH ENABLE
Заходим по SSH login: volumio pass: volumio

Выставляем временную зону:

dpkg-reconfigure tzdata

Выставляем локаль:

echo "export LC_ALL=en_US.UTF-8" >> ~/.bashrc
echo "export LANG=en_US.UTF-8" >> ~/.bashrc
echo "export LANGUAGE=en_US.UTF-8" >> ~/.bashrc

Качаем шрифты:
https://www.1001freefonts.com/d/2596/unispace.zip
Ложим их в /home/volumio/fonts

Устанавливаем зависимости и софт:

sudo apt-get update
sudo apt-get install build-essential python-dev python-smbus python-pip python-imaging python-numpy git raspi-config mc
sudo pip install RPi.GPIO python-mpd2

В raspi-config включаем SPI:
Заходим в Interfacing Options и включаем SPI

Качаем устанавливаем модули дисплея:

cd /home/volumio
git clone https://github.com/adafruit/Adafruit_Python_ILI9341.git
cd Adafruit_Python_ILI9341
sudo python setup.py install

Создаем скрипт запуска run_display.sh

#!/bin/bash

sleep 60
nohup python /home/volumio/display.py  > /dev/null 2>&1 &

exit 0

В файл /etc/rc.local добавляем в автозагрузку скрипт:

/home/volumio/run_display.sh

exit 0

Создаем скрипт работы дисплея display.py

import Image
import ImageDraw
import ImageFont
import time
import subprocess
import os
import glob
import socket
import Adafruit_ILI9341 as TFT
import Adafruit_GPIO as GPIO
import Adafruit_GPIO.SPI as SPI
import gc
import mpd
client = mpd.MPDClient(use_unicode=True)
client.timeout = 10
client.connect("localhost", 6600)

#setup to monitor pin for shutdown on power stich off
gpio = GPIO.get_platform_gpio()

#gpio.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) # monitor power switch

#gpio.setup(21, GPIO.OUT) # Take control of Keep Alive signal to relay board
gpio.setup(16, GPIO.OUT) # Takecontrol of Power LED
gpio.setup(12, GPIO.OUT) # Take control of LCD Backlight

#gpio.output(21, True) # Turn on Keep Alive
gpio.output(16, True) # Turn on Power LED
gpio.output(12, True) # Turn on LCD backlight


# Raspberry Pi pin configuration for screen
DC = 24
RST = 23
SPI_PORT = 0
SPI_DEVICE = 0

# Create TFT LCD display class.
disp = TFT.ILI9341(DC, rst=RST, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=64000000))

# Initialize display.
disp.begin()

# Clear the Display
disp.clear((0, 0, 0))   #DON'T DO THIS OFTEN, IT LEAKS MEMORY IF REPEATED FREQUENTLY

# Get a PIL Draw object to start drawing on the display buffer.
draw = disp.draw()

# load a TTF font
# I chose fixed width for now, to make it easier to center things

font = ImageFont.truetype('/home/volumio/fonts/unispace.ttf', 14)
smallfont = ImageFont.truetype('/home/volumio/fonts/unispace.ttf', 12)
bigfont = ImageFont.truetype('/home/volumio/fonts/unispace.ttf', 50)

orig_time = time.time() # Initialise timer for track timer


# Try to connect to gmail, then note which IP address resolves to the internet to identify the correct
# network interface to print on the screen in idle mode
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("gmail.com",80))
ipaddress=(s.getsockname()[0])
s.close()

# Return CPU temperature as a character string                                      
def getCPUtemperature():
    res = os.popen('vcgencmd measure_temp').readline()
    return(res.replace("temp=","").replace("'C\n",""))
   

#The screen is usually setup portrait, so ghere i used some adafruit code to draw text at 90 degrees:

def draw_rotated_text(image, text, position, angle, font, fill=(255,255,255)):
   # Get rendered font width and height.
   draw = ImageDraw.Draw(image)
   width, height = draw.textsize(text, font=font)
   # Create a new image with transparent background to store the text.
   textimage = Image.new('RGBA', (width, height), (0,0,0,0))
   # Render the text.
   textdraw = ImageDraw.Draw(textimage)
   textdraw.text((0,0), text, font=font, fill=fill)
   # Rotate the text image.
   rotated = textimage.rotate(angle, expand=1)
   # Paste the text into the image, using it as a mask for transparency.
   image.paste(rotated, position)


#Initial MPC queries. !!!"Always use try/except !!!!! otherwise the program crashes out when one is blank!
   
try:
        artist = client.currentsong()['artist']
except:
        artist = "No Artist"
        
try:
        title = client.currentsong()['title']
except:
        title = "No Title"

try:
        album = client.currentsong()['album']
except:
        album = "No Album"
    
start_time = time.time()


# initial setup of variables and lines
oldtitle = title
artist2 = artist[28:56].center(27)
artist = artist[0:28].center(27)
title2 = title[28:56].center(27)
title = title[0:28].center(27)
album2  = album[28:56].center(34)
album = album[0:28].center(34)


#inital draw of screen

disp.clear((0, 0, 0))
draw_rotated_text(disp.buffer, artist, (20, 0), 90, font, fill=(255,255,255))
draw_rotated_text(disp.buffer, artist2, (40, 0), 90, font, fill=(255,255,255))        
draw_rotated_text(disp.buffer, title, (70, 0), 90, font, fill=(255,255,255))
draw_rotated_text(disp.buffer, title2, (90, 0), 90, font, fill=(255,255,255))
draw_rotated_text(disp.buffer, album, (150, 0), 90, smallfont, fill=(255,255,255))
draw_rotated_text(disp.buffer, album2, (180, 0), 90,smallfont, fill=(255,255,255))

oldstate="Random nonzero"

while(True):

        #if ( gpio.input(20) == False ):    ##I have this pin wired to the power switch on the front of the case. If the input goes low, the pi shuts down! Remove this if you don't want it to happen
        #        print "shutdown"
        #        disp.clear((0, 0, 0))
        #        draw_rotated_text(disp.buffer, "Shutdown", (70, 10), 90, bigfont, fill=(255,255,255))
        #        disp.display()
        #        time.sleep(5)
        #        os.system('sudo shutdown -h now')
                
        try:
                oldstate=state
                state = client.status()['state']
        except:
                state = "fail"
                
        if (state != oldstate): disp.clear((0, 0, 0))
        ## only clear when we have to, disp.clear has a memory leak!!!

        if (state != "play") and (state != "pause"):  # If device idle, show current time/date, IP and CPU temp


                draw_rotated_text(disp.buffer, ipaddress, (20, 10), 90, smallfont, fill=(255,255,255))
                draw_rotated_text(disp.buffer, getCPUtemperature(), (20, 280), 90, smallfont, fill=(255,255,255))
                
                localtime = time.asctime( time.localtime(time.time()) )
                draw_rotated_text(disp.buffer, localtime[0:10], (70, 10), 90, font, fill=(255,255,255))
                draw_rotated_text(disp.buffer, localtime[11:19], (90, 10), 90, bigfont, fill=(255,255,255))

                disp.display()
                time.sleep(0.1)    # YOu can remove this to make display update quicker, but it becomes even more of a CPU hog
                oldtitle = "Random Nonsense placeholder"
                print state    # just for debugging

        else:   # If Playing or Paused
                
        
                print state   ## just for debugging
                draw_time = time.time()  ## also debug
                
                try:
                        newtitle = client.currentsong()['title'] 
                except:
                        newtitle = "failed to get title"

                if (newtitle == oldtitle):     # Mark time on track change

                        seconds = time.time() - start_time
                        m, s = divmod(seconds, 60)
                        h, m = divmod(m, 60)      
                        
                        draw_rotated_text(disp.buffer,"%d:%02d:%02d" % (h, m, s) , (195, 5), 90, font, fill=(255,255,255))
                else:
                        start_time = time.time()
                                
                        try:                            # ALWAYS TRY /EXCEPT ON MPC CALLS
                                artist = client.currentsong()['artist']
                        except:
                                artist = "No Artist"
                                
                        try:
                                title = client.currentsong()['title']
                        except:
                                title = "No Title"

                        try:
                                album = client.currentsong()['album']
                        except:
                                album = "No Album"


                         #Format display strings
                        oldtitle = title              
                        artist2 = artist[28:56].center(27)
                        artist = artist[0:28].center(27)
                        title2 = title[28:56].center(27)
                        title = title[0:28].center(27)
                        album2  = album[28:56].center(34)
                        album = album[0:28].center(34)

                        disp.clear((0, 0, 0))
                        draw_rotated_text(disp.buffer, artist, (20, 0), 90, font, fill=(255,255,255))
                        draw_rotated_text(disp.buffer, artist2, (40, 0), 90, font, fill=(255,255,255))        
                        draw_rotated_text(disp.buffer, title, (70, 0), 90, font, fill=(255,255,255))
                        draw_rotated_text(disp.buffer, title2, (90, 0), 90, font, fill=(255,255,255))
                        draw_rotated_text(disp.buffer, album, (150, 0), 90, smallfont, fill=(255,255,255))
                        draw_rotated_text(disp.buffer, album2, (180, 0), 90,smallfont, fill=(255,255,255))



                                
                disp.display()
                time.sleep(0.1)   # Can reduce this delay for snappyer display, but it will hog much more CPU for little benefit.

Перезагружаемся, если на дисплее появилось время, значить все вы сделали верно.
Далее настраиваем Volumio по вкусу.
Скрипт можно расширить и выводить различную дополнительную информацию.

2 комментария

  1. Юрий Reply
    02.09.2019 at 21:06

    Здравствуйте. Скажите, а у вас получилось выводить кириллицу на экран?

    • onx Reply
      17.11.2019 at 05:25

      Добрый день.
      У меня не стояла такая задача, поскольку я очень редко слушаю русскую музыку.
      Но в целом сложности быть не должно.
      Нужно заменить шрифты на кириллические font и smallfont (а так же выровнять их в размере и в позиции)
      Сама кодировка в клиенте вроде бы поддерживается, детальнее тут: https://python-mpd2.readthedocs.io/en/latest/topics/advanced.html

Leave A Comment

Please be polite. We appreciate that. Your email address will not be published and required fields are marked