mui開発者キットをMPDのコントローラにしてみる

#この記事について
muiを入手したので、その開発情報を自分用にまとめたものです。

[mui Developer Editionで遊んでみる（準備編）](https://qiita.com/kobayuta/items/9a78b923648a2478604d)
[mui Developer Editionで遊んでみる（HOME画面のカスタマイズ）](https://qiita.com/kobayuta/items/76cd0d2483e42ae6e34d)
[mui Developer EditionでSORACOMを使ってみる](https://qiita.com/kobayuta/items/b8b15639be6c866641e1)
[muiのサンプルで点灯イベントを取得する](https://qiita.com/kobayuta/items/57e98b74b093b0792018)

#なぜmuiにmpdを入れるのか
家のコンポが壊れてしまったので、置き換えを検討していました。最近はCDも聞かないので、Music Serverを作ろうかと思ったのですが、ケースやらがいまいちでどうしようか迷っていました。で、ふと思ったのがmuiの開発者キット。
muiの開発者キットはRaspberry Piを使いますが、基本電源入れっぱなしです。Raspberry Pi 3A+の技適が取れた暁には素敵な見た目になること間違いなし。ということで作ってみりゅ事にしました。

#MPDのインストール
MPDのインストールはこの記事を参考に行いました。
[MPD (MUSIC PROTOCOL DAEMON)のインストール](https://tech.nosuz.jp/2017/02/install-mpd/)

Bluetoothのスピーカーを使ったのですが、デバイス名を間違えていてはまりました。。。
あと、なぜか6600番ポートが使えなかったので、ポート番号を変えて対応しています。

#MPDの制御方法
せっかくなので、muiでMPDの制御を行うアプリを作ってみました。
MPDのコントロールにはpython-mpd2を用いました。

MPDを使ったおかげでスマフォからもmuiからも制御ができるので快適です。満足。

以下ソースコードです。

```python
# My Application Class
class MyMUIApp(AbsApp):
    def __init__(self, appEventListener: AppEventListener):
        super().__init__(appEventListener)
        
    def onTurnOnDisplay(self):
        raise NotImplementedError

class MpdClientApp(MyMUIApp):
    mpdClient = MPDClient()
    volume = 50

    def __init__(self, appEventListener: AppEventListener, lang='ja-JP'):

        self._timer = None

        super().__init__(appEventListener)

        # Initialize MPD Client
        self.mpdClient.timeout = None
        self.mpdClient.idletimeout=None
        self.mpdClient.connect("localhost",6600) # default port:6600

        strAlbumTitle = ""
        strSongTitle = ""

        self.mpdClient.setvol(self.volume)

        TitleView = Text(strAlbumTitle)
        TitleView.setSize(0,0,189,8)
        self.addView(TitleView)
        self.setView(TitleView, 'album')

        SongInfoView = Text(strSongTitle)
        SongInfoView.setSize(0,10,189,8)
        self.addView(SongInfoView)
        self.setView(SongInfoView, 'title')

        iconPrev = Image(os.path.normpath(os.path.join(dir, './assets/music/icon_prev.png')))
        iconPrev.setSize(8, 20, iconPrev.width, iconPrev.height)
        iconPrev.addOnTouchViewListener(self)
        self.addView(iconPrev)
        self.setView(iconPrev, 'prev')

        iconPause = Image(os.path.normpath(os.path.join(dir, './assets/music/icon_pause.png')))
        iconPause.setSize(20, 20, iconPause.width, iconPause.height)
        iconPause.addOnTouchViewListener(self)
        self.addView(iconPause)
        self.setView(iconPause, 'pause')

        iconPlay = Image(os.path.normpath(os.path.join(dir, './assets/music/icon_play.png')))
        iconPlay.setSize(32, 20, iconPlay.width, iconPlay.height)
        iconPlay.addOnTouchViewListener(self)
        self.addView(iconPlay)
        self.setView(iconPlay, 'play')

        iconNext = Image(os.path.normpath(os.path.join(dir, './assets/music/icon_next.png')))
        iconNext.setSize(44, 20, iconNext.width, iconNext.height)
        iconNext.addOnTouchViewListener(self)
        self.addView(iconNext)
        self.setView(iconNext, 'next')


        # create slider. set width and max value to 100.
        slider = Slider(width=50, maxVal=100) 
        slider.setPos(100, 20) # set position x : 100, y : 20
        slider.addOnUpdateViewListener(self) # add update view listener
        slider.setValue(self.volume) # set initial value
        slider.addEventListener(self) # set callback for change value
        self.addView(slider)
        self.setView(slider, 'volume')

        # common area
        def createCommonMenu():
            # add home icon
            iconHome = Image(os.path.normpath(os.path.join(dir, './assets/icon_home.png')))
            iconHome.setSize(190, 0, iconHome.width, iconHome.height)
            iconHome.addOnTouchViewListener(self)
            self.addView(iconHome)
            self.setView(iconHome, 'home')
            
            # add current icon(this icon has no action)
            iconWeather = Image(os.path.normpath(os.path.join(dir, './assets/icon_sound.png')))
            iconWeather.setSize(191, 23, iconWeather.width, iconWeather.height)
            self.addView(iconWeather)

        createCommonMenu()

        self._next = None
        self._prev = None

    def setRelativeApps(self, next: AbsApp, prev: AbsApp):
        self._next = next
        self._prev = prev

    def updateTitle(self):
        currentSong = self.mpdClient.currentsong()
        print(currentSong)
        strAlbumTitle = ""
        strSongTitle = ""
        try:
            strAlbumTitle = currentSong['album']
            strSongTitle = currentSong['title']
        except Exception as e:
            print(e)
            print(type(e))


        self.getView('album').setText(strAlbumTitle)
        self.getView('title').setText(strSongTitle)

        self.updateRequest(0)


    def startTimer(self):
        if self._timer is not None:
            self._timer.cancel()            
        self._timer = Timer(1, self._timerHandler)
        self._timer.start()

        pass

    def stopTimer(self):
        if self._timer is not None:
            self._timer.cancel()            
        pass

    def _timerHandler(self):
        self.updateTitle()
        self.startTimer()
        pass
# ------------------------
    # override AbsApp methods

    def startTask(self):

        self.mpdClient.clear()
        for filename in self.mpdClient.list('file'):
            self.mpdClient.add(filename)

        self.updateRequest(0)
        self.startTimer()

    def stopTask(self):
        self.stopTimer()
        pass

    def dispatchFlingEvent(self, e1, e2, x, y):
        if e1.x > e2.x:
            # next
            self.setNextApp(self._next)

        elif e1.x < e2.x:
            # prev
            self.setNextApp(self._prev)

    def dispathLongPressEvent(self, e):
        pass    

    def onTurnOffDisplay(self):
        # always allow turn off
        self.updateRequest(0)
        return True

    def onTurnOnDisplay(self):
        self.updateRequest(0)
        return True

    # ------------------------
    # OnUpdateRequestListener implementation

    def onUpdateView(self, view):
        self.updateRequest(0)

    def onSliderValueChanged(self, slider, prevVal, newVal):
        # this method is callback of SliderEventListener
        # if user touch or slide thumb control on slider, invoked this method.

        self.mpdClient.setvol(newVal)
        self.volume = newVal

        # update UI
        self.updateRequest(0)


    # ------------------------
    # OnTouchEventListener implementation

    def onTouch(self, view, e):
        iconHome = self.getView('home')
        iconPrev = self.getView('prev')
        iconPause = self.getView('pause')
        iconPlay = self.getView('play')
        iconNext = self.getView('next')

        if view == iconPlay:
            print("play")
            self.mpdClient.play()
            self.updateTitle()
            return

        if view == iconPause:
            print("pause")
            self.mpdClient.stop()
            self.updateTitle()
            return

        if view == iconNext:
            print("next")
            self.mpdClient.next()
            self.updateTitle()
            return

        if view == iconPrev:
            print("prev")
            self.mpdClient.previous()
            self.updateTitle()
            return

        if view == iconHome:
            # close this application
            self.close()
```