1.はじめに
Elixirでブロードキャストに投げられたUDPデータを受信するまでの手順をまとめました。
最終的に、下記リンク先のツール「UECS通信実用規約に基づいたロギング用ソフトウェア」のElixir版を作るのを目標にしています。
ユビキタス環境制御システム(UECS)技術
ユビキタス環境制御システム「UECS」
(20/5/25)完成版:UecsListner
2.文字を受信して表示
まず最初に、UDPのブロードキャストで受信した文字列を、表示するだけの機能を作ります。
$ cd ~/gitwork/elixir
# プロジェクトをつくる(スーパーバイザも有効に)
elixir$ mix new udpapp --sup
elixir$ cd udpapp/lib/udpapp/
# udpのソースファイル
elixir/udpapp/lib/udpapp$ touch udp.ex
ソースファイル
defmodule Udpapp.Udp do
@moduledoc """
Documentation for `Udp`.
"""
use GenServer
require Logger
def start_link(port, opts) do
#application.exのworkerから起動したときに、この関数が実行されます
Logger.info("* #{__MODULE__}: start_link call")
#GenServerを起動します
GenServer.start_link(__MODULE__, port, opts)
end
def init(port) do
#GenServerが起動したときにこの初期化関数が実行されます
Logger.debug("* #{__MODULE__}: UDP server binding #{port}")
#UDPソケットを開きます
:gen_udp.open(port, [:binary])
{:ok, port}
end
def handle_info({:udp, socket, ip, port, data}, server_socket) do
#UDPを受信したらこの関数が実行されます
Logger.debug("* #{__MODULE__}: handle_info > #{inspect(socket)}, #{inspect(ip)}, #{inspect(port)}, #{inspect(data)}")
{:noreply, server_socket}
end
end
スーパーバイザを実装します。
このファイルは、プロジェクトを生成したときに自動的に作られるので、#↓ここを追記
の所を書き加えます。
defmodule Udpapp.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
#↓ここを追記
import Supervisor.Spec
def start(_type, _args) do
children = [
# Starts a worker by calling: Udpapp.Worker.start_link(arg)
# {Udpapp.Worker, arg}
#↓ここを追記。10000はポート番号
worker(Udpapp.Udp, [10000, [name: :udpserver]]),
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Udpapp.Supervisor]
Supervisor.start_link(children, opts)
end
end
サーバ側実行
iexを起動して待機状態にします。
$ iex -S mix
Erlang/OTP 22 [erts-10.6.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]
23:47:00.223 [info] * Elixir.Udpapp.Udp: start_link call
23:47:00.229 [debug] * Elixir.Udpapp.Udp: UDP server binding 10000
Interactive Elixir (1.9.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
# ここから先は後述のncコマンド実行時に表示されます。
23:49:57.845 [debug] * Elixir.Udpapp.Udp: handle_info > #Port<0.6>, {192, 168, 0, 6}, 38576, "hoge-\n"
23:50:04.025 [debug] * Elixir.Udpapp.Udp: handle_info > #Port<0.6>, {192, 168, 0, 6}, 34197, "fuga\n"
# 終了
[Ctrl-\]
$
クライアント側実行
別のターミナルを開いて、nc
コマンド3を実行します。
$ echo "hoge-" | nc -ub 192.168.0.255 10000
[Ctrl-c]
$ echo "fuga" | nc -ub 192.168.0.255 10000
[Ctrl-c]
nc 引数 |
意味 |
---|---|
-ub | u: udp通信、b: ブロードキャスト |
192.168.0.255 | 送信先アドレス |
10000 | ポート番号 |
とりあえず、文字列の受信まで確認できました。
3.XMLデータを受信したあと、パースして表示
ユビキタス環境制御システム「UECS」では、下記のXML形式のデータをUDPで送信しています。
<?xml version="1.0"?>
<UECS ver="1.00-E10">
<DATA type="InAirCO2" room="1" region="101" order="0" priority="29">447</DATA>
<IP>192.168.5.191</IP>
</UECS>
このXMLデータをパースして、表示してみます。
※上記のデータを、uecs_rawdata.xml
として保存しておいてください。(あとでncコマンドでこの内容を送ります)
ソースファイル
UDPの処理を実装したモジュールに追記します。
defmodule Udpapp.Udp do
(・・・省略・・・)
use GenServer
require Logger
#↓ここを追記
import SweetXml
(・・・省略・・・)
def handle_info({:udp, socket, ip, port, data}, server_socket) do
#UDPを受信したらこの関数が実行されます
Logger.debug("* #{__MODULE__}: handle_info > #{inspect(socket)}, #{inspect(ip)}, #{inspect(port)}, #{inspect(data)}")
{:noreply, server_socket}
#↓ここから追記
uecs_data =
data
# 内容をパース
|> perse_uecs()
# XMLの内容を表示
Logger.debug(" ip: #{inspect(uecs_data[:ip])}")
Logger.debug(" data: #{inspect(uecs_data[:data])}")
Logger.debug(" type: #{inspect(uecs_data[:type])}")
Logger.debug(" room: #{inspect(uecs_data[:room])}")
Logger.debug(" region: #{inspect(uecs_data[:region])}")
Logger.debug(" order: #{inspect(uecs_data[:order])}")
Logger.debug(" priority: #{inspect(uecs_data[:priority])}")
end
#↓この関数を追記
def perse_uecs(xmldoc) do
# XMLデータを読み込んでパースして、
# マップに変換して返り値にする
%{
:ip => xmldoc |> xpath(~x"/UECS/IP/text()"l),
:data => xmldoc |> xpath(~x"/UECS/DATA/text()"l),
:type => xmldoc |> xpath(~x"/UECS/DATA/@type"l),
:room => xmldoc |> xpath(~x"/UECS/DATA/@room"l),
:region => xmldoc |> xpath(~x"/UECS/DATA/@region"l),
:order => xmldoc |> xpath(~x"/UECS/DATA/@order"l),
:priority => xmldoc |> xpath(~x"/UECS/DATA/@priority"l)
}
end
end
mixファイルに、xmlパーサ「sweet_xml」のライブラリを追記します。
defmodule Udpapp.MixProject do
use Mix.Project
(・・・省略・・・)
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
#↓ここを追記
{:sweet_xml, "~> 0.6.5"}
]
end
end
サーバ側実行
# ライブラリの依存関係の処理
$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
sweet_xml 0.6.6
* Getting sweet_xml (Hex package)
# コンパイル・実行
$ iex -S mix
Erlang/OTP 22 [erts-10.6.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]
00:30:25.217 [info] * Elixir.Udpapp.Udp: start_link call
00:30:25.224 [debug] * Elixir.Udpapp.Udp: UDP server binding 10000
Interactive Elixir (1.9.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
# ここから先は後述のncコマンド実行時に表示されます。
00:30:26.667 [debug] * Elixir.Udpapp.Udp: handle_info > #Port<0.6>, {192, 168, 0, 6}, 35867, "<?xml version=\"1.0\"?><UECS ver=\"1.00-E10\"><DATA type=\"InAirCO2\" room=\"1\" region=\"101\" order=\"0\" priority=\"29\">447</DATA><IP>192.168.5.191</IP></UECS>\n\n"
00:30:26.754 [debug] ip: ['192.168.5.191']
00:30:26.755 [debug] data: ['447']
00:30:26.755 [debug] type: ['InAirCO2']
00:30:26.755 [debug] room: ['1']
00:30:26.755 [debug] region: ['101']
00:30:26.755 [debug] order: ['0']
00:30:26.755 [debug] priority: ['29']
クライアント側実行
クライアント側から、先ほど作ったxmlファイルを送信します。
$ nc -ub 192.168.0.255 10000 < uecs_rawdata.xml
[Ctrl-c]
ここまで、UDPデータの受信と、XMLのパーサまで試してみました。
次回はデータベースへのINSERTとSELECTを試してみます。
Appendix.UECSのデータ形式
UECSの通信規約として定義されています。
UECSでのノード間の通信には、XMLで記述された通信パケットを使用。
共用通信子(CCM: Common Correspondence Message)には、計測値、状態変数、動作指示情報、設定値、目標値などを記述。
属性 | 意味 | 型 | 範囲 |
---|---|---|---|
ip | 発信ノードのIPアドレス | string | - |
data | データ | float | |
type | CCM識別子 | string | 3文字以上19文字以下の英数字。内容はUECS通信基本規約の中で定義済。 |
room | 部屋番号 | int | 0~127、0は全部屋に対するブロードキャスト |
region | 系統番号 | int | 0~127、0は同一部屋・全系統番号に対するブロードキャスト |
order | 通し番号 | int | 0~30000、0は同一系統・全通し番号に対するブロードキャスト |
priority | 優先順位 | int | 0~30、優先順位の管理を行わない場合は30に固定 |