Raspberry PiでWebSocket|Lチカ、WebSocketの理解

WebSocketでLチカ
ここではHTMLとJavaScriptでボタンを作り、MacのウェブブラウザからRaspberry Piを遠隔操作する方法を説明する。通信方法はWebSocketで行っていく。遠隔操作できるようになるのが目的なので、LEDをオンオフ(Lチカ)するだけの簡単なプログラムとなる。

ここではRaspberry Pi zeroを使ったが、WiFi機能があれば他のRaspberry PiでもOK。
Lチカの動作テスト
まずはRaspberry PiとLEDだけで簡単な動作テストをしておこう。図のように、Raspberry PiのGPIO11に1kΩを通してLEDをつないでおく。

RPi.GPIO
を使って、LEDを5回点滅させるだけの簡単なプログラムだ。これでLEDをオンオフできるようになった。
import RPi.GPIO as GPIO
from time import sleep
= 11
LED
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED, GPIO.OUT)
for t in range(0, 5):
True)
GPIO.output(LED, 1)
sleep(False)
GPIO.output(LED, 1) sleep(
Raspberry PiにWebSocketサーバーを設定
さきほどのLEDをWebSocketで点灯できるようにしてみよう。Raspberry
PiでWebSocketのデータを受信できるようにするため、Pythonで動く
websocket-server
ライブラリを使った。pipでインストールできるので次のようにしてインストールしてく。
$ sudo pip install websocket-server
そして作成したPythonプログラムがこちら。ただしIPアドレスはRaspberry
PiのIPアドレスに置き換える。クライアントからWebSocketのメッセージを受信できるようになっている。メッセージが
led_on
であればLEDを点灯、led_off
であれば消灯するプログラムである。ファイル名をled_switch_sever.py
にしてRaspberry Piに保存しておこう。
import RPi.GPIO as GPIO
from time import sleep
from websocket_server import WebsocketServer
= 11
LED
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED, GPIO.OUT)
def receivedMessage(client, server, message):
print(message)
if message == 'led_on':
True)
GPIO.output(LED, elif message == 'led_off':
False)
GPIO.output(LED, else:
print("Unknown Message: {}".format(message))
= WebsocketServer(5555, host="192.168.100.136")
server
server.set_fn_message_received(receivedMessage) server.run_forever()
WebSocketクライアントを作る
次に、HTMLとJavaScriptでWebSocketクライアントを作っていく。今回はMacを使ったが、HTMLファイルが実行できればWindowsでもスマホからでも動作するはず。好きな端末をクライアントマシンにしよう。
作ったクライアントプログラムがこちら。ボタンを配置し、それを押すとRaspberry
PiへWebSocketを投げるプログラムとなっている。led_switch_client.html
として適当な場所に保存しておこう。
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.min.js">
</script>
<script>
$(function () {
var ws = new WebSocket("ws://192.168.100.136:5555/");
$('#btn').on('click', function () {
if($('#btn').text() == "OFF") {
$('#btn').text("ON")
.send('led_on');
wselse {
} $('#btn').text("OFF")
.send('led_off');
ws
};
})
})</script>
<style>
#btn{
width: 500px;
height:200px;
font-size:100px;
}</style>
</head>
<body>
<button id="btn">OFF</button>
</body>
</html>
WebSocketでLチカ実践
それでは最後に、プログラムを実行してRaspberry
PiのLEDを点滅させてみよう。まずRaspberry Piで
$ python led_switch_sever.py
を実行して、WebSocketサーバーを立ち上げておく。次に
led_switch_client.html
をウェブブラウザ開いてみよう。下の動画のようにボタンを押すごとに、Raspberry
Piに接続されたLEDが点灯消灯を繰り返すはずだ。

WebSocketの仕組みを理解する
ここらはHTMLとJavaScrpitを使ってクライントを作り、できるだけ簡単にWebSocketの通信を実現し、WebSocketの全体像をつかみやすくした。
Raspberry PiからWebSocketでブラウザにメッセージを送る

websocket-sever
のインストールができたところで、Raspberry
PiからWebSocketでブラウザにメッセージを送ってみよう。
次の簡単なWebSocketサーバーをPythonで作った。Raspberry PiからWebSocketでブラウザへ「Hello world! This is Raspberry Pi!」を送信するプログラムである。
from websocket_server import WebsocketServer
def sendMessage(client, server):
"Hello world! This is Raspberry Pi!")
server.send_message_to_all(
= WebsocketServer(5555, host="192.168.100.136")
server
server.set_fn_new_client(sendMessage) server.run_forever()
hostにはRaspberry
PiのIPアドレスを設定する。IPアドレスを調べるには、$ ifconfig
を実行して wlan0
の inet
に書かれている。
JavaScriptでWebSocketクライアントの作成
次に、Raspberry PiでWebSocketサーバーへ通信するために、WebSocketクライアントをJavaScriptで作ってみた。このコードはMacで動かすものとする。
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.min.js">
</script>
<script>
$(function () {
var ws = new WebSocket("ws://192.168.100.136:5555/");
.onmessage = function (message) {
wsdocument.getElementById("recived").innerHTML = message.data;
}
})</script>
</head>
<body>
<div id="recived"></div>
</body>
</html>
このコードをたとえば websocket-client.html
として保存し、それをSafariで表示させてみよう。
Raspberry Piで設定したメッセージ「Hello world! This is Raspberry Pi!」が、ブラウザに表示されればWebSocketの通信成功である。
JavaScriptでブラウザからRaspberry PiへWebSocketメッセージを送る

今度は逆に、クライアントからサーバーへWebSocketでメッセージを送れるようにしてみよう。
Raspberry Piのサーバープログラム
websocket-sever.py
を少し改造する。
from websocket_server import WebsocketServer
def sendMessage(client, server):
"Hello world! This is Raspberry Pi!")
server.send_message_to_all(
def receivedMessage(client, server, message):
print message
"recived message: " + message)
server.send_message(client,
= WebsocketServer(5555, host="192.168.100.136")
server
server.set_fn_new_client(sendMessage)
server.set_fn_message_received(receivedMessage) server.run_forever()
クライアント側の websocket-client.html
からRaspberry
PiへWebSocketでメッセージを送信できるように修正した。
<html>
<head>
<script src="http://code.jquery.com/jquery-latest.min.js">
</script>
<script>
$(function () {
var ws = new WebSocket("ws://192.168.100.136:5555/");
.onmessage = function (message) {
wsdocument.getElementById("recived").innerHTML = message.data;
}
$('#btn').on('click', function () {
.send($('#btn').text());
ws;
})
})</script>
</head>
<body>
<div id="recived"></div>
<button id="btn">Hi, I'm from Mac.</button>
</body>
</html>
ブラウザで表示し、ボタンを押すと次のようにメッセージを送れたと思う。
WebSocketのHTTPリクエストを手作りしてみる
今度はよりWebSocket通信の理解を深めるため、JavaScriptではなくPythonを使ったWebSocketクライアントを作ってみよう。Pythonのscoket
ライブラリを使って、HTTPヘッダーを手作りしてみる。
WegSocketのヘッダーの構成
ところでWebSocketは次のようにHTTPリクエストを使ってWebSocketのハンドシェイクする形になっている。
GET / HTTP/1.1
Host: 192.168.100.136
Connection: Upgrade
Sec-WebSocket-Key: vTu2gqiamRGo9gXd9dKbXg==
Sec-WebSocket-Version: 13
Upgrade: websocket
Sec-WebSocket-Key
は16byteのランダムな値をbased64でエンコードしたもの。これはサーバーとクライアントとのハンドシェイクの受信を立証するために必要なものだ。
上記のHTTPリクエストをWebSocketサーバーへ投げると、次のようにレスポンスが返ってくる。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: Od7wpaRgvxXMSnWrv2qkQqREX7I=
これは、これからWebSocket通信に切り替わるメッセージ。このハンドシェイク後、WebSocketでのデータ通信が行えるようになる。
WebSocketのデータフレーム
さらに、WebSocketのデータフレームがどうなっているのか見てみよう。

RFC6455より https://tools.ietf.org/html/rfc6455#page-28
流石にこのデータフレームを1から作り上げていくのはシンドイ。今回は「へぇ、こんな感じなんだ」で流して、オープンソースを参考にクライアントを作成する。
最終的にPythonで書いたWebSocketクライアントプログラムがこちら。
import time
import os
import base64
import socket
import struct
import array
try:
import thread
except ImportError:
import _thread as thread
## python3で動作
def ws_client(host, port, header):
print('++++++++++++HEADER START+++++++++++++++++')
print(header + '+++++++++++++HEADER END++++++++++++++++++')
= (host, port)
address = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s connect(address)
s.
s.send(header.encode())
= b''
response while True:
= s.recv(1)
recv if not recv:
break
+= recv
response if b'\r\n\r\n' in response:
return response.decode(), s
def make_ws_data_frame(data):
= 0x80 # 128(8bit分を確保、そしてFINフラグは1である -> 0b10000000)
FIN = 0x0
RSV1 = 0x0
RSV2 = 0x0
RSV3 = 0x1
OPCODE # [OPCODE]
# 0x0 継続フレーム
# 0x1 テキストフレーム
# 0x2 バイナリフレーム
# 0x8 Closeフレーム(接続切断)
# 0x9 ping
# 0xA pong
# FINと同様の考えでMASKから8bit分確保する
= 0x80
MASK = 0x0
payload
= struct.pack('B', FIN | RSV1 | RSV2 | RSV3 | OPCODE)
frame
# frame=b'\x81' -> 0b10000001
# print(bin(int(frame.hex(), 16)))
= len(data)
data_len # print(data_len)
if data_len <= 125:
= struct.pack('B', MASK | data_len)
payload elif data_len < 0xFFFF:
= struct.pack('!BH', 126 | MASK, data_len)
payload else:
# B(unsigned char, 1byte) Q(unsigned long long, 8byte)
= struct.pack('!BQ', 127 | MASK, data_len)
payload
+= payload
frame #print(bin(int(frame.hex(), 16)))
# 0b10000001 111111100000000011111110
= os.urandom(4)
masking_key #print(masking_key)
= array.array('B', masking_key)
mask_array = array.array('B', data.encode('UTF-8'))
unmask_data
for i in range(data_len):
= unmask_data[i] ^ masking_key[i % 4]
unmask_data[i]
= unmask_data.tobytes()
mask_data += masking_key
frame += mask_data
frame
return [frame, len(frame)]
if __name__ == "__main__":
= '192.168.100.136'
HOST = 5555
PORT = 'GET / HTTP/1.1\r\n'
HEADER += 'Host: ' + HOST + '\r\n'
HEADER += 'Connection: Upgrade\r\n'
HEADER += 'Sec-WebSocket-Key: ' + base64.b64encode(
HEADER 16)).decode('UTF-8') + '\r\n'
os.urandom(+= 'Sec-WebSocket-Version: 13\r\n'
HEADER += 'Upgrade: websocket\r\n'
HEADER += '\r\n'
HEADER
= ws_client(HOST, PORT, HEADER)
res, client
print(res)
if header != '':
def run(*args):
= make_ws_data_frame("Hello, I'm from Mac!")
frame, frame_len
client.send(frame)1)
time.sleep(# client.close()
print("thread terminating...")
thread.start_new_thread(run, ())
= b''
response while True:
= client.recv(1024)
recv print(recv)
if not recv:
break
+= recv
response if b'\r\n' in response:
print(response.decode())
= b'' response
データフレームのプログラムはこちらを参考 https://qiita.com/nk_yohn3301/items/ea42d5778b1400491803
Pythonでプログラムを実行すれば、Raspberry PiのWebSocketサーバーと通信できるはずだ。JavaScriptではあれほど簡単だったWebScocket通信も、実際は複雑な処理を内部で行っている。
とは言え、データフレーム以外はHTTPリクエストと同じなので難しくないと思う。WebSocketでの通信は今後ますます普及していくので、Raspberry Piなどで遊びながら仕組みを理解すると良い。
関連記事
- Android で WebSocketサーバー 【Kotlin/アプリ開発】
- jQuery 中級者向け〜使い方をいろいろまとめ
- ESP32でHTTPSアクセス、ただし証明書検証なし
- ESP32でESP-NOWを使って通信してみよう
- 【Raspberry Piではじめるnginx①】nginxをインストールしてHTMLを表示させるまで