PythonでRabbitMQメッセージの通知アプリ with Growl ～ラズパイとJuliusを添えて～

# <i class="fa fa-forward" style="font-size:1em;"></i> はじめに
年末に家のタワー型のサーバ・ノートパソコンが一度に壊れてしまい、ごった煮環境の復旧に勤しんで新しい事に手が回ってませんでしたが、復旧が一段落してきたので前からやりたかったRabbitMQメッセージの通知アプリを作ってみました。
＃タイトルにラズパイとJulius入れてますが、ホントに添えてるだけですスミマセン。

# <i class="fa fa-cubes" aria-hidden="true"></i> 構成・動作
去年、ラズパイにUSBカメラと赤外線LEDをつなげて、音声認識でテレビを操作する感じのプチ電子工作環境を構築していたので、この環境に追加する感じでWindowsアプリを作ってみました。
![環境.png](https://qiita-image-store.s3.amazonaws.com/0/82760/f99026ef-ecb6-0400-3d59-54ac8058a1ef.png)

※注 図以外にも色々と詰め込んでいて、ちょっと複雑な構成になっています。

データの流れは、以下のような感じ。
1. USBカメラの内蔵マイクから入力した音声データをJuliusで認識。
2. 認識した結果を自作のJuliusClientアプリで解釈・スコアの判定を行い、一定以上のスコアで「テレビ電源ON」と認識したら、RabbitMQにメッセージ送信。
3. LIRCコマンドを発行するPythonアプリがRabbitMQからメッセージを受取りIRコマンドを実行。
4. テレビに電源ONの信号を送信。

**⇒2で送られるメッセージをノートパソコン側でも受信して、メッセージをポップアップさせました。**

# <i class="fa fa-eye" aria-hidden="true"></i> 出来た
ポップアップはGrowlを使ってこんな感じになりました。
![通知.png](https://qiita-image-store.s3.amazonaws.com/0/82760/41e16cb0-a499-6b6e-ee5c-38b95ea217e7.png)
Growlお手軽で使いやすい！！

# <i class="fa fa-cogs" style="font-size:1em;"></i> 動作環境

- Raspberry PI
   - RabbitMQ 2.8.4

- Windows10
   - Python2.7（※）
      - pika 0.10.0 # RabbitMQ接続
      - pywin32 220 # Windowsサービス実装
      - gntp 1.0.3 # Growlへの通知
   - Growl 2.0.9

※Python3で作ったけど、Windowsのシステム環境変数のPATHは諸事情でPython2のパスを登録していて簡単に変えることは出来なかったので、Python2で動作してます。。

ちなみにpywin32のインストール・使い方でちょいハマりました。先駆者の方々の情報に感謝です。
[pythonでのWindowsサービスの書き方](http://masahito.hatenablog.com/entry/20110511/1305107553)
[自分で作った Python スクリプトを Windows Server にサーヴィスとして登録するショートコント（1）](http://inokara.hateblo.jp/entry/2016/02/07/200643)
[Python3.4でpywin32が動かなかったこと ―](http://yuan-jiu.asablo.jp/blog/2014/04/08/7270185)
[Python、pywin32でWindowsサービスが起動できない](https://teratail.com/questions/43871)

あとGrowlについてはこちらを参考にさせて頂きました。（パスワードは不要でした、環境差異かな？）
[Windows で Growl 通知を行う](http://qiita.com/rohinomiya/items/5e485d6700eac256af9f)


# <i class="fa fa-file-code-o" aria-hidden="true"></i> ソースコード
ソース丸ごと貼っつけます。動いてますが、色々と適当なのはご容赦です。
なにげにpikaでRabbitMQに接続する時は、これまでBlockingConnectionに頼り切っていたので、
ジェネレータを使って回すのは初めてでした。

```win_notification.py

import logging
import logging.handlers

import pika
import win32serviceutil
import win32service
import win32event
import servicemanager
from gntp.notifier import GrowlNotifier

# ログ
LOG_FILE_PATH = 'C:\\tmp\win_notification_consumer.log'
LOG_BK_COUNT = 2
LOG_LEVEL = logging.INFO

# MQ
MQ_IP = "x.x.x.x"
MQ_QUEUE = "Queue2"

# 通知
GROW_IP = "127.0.0.1"
GROW_PASS = None
GROW_APP_NAME = "amqp notif"
GROW_NOTIFS = ["Message"]
GROW_NOTE_TYPE = "Message"
GROW_TITLE = "Get Message"

# Windowsサービス
WIN_SRV_NAME = "WinNotificationConsumer"
WIN_DISP_NAME = "WinNotificationConsumer"


class WinNotificationConsumer(win32serviceutil.ServiceFramework):

    # Required Attributes:
    _svc_name_ = WIN_SRV_NAME
    _svc_display_name_ = WIN_DISP_NAME

    def __init__(self, args):
        # ログ初期化
        self.logger = logging.getLogger()
        self.logger.setLevel(LOG_LEVEL)
        formatter = logging.Formatter(
            '%(asctime)s - %(levelname)s - %(message)s')
        handler = logging.handlers.TimedRotatingFileHandler(
            filename=LOG_FILE_PATH,
            when='D',
            backupCount=LOG_BK_COUNT
        )
        handler.setLevel(logging.DEBUG)
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)
        self.logger.debug('init log end.')

        # Win32API周り初期化
        win32serviceutil.ServiceFramework.__init__(self, args)
        self.stop_event = win32event.CreateEvent(None, 0, 0, None)
        self.logger.debug('init win32 end.')

        # 通知の初期化
        self.notifier = GrowlNotifier(
            applicationName=GROW_APP_NAME,
            notifications=GROW_NOTIFS,
            hostname=GROW_IP,
            password=GROW_PASS)
        self.notifier.register()
        self.logger.debug('init notif end.')

    def SvcDoRun(self):
        # service start
        self.logger.info('service starting...')
        # イベントログ出力
        servicemanager.LogMsg(
            servicemanager.EVENTLOG_INFORMATION_TYPE,
            servicemanager.PYS_SERVICE_STARTED,
            (self._svc_name_, 'test')
        )
        self.consuming_and_notificate()

    def consuming_and_notificate(self):
        self.logger.debug('consuming and notificate starting...')

        # RabbitMQの接続設定
        self.connection = pika.BlockingConnection(
            pika.ConnectionParameters(host=MQ_IP)
        )
        self.channel = self.connection.channel()

        # キューが無ければ作成
        self.channel.queue_declare(
            queue=MQ_QUEUE,
            durable=True
        )

        # キューから取得したメッセージをgeneratorで回す。
        self.logger.info('start consuming message.')
        for msg in self.channel.consume(queue=MQ_QUEUE, no_ack=True):
            message_body = msg[2]
            self.logger.debug(message_body)
            self.notifier.notify(noteType=GROW_NOTE_TYPE,
                                 title=GROW_TITLE,
                                 description=message_body)

    def SvcStop(self):
        # service stop
        self.logger.info('service stopping...')

        # 上位クラスのメソッドを利用
        self.ReportServiceStatus(
            win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.stop_event)

        # MQ切断
        self.channel.close()
        self.connection.close()
        self.logger.info('service stopped.')


def main():
    # 引数('start','stop','install' etc.)を元にサービス起動/登録/停止/削除等を実行。
    win32serviceutil.HandleCommandLine(WinNotificationConsumer)

if __name__ == '__main__':
    main()
```

# <i class="fa fa-check-square-o" style="font-size:1em;"></i> Windowsサービス登録
こんな感じで登録＆自動起動できます。

    C:\\Python27\python.exe win_notification.py --auto install



# <i class="fa fa-line-chart" style="font-size:1em;"></i> 所感
結構かんたんにやりたいことが出来た（ポップアップに関してはGrowl様々感）。RabbitMQをWindowsサービスから接続しっぱなしに出来たので、パソコンも音声操作で色々出来る気がしてきた。
あとは他のPythonアプリについても何か書きたいな…既存コードはリファクタしなきゃ見せられたもんじゃないけども…。