ブログ

Armadillo-640:Node-REDを使ってModbus通信でデータを収集しグラフ化する

at_takuma.fukuda
2022年6月27日 16時25分

概要

Armadillo-640を使ってセンサからのデータ収集・グラフ表示を行います。
市販の温湿度センサから有線でデータを読み出し、読みだしたデータからリアルタイムにグラフを生成・表示しています。
Armadillo-640にLCD等を接続してグラフを直接表示することも可能ですが、
今回は、Armadillo-640をWebサーバとして機能させて、そのサーバにアクセスした機器のWebブラウザ上にグラフを表示させています。

今回のデモは、Node-REDを使って極力プログラムを書かずに作成しています。
最低限のコマンド入力は必要になりますが、あまりソフトウェア開発に知見の無いユーザ様でも実践できるものとなっています。
また、機器構成についても市販品のみで構成しておりますので、簡単に構成を再現してお試しいただけるものとなっています。

機器構成

必要な機器は以下の通りです。

  • Armadillo-640開発セット一式
    Armadillo-640 ベーシックモデル開発セット
  • Modbus通信でデータ読出し可能な機器
    今回は市販の温湿度センサを使用しています。
    KKmoonRS485温度湿度 トランスミッタ
    また、このセンサへの電源供給のためDC12V出力の電源装置を接続しています。
    AK20W-DL-B (DC5V/1.6A、DC12V/1.0A)
  • Armadillo-640のインタフェースに接続可能なRS-485通信変換機器
    Armadillo-640には直接RS-485に接続可能なインタフェースが備わっておりません。
    安定して量産・運用を行う際には、UARTとRS485の変換ICや端子台などを実装した基板を作成し、
    拡張インタフェースに接続するのが望ましいですが、
    今回は検証であるため市販のUSB-RS485変換アダプタを使用しています。 DTECH USB to RS422 RS485 シリアル ポート コンバーター アダプター ケーブル
  • SDカード
    インストールディスクとして使用します。内部のデータが消去されても問題無いSDカードをご用意ください。
  • Ethernetケーブル
  • ルーターもしくはEthernetハブ
    *インターネットに繋がっていて、EthernetケーブルでArmadilloと接続可能なものである必要があります。

それぞれの具体的な接続についてはこの図を参考にしてください。

機器が揃ったら、実際に機器を接続・操作して設定を行っていきます。

Armadillo準備

まず、Armadillo-640とPCとを接続して、コンソールから操作可能な状態にしてください。
初めてArmadillo-640の操作を行われる場合は、このArmadillo-640開発基礎セミナーの動画を参考にセッティングを行ってください。

また、Armadillo-640は当社から出荷する際にOSが書きこまれているため、すぐに電源を投入して使用する事が出来ますが、
設定等を行う前に、インストールディスクを使って、最新のOSを書き込んでおくことをお勧めいたします。

最新のソフトをインストールするためのインストールディスクイメージファイルは当社のWebページからダウンロード出来ます。
Armadillo-640 インストールディスクイメージ

インストールディスクイメージを使ってSDカードをインストールディスクにする方法は、マニュアルをご参考下さい。
11.1.1. インストールディスクの作成
マニュアルではArmadilloの開発環境であるATDEを使ってインストールディスクを作成する方法をご紹介していますが、
ATDEを用意することが難しい場合は、Windows上でインストールディスクを作成する方法もブログでご紹介していますので、ご参考下さい。

Armadillo-X1/IoT(G3/G3L)/640/610:ATDEを使わずに標準のインストールディスクイメージでインストールする手順

Armadillo-640が操作可能な状態になったら、Armadillo-640に電源を投入します。
この先の手順で、Armadillo-640をインターネットに接続する必要がありますので、
インターネットに接続しているルータやEthernetハブと、Armadillo-640をEthernetケーブルで接続しておいてください。

ネットワーク設定・確認

Armadillo-640が起動し、ログインしたら、Armadillo-640がインターネットに接続出来ているかを確認します。
DHCPで自動的にIPアドレスが割り振られる場合はそのままネットワークに接続出来るはずですが、固定IPアドレスの設定等が必要な場合は、マニュアルを参考に設定してください。
6.2.2. ネットワークの設定方法
pingコマンドを実行して、ネットワークの導通を確認してください。
pingコマンドの実行と確認についてはマニュアルの「6.2.3.7. 有線LANの接続を確認する」に記載がありますので、 こちらをご参考下さい。
インターネットとの接続を確認する場合は、対象のIPアドレスを8.8.8.8とするのが良いかと思います。
図6.8 有線LANのPING確認

ネットワークの導通が取れていない場合は、経路やネットワーク設定を再度確認してください。

この後使用する事になるので、このタイミングでArmadillo-640のIPアドレスを確認しておいてください。
ip addressコマンドを実行すると、このようにIPアドレスが表示されます。

[armadillo ~]# ip address
ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group defaul
t qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP gr
oup default qlen 1000
    link/ether 00:11:0c:00:07:a4 brd ff:ff:ff:ff:ff:ff
    inet 172.16.2.107/16 brd 172.16.255.255 scope global eth0
       valid_lft forever preferred_lft forever

ファイルダウンロード・実行

インターネットに接続出来ていることが確認出来たら、必要なソフトウェアのインストールや設定を行います。
一括で実行できるようにスクリプトファイルを用意しましたので、こちらをダウンロードしてご使用ください。
set_node_red_serial.sh
flows.zip

wgetコマンドで必要なファイルをダウンロードしてください

スクリプトファイルダウンロード
[armadillo ~]# wget https://armadillo.atmark-techno.com/system/files/blog/set_node_red_serial.sh
フロー設定ファイルダウンロード
[armadillo ~]# wget https://armadillo.atmark-techno.com/system/files/blog/flows.zip

ダウンロードしたスクリプトファイルを実行して設定を開始してください。

権限の設定
[armadillo ~]# chmod +x set_node_red_serial.sh
ファイル実行
[armadillo ~]# ./set_node_red_serial.sh

スクリプトの実行が完了するまで、ネットワーク環境にもよりますが10分程度かかります。
コマンド入力が可能になるまでお待ちください。

このような表示になったらスクリプトの実行が完了しています。

Please Wait 180seconds
It's done
[armadillo ~]# 

動作確認

スクリプトの実行が完了したら、Armadillo-640と同じネットワークに接続しているPCから、次のURLへアクセスしてください。

http://[Armadillo-640のIPアドレス]:1880

正常に設定が完了していれば、以下のようなnode-redのフロー画面が表示されます。

表示されない場合はIPアドレスが間違っていないかを確認してください。
IPアドレスが正しい場合、PCからArmadilloのIPアドレスに対してpingコマンドを実行し、
PCとArmadilloの間のネットワークが繋がっているかを確認してください。
Windows PCの場合はコマンドプロンプトなどからpingコマンドが実行出来ます。
詳細な手順などは環境によって異なるかと思いますので、ユーザー様自身にて検索願います。

ネットワークが繋がっていない場合は、PC・Armadilloそれぞれの接続先ネットワークを確認してください。

ネットワークが繋がっていれば、数分待ってもう一度アクセスしてみて下さい。
それでも表示されない場合は、設定に失敗している可能性がありますので、
再度設定を見直してみて下さい。
(設定用のコマンドを1行ずつ実行してレスポンスを確認してみてください。)

また次のURLにアクセスすると、グラフ等が表示されます。

http://[Armadillo-640のIPアドレス]:1880/ui

グラフは自動的に更新されます。まったく表示が変わらない場合はシリアル通信が正常に行えていない可能性がありますので、
配線や通信対象の機器が正常に動作しているか等を確認してください。

Node-RED設定解説

nod-redは、複数のノードを配置して、それらを繋げることでフローを形成し、機能が実装されます。
このフローでは、起点としてModbus通信でデータの読出しを行うModbus Readノードが設置されています。
このノードが取得したデータをfunctionノードで処理して、グラフ表示のためのノードやcsvファイル保存のためのノードに渡しています。

それぞれのノードの設定についてより詳細に説明します。

Modbus Readノード

  • シリアル通信に使用するポート
  • シリアル通信速度
  • 通信対象機器のアドレス
  • 送信するコマンド
  • コマンドの対象となるアドレス
  • コマンドの対象サイズ
  • 通信間隔

等を設定しています。 これは機器ごとに異なりますが、今回はデモに使用した温湿度センサに合わせて設定しています。
この設定通りにシリアル通信を行い、レスポンスがあれば取得したデータを配列として次のノードに渡します。

functionノード

このノードでは、Modbus Readノードから渡された配列から必要なデータを取り出して処理しています。

グラフ表示のためのノードに繋がっているノードでは、桁数の調整を行って次のノードに渡しています。 CSVファイル保存のためのノードに繋がっているノードでは、
現在日時を取得し、それぞれ日時・気温・湿度の列名を付加してカンマ区切りで並べて次のノードに渡しています。

グラフ表示のためのノード(chart,gauge,text)

それぞれ項目名や単位・表示範囲などの設定を行っています。
今回はデフォルトのままですが、各項目の配置の調整もnode-red上で編集する事が可能です。

csvノード

受け取ったデータを、CSVに出力する形式に変換してfileノードに出力しています。

fileノード

受け取ったデータを指定のファイルに出力します。

これらのノードは今回のデモに合わせて設定していますが、
それぞれ、利用する環境毎に設定し直して使用してください。
設定の変更なども簡単に行えるようになっていますので、機器の構成や、データの取り扱いなど、
それぞれの環境に合わせて自由にカスタマイズしてご使用ください。

おまけ:各設定ファイルについて

設定用シェルスクリプトと、フロー設定用jsonファイルの内容をそれぞれテキストで記載しておきます。
シェルスクリプトについては詳細解説を付記しています。

set_node_red_serial.sh

#/bin/bash
#パッケージ一覧の更新と、インストール済みパッケージの更新を行い、この後使用するcurlとunzipのインストールを行います。
apt-get update --allow-releaseinfo-change
apt-get -y upgrade
apt-get install -y curl unzip
update-ca-certificates --fresh
#Node.jsのインストールを行うための依存パッケージをインストールします。
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get install -y gcc g++ make
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | tee /etc/apt/sources.list.d/yarn.list
#Node.jsをインストールします。
apt-get install -y nodejs
apt-get update && apt-get install -y yarn
#Node-REDと、Modbus通信モジュール・ダッシュボードモジュールをインストールします。
npm install -g --unsafe-perm node-red
npm install -g --unsafe-perm node-red-contrib-modbus
npm install -g --unsafe-perm node-red-dashboard
#pm2モジュールをインストールします。
npm install -g --unsafe-perm pm2
#zipファイルを解凍し、フロー設定用のjsonファイルを取り出します。
unzip flows.zip
#Node-REDの設定格納先ディレクトリを作成します。
mkdir /root/.node-red/
#解凍したjsonファイルを上記ディレクトリにコピーします。
cp flows.json /root/.node-red/
#node-redの作業ディレクトリに移動します。
cd /root
#pm2でNode-REDを自動起動するよう登録します。
pm2 start /usr/bin/node-red -- -v
pm2 save
pm2 startup systemd
systemctl start pm2-root
#Node-RED起動までじかんがかかるので、180秒sleepさせます。
echo "Please Wait 180seconds"
sleep 180
echo "It's done"

flows.json

[
    {
        "id": "72b47fbc1d8980c0",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "7ee537264a0ef06f",
        "type": "modbus-read",
        "z": "72b47fbc1d8980c0",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "unitid": "1",
        "dataType": "HoldingRegister",
        "adr": "0",
        "quantity": "2",
        "rate": "1",
        "rateUnit": "s",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "e05bc2f36b061110",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 110,
        "y": 140,
        "wires": [
            [
                "1df04a58f0837b9e",
                "84eb818cb4e468b8",
                "0615356754ea0df0"
            ],
            [
                "d9797dae778b0ff6"
            ]
        ]
    },
    {
        "id": "597ca64e6a772db8",
        "type": "ui_chart",
        "z": "72b47fbc1d8980c0",
        "name": "",
        "group": "83458d4503ecd418",
        "order": 1,
        "width": 0,
        "height": 0,
        "label": "気温",
        "chartType": "line",
        "legend": "false",
        "xformat": "HH:mm:ss",
        "interpolate": "linear",
        "nodata": "",
        "dot": false,
        "ymin": "",
        "ymax": "",
        "removeOlder": 1,
        "removeOlderPoints": "",
        "removeOlderUnit": "3600",
        "cutout": 0,
        "useOneColor": false,
        "useUTC": false,
        "colors": [
            "#1f77b4",
            "#aec7e8",
            "#ff7f0e",
            "#2ca02c",
            "#98df8a",
            "#d62728",
            "#ff9896",
            "#9467bd",
            "#c5b0d5"
        ],
        "outputs": 1,
        "useDifferentColor": false,
        "className": "",
        "x": 590,
        "y": 140,
        "wires": [
            []
        ]
    },
    {
        "id": "923b1b9ce26fa715",
        "type": "ui_text",
        "z": "72b47fbc1d8980c0",
        "group": "83458d4503ecd418",
        "order": 2,
        "width": 0,
        "height": 0,
        "name": "",
        "label": "気温",
        "format": "{{msg.payload}}℃",
        "layout": "row-left",
        "className": "",
        "x": 590,
        "y": 180,
        "wires": []
    },
    {
        "id": "d9797dae778b0ff6",
        "type": "modbus-response",
        "z": "72b47fbc1d8980c0",
        "name": "",
        "registerShowMax": 20,
        "x": 170,
        "y": 300,
        "wires": []
    },
    {
        "id": "1a1aab96a3f46597",
        "type": "file",
        "z": "72b47fbc1d8980c0",
        "name": "",
        "filename": "/root/data.csv",
        "appendNewline": false,
        "createDir": false,
        "overwriteFile": "false",
        "encoding": "none",
        "x": 680,
        "y": 400,
        "wires": [
            []
        ]
    },
    {
        "id": "0615356754ea0df0",
        "type": "function",
        "z": "72b47fbc1d8980c0",
        "name": "",
        "func": "var now = new Date();\nvar nowStr =\n    \"\" + now.getFullYear() +\n    \"/\" + ('0' + (now.getMonth() + 1)).slice(-2) +\n    \"/\" + ('0' + now.getDate()).slice(-2) +\n    \" \" + ('0' + now.getHours()).slice(-2) +\n    \":\" + ('0' + now.getMinutes()).slice(-2) +\n    \":\" + ('0' + now.getSeconds()).slice(-2);\n \nmsg.payload = {\n    // 日時\n    \"日時\": nowStr,\n    // 気温\n    \"気温\":msg.payload[0]/10,\n    // 湿度\n    \"湿度\":msg.payload[1]/10\n}\n \nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 620,
        "y": 300,
        "wires": [
            [
                "8458a14057a7f141"
            ]
        ]
    },
    {
        "id": "8458a14057a7f141",
        "type": "csv",
        "z": "72b47fbc1d8980c0",
        "name": "",
        "sep": ",",
        "hdrin": "",
        "hdrout": "once",
        "multi": "mult",
        "ret": "\\n",
        "temp": "日時,気温,湿度",
        "skip": "0",
        "strings": false,
        "include_empty_strings": "",
        "include_null_values": "",
        "x": 490,
        "y": 400,
        "wires": [
            [
                "1a1aab96a3f46597"
            ]
        ]
    },
    {
        "id": "1df04a58f0837b9e",
        "type": "function",
        "z": "72b47fbc1d8980c0",
        "name": "Temperature",
        "func": "msg.payload = msg.payload[0]/10\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 120,
        "wires": [
            [
                "597ca64e6a772db8",
                "923b1b9ce26fa715"
            ]
        ]
    },
    {
        "id": "84eb818cb4e468b8",
        "type": "function",
        "z": "72b47fbc1d8980c0",
        "name": "Humid",
        "func": "msg.payload = msg.payload[1]/10\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 350,
        "y": 200,
        "wires": [
            [
                "8296ccfd7360da65"
            ]
        ]
    },
    {
        "id": "8296ccfd7360da65",
        "type": "ui_gauge",
        "z": "72b47fbc1d8980c0",
        "name": "",
        "group": "83458d4503ecd418",
        "order": 2,
        "width": 0,
        "height": 0,
        "gtype": "gage",
        "title": "湿度",
        "label": "パーセント",
        "format": "{{value}}",
        "min": 0,
        "max": "100",
        "colors": [
            "#00b500",
            "#e6e600",
            "#ca3838"
        ],
        "seg1": "",
        "seg2": "",
        "className": "",
        "x": 570,
        "y": 220,
        "wires": []
    },
    {
        "id": "e05bc2f36b061110",
        "type": "modbus-client",
        "name": "",
        "clienttype": "simpleser",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "tcpHost": "127.0.0.1",
        "tcpPort": "502",
        "tcpType": "DEFAULT",
        "serialPort": "/dev/ttyUSB0",
        "serialType": "RTU",
        "serialBaudrate": "9600",
        "serialDatabits": "8",
        "serialStopbits": "1",
        "serialParity": "none",
        "serialConnectionDelay": "100",
        "serialAsciiResponseStartDelimiter": "0x3A",
        "unit_id": "1",
        "commandDelay": "1",
        "clientTimeout": "1000",
        "reconnectOnTimeout": true,
        "reconnectTimeout": "2000",
        "parallelUnitIdsAllowed": true
    },
    {
        "id": "83458d4503ecd418",
        "type": "ui_group",
        "name": "計測データ",
        "tab": "7c45c70b23a7c3b7",
        "order": 1,
        "disp": true,
        "width": "6",
        "collapse": false,
        "className": ""
    },
    {
        "id": "7c45c70b23a7c3b7",
        "type": "ui_tab",
        "name": "計測データ",
        "icon": "dashboard",
        "disabled": false,
        "hidden": false
    }
]