Gitlab Pipeline信号機を作る話 -設計とブレッドボード編-

前回 Raspberry piにてGitlabのpipelineを監視することができる様になりました。

GitlabのPipeline通知をRaspberry piで受ける方法の話 (2022年6月1日)

今回はこのPipelineの状態をLEDに表示する信号機を作ってみたいと思います。

材料とツール

ツール

  • frizing : Version 0.9.4 (CD-498-0-a1ffcea 2019-12-01) Cocoa [Qt 5.13.2] : https://fritzing.org/
  • PyCharm 2021.3 (任意)

材料

  • Raspberry pi 3 Model B v 1.2
  • ブラッドボード (適当)
  • LED (黄色、緑、赤、青)
  • 抵抗 (10KΩ程度)

信号機の設計

初回ということで今回は細かく書いておきたいと思います。

信号機の機能

前日の記事からGitlabのPipelineのステータスは4つの状態となっております

今回信号機はそれぞれの状態を各色に割り当てて表示すると言うものを作成します

  • pending : Yellow (黄色)
  • running : Blue (青)
  • success : Green (緑)
  • fail: Red (赤)

利用ポート

はじめにRaspberry piのGPIOポートの割り当てを考えます。
どのポートにどのLEDを割り当てるのかを分かりやすく、とにかく配線しやすく考えます。
順番通りにピン配列があるわけではないので配線しやすさは重要になります

Raspberry piのGPIOポートのアサインは以下の図(コマンド)で確認可能です。

コマンドからの確認もできます。

pi@raspberrypi:~ $ gpio readall
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |  OUT | 0 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |  OUT | 0 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |  OUT | 0 |  7 || 8  | 0 | OUT  | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | IN   | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+

GPIOポートはBCMとかNAMEとかwPiとか色々ありますが、今回は BCMと思ってください。

GPIOピンは出力よっては別の割り当てがあります。
なので今回はGPIO専用のポートを使います。 GPIO 2(SDA)とか別の割り当てがあります

今回は配線しやすさからGPIO 17,GPIO 27,GPIO 22,GPIO 23を使いたいと思います。

それぞれの割り当ては…

  • GPIO 17 : Yellow LED (pending)
  • GPIO 27 : Blue LED (running)
  • GPIO 22 : Green LED (success)
  • GPIO 23 : Red LED (fail)

配線を考える

GPIOポートとLEDが決まりましたので配線図としてみます。
配線図はfrizingを使って書きました。
配線図にRaspberry piとLEDを置きます。
先に計画したポートとLEDを線で繋げばOKです。
抵抗は必ず入れましょう。

抵抗は必ず入れましょう…

抵抗の10KΩは適当です。
手元に4本も抵抗がなかっただけですw

0Ω(直接接続する)ならパーツの設計によりますが物によってはLEDが焼き切れます。(煙出ましたw)
なので必ず!1Ωでも入れてください!
LEDが壊れるだけならいいですが、最悪GPIOポートが死にます…
死んだら泣くしかないです。

今回は動作検証用なので抵抗を1本でやっています。
注意としては全点灯状態で放置しているとかはしない方がいいかも…

本来はGPIOポートとLEDの間に1本ずつ入れた方が良いです。

LEDは各色それぞれの明るさが変わります。
なので同一抵抗値では明るさがまちまちになります。
なので製品とする場合には抵抗を調整して明るさを一定にする方が良いです。

今回は同じ型のLEDを使いましたが各色で明るさは違います。
黄色 : 普通とすると、緑 : 暗い 、 青 : 明るい 、 赤 : 明るい

ブレッドボードで配線計画をしてみる

frizing(配線図を描いたツール)ではブレッドボードの配線設計もできます。
なのでそれでちょちょちょっと配線を繋いで設計します。
配線は配線図と接続が同期されているので接続のヒントは点線で表示されますので配線だけを決めればOKです。
ただし、LEDアノード/カソードやパーツの回転とかが慣れるまで大変ですが慣れればすぐに使えます。
あとパターン図も書けますが… ブレッドボードを作った後基盤を作る時に便利ですが今回は省きます。

はい、ざっくり書くとこんな感じでしょうかね。

ポイントは

  • ケーブルの色を変えられるのでこれから作る色に合わせましょう
  • LEDの色も周波数とか意識せず色だけ合わせます
  • 抵抗の値も意識はしなくてOK

といったところです。

いざ、配線!!

ブレッドボードで配線する!

次にfrizingで計画したブレッドボードの配線計画を実物で組みます…

出来上がりはこんな感じ… (ボタン、プラスマイナスは無視してください)

配線のコツ

組むにあたってGPIOポートに刺すの難しいです。
ピンにはラベルがないので何番目のピンに刺すのかかなり大変です。
今回は、GPIO 17に当たりをつけて、そこを中心に挿していきました。
GPIOポート出力を出した状態(後に解説)で探り、LEDが光る?光らない判断します。
ただし、Vcc(3V ,5V)や信号系(SDA , TX , RXなど)はLEDが光りますので注意ください

ブレッドボードの動作確認をする

次にブレッドボードの回路が正しく接続されたかを確認したいと思います。
Pythonとかで出力してもいいですが今回はコマンドで出して確認します。
先に書きました出力ピンを探るのにはこの方法が楽でいいです

コマンドでの実行は以下の様になっております。

pi@raspberrypi:~ $ sudo echo 27 > /sys/class/gpio/export 
pi@raspberrypi:~ $ sudo echo out > /sys/class/gpio/gpio27/direction 
pi@raspberrypi:~ $ sudo echo 1 > /sys/class/gpio/gpio27/value 
pi@raspberrypi:~ $ sudo echo 0 > /sys/class/gpio/gpio27/value 
pi@raspberrypi:~ $ sudo echo 27 > /sys/class/gpio/unexport 

それぞれは

  • ポートを割り当て(export)
  • モードを出力にする(direction)
  • 出力 (value = 1)
  • 出力停止 (value = 0)
  • ポート解放 (unexport)

value = 1でちゃんとLEDの出力がされることを確認してください。

今回使用する各ポートで出力を確認してください

重要な部分です。 必ず、解放(unexport)を実行してください。
のちのち問題になります…

私の環境では全てのポート出力した時に青が点灯しない。言う不具合状態になりましたが…
青色1本の点灯では動くので問題を放置しました。
そんなこともあります

Pythonで出力確認するだけのプログラムを書く

次にGPIOポートがちゃんとPythonで出力できることを確認しました。
毎回この確認は不要とは思いますが、
「コマンドでは出力されるが、本番プログラムでは動かない」と言う問題が出た時に、切り分けできるために製品の利用ポートを確認するプログラムはあった方がのちのち便利ですね

サンプルプログラムでは1秒ごとにLEDの点灯が移っていきます。

#! /usr/bin/python3
import RPi.GPIO as GPIO
import time

LED_Yellow = 17
LED_Blue = 27
LED_Green = 22
LED_Red = 23

GPIO.setmode(GPIO.BCM)

GPIO.setup(LED_Yellow, GPIO.OUT)
GPIO.setup(LED_Blue, GPIO.OUT)
GPIO.setup(LED_Green, GPIO.OUT)
GPIO.setup(LED_Red, GPIO.OUT)

GPIO.output(LED_Yellow, True)
time.sleep(1)
GPIO.output(LED_Yellow, False)
GPIO.output(LED_Blue, True)
time.sleep(1)
GPIO.output(LED_Blue, False)
GPIO.output(LED_Green, True)
time.sleep(1)
GPIO.output(LED_Green, False)
GPIO.output(LED_Red, True)
time.sleep(1)
GPIO.output(LED_Red, False)

GPIO.cleanup(LED_Yellow)
GPIO.cleanup(LED_Blue)
GPIO.cleanup(LED_Green)
GPIO.cleanup(LED_Red)

信号機のプログラムを書く

では早速、プログラムを組みます!
先の動作確認用のプログラム程度でもエディタ(vi)での書くのが大変でしたが、
インデントが入ると超めんどくさくなります…
私は早々にちょうどPyChamがあったのでそちらを利用しました。
エディタで記述を行い、Raspberry piにはviにてコピペして作業します。

プログラム自体は先日のmqtt購読プログラムを基に作成しております。

出来上がりソース

#! /usr/bin/python3
import RPi.GPIO as GPIO
import paho.mqtt.client as mqtt
import json
import time

host = '192.168.1.201'
port = 1883
topic = 'pipeline'

LED_Pending = 17 # 黄色
LED_Running = 27 # 青
LED_Success = 22 # 緑
LED_Fail = 23 # 赤

GPIO.setmode(GPIO.BCM)

GPIO.setup(LED_Pending, GPIO.OUT)
GPIO.setup(LED_Running, GPIO.OUT)
GPIO.setup(LED_Success, GPIO.OUT)
GPIO.setup(LED_Fail, GPIO.OUT)

def on_message(client, userdata, msg):
    print(str(msg.payload,'utf-8'))
    message = json.loads(str(msg.payload,'utf-8'))

    GPIO.output(LED_Pending, False)
    GPIO.output(LED_Running, False)
    GPIO.output(LED_Success, False)
    GPIO.output(LED_Fail, False)

    if message['status'] == 'pending':
        GPIO.output(LED_Pending, True)
    elif message['status'] == 'running':
        GPIO.output(LED_Running, True)
    elif message['status'] == 'success':
        GPIO.output(LED_Success, True)
    elif message['status'] == 'fail':
        GPIO.output(LED_Fail, True)

    # time.sleep(1)

def run():
    client = mqtt.Client()
    client.connect(host, port=port, keepalive=60)

    client.subscribe(topic)
    client.on_message = on_message

    client.loop_forever()

if __name__ == '__main__':
    try:
        run()
    finally:
        GPIO.cleanup(LED_Pending)
        GPIO.cleanup(LED_Running)
        GPIO.cleanup(LED_Success)
        GPIO.cleanup(LED_Fail)

ポイントを抜粋すると

パッケージを読み取って

import RPi.GPIO as GPIO

ボードの初期化とポートの初期化

GPIO.setmode(GPIO.BCM)

GPIO.setup(LED_Pending, GPIO.OUT)
GPIO.setup(LED_Running, GPIO.OUT)
GPIO.setup(LED_Success, GPIO.OUT)
GPIO.setup(LED_Fail, GPIO.OUT)

ステータス変更メッセージが来たら(on_message)
LEDを全て消灯する。
Pipelineのステータスでそれぞれ点灯するLEDを光らせる。

    GPIO.output(LED_Pending, False)
    GPIO.output(LED_Running, False)
    GPIO.output(LED_Success, False)
    GPIO.output(LED_Fail, False)

    if message['status'] == 'pending':
        GPIO.output(LED_Pending, True)
    elif message['status'] == 'running':
        GPIO.output(LED_Running, True)
    elif message['status'] == 'success':
        GPIO.output(LED_Success, True)
    elif message['status'] == 'fail':
        GPIO.output(LED_Fail, True)

今回試行錯誤した結果ですが、mqttはloopになるのでポートの解放が終了時にできなかったですが…
例外処理の後処理に書けばOKでした。

    try:
        run()
    finally:
        GPIO.cleanup(LED_Pending)
        GPIO.cleanup(LED_Running)
        GPIO.cleanup(LED_Success)
        GPIO.cleanup(LED_Fail)

ま、それだけですね。

動作確認する

はい、これで十分動きますので GitlabでPipelineを動かします…

pi@raspberrypi:~ $ python3 pilepine_signal.py 
{"type":"Pipeline Hook","project":"tsundere-book","id":516,"status":"running","massage":"","created_at":"2022-06-02T10:38:34+09:00","finished_at":"2022-06-02T10:39:02+09:00"}
{"type":"Pipeline Hook","project":"tsundere-book","id":516,"status":"success","massage":"","created_at":"2022-06-02T10:38:34+09:00","finished_at":"2022-06-02T10:40:00+09:00"}
{"type":"Pipeline Hook","project":"tsundere-book","id":517,"status":"pending","massage":"","created_at":"2022-06-02T10:40:22+09:00","finished_at":""}
{"type":"Pipeline Hook","project":"tsundere-book","id":517,"status":"running","massage":"","created_at":"2022-06-02T10:40:22+09:00","finished_at":""}
{"type":"Pipeline Hook","project":"tsundere-book","id":517,"status":"canceled","massage":"","created_at":"2022-06-02T10:40:22+09:00","finished_at":"2022-06-02T10:40:28+09:00"}
{"type":"Pipeline Hook","project":"tsundere-book","id":517,"status":"running","massage":"","created_at":"2022-06-02T10:40:22+09:00","finished_at":"2022-06-02T10:40:28+09:00"}
{"type":"Pipeline Hook","project":"tsundere-book","id":517,"status":"canceled","massage":"","created_at":"2022-06-02T10:40:22+09:00","finished_at":"2022-06-02T10:41:37+09:00"}

それぞれで点灯を確認できました!

最後のcanceledは今見つけましたが、全消灯となるのでちょうどよかったです。

終いに

今回はGitlab(Pipeline)信号機を作りました!

Pipelineの状態によりLEDが点灯しますのでわざわざWebページを確認しなくてもよくなりました!

信号が緑に点灯状態だとプロジェクトは安心状態、赤信号なら必至のパッチとなるわけですね。

しかしながら… 正常動作って確認したら特に表示されている必要はないですよね。
なので、全消灯のリセットボタンがあってもいいかな…

その当たりのカスタマイズは今後やってみたいと思います!

次回は… どうしようかな…