W5500を使ってArduinoでUDP通信する
WIZnetのW5500を使ってArduinoでUDP通信を行ってみます。
1. 準備
W5500を入手します。日本国内で格安で扱っているところはあまりなさそうだったのでAliexpressで注文しました。1個$3.82。送料無料でも送ってくれますが、トラッキングができないのと配送期間も長くなる傾向があるので、商品価格よりも高くなりますが、$3~$5支払ってトラッキングと配送期間を短縮できるようにしました。 (今回は注文から12日程度で到着)
1 個 USR ES1 W5500 チップ新 spi 蘭イーサネット変換 TCPIP Mod - Aliexpress.com | Alibaba グループ上の 電子部品&用品 からの 集積回路 の中
ArduinoはUNO R3を使います。
2. 接続
ArduinoとはSPIで通信を行うため、W5500を以下のように接続します。IOレベルは3.3Vですが5VトレラントでもあるのでArduinoへ直結することができます。その他の注意点としてはW5500の消費電流が大きいため3.3Vの供給をArduinoから行わずに外部から加えるようにしています。
Arduino | W5500 |
---|---|
D10 (SS) | CS |
D11 (MOSI) | MOSI |
D12 (MISO) | MISO |
D13 (SCK) | SCLK |
GND | GND |
データシートを見ると100M Trasmittingのときに132mA消費すると書いてあるので、外部電源は200mA以上供給できるものを用意すると良いでしょう。
products:w5500:datasheet [Document Wiki]
(2019.06.15 追記)
ブレッドボードで接続する場合はワイヤーをなるべく短くしてください。ワイヤーが長いと通信が不安定になります。
3. コード
以下のコードをArduinoに書き込みます。Arduinoの標準サンプルコードの[ファイル] > [スケッチ例] > [Ethernet] > [UDPSendReceiveString]を参考にしています。
ArduinoのEthernetライブラリの詳細はこちらから確認することができます。
#include <Ethernet.h> #include <EthernetUdp.h> byte mac[] = { 0x70, 0x69, 0x69, 0x2D, 0x30, 0x31 }; byte ip[] = { 10, 0, 1, 11 }; unsigned int localPort = 1337; char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; char ReplyBuffer[] = "acknowledged"; EthernetUDP Udp; void setup() { Serial.begin(9600); Serial.println("starting..."); Ethernet.init(10);// CS pin 10 Ethernet.begin(mac, ip); // Check for Ethernet hardware present if (Ethernet.hardwareStatus() == EthernetNoHardware) { Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); while (true) { delay(1); // do nothing, no point running without Ethernet hardware } } if (Ethernet.linkStatus() == LinkOFF) { Serial.println("Ethernet cable is not connected."); } Serial.print("IP: "); Serial.println(Ethernet.localIP()); Udp.begin(localPort); } void loop() { // if there's data available, read a packet int packetSize = Udp.parsePacket(); if (packetSize) { Serial.print("Received packet of size "); Serial.println(packetSize); Serial.print("From "); IPAddress remote = Udp.remoteIP(); for (int i=0; i < 4; i++) { Serial.print(remote[i], DEC); if (i < 3) { Serial.print("."); } } Serial.print(", port "); Serial.println(Udp.remotePort()); // read the packet into packetBufffer Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); Serial.print("Contents: "); Serial.println(packetBuffer); // send a reply to the IP address and port that sent us the packet we received Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); Udp.write(ReplyBuffer); Udp.endPacket(); } delay(10); }
MACアドレスやIPアドレスは環境に合わせて変更してください。
4. 実行
シリアルモニタでログを確認します。通信相手はNode.jsを使うと簡単に用意することができます。正常に動作していれば、ログに受信したメッセージと相手先に"acknowledged"を送信していることが確認できます。
5. 参考リンク
http://nopnop2002.webcrow.jp/Arduino_Networking/Network-2.html
mbedでUDP通信する
前回はmbedでTCP通信を行いました。今回はUDP通信を試してみたいと思います。
1. 準備
以下の部品を用意します。
mbed LPC1768
LANコネクタDIP化キット
秋月電子で購入することができます。
LANコネクタDIP化キット: パーツ一般 秋月電子通商-電子部品・ネット通販
2. 接続
mbedとLANコネクタDIP化キットを以下のように接続します。
MBED | LANコネクタDIP化キット |
---|---|
RD- | pin 6 |
RD+ | pin 3 |
TD- | pin 2 |
TD+ | pin 1 |
3. コード(mbed側)
ここからTCPの記事と異なります。 mbedの公式handbookのページからSocket通信するためのコードとライブラリをインポートします。
UDP Echo Server
Socket - Handbook | Mbed
[Import program]ボタンを押すとmbedコンパイラが開いてコードとライブラリをインポートすることができます。
インポート画面でImport AsがProgramにチェックがはいっていることと、Update all libraries to the latest versionにチェックが入っていないこと( NOT CHECKED )を確認してインポートします。Import Nameは任意の値で構いません。
【重要】ライブラリのアップデートは正常動作しなくなるためおすすめしません。
インポートしたらそのままコードを使用しても構いませんが、もう少し動作がわかりやすいように以下のコードをmbedに書き込みます。UDP通信はTCP通信と異なり、サーバ/クライアントの概念がないため今回mbed側をA、相手側のPCをBとします。
/* A: 自分(mbed) B: 相手(PC) */ #include "mbed.h" #include "EthernetInterface.h" const char* A_ADDRESS = "10.0.1.25";// 自分 const int A_PORT = 33333; const char* B_ADDRESS = "10.0.1.7";// 相手 const int B_PORT = 33333; int main() { printf("starting...\n"); char u_packet[2] = { 0x4F, 0x4B };// "OK" EthernetInterface eth; eth.init(A_ADDRESS, "255.255.255.0", ""); printf("IP : %s\n", eth.getIPAddress()); if (eth.connect() < 0) { printf("Failed to connect\n\r"); return -1; } UDPSocket socket; if (socket.bind(A_PORT) < 0) { printf("Failed to bind UDP Socket to PORT : %d\n\r", A_PORT); return -1; } else { printf("Bind UDP Socket to PORT : %d\n\r", A_PORT); } socket.set_blocking(false); Endpoint EndpointB; EndpointB.set_address(B_ADDRESS, B_PORT); socket.sendTo(EndpointB, (char*)u_packet, sizeof(u_packet)); while(1) { char u_buff[12]; int n_receive = socket.receiveFrom(EndpointB, (char*)u_buff, sizeof(u_buff)); if (n_receive > 0) { u_buff[n_receive] = '\0';// 終端文字追加(printfしないときは不要) printf("Received [%d]: %s\n", n_receive, u_buff); } } }
内容は起動したら"OK"[0x4F, 0x4B]メッセージを相手側(B)に送り、その後は相手側からのメッセージ受け続けます。
4. コード(PC側)
通信相手としてPC側のコードを用意します。UDP通信ができれば何でも構いせませんが、今回はnode.jsを使いました。
const dgram = require('dgram'); const PORT_A = 33333; const HOST_A = '10.0.1.25';// 相手 const PORT_B = 33333; const HOST_B = '10.0.1.7';// 自分 const socket = dgram.createSocket('udp4'); var count = 0; setInterval(() => { count++; const data = Buffer.from(String(count)); socket.send(data, 0, data.length, PORT_A, HOST_A, (err, bytes) => { if (err) throw err; }); }, 500); socket.on('message', (message, remote) => { console.log(remote.address + ':' + remote.port +' - ' + message); }); socket.bind(PORT_B, HOST_B);
内容は500msec毎に数値をカウントアップしてmbed側に送信しています。
以下のコマンドで上記のサーバ側のコードを実行することができます。
$ npm init $ npm install dgram $ node server.js
5. 実行
LANコネクタにLANケーブルを接続し、mbedもシリアル通信でログを確認します。正常に動作していればシリアル通信で以下のようにログを確認することができます。
6. 参考リンク
mbedでTCP通信する
mbed LPC1768ではチップにEthernetの物理層(PHY)を内蔵しているため、LANコネクタ(RJ45)を追加するだけでEthernetを使った通信を行うことができます。ここではmbedを使って実際にTCP通信を行ってみます。
1. 準備
以下の部品を用意します。
mbed LPC1768
LANコネクタDIP化キット
秋月電子で購入することができます。
LANコネクタDIP化キット: パーツ一般 秋月電子通商-電子部品・ネット通販
2. 接続
mbedとLANコネクタDIP化キットを以下のように接続します。
MBED | LANコネクタDIP化キット |
---|---|
RD- | pin 6 |
RD+ | pin 3 |
TD- | pin 2 |
TD+ | pin 1 |
3. コード(mbed側)
mbedの公式handbookのページからSocket通信するためのコードとライブラリをインポートします。
[Import program]ボタンを押すとmbedコンパイラが開いてコードとライブラリをインポートすることができます。
インポート画面でImport AsがProgramにチェックがはいっていることと、Update all libraries to the latest versionにチェックが入っていないこと( NOT CHECKED )を確認してインポートします。Import Nameは任意の値で構いません。
【重要】ライブラリのアップデートは正常動作しなくなるためおすすめしません。
インポートしたらそのままコードを使用しても構いませんが、もう少し動作がわかりやすいように以下のコードをmbedに書き込みます。今回mbedはクライアントで動作します。
#include "mbed.h" #include "EthernetInterface.h" const char* ECHO_SERVER_ADDRESS = "10.0.1.7"; const int ECHO_SERVER_PORT = 3000; int main() { printf("starting...\n"); EthernetInterface eth; eth.init(); // Use DHCP eth.connect(); printf("Client IP Address is %s\n", eth.getIPAddress()); // Connect to Server TCPSocketConnection socket; while (socket.connect(ECHO_SERVER_ADDRESS, ECHO_SERVER_PORT) < 0) { printf("Unable to connect to (%s) on port (%d)\n", ECHO_SERVER_ADDRESS, ECHO_SERVER_PORT); wait(1); } printf("Connected to Server at %s\n",ECHO_SERVER_ADDRESS); // Send message to server char hello[] = "Hello World"; printf("Sending message to Server : '%s' \n",hello); socket.send_all(hello, sizeof(hello) - 1); // Receive message from server while (1) { char buf[256]; int n = socket.receive(buf, 256); if (n > 0) { buf[n] = '\0'; printf("Received message from server: '%s'\n", buf); } } // Clean up socket.close(); eth.disconnect();
内容は起動したら"Hello World"メッセージをサーバに送り、その後はサーバからのメッセージ受け続けます。
4. コード(サーバ側:PC)
PC側にサーバを用意します。TCPサーバが立てられれば何でも構いませんが、今回はnode.jsを使いました。
server.js
const net = require('net'); var count = 0; const server = net.createServer(socket => { socket.on('data', data => { console.log(data + ' from ' + socket.remoteAddress + ':' + socket.remotePort); setInterval(() => { count++; console.log(count) socket.write(String(count)); }, 500); }); socket.on('close', () => { console.log('client closed connection'); }); }).listen(3000); console.log('listening on port 3000');
内容は500msec毎に数値をカウントアップしてmbed(クライアント)に送信しています。
以下のコマンドで上記のサーバ側のコードを実行することができます。
$ npm init $ npm install net $ node server.js
5. 実行
LANコネクタにLANケーブルを接続し、mbedもシリアル通信でログを確認します。mbedはDHCPでネットワーク設定を取得するため、ルーターはDHCPサーバが機能していることを確認してください。正常に動作していればシリアル通信で以下のようにログを確認することができます。
6. 参考リンク
Node.jsでTCP通信する
以下のサイトを参考にしてNode.jsでTCP通信を行ってみました。
nodeによるTCP通信は net
モジュールを使用します。APIリファレンスは以下を参照ください。
Net | Node.js v12.4.0 Documentation
1. 準備
以下のコマンドを入力します。
$ npm install net
2. コード
server用とclient用のコードを以下のようにそれぞれ準備します。
server.js
const net = require('net'); const server = net.createServer(socket => { socket.on('data', data => { console.log(data + ' from ' + socket.remoteAddress + ':' + socket.remotePort); socket.write('server -> Repeating: ' + data); }); socket.on('close', () => { console.log('client closed connection'); }); }).listen(3000); console.log('listening on port 3000');
client.js
const net = require('net'); const client = net.connect('3000', 'localhost', () => { console.log('connected to server'); client.write('Hello World!'); }); client.on('data', data => { console.log('client-> ' + data); client.destroy(); }); client.on('close', () => { console.log('client-> connection is closed'); });
3. 実行
serverとclient別々のターミナルを立ち上げ、以下のコマンドを実行してメッセージが表示されれば成功です。
$ node server.js
$ node ceient.js
参考にしたサイトではクライアントからの標準入力をサーバでエコーバックし続けていますが、今回のこのコードはclientから「Hello World!」を送って返ってきたら destroy
して接続を終了しています。
Node.jsでUDP通信する
以下のサイトを参考にして実際にNode.jsでUDP通信を行ってみました。
dgramを使うと簡単にUDP通信できるようです。
1. 準備
以下のコマンドを入力します。
$ npm init $ npm install dgram
2. コード
UDP通信では明確にサーバとクライアントで区別できないため、今回は便宜上、片方をterminal_a、もう一方をterminal_bとします。以下のサンプルコードは同じPC上で動かすため、IPアドレスは同じで異なるPORTで通信を行います。
terminal-a.js
const dgram = require('dgram'); const PORT_A = 3002; const HOST_A = '127.0.0.1'; const PORT_B = 3003; const HOST_B = '127.0.0.1'; const socket = dgram.createSocket('udp4'); socket.on('listening', () => { const address = socket.address(); console.log('UDP socket listening on ' + address.address + ":" + address.port); }); socket.on('message', (message, remote) => { console.log(remote.address + ':' + remote.port +' - ' + message); socket.send(message, 0, message.length, PORT_B, HOST_B, (err, bytes) => { if (err) throw err; }); }); socket.bind(PORT_A, HOST_A);
terminal-b.js
const dgram = require('dgram'); const PORT_A = 3002; const HOST_A ='127.0.0.1'; const PORT_B = 3003; const HOST_B ='127.0.0.1'; const socket = dgram.createSocket('udp4'); var count = 0; setInterval(() => { count++; const data = Buffer.from(String(count)); socket.send(data, 0, data.length, PORT_A, HOST_A, (err, bytes) => { if (err) throw err; }); }, 500); socket.on('message', (message, remote) => { console.log(remote.address + ':' + remote.port +' - ' + message); }); socket.bind(PORT_B, HOST_B);
3. 実行
terminal-aとterminal-bを別々のコンソールを立ち上げて以下のコマンドをそれぞれ入力します。正常に動作していればterminal-bからintervalでカウントアップ文字列を送り、terminal-aでエコーバックしていることが確認できます。
$ node terminal-a.js
$ node terminal-b.js
UniRxを使ってみる
ここではUniRxの基本的なオペレータとそのサンプルコードを見ていきたいと思います。
概念を理解するのも大切ですが実際に使って動かしてみるのも理解への近道です。
全体のコードは以下のリポジトリにありますので、別記事の UnityでLINQを使って楽をする も合わせてご覧ください。
また、本記事は全体的に以下のサイトの記事(RxJava)を参考にさせて頂きました。
What's UniRx?
http://slides.com/robwormald/everything-is-a-stream#/
特徴
- ReactiveXのUnity版
- マーブルダイアグラム
- Observable / Subscribe の世界
- 豊富なオペレータ
- 非同期並列処理
- Asset StoreからUniRxをImportして
using UniRx
で使えるようになるよ
Timer
指定時間後に処理を行います。
using UniRx; using System; Debug.Log("Timer START"); Observable.Timer(TimeSpan.FromSeconds(3)) .Subscribe(_ => Debug.Log("OK")); // 3秒後に"OK"
Interval
指定時間間隔で繰り返します。
using UniRx; using System; Observable .Interval(TimeSpan.FromSeconds(1)) .Subscribe(x => Debug.Log(x)); // 0, 1, 2, 3, 4, 5......
Where
条件式がTrueとなるものを流します。
using UniRx; var list = new List<int> { 1, 2, 3, 4, 5 }; list.ToObservable() .Where(x => x > 2) .Subscribe(x => Debug.Log(x)); // 3, 4, 5
Distinct
重複を削除します。
using UniRx; var list = new List<int> { 1, 2, 3, 3, 4, 5, 5 }; list.ToObservable() .Distinct() .Subscribe(x => Debug.Log(x)); // 1, 2, 3, 4, 5
Take
指定回数だけ流します。
using UniRx; var list = new List<int> { 1, 2, 3, 4, 5 }; list.ToObservable() .Take(3) .Subscribe(x => Debug.Log(x)); // 1, 2, 3
Skip
指定回数だけスキップします。
using UniRx; var list = new List<int> { 1, 2, 3, 4, 5 }; list.ToObservable() .Skip(3) .Subscribe(x => Debug.Log(x)); // 4, 5
Scan
畳み込みを行います。
using UniRx; var list = new List<int> { 1, 2, 3, 4, 5 }; list.ToObservable() .Scan((a, b) => a + b) .Subscribe(x => Debug.Log(x)); // 1, 3, 6, 10, 15
Range
範囲を指定して流します。
using UniRx; Observable .Range(1, 5) .Subscribe(x => Debug.Log(x)); // 1, 2, 3, 4, 5
Create
Observableを作成します。
using UniRx; var observable = Observable.Create<int>(observer => { observer.OnNext(1); observer.OnNext(2); observer.OnNext(3); observer.OnNext(4); observer.OnNext(5); observer.OnCompleted(); return Disposable.Empty; }); observable.Subscribe(x => Debug.Log(x)); // 1, 2, 3, 4, 5
ボタンクリックイベント
OnClickイベントをObservable化します。
using UniRx; using UnityEngine.UI; [SerializeField] Button button = default; button .OnClickAsObservable() .Subscribe(_ => Debug.Log("on click"));
SubscribeOn
Observerを処理するスレッドを指定します。
using UniRx; using System.Threading; Debug.Log("START"); var observable = Observable.Create<int>(observer => { Thread.Sleep(3000); observer.OnNext(1); observer.OnCompleted(); return Disposable.Empty; }).SubscribeOn(Scheduler.ThreadPool); observable.Subscribe(x => Debug.Log(x)); Debug.Log("END"); // START // END // 1 (3秒後)
Merge
Observableを合成します。順不同
using UniRx; using System.Threading; var list1 = Observable.Create<string>(observer => { Thread.Sleep(1); observer.OnNext("A1"); Thread.Sleep(8); observer.OnNext("A2"); Thread.Sleep(2); observer.OnNext("A3"); observer.OnCompleted(); return Disposable.Empty; }).SubscribeOn(Scheduler.ThreadPool); var list2 = Observable.Create<string>(observer => { Thread.Sleep(3); observer.OnNext("B1"); Thread.Sleep(1); observer.OnNext("B2"); Thread.Sleep(12); observer.OnNext("B3"); observer.OnCompleted(); return Disposable.Empty; }).SubscribeOn(Scheduler.ThreadPool); Observable .Merge(list1, list2) .Subscribe(x => Debug.Log(x)); // A1, B1, B2, A2, A3, B3
Concat
Observableを合成します。流れる順序は引数受け取り順です。
using UniRx; using System.Threading; var list1 = Observable.Create<string>(observer => { Thread.Sleep(1); observer.OnNext("A1"); Thread.Sleep(8); observer.OnNext("A2"); Thread.Sleep(2); observer.OnNext("A3"); observer.OnCompleted(); return Disposable.Empty; }).SubscribeOn(Scheduler.ThreadPool); var list2 = Observable.Create<string>(observer => { Thread.Sleep(3); observer.OnNext("B1"); Thread.Sleep(1); observer.OnNext("B2"); Thread.Sleep(12); observer.OnNext("B3"); observer.OnCompleted(); return Disposable.Empty; }).SubscribeOn(Scheduler.ThreadPool); Observable .Concat(list1, list2) .Subscribe(x => Debug.Log(x)); // A1, A2, A3, B1, B2, B3
Zip
MergeやConcatとの違いは異なる型のObservableを扱えます。
using UniRx; var list1 = new List<string> { "A", "B", "C", "D", "E" }.ToObservable(); var list2 = new List<int> { 1, 2, 3, 4, 5 }.ToObservable(); Observable .Zip(list1, list2, (a, b) => a + b) .Subscribe(x => Debug.Log(x)); // A1, B2, C3, D4, E5
Subject
外部から任意のタイミングでOnNext、OnComplete、OnErrorを送ることが可能です。
using UniRx; var subject = new Subject<int>(); subject.Subscribe(x => Debug.Log(x)); subject.OnNext(1); subject.OnNext(2); subject.OnNext(3); // 1, 2, 3
BehaviorSubject
Subjectとの違いは初期値の有無です。
using UniRx; var subject = new BehaviorSubject<int>(5); subject.Subscribe(x => Debug.Log(x)); subject.OnNext(1); subject.OnNext(2); subject.OnNext(3); // 5, 1, 2, 3
終わりに
オペレータはこの他にもたくさんありますので、使い方などの詳しい情報はUniRxのリポジトリやwikiをみると良いでしょう。それでは良いUniRxライフを。
UnityでLINQを使って楽をする
LINQを使うとデータ処理を簡単に行うことができます。 全体のコードは以下のリポジトリにありますので、別記事の UniRxを使ってみる も合わせてご覧ください。
What's LINQ?
C# の統合言語クエリ (LINQ) | Microsoft Docs
LINQの詳しい解説は他のサイトに譲るとして、ここではサンプルコードを記載します。実際に実行して挙動を確認してみると理解が進むはずです。
LINQ Operators
Average
平均値を抽出
using System.Linq; double average = new List<int> { 0, 1, 2, 3 }.Average(); Debug.Log(average); // 1.5
Min
最小値を抽出
using System.Linq; int min = new List<int> { 21, 9, 15, 3, 8, 11 }.Min(); Debug.Log(min); // 3
Max
最大値を抽出
using System.Linq; int max = new List<int> { 5, 9, 15, 3, 21, 8, 11 }.Max(); Debug.Log(max); // 21
Sum
合計値を算出
using System.Linq; int sum = new List<int> { 1, 2, 3, 4, 5 }.Sum(); Debug.Log(int); // 15
Where
条件式がTrueとなる要素で再構成します。
using System.Linq; var list = new List<int> { 1, 2, 3, 4, 5 }.Where(x => x > 2); Debug.Log(string.Join(", ", list)); // { 3, 4, 5 }
Select
式を評価した値で再構成します。
using System.Linq; var list = new List<int> { 1, 2, 3, 4, 5 }.Select(x => x * x); Debug.Log(string.Join(", ", list)); // { 1, 4, 9, 16, 25}
メソッドチェーン
メソッド(オペレータ)を連結することで連続して処理することができます。
using System.Linq; var list1 = new List<int> { 1, 2, 3, 4, 5 }; var list2 = list1.Where(x => x > 2); var list3 = list1.Where(x => x > 2).Select(x => x * x); Debug.Log(string.Join(", ", list1));// { 1, 2, 3, 4, 5 } Debug.Log(string.Join(", ", list2));// { 3, 4, 5 } Debug.Log(string.Join(", ", list3));// { 9, 16, 25 }
(foreach)
foreachはLINQではありませんが、要素を取り出す操作としてLINQと組み合わせて使うこともできます。
var list = new List<int> { 1, 2, 3, 4, 5 }; list.ForEach(x => Debug.Log(x * x)); // 1, 4, 9, 16, 25 // list.ForEach(x => { // Debug.Log("-----"); // Debug.Log(x * x); // });