ロボットで遊ぶ その1(LED操作)

WS2812、 NeoPixel

WS2812とNeoPixelはArduinoやRaspberryPiでよく利用されているフルカラーLEDのチップとライブラリのようです。
WS2812の仕組みについては以下のサイトが勉強になりました。
Raspberry Pi 3に PWM駆動方式 WS2812 RGBフルカラー LEDを接続する方法 (ラズパイに PWM制御方式のフルカラー LED WS2812Bモジュールを接続して制御する方法)

mag.switch-science.com

GUIでLEDを制御する仕組み

PC側

GUI.pyの赤緑青のスライドバーを操作すると、以下の関数によりRaspberryPiへwsR/wsG/wsBと数値データが送信されていました。
単純にLEDの色を変えるだけならGUI側はそのままでよさそうです。

def set_R(event):
	time.sleep(0.03)
	tcpClicSock.send(('wsR %s'%var_R.get()).encode())

def set_G(event):
	time.sleep(0.03)
	tcpClicSock.send(('wsG %s'%var_G.get()).encode())

def set_B(event):
	time.sleep(0.03)
	tcpClicSock.send(('wsB %s'%var_B.get()).encode())

RaspberryPi側

GUIからのデータ受信処理

前回も少し解説しましたが、RaspberryPi側はserver.pyのrun関数でGUIからのデータを受信しています。

def run():
    while True: 
        data = ''
        data = str(tcpCliSock.recv(BUFSIZ).decode())
        if not data:
            continue

        elif 'forward' == data:
            print('1')
            SpiderG.walk('forward')

data変数の文字列を確認して、処理内容を識別しているようです。
今回の場合はGUIからwsR/wsG/wsBのいずれかの文字列が渡されるため、if分に追記して処理を追加します。

サンプルプログラムのGUIでLEDを操作する

追加内容

server.pyのrun関数

run関数のIF分にwsR/wsG/wsBの場合の処理を追加します。
なぜかwsBの処理のみが実装されていて、speed_setという値の変更に利用していました。
特に意味はない処理なので以下のようにwsBを修正して、wsG/wsRを追加します。
変更前

        elif 'wsB' in data:
            try:
                set_B=data.split()
                speed_set = int(set_B[1])
            except:
                pass

変更後

        elif 'wsB' in data:
                set_B=data.split()
                param_wsB = int(set_B[1])
                LED.colorWipe(param_wsR, param_wsG, param_wsB)

        elif 'wsG' in data:
            set_G=data.split()
            param_wsG = int(set_G[1])
            LED.colorWipe(param_wsR, param_wsG, param_wsB)
           
        elif 'wsR' in data:
            set_R=data.split()
            param_wsR = int(set_R[1])
            LED.colorWipe(param_wsR, param_wsG, param_wsB)

解説

ポイントを二つ解説します。

ポイント1:'wsB' in dataについて

GUIからwsR/wsG/wsBを送信する際に、wsR/wsG/wsBに加えて、数値をdataに格納して送信しています。
そのため、他のIFと同じようにwsB == dataを書いてしまうと、IF分が成立しないため、'wsB' in dataとしています。

ポイント2:LED.colorWipeについて

LED.colorWipeはLEDの色を設定するためにサンプルコードに実装されたメソッドです。
LED.pyを見てみると、Adafruit_NeoPixelで初期化したオブジェクトに対して、
colorWipeメソッドで指定したRGPを適用しています。

## LED.py抜粋
 def colorWipe(self, R, G, B):
        for i in range(self.strip.numPixels()):
            self.strip.setPixelColor(i, color)
            self.strip.show()

forですべてのLEDに同じRGBを適用しているため、今の実装では6個のLEDが同じ色に変化します。

動作確認

server.pyが自動起動する設定の場合

RaspberryPi側のソースコードを編集した場合は一度RaspberryPiを再起動させてください

自動起動しない設定の場合

「sudo python3 server.py」で起動させてください。

動作結果

GUI.pyとserver.pyを実行してRGBスライダーを動かすとRaspberryPiのプロンプトにwsR/wsG/wsBの値が表示されて、DarkPawのLEDが変化します。

f:id:da-yamax:20200502173005g:plain
LED制御

WEB GUIを自作してLEDを単体操作

サンプルコードの実装では6個のLEDがすべて同じ色に制御されてしまうため、任意のLEDの色を手軽に変更できる仕組みを作ります。

server.py、LED.pyへの追加実装

LED.py

任意の番号のLED色を変更するメソッドを追加します。

    # 指定した番号のLEDの色を変更する
    def setLedColor(self, RGB, ledNum):
        color = Color(RGB[0],RGB[1],RGB[2])
        for i in ledNum:
            self.strip.setPixelColor(int(i), color)
            self.strip.show()

RGBにRed/Green/Blueの値を格納して渡します。
また、ledNumに変更するLED番号(0~5)を格納して渡します。

server.py

独自にsetLedColorというデータを受け取ったら、setLedColorメソッドを呼び出す処理をrunメソッドへ追加します。

  elif 'setLedColor' in data:
            # LEDの色を変更する
            # 連続でデータを受け取る場合があるため、
            if(len(data)<=30):
                ledParams = data.split()
                wsRGB = tuple(int(ledParams[1][i:i+2], 16) for i in (0, 2, 4))
                ledNum = ledParams[2].split(',')
                LED.setLedColor(wsRGB, ledNum)

dataの中身は後述するクライアント側で定義しますが、以下のようなデータが文字列として格納されています。

  • フォーマット:"setLedColor <変更するLED番号>"
  • 格納例:"setLedColor 00ff00 0,1,2,3"
    • R(00)、G(ff)、B(00)、LED番号(0,1,2)

クライアントの実装

サンプルコードのGUIにはLEDを選択する画面がないため、WEB GUIを新しく作成します。

必要なライブラリをインストール
pip3 install flask imutils
GUI概要
  • PythonのWEBライブラリであるFlaskを使ってWEBサーバを構築する。
  • Javascriptでカラーピッカーを用意して、カラーピッカーで設定した値をPython経由でserver.pyへ送信する。

参考:【Python】フレームワークFlaskの基本をマスター | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

プログラム構成

GUIのプログラムは以下の構成になっています。

led_ColorPicker
├── led_color_picker.py
├── static
│   ├── css
│   │   ├── colorpicker.css
│   │   └── layout.css
│   ├── images
│   ├── js
│   │   ├── colorpicker.js
│   │   ├── eye.js
│   │   ├── jquery.js
│   │   ├── layout.js
│   │   └── utils.js
└── templates
    └── color_picker.html

Javascript/CSSはcolorpickerのサンプルソースをそのまま使っているので今回重要なのはled_color_picker.pylayout.jscolor_picker.htmlの3つだけです。

処理の流れ
    • led_color_picker.pyを実行すると、flaskによりcolor_picker.htmlを読み込んだWEBサーバが起動します。
    • color_picker.htmlで操作した色の変更、LEDの選択をlayout.js内のonChangeメソッドで受け取って、flaskの内部通信を使って、layout.jsからled_color_picker.pyへデータが渡されます。
    • led_color_picker.pyはlayout.jsから受け取ったデータを"setLedColor <変更するLED番号>" の文字列フォーマットでTCP通信でロボット(server.py)へ渡します。
led_color_picker.py 抜粋
# HTTPサーバのIPアドレス、ポート番号
SERVER_IP = "192.168.0.31"
PORT_NUM = "8080"
outputFrame = None
lock = threading.Lock()

app = Flask(__name__)

# HTML生成
@app.route("/")
def index():
	data = {'serverIp' : SERVER_IP}
	return render_template("color_picker.html", data=data)

# Javascriptからのデータを受信する関数を作成
@app.route('/setLedColor', methods=['POST'])  
def color_post():
	result = request.form["param"]
	print(result)
	# Javascriptから受け取ったデータをsocket_connectで生成した通信ソケットを使ってロボットへ送信
	tcpClicSock.send(result.encode())

	return "OK"

## ロボットへ接続 (この辺はサンプルコードそのまま)
def socket_connect():	
	global ADDR,tcpClicSock,BUFSIZ,ip_stu,ipaddr
	SERVER_PORT = 10223
	BUFSIZ = 1024		
	ADDR = (SERVER_IP, SERVER_PORT)
	tcpClicSock = socket(AF_INET, SOCK_STREAM) 
	print("Connecting to server @ %s:%d..." %(SERVER_IP, SERVER_PORT))
	print("Connecting")
	tcpClicSock.connect(ADDR)
	print("Connected")

if __name__ == '__main__':
	# robot通信用スレッド生成
	sc=thread.Thread(target=socket_connect)
	sc.setDaemon(True)					  
	sc.start()							  

	# FlaskのWEBサーバ起動
	app.run(host=SERVER_IP, port=PORT_NUM, debug=False,
		threaded=True, use_reloader=False)
layout.js 抜粋

javascriptはlayout.jsにonChangeを追加しました。

$('#colorpickerHolder').ColorPicker({
	flat: true,
	// カラーピッカーが操作されたら呼び出されるメソッド
	onChange: function (hsb, hex, rgb) {
		// Pythonで設定した関数へのURLを指定
		var url = "http://"+serverIp+":8080/setLedColor"

		// LEDのチェックボックスの値を取得
		var e = document.getElementById('ledNum');
		var ary = new Array();
		var num = 0;

		//optionを順番に見て、selectedとなっているものの添え字を配列にいれる
		for(var i = 0; i < e.childElementCount; i++){
			if(e.getElementsByTagName('option')[i].selected){
				ary[num] = i;
				num++;
			}
		}

		// LEDが選択されている場合のみ変更を送信する。
		if(num > 0) {
			let formData = new FormData();
			formData.append('param', "setLedColor "+ hex + " " + ary )  ;
			
			fetch(url, {
				method: 'POST',  // methodを指定しないとGETになる
				body: formData,  // Postで送るパラメータを指定
			});
		}
	}
});
color_picker.html 抜粋

color_picker.htmlはflaskで読み込まれるWEBページのテンプレートです。
LEDのチェックボックスとColorPickerを設置したシンプルな作りになってます。

<head>
    <script type="text/javascript">
      var serverIp = "{{ data.serverIp }}";
    </script>
</head>
<body>
    <div class="wrapper">
        <ul class="navigationTabs">
            <li><a href="#color" rel="ColorPicker">ColorPicker</a></li>
            <li><a href="#dummy" rel="dummy">Dummy</a></li>
            <li><a href="#dummy2" rel="dummy2">Dummy2</a></li>
        </ul>
        <div class="tabsContent">
            <div class="tab">
                <h2>ColorPicker</h2>
                <br>
                    <select multiple id="ledNum">
                      <option value="0">LED#0</option>
                      <option value="1">LED#1</option>
                      <option value="2">LED#2</option>
                      <option value="3">LED#3</option>
                      <option value="4">LED#4</option>
                      <option value="5">LED#5</option>
                    </select>
                  
                <p id="colorpickerHolder">
                </p>

               
            </div>
            <div class="tab">
                <h2>Dummy</h2>
                
            </div>
            <div class="tab">
                <h2>Dummy2</h2>
                
            </div>
        </div>
    </div>
</body>
</html>

動作

GUIで指定したRGBとLED番号がRaspberryPiのPythonServerへ渡されて、任意のLED色を変更することができました。

f:id:da-yamax:20200502173609g:plain
LED操作GUI
f:id:da-yamax:20200502173731g:plain
LED単体制御

まとめ

サンプルプログラムを改変してLEDを制御してみました。
PythonのWEB GUIからDarkPawへ制御通信を行うこともできたので、
次回はWEB GUIを通してスマートフォンからDarkPawを制御するコントローラをつK就てみたいと思います。