今まで、スマホからマイコンボードのLEDやらモーターを制御させるための無線通信手段として「Apache等のミドルウェアを使ったWEBサーバー経由」または「Bluetooth」を主に利用してきたのですが、今回コントローラー側をスマホでは無くマイコンボードでやってみた際に、これらが使えない状況だったので、その時のメモになります。
新たに試してみた無線通信手段は、UDP通信になります。これもWEB通信なので、サーバー経由になるっちゃあなるんでしょうが、冒頭に書いた「 Apache等のミドルウェアを使ったWEBサーバー経由 」とは異なります。 ミドルウェアを使った場合、WEB通信のやり取り等は特段気にする必要性が無いためです。つまり、無線のやり取り部分はブラウザとApacheが勝手にやってくれるのでプログラムを作る必要性がありません。
ですので、サーバー側にApache等のミドルウェアを入れることが出来ない、もしくはクライアント側でブラウザが使えない、といった場合ですと、必然的にデバイス間でWEB通信のプログラムを作らなければなりません。
今回の経緯としましては、ラズパイで作ったラジコンをM5stickCの加速度センサで無線操作してみたいと思ったからです。恐らく、C使いならばBluetoothで行けたんでしょうが、生憎Cが全く使えないので、M5側はmicropythonで行くしかありません。しかし、micropythonではBluetooth通信が簡単に出来ません(何故か情報が少ない)。OSは無いのでブラウザもない。ってことで、WEB通信のUDP通信プログラムを作ってみる事になりました。
前置きがかなり長くなってしまいましたが、ここで詳細をまとめておこうと思います。
なお、参考にさせて頂いたサイトのリンクは以下になります。↓
初めに
やりたいこと
サーバー側をRaspberry Pi ZERP Wとし、pythonで記述。クライアント側にM5stackを使い、micropythonで記述。
M5stackでボタンを押せば文字列を送信し、Raspberry pi側で受け取ってシェルで表示。これをUDP通信で実現する。
UDP通信について
WEB通信のトランスポート層で動作するプロトコルで、TCPとUDPがあります。前者がコネクション型、後者がコネクションレス型と呼ばれています。TCPは送ったパケットがちゃんと届いているかを確かめて、届いていなければ再送するといった動作をします。確認や再送の手間がある分時間がかかりますが、データの完全性が高いのでWEB閲覧時等などに使われています。UDPはパケットが届いたかどうかの確認をしないので、速度が速い代わりにデータの完全性が損なわれます。音声通話や動画配信など、リアルタイム性重視の場面で利用されています。
今回はコントローラーとして文字列を送るだけなので、とりあえずUDPでやってみました。
ソケットについて
通信を確立させるためにソケットが使われています。クライアント側、サーバー側にそれぞれソケットがあり、下図のような順序で通信を確立させています。
プログラム
M5stack(クライアント側)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import network import time from socket import socket, AF_INET, SOCK_DGRAM from m5stack import * from m5ui import * from uiflow import * from m5stack import lcd wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') wlan.connect('SSID', 'PASS') while not wlan.isconnected(): pass print('network config:', wlan.ifconfig()) serv_address = ("サーバーのIP", ポート番号) s = socket(AF_INET, SOCK_DGRAM) while True: if btnA.wasPressed(): # ボタンAが押されているか lcd.clear(lcd.BLACK) lcd.clear(lcd.BLUE) message = "A" s.sendto(message.encode("utf-8"), serv_address) time.sleep(0.1) elif btnB.wasPressed(): # ボタンBが押されているか lcd.clear(lcd.BLACK) lcd.clear(lcd.RED) message = "B" s.sendto(message.encode("utf-8"), serv_address) time.sleep(0.1) elif btnC.wasPressed(): # ボタンCが押されているか lcd.clear(lcd.BLACK) lcd.clear(lcd.GREEN) message = "C" s.sendto(message.encode("utf-8"), serv_address) time.sleep(0.1) s.close() |
Raspberry Piと同じLANに接続されていなければダメですので、まず無線LANにステーションモードで接続しています。
続いてサーバーの情報(IPとポート番号)をインスタンス化します。
最後のループではボタン押下するとボタンに応じた文字列がサーバーに送られます。
Raspberry Pi(サーバー側)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from socket import socket, AF_INET, SOCK_DGRAM import time #import network #wlan = network.WLAN(network.STA_IF) #wlan.active(True) #if not wlan.isconnected(): # print('connecting to network...') # wlan.connect('SSID', 'PASS') # while not wlan.isconnected(): # pass # print('network config:', wlan.ifconfig()) HOST = "サーバーのIP" PORT = ポート番号 s = socket(AF_INET, SOCK_DGRAM) s.bind((HOST, PORT)) while(True): msg, address = s.recvfrom(8192) #print(f"message: {msg}\nfrom: {address}") msg = msg.decode() print(msg) time.sleep(0.1) s.close() |
無線LANにステーションモードで接続しています。
その後ソケットを作り、サーバー側のIPとポートを紐づけています。
最後のループで、作成したソケットに対して文字列のデータが飛んできているか確認し、データがあれば文字列を表示させています。
テスト
こんな感じになります。↓
次回は、これを応用してM5stickCの加速度センサーの位置情報を、Raspberry PiにUDP通信で送信してみます。