2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RaspberryPiで「ほんわか お知らせランプ」Part 3

Last updated at Posted at 2015-06-11

RaspberryPiで監視をお助け

このシリーズでは、RaspberryPiを使ったお知らせランプを製作します。
Blue.GIF

第3回の今回は、「お知らせランプ」の全体制御を行うメイン部分です。
このシリーズは、今回で完成となります。

SystemDiagram2.png

Abu(アブー)登場

Detector経由でOpsGenieのAlert情報を取得して、監視対象の状態を特定し、LED表示を設定する仕事は、Alert Buster(略してAbu)が担います。
新たなAlertが発生した時に一度だけチャイムを鳴らすのもAbuの仕事です。

同時に複数存在するかもしれないAlertの状態に対し「お知らせランプ」が表示できるのは一つの状態のみ。
そこで、状態を優先度付けして、最も優先度の高い状態を表示します。

Abuが扱う状態と表示の関係

Abuが扱う状態は、以下の6種類です。
レベルは「エラー」と「ワーニング」の2種類。
「エラー」は急いで対処するもの、「ワーニング」は急ぎではないけど、確認が必要なものを表します。
それぞれ、認知済みかどうかの2状態があります。

「不明」は、起動直後やネットワークの不具合などで、状態がわからない時に使用します。
各状態に割り当てるべき色や、明滅周期も決めておきます。
なお、"Magenta"などの色の名称は、前回の記事のLed.COLORSで定義しているものを使います。

優先度 Alert種別 認知状態 状態値 明滅周期 メモ
1 不明 - 1 Magenta 0(明滅なし)
2 エラー 未認知 2 Red 0.5秒
3 ワーニング 未認知 4 Yellow 2秒
4 エラー 認知済み 8 Green 2秒
5 ワーニング 認知済み 16 Green 6秒
6 Newエラー - 32 - - チャイム用
7 Newワーニング - 64 - - チャイム用
8 正常 - 0 Blue 8秒

Abuはこれらの状態を単一の整数値「状態値」として扱います。
「状態値」は、ビット演算で扱いやすい様に、2進数の各ビット値を割り当てます。
「正常」は、全部のビットが0の状態なので、bit割り当てはありません。

内部では、上表のチャイム制御用のフラグも合わせた7bitのbit単位の論理和を一つの状態値として使用します。
例えば、まだ認知されていない新たなエラー(チャイムを鳴らす前)がある場合の状態値は、0x22です。

状態値はbit毎に意味をもたせた単一の整数値で表す方式としましたが、可読性を重視してPythonのSet型を使っても良いでしょう。

チャイム

鳴らし方

新たなエラーやワーニングが見つかった時は、気付きやすいように一度だけチャイムを鳴らします。
チャイムは、サウンドファイルとして用意したものを、aplayコマンドに渡して再生します。

$ aplay --quiet /usr/share/sounds/alsa/Rear_Center.wav

あらかじめ、本体のイヤホンジャックもしくは、USBポートにスピーカを接続して、音が鳴るように設定しておきます。
参考:https://www.google.co.jp/search?q=raspberrypi+aplay

音源

チャイムの音源は、aplayがサポートする形式ならなんでもOKです。
Abuではfreesound.orgより以下を使いました。
ユーザ登録するとダウンロード可能になりますので、ライセンスを確認して使用します。

Abuの動作

Abuの仕事は以下の通りです。

  • Detectorを初期化
  • LED Deamonを起動
  • 以下を繰り返す
    • Detectorを呼び出して、Alertのリストを取得する
    • Alertリストをまとめて、状態値を作成
    • 状態値に応じて、LED表示を設定
    • 新たなエラーやワーニングがあれば、チャイムを鳴らす
    • 1分間Sleep

起動方法

$ ./abu.py ********-****-****-****-************

********-****-****-****-************の部分には、前回の記事で紹介した、OpsGenieのAPI Keyを指定します。
OS起動時に自動起動するように、cronに登録する場合は、例えば以下のようにcronに追記すると良いでしょう。

$ crontab -e
@reboot cd path-to-module-dir;./abu.py ********-****-****-****-************ >/dev/nul 2>&1

ソースコード

abu.py
#!/usr/bin/env python
#coding:utf-8


import time
import os
from opsgenie import OpsGenie
from led_deamon import LedDeamon


class Abu(object):
    DOWN = 1
    ERR_UNACKED = 2
    WARN_UNACKED = 4
    ERR_ACKED = 8
    WARN_ACKED = 16
    ERR_NEW = 32
    WARN_NEW = 64
    NORMAL = 0
    ERROR_SOUND = "./204425__jaraxe__alarm-2.wav"
    WARNING_SOUND = "./112860__paulnorthyorks__eighties-synth.wav"

    STATE_MAP = {
        DOWN: {"color": "Magenta", "interval": "0"},
        ERR_UNACKED: {"color": "Red", "interval": "0.5"},
        WARN_UNACKED: {"color": "Yellow", "interval": "4"},
        ERR_ACKED: {"color": "Green", "interval": "4"},
        WARN_ACKED: {"color": "Green", "interval": "8"},
        NORMAL: {"color": "Blue", "interval": "8"}}

    def _state2mode(self, state):
        if state == Abu.NORMAL:
            return Abu.STATE_MAP[Abu.NORMAL]
        for s in (Abu.DOWN, Abu.ERR_UNACKED, Abu.WARN_UNACKED,
                  Abu.ERR_ACKED, Abu.WARN_ACKED):
            if state & s:
                return Abu.STATE_MAP[s]
        else:
            return Abu.STATE_MAP[Abu.NORMAL]
            return None

    def __init__(self, url, api_key):
        self.opsgenie = OpsGenie(url, api_key)

    def _summarize(self, list):
        state = Abu.NORMAL
        if list is None:
            state = Abu.DOWN
        else:
            for l in list:
                if l["error"]:
                    if l["acknowledged"]:
                        state |= Abu.ERR_ACKED
                    else:
                        state |= Abu.ERR_UNACKED
                        if l["new"]:
                            state |= Abu.ERR_NEW
                else:
                    if l["acknowledged"]:
                        state |= Abu.WARN_ACKED
                    else:
                        state |= Abu.WARN_UNACKED
                        if l["new"]:
                            state |= Abu.WARN_NEW
        return state

    def start(self):
        ld = LedDeamon()
        ld.set_mode(self._state2mode(Abu.DOWN))
        ld.start()
        while True:
            alert_list = self.opsgenie.detector()
            # print alert_list
            state = self._summarize(alert_list)
            # print hex(state)
            mode = self._state2mode(state)
            if mode:
                ld.set_mode(mode)
            if state & Abu.ERR_NEW:
                os.system("aplay " + Abu.ERROR_SOUND + "&")
            else:
                if state & Abu.WARN_NEW:
                    os.system("aplay " + Abu.WARNING_SOUND + "&")
            time.sleep(60)

if __name__ == '__main__':
    import sys

    if len(sys.argv) != 2:
        print("Usage: %s 'api-key for your OpsGenie account'." % sys.argv[0])
        print("Example: %s ********-****-****-****-************." % sys.argv[0])
        print("You can get your OpsGenie account at https://www.opsgenie.com.")
        exit()
    apiKey = sys.argv[1]
    a = Abu(apiKey)
    a.start()

振り返り

Pythonの勉強方々RaspberryPiを使ってみました。
安価でPowerfulそしてGPIOを手軽に使えるライブラリもあり、こうした用途にはとてもマッチしていることがわかりました。
PWMによるLEDのカラー制御、非同期処理、状態値の取り扱い、OpsGenie連携など、いくつか悩むポイントはありましたが、幸いにして、それぞれ無理のない解決策を見出すことが出来、完成に至ることができました。
今回製作した「お知らせランプ」は、職場の雰囲気にもあまり違和感なく溶け込んで、実運用でも役立っています。
興味のある方はお試しになってみてください。

このシリーズは、この記事で完結となりますが、次のアイディアが固まったら、また投稿しようと思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?