LUA言語拡張ハンドラ

LUA言語は、C言語ベースのバイナリー実行モジュールに、高級言語スクリプト実行環境をアドオンできるシステムです。
OpenBlocksのPD Handlerではシステムソフトウェアの主だった処理系がLUA言語から利用できる関数として用意されており、LUA言語スクリプトを用いて機能を拡張することができます。 例えばEnOceanデバイスの様にプロファイルがあれば、そのプロファイルによってバイナリーデータ列が規則化されているので、EnOceanコミュニティからプロファイルの仕様を手に入れれば、容易にデータを抽出しjsonテキスト化ができます。
本章ではBLE(ビーコン型センサー)とEnOcean(EEP仕様)およびRS-232CやRS-485などのシリアル通信の機能拡張ハンドラの作成について解説しています。
また、Luaを利用する前には、BLE/EnOcean/RS-SERIALなどの各ハンドラを前もってモジュール起動制御でアクティブにしておく必要があります。

Luaソースコードの配置

それぞれBLE/EnOcean/RS-SERIAL各ハンドラ毎にソースコードを下図例の通りディレクトリ分けして配置してあります。

また、それぞれのLuaファイルはdevicesディレクトリとdevices_customディレクトリの二つの領域に区別し保存しています。

devicesディレクトリ

ぷらっとホームから提供される標準対応センサー用のLuaファイルの保存領域です。

devices_customディレクトリ

ユーザーが自分でカスタマイズしたLuaファイルを保存する領域です。

funcディレクトリ

ぷらっとホームから提供される標準ユーティリティ関数のLuaファイルを保存する領域です。

func_customディレクトリ

ユーザーが自分でカスタマイズしたユーティリティ関数のLuaファイルを保存する領域です。

標準サポートセンサーのLuaファイルを手直して使う

devicesディレクトリから標準の任意センサーのLuaソースコードをダウンロードして、改造した後にファイル名をそのまま変えずにdevices_customディレクトリへアップロードすれば、 標準のdevicesディレクトリあるLuaスクリプトよりも優先して実行(実際には上書)されます。
クラウドに送信されるjsonメッセージは、jo.で始まるテーブルに例えばメンバを jo.temperature = 30 とすることで {"temperature":30} がjsonに追加されます。
またはもともとjo.temperature = 30たっだのをjo.temp = 30とすれば出力は {"temp":30} と変更されます。
(ここで説明されているjson例は該当部分のみです。)

カスタムしたLuaを有効にする

Luaスクリプトを編集・作成後、Luaファイルを保存しただけではシステムに反映されません。
このためプロセスの再起動が必要になります。
プロセスの再起動はダッシュボードで行います。

BLE Lua

BLE 接続で知っておくこと

通常、BLEのアドバタイジングでセンサデータを受けるビーコン型センサーが、IoTシステムとして何年もの間に安定してデータを受信し続けられるために一般的に使われています。
これはBLEデバイスがアドバタイズ(定期的に発するビーコン)によって一方的に送出してくる電波を、単に受信するだけの一方通行通信であるため、プロトコルを必要とせず安定しているためです。

これに対してBLEコネクションモード(GATT通信とも呼ばれる)は、電波の状態に影響を受けやすいため非常に安定性が悪く、また、センサーメーカー毎のプロトコルの作りに互換性が無く、コネクション切断毎の処理系がマチマチでその復旧方法も曖昧でした。
どちらかと言うとスマートフォンでデータを見たい時、一時的なコネクションで一気にデータを取り込むような使い方あれば多機能なコネクション型が向いていますが、IoTの様に24/365での接続を求めるシステム向きではありません。
長時間接続するといつの間にかにコネクションが切れたり、BLEデバイスとホスト装置のステートが不一致になり、最悪、現場に設置してあるBLEデバイスの電源を入れなおさないと復帰できないようなケースもあります。
こう言った現象はホスト側であれば再起動などでの復帰も可能ですが、BLEデバイスによっては電源を切るしか方法が無いものが多くあります。

しかしIoTでも下流制御が必要な場合もあるため、この場合は、センサーの通常ステータスの取得は安定したアドバタイジングデータで受信をします。
そして制御に必要になった場合にのみ、コネクションモードでBLEデバイスを制御し、制御が終わった時点で即ディスコネクトすることで、デッドロックリスクの高いコネクションモードでの接続を短時間で終わらせる事ができます。

BLEのLuaハンドラの関数構成

BLEのLuaハンドラは、1つのセンサーに1つのスクリプトが用意されています。 このスクリプトにはアドバタイジング用のビーコンハンドラと、コネクションモード用のGATT通信ハンドラが混載されており、各々に対応した関数で動作を分けています。
LuaスクリプトはBLEのデバイス登録の際、そのデバイス用のLuaハンドラーを各々選んで登録して下さい。

.parse_adv()

アドバタイジング用関数は、センサーからのアドバタイジングを受けた時に呼ばれる関数です。

function <makerneme>_<model>.parse_adv(jo, manufacturerData, localname)
アドバタイズからセンサデータをセンサデータをパースする関数です。通常はこちらを使用する。
<makerneme> 特にUSERにわかれば何でも良いのですが、基本的な法則としてメーカ名を指定しています。
<model> 基本的な法則としてモデル名としていますが、略称など分かり易い名前にしています。
.parse_beacon()

ビーコン用関数はWEB-UIでビーコン指定(dataのプレフィックスで指定されるデバイス)されたアドバタイジングを受信した時に起動する関数です。

function <makerneme>_<model>.parse_beacon(jo, manufacturerData, localname)
ビーコンとして登録されたデバイスのセンサデータをパースする関数です。通常は使わない。
<makerneme> 特にUSERにわかれば何でも良いのですが、基本的な法則としてメーカ名を指定しています。
<model> 基本的な法則としてモデル名としていますが、略称など分かり易い名前にしています。
.gatt_exec()

コネクションモード(GATT)用の関数は、WEB-UIで設定したシステムインターバルタイマーの間隔で起動されます。

function <makerneme>_<model>.gatt_exec(sensor, deviceId, infos)
GATTでBLEデバイスをアクセスするルーチンでWEB-UIで設定されたインターバルで呼び出される関数です。
<makerneme> 特にUSERにわかれば何でも良いのですが、基本的な法則としてメーカ名を指定しています。
<model> 基本的な法則としてモデル名としていますが、略称など分かり易い名前にしています。
引数
sensor クラウドへの送信関数 LUA_send_sensor_data を呼ぶ時の第一引数としてそのまま使う。
deviceId BLEデバイスのID
infos WEB-UIの固定情報付与(JSON)で設定された情報
.downstream_exec()

クラウド(上流)からの制御命令を受けた時に起動する関数は以下の通りです。

function <makerneme>_<model>.downstream_exec(sensor, deviceId, msg, infos)
上流(クラウド)から制御命令を受信した時に呼び出される関数です。(受信設定有効時のみ)
<makerneme> 特にUSERにわかれば何でも良いのですが、基本的な法則としてメーカ名を指定しています。
<model> 基本的な法則としてモデル名としていますが、略称など分かり易い名前にしています。
引数
sensor クラウドへの送信関数 LUA_send_sensor_data を呼ぶ時の第一引数としてそのまま使う。
deviceId BLEデバイスのID
msg 上流から受信した制御メッセージ(json文字列)
infos WEB-UIの固定情報付与(JSON)で設定された情報

その他、ユーザーが別の名前の関数を定義してサブルーチンを作ることは自由です。

BLEアドバタイジング対応のLua

BLEデバイスが一方的にアドバタイジングによって送信してくるセンサーデータを受けるためのハンドラ作成について説明します。
この例はビーコン用のハンドラの参考にもなります。

BLEハンドラはC言語部分とLUA言語部分で構成され、共通の部分をC言語で、カスタム部分をLua言語で記述する構成となっています。
標準サポートのセンサーのLuaハンドラーは以下のディレクトリに保存されています。

BLEから受信したアドバタイズのデータ部分であるペイロードには、デバイスメーカーが自由に使っていい領域があり、BLEハンドラではこのペイロード部分をLua言語に渡しています。

OpenBlocksが起動後、PD HandlerがBLEビーコンを受信するとBLE登録で登録されたデバイスのみ選別されWEB-UIで設定したLua関数に送り込まれます。
ここではナカヨ製ボタン付きBLEビーコンのハンドラソースコードをサンプルに説明します。
※このビーコンはiBeaconに互換です。
※仕様変更もありえる詳細はナカヨ様の最新仕様書で確認して下さい。

NAKAYO_NYCBCON.lua
NAKAYO_NYCBCON = {} -- 配列の初期化
-- iBeacon規格互換でナカヨビーコンのIDとなるマッチング変数の宣言
nakayo_uuid = string.char(0xa9, 0x03, 0x01, 0x00, 0x14, 0x78, 0x48, 0x24, 0xb2, 0x98, 0x8e, 0x68, 0x23, 0xcf, 0xde, 0xfa)
nakayo_minor_normal = string.char(0xff, 0xe0)
nakayo_minor_1 = string.char(0xff, 0xe8)
nakayo_minor_2 = string.char(0xff, 0xf0)
nakayo_minor_3 = string.char(0xff, 0xf8)
-- ハンドラのメインから順次呼び出される関数
-- 引数 jo = jsonテキストに変換される連想配列、manufacturerData = バイナリデータ列
-- localname = BLE規格で設定される名前(付けられるかはメーカーによってまちまち)
function NAKAYO_NYCBCON.parse_adv(jo, manufacturerData, localname)
if is_ibeacon(manufacturerData) then -- 先ずiBeaconであるかチェック
local uuid = string.sub(manufacturerData, 5, 20) -- バイナリー列からUUID部取り込み
local major = string.sub(manufacturerData, 21, 22)-- バイナリー列からmajor部取り込み
local minor = string.sub(manufacturerData, 23, 24)-- バイナリー列からminor部取り込み
if string.match(uuid, nakayo_uuid) ~= nil then -- UUIDがマッチングするかチェック
pdHandlerUtil.log_debug("NAKAYO NYC-BCON") -- デバッグログへUUIDなどを出力
pdHandlerUtil.log_debug("uuid: "..pdHandlerUtil.bin2str(uuid, #uuid))
pdHandlerUtil.log_debug("major: "..pdHandlerUtil.bin2str(major, #major))
pdHandlerUtil.log_debug("minor: "..pdHandlerUtil.bin2str(minor, #minor))
jo.uuid = pdHandlerUtil.bin2str(uuid, #uuid) -- jsonへ変換されるUUIDの保存
jo.major = pdHandlerUtil.bin2str(major, #major) -- jsonへ変換されるmajorの保存
jo.minor = pdHandlerUtil.bin2str(minor, #minor) -- jsonへ変換されるminorの保存
if string.match(minor, nakayo_minor_normal) ~= nil then
jo.push = 0 -- ボタンが押されていないステータスをjson変換される配列に保存
elseif string.match(minor, nakayo_minor_1) ~= nil then
jo.push = 1 -- ボタンが押されたステータスをjson変換される配列に保存
elseif string.match(minor, nakayo_minor_2) ~= nil then
jo.push = 2 -- ボタンが押されたステータスをjson変換される配列に保存
elseif string.match(minor, nakayo_minor_3) ~= nil then
jo.push = 3 -- ボタンが押されたステータスをjson変換される配列に保存
else
jo.push = -1 -- ボタンのステータスがおかしい時
end
end
end
end
return NAKAYO_NYCBCON

ナカヨ製ボタン付きBLEビーコンは、ボタンを長押しするとjo.pushの値が "1 → 2 → 3 → 1 → 2" と変化します。
BLEビーコンセンサにはiBeacon規格以外のもあるので、デバイスマッチングについては他のソースコードも参考にして下さい。
以上のコードでIoTクラウドへ送信されるjsonテキストは以下の通りになります。

{
"time": "2017-12-08T12:34:56.789+09:00", ← ここから3つがOpenBlocksで設定されるデータ
"deviceId": "xxxxxxxxxxxx",
"rssi": -68,
"uuid": "a903010014784824b2988e6823cfdefa", ← ここから下がハンドラで追加されたデータ
"major": "00c8",
"minor": "ffe0",
"push": 0
}

その他のセンサーのjsonテキストはこちらを参照して下さい。
それぞれのセンサ毎にLuaで書かれたソースコードがdevicesディレクトリにあります。

編集が終わったら、<メーカー名>_<デバイス名>.luaと名前を付けてdevices_customディレクトリにアップロードして下さい。

GATT通信(双方向通信)

BLEはGATT通信を使う事で双方向通信となり下流デバイスの制御が可能となります。

GATTを使う上で理解しておくこと

BLEを使ったIoT機器にはビーコンの様に、アドバタイジングと言うデータを垂れ流すだけの一方通行の通信方式が、動作安定性が高く一般的に使われている通信方法です。
このアドバタイジングにセンサーデータを一緒に乗せてしまう事で複数の受信機でセンサーデータを得る事ができます。
このアドバタイジングに対し通信プロトコルによって双方向のやり取りを行い、単一の受信機でセンサーなどのデータをやり取りする方式がGATT通信です。
GATTは1対1接続でかつ暗号化などの機能を持つためセキュリティの高い通信ではありますが、BLEの使う通信帯域は非常に混雑しているためにエラーリトライが多発する不安定な要素も多々あります。
このため秘匿する様なデータでは無く、また機器制御(下流制御)を必要の無い通信では、アドバタイジングモードでの通信を奨めてます。

GATT通信の概要

GATT通信はとあるレジスタに対して読み書きを行う事で通信を行います。
このとあるレジスタはUUIDという名前で管理されており、このレジスタは複数バイトの16進数を用いられて表現されています。
このUUIDで表現されるレジスタは、デバイスによって複数バイトで構成されており、大体20Byte程度に収まる様な構成になっており、このUUIDの何バイト目で何ビット目の位置などで各々に意味を持たせています。

特定のBLEデバイスにアクセスするには、ホストとなる装置がscanと言う電波を送信すると、各々の応答可能なBLEデバイスが、アドバタイジング電波を送信します。
ホストはこのアドバタイジング情報から任意のBLEデバイスを見つけてコネクション動作に入ります。
コネクションに成功すると、接続されたBLEデバイスが持つUUIDのリストや接続ハンドル、通信パラメータをホストが得ます。
GATT通信ではペアリングによって、特定のホストとの接続限定や暗号化する場合もあれば、ペアリングなしで接続できるデバイスもあります。 これらの情報交換も行われてコネクションが完了します。
この状態から指定したUUIDに対して読み書きする事で通信が始まります。
それぞれのUUIDには書き込み専用・読み込み専用・読み書き両用があるので各々のデバイスの仕様書で確認して下さい。
そして通信が終わった時点で、ディスコネクトする事で一連の処理が完了します。

OpenBlocksでのGATTアクセス関数

OpenBlocksではGATTアクセスライブラリを用意しており、以下の手順通りライブラリで操作を行うことにより容易にGATT通信を可能にします。
またライブラリの使用においてUUIDをhandleに置き換えてアクセスする方法を取っているので、これを混同しないように注意して下さい。
このライブラリではUUIDとhandleが1対1で紐づけられています。

以下にGATTアクセスに用意されたLua関数を示します。

LUA_gatt_connect()

BlutoothデバイスアドレスdeviceIdにGATT接続します。

LUA_gatt_connect(deviceId, type)
引数
deviceId(文字列)
接続するリモートのBluetoothデバイスアドレス
type(文字列)
アドレスタイプ(“public”または”random”)
戻り値
cli(ユーザーデータ)
デバイスのアクセス用デスクリプタ
接続に失敗した場合はnilが返る
注意
失敗の確率が高いためリトライする事を推奨。
LUA_ble_read()

Bluetoothデバイスの指定したUUIDハンドルからデータを読み込みます。

LUA_ble_read(cli, handle)
引数
cli(ユーザーデータ)
LUA_ble_connect()で取得したデバイスアクセス用デスクリプタ
handle(数値)
characteristicsのUUIDをアクセスするハンドル
戻り値
data(バイナリー)
取得した値のバイト列
失敗した場合はnilが返る
LUA_ble_write()

Bluetoothデバイスの指定したUUIDハンドルにデータを書き込みます。

LUA_ble_write(cli, handle, value)
引数
cli(ユーザーデータ)
LUA_ble_connect()で取得したデバイスアクセス用デスクリプタ
handle(数値)
値を書くcharacteristics値のハンドル
value(バイナリー)
書き込むバイナリーデータ列
戻り値
num(数値)
書いた値のバイト数
失敗した場合はnilが返る
LUA_gatt_disconnect()

BluetoothデバイスとのGATT接続を切断します。

LUA_gatt_disconnect(cli)
引数
cli(ユーザーデータ)
LUA_ble_connect()で取得したデバイスアクセス用デスクリプタ
戻り値
無し

GATT通信の応用のUARTサービスとインターバルによる自発的なデータ送信関数

BLEデバイスによっては、UUIDを受信専用のものと送信専用のものと分けて実装し、データ転送を20Byte程度の単位に抑えて少しずつ転送を行い一般のシリアル通信の様な動作機能を持つデバイスがあります。
ホストはこういったデバイスを相手にする時、特定のUUIDを受信ポート・送信ポートと見立ててシリアル通信を行う事が必要があります。

もう一つは、BLEデバイスが自分のインターバルタイマによって定期的にデータを送信してくる機能への対応です。
例えば1分毎に消費電力を通知させるような場合に使います。

OpenBlocksのGATTライブラリでは、こういった動作もサポートできるように作られています。
なお、これらの関数は、前記、LUA_gatt_connect、LUA_gatt_disconnect、と一緒に利用します。 以下がBLEデバイスの自発的な通知を制御する関数です。

LUA_ble_register_notify()

BLEデバイスの自発的な通知を有効にします。

LUA_ble_register_notify(cli, handle)
引数
cli(ユーザーデータ)
LUA_ble_connect()の戻り値で取得したデバイスアクセス用ディスクリプタ
handle(数値)
通知を有効にするcharacteristics値のハンドル
戻り値
id(数値)
有効にした通知の登録ID
失敗した場合はnilが返る
LUA_ble_notify_show()

BLEデバイスからの通知されたデータを取得します。

LUA_ble_notify_show(cli)
引数
cli(ユーザーデータ)
LUA_ble_connect()の戻り値で取得したデバイスアクセス用ディスクリプタ
戻り値
data(文字列)
取得した値のバイト列
失敗した場合はnilが返る
LUA_ble_unregister_notify()

BLEデバイスの自発的な通知を無効にします。

LUA_ble_unregister_notify(cli, id)
引数
cli(ユーザーデータ)
LUA_ble_connect()の戻り値で取得したデバイスアクセス用ディスクリプタ
id(数値)
LUA_ble_register_notify()の戻り値で取得した通知の登録ID
戻り値
id(数値)
無効にした通知の登録ID
失敗した場合はnilが返る

BLEハンドラ用のユーティリティ関数

その他ユーティリティ関数です。

LUA_send_sensor_data()

データをクラウド(上流)へ送信します。

LUA_send_sensor_data(sensor, data)
引数
sensor(ユーザーデータ)
xxxxx_xxxxxx.gatt_exec()関数またはxxxxx_xxxxxx.downstream_exec()関数で渡された引数をそのまま渡す。
data(文字列)
送信するjson文字列のデータ(必ず文字列としてエンコードする)
LUA_ble_getcrc8()

CRC8を取得する(UARTサービスなどで必要な場合に使う)

LUA_ble_getcrc8(payload)
引数
payload(文字列)
CRC8を取得する値
戻り値
crc8(数値)
取得したCRC8
LUA_ble_sleep()

プロセスをスリープする(秒単位)

LUA_ble_sleep(sec)
引数
sec(数値)
スリープする秒数
戻り値
無し
LUA_ble_usleep()

プロセスをスリープする(マイクロ秒単位)

LUA_ble_usleep(usec)
引数
usec(数値)
スリープするマイクロ秒数
戻り値
無し

GATT通信のLuaハンドラーサンプル

このサンプルではセンサーデータのパースを行っていないので、パースについては他のセンサーハンドラを参考にして下さい。 このサンプルはdevices_customディレクトリの skelton.lua です。

skelton.lua
skelton = {} -- ハンドラーのテーブル宣言
-- definition
local write_uuid = "01234567-89ab-cdef-0123-456789abcdef"
local read_uuid = "0123456f-89ab-cdef-0123-456789abcdef"
-- WEB-UIでビーコン登録されたアドバタイズデータのパーサー
-- (例えばビーコンの電池残量をアドバタイズデータからパースしたい時に使用)
function skelton.parse_beacon(jo, manufacturerData, localname)
pdHandlerUtil.log_debug("skelton.parse_beacon")
if #manufacturerData == 0 then
jo.send = false
else
-- このサンプルでデータのペイロードをそのまま16進文字列に変換しています。
-- 通常はこの中でセンサーデータなど"jo."の任意のメンバーにパースして代入します。
local str = pdHandlerUtil.bin2str(manufacturerData, #manufacturerData)
if str ~= nil then
jo.manufacturerData = str
end
end
end
-- WEB-UIでビーコンセンサー登録されたアドバタイズデータのパーサー
-- (通常はビーコンセンサーのデータをパースする場合こちらを使用)
function skelton.parse_adv(jo, manufacturerData, localname)
pdHandlerUtil.log_debug("skelton.parse_adv")
if #manufacturerData == 0 then
jo.send = false
else
-- このサンプルでデータのペイロードをそのまま16進文字列に変換しています。
-- 通常はこの中でセンサーデータなど"jo."の任意のメンバーにパースして代入します。
local str = pdHandlerUtil.bin2str(manufacturerData, #manufacturerData)
if str ~= nil then
jo.manufacturerData = str
end
end
end
-- GATTでアクセスするセンサーで使用する関数
-- (WEB-UIで設定されたインターバルタイマーで定期的に呼び出される関数)
function skelton.gatt_exec(sensor, deviceId, infos)
pdHandlerUtil.log_debug("skelton.gatt_exec")
local jo = {}
jo.deviceId = deviceId
jo.time = pdHandlerUtil.get_current_time()
add_infos(jo, infos)
-- connect and get handle table
local cli, handle_table = LUA_gatt_connect(deviceId, "random")
if cli == nil then
pdHandlerUtil.log_err("failed: LUA_gatt_connect")
return
end
-- read 単純に任意のUUIDを読み込む時
local read_value = LUA_ble_read(cli, handle_table[read_uuid])
if read_value == nil then
pdHandlerUtil.log_err("failed: LUA_ble_read")
LUA_gatt_disconnect(cli)
return
else
jo.read_value = read_value
end
-- BLEデバイスの自発送信を有効にする場合
local id = LUA_ble_register_notify(cli, handle_table[read_uuid])
if id == nil then
pdHandlerUtil.log_err("failed: LUA_ble_register_notify")
LUA_gatt_disconnect(cli)
return
end
LUA_ble_sleep(10) -- モード設定したら少し待つ
-- BLEデバイスの自発送信のデータを読み込む
local notify_data = LUA_ble_notify_show(cli)
if notify_data == nil then
-- 自発送信なので送信されていない時もある
pdHandlerUtil.log_err("failed: LUA_ble_notify_show")
else
-- 自発送信データがバッファにあった場合
jo.notify_data = notify_data
end
-- BLEデバイスの自発送信を有効にした場合は使用後に無効にする
LUA_ble_unregister_notify(cli, id)
-- 一連の処理が終わったらディスコネクトする
LUA_gatt_disconnect(cli)
-- 取り込んだデータをクラウド(上流)に送信する
LUA_send_sensor_data(sensor, json.encode(jo))
end
-- クラウド(上流)からの制御命令を受信した時起動する関数
function skelton.downstream_exec(sensor, deviceId, msg, infos)
pdHandlerUtil.log_debug("skelton.downstream_exec")
local jo = {}
jo.deviceId = deviceId
jo.time = pdHandlerUtil.get_current_time()
add_infos(jo, infos)
jo.result = "error"
-- connect
local cli, handle_table = LUA_gatt_connect(deviceId, "random")
if cli == nil then
pdHandlerUtil.log_err("failed: LUA_gatt_connect")
LUA_send_sensor_data(sensor, json.encode(jo))
return
end
-- 受信したクラウド(上流)からのメッセージを連想配列にデコードする
local upstream_data = json.decode(msg)
local write_data = upstream_data.data
-- 受信したデータ部分をBLEデバイスに送る
if nil == LUA_ble_write(cli, handle_table[write_uuid], write_data) then
pdHandlerUtil.log_err("failed: LUA_ble_write")
LUA_gatt_disconnect(cli)
LUA_send_sensor_data(sensor, json.encode(jo))
return
else
pdHandlerUtil.log_info("succeeded: LUA_ble_write")
jo.result = "success"
end
-- GATTをディスコネクト
LUA_gatt_disconnect(cli)
-- クラウド(上流)にレスポンスする
LUA_send_sensor_data(sensor, json.encode(jo))
end
return skelton

アップロード/ダウンロード操作

作成したLuaハンドラのアップロードや、参考にするLuaハンドラのダウンロード操作は以下の通りです。

BLE Lua メニューの操作項目

設定項目説明
更新Luaファイルのアップロード先のファイル一覧表示を更新します。
削除ファイルを選択後削除ボタンで削除します。
削除可能なファイルはカスタム用ディレクトリ配下のファイルです。
ダウンロードファイルを選択後ダウンロードボタンでクライアントパソコンのダウンロードフォルダにファイルをダウンロードします。
実行権付与ファイルを選択後実行権付与ボタンで実行権を付与します。通常では不要です。
アップロード先アップロードしたいディレクトリをプルダウンから選択して下さい。
※対象ディレクトリ配下がプルダウン対象です。
アップロードファイルを選択ボタンを押すと、クライアントパソコンのファイル一覧から指定のファイルを選択してアップロードできます。*1
info
  1. アップロード後にプロセスの再起動が必要なので、ダッシュボードから停止・起動の操作を行って下さい。
caution

アップロード先にLuaファイル以外が存在している場合には正常に動作しません。
Luaファイル以外を置かないで下さい。

EnOcean Lua

EnOceanはEEP(EnOcean Equipment Profile)というプロファイルでデバイスが定義されています。
また一部のGP(Generic Profile)のデバイスについても対応していますが、Tech-inデータを用いない為EEPデバイスと同様にGPから始まるプロファイルを使用します。

EnOcean登録とLua関数の関係

EnOceanデバイスはBLE機器に比べプロファイルと言う形式をとっているため、データ形式が規格化されているため扱い易いです。
デバイス毎のハンドラは以下の通り、devicesディレクトリにプロファイル名でLuaハンドラが登録されています。
IoTデータ⇒EnOcean Luaタブの操作画面の以下のディレクトリです。

EnOceanを受信登録するする時はEnOcean登録デバイスIDEEPの登録が必須です。

ここで登録されたEEPあわせて、devicesディレクトリに登録されたLuaハンドラーの中から該当するハンドラーが選択され実行します。
※EnOceanハンドラはBLEハンドラとは違ってLuaを呼ばれた時点でデバイスのプロファイルが決定しています。

ユーザースクリプトは以下のdevices_customディレクトリに保存するので、ここに置いてある"skelton.lua"をダウンロードして、テンプレートにすると良いです。

skelton.lua
skelton = {}
function skelton.skelton_decode(jo, data)
pdHandlerUtil.log_debug("skelton")
local str
str = pdHandlerUtil.bin2str(data, #data)
pdHandlerUtil.log_info("str: ", str)
if str ~= nil then
jo.data = str
end
end
return skelton

登録したいEEPは次のようなデバイスのものです。

EnOceanアライアンスのEEP仕様書から A50205

最初に"skelton"文字列をデバイスのEEPに置換します。
かつ、クラウド送信用のjo配列のメンバーにtemperatureを追加しtemperaturen値を計算し代入します。
※元のskeltonは受け取ったデータをそのまま16進テキスト文字列でjsonで送るものです。

skelton.lua(編集後)
A50205 = {}
function A50205.A50205_decode(jo, data)
local temperature = string.byte(data, 3)
pdHandlerUtil.log_debug("A50205")
temperature = (255 - temperature) * 40 / 255 -- 仕様書の計算式のまま
pdHandlerUtil.log_info(" temperature: ", temperature)
jo.temperature = temperature
end
return A50205

以上のコードでIoTクラウド送信用のjsonテキストは以下の様になります。

{
"deviceId": "xxxxxx",
"time": "2016-03-14T16:17:02.269+09:00",
"temperature": 25.41176470, ← ここだけハンドラで追加されたデータ
"EEP": "A50701",
"memo": "temperature Sensor",
"rssi": -71
}

その他のセンサーのjsonテキストはこちらを参照して下さい。

編集が終わったら、<プロファイル名>.luaと名前を付けてdevices_customディレクトリにアップロードして下さい。

アップロード/ダウンロード操作

作成したLuaハンドラのアップロードや、参考にするLuaハンドラのダウンロード操作は以下の通りです。

EnOcean Lua メニューの操作項目

設定項目説明
更新Luaファイルのアップロード先のファイル一覧表示を更新します。
削除ファイルを選択後削除ボタンで削除します。
削除可能なファイルはカスタム用ディレクトリ配下のファイルです。
ダウンロードファイルを選択後ダウンロードボタンでクライアントパソコンのダウンロードフォルダにファイルをダウンロードします。
実行権付与ファイルを選択後実行権付与ボタンで実行権を付与します。通常では不要です。
アップロード先アップロードしたいディレクトリをプルダウンから選択して下さい。
※対象ディレクトリ配下がプルダウン対象です。
アップロードファイルを選択ボタンを押すと、クライアントパソコンのファイル一覧から指定のファイルを選択してアップロードできます。*1
info
  1. アップロード後にプロセスの再起動が必要なので、ダッシュボードから停止・起動の操作を行って下さい。
caution

アップロード先にLuaファイル以外が存在している場合には正常に動作しません。
そのため、ファイル内容については skelton.lua を参考に作成して下さい。

標準登録センサーのLuaハンドラ編集

例えば"EEP:A50205"と言うセンサーでクラウドに送信するキー名"temperature"となっているのを"temp"と変更したい場合、"devices"ディレクトリから"a50205.lua"というファイルをダウンロードします。

a50205.lua
a50205 = {}
function a50205.a50205_decode(jo, data)
local temperature = pdHandlerUtil.round((255 - string.byte(data, 3)) * 40 / 255, 2)
jo.temperature = temperature
end
return a50205

この行

jo.temperature = temperature

jo.temp = temperature

に変更します。
そして変更したファイルを"devices_custom"ディレクトリにファイル名を変更せずに"a50205.lua"と言う名前のままアップロードして下さい。
この作業だけでオリジナルの送信データを作成できます。

新規センサーのLuaハンドラ作成

未サポートのデバイスのハンドラを新規に追加する時は、"devices_custom"ディレクトリに"skelton.lua"というファイルが置いてあるので、このファイルをテンプレートとして使って下さい。
このファイルの中の"skelton"と言う部分をEEPプロファイル名に変更して使います。 上記の"EEP:A50205"のソースコードが参考になると思います。
あとはxxxxxx.xxxxxx_decode関数の中にバイナリデータからの変換ルーチンを用意するだけです。
完成したらEEPプロファイル名.luaというファイル名で"devices_custom"ディレクトリにアップロードして下さい。
なお、"skelton.lua"テンプレートにあるxxxxxx.xxxxxx_decode関数内のルーチン

skelton.skelton_decode()
function skelton.skelton_decode(jo, data)
pdHandlerUtil.log_debug("skelton")
local str
str = pdHandlerUtil.bin2str(data, #data)
pdHandlerUtil.log_info("str: ", str)
if str ~= nil then
jo.data = str
end
end

は、バイナリデータをそのまま16進数ASCII文字列に変えているだけなので、変換ルーチンを別途用意する場合は全て不要になります。

info

ファームウェア 1.x、2.x は、Lua に対応しておりません。
EEPタイプとGPタイプに対応しています。
EEPタイプはプロファイルコードで検索して下さい。
GPタイプはEEPの欄に"GP"から始まるプロファイル名を入力します。(GP_xxxx)
GPセンサーはファームウェア 4.xからのサポートになります。

RS-SERIAL Lua

PD Handler RS-SERIALでは、対向のシリアルデバイス(RS-232C/RS-485など)向けに処理内容をフルカスタムできるユーザー定義ハンドラです。
IoTデータ⇒SERIAL Luaタブから、カスタマイズしたLuaファイルをアップロードし使用します。

info
  • デフォルトでインストールされているrs-serial.luaはサンプルとなります。このファイルを参考に編集して下さい。
  • PD Handler RS-SERIALは指定したLuaファイルを読み込み、LUA_main関数を実行します。
  • PD Handler RS-SERIALはPD Repeaterへのアクセスするパスのデバイス名部をLuaファイル内にて定義しています。そのため、環境に合わせてLuaファイルを編集する必要があります。
caution

Luaファイルが不正(フォーマットエラーやシンタックスエラー等)の場合エラー終了となるので、ログを確認して下さい。

RS-SERIAL Lua のカスタマイズ(シリアルポート側)

基本的には一般の高級言語でシリアルインターフェース機器との通信全般を書くのと違いがありません。
※実際にはシリアル通信にこだわらずLuaだけで汎用的なアプリケーションもここで作れてしまいます。

システムの単純な流れとしては

シリアルポートのオープン
速度などの通信パラメータ設定
データ送受信
シリアルポートのクローズ

この作り方になりますが、OpenBlocks上ではデーモンとして起動するので、シリアルポートのクローズはSTOPシグナルが入った時となるので、データ送受信部分で無限ループする作りになります。

このディレクトリにあるrs-serial.luaをテンプレートとしてダウンロードしてから編集して使って下さい。
※利用開始前にはモジュール設定でPD Handler RS-Serialの起動を忘れずに!

以下はrs-serial.luaのデフォルトソースコードです。
OpenBlocksとパソコンをRS-232Cクロスケーブルで接続しテストを行って下さい。
動作はパソコン側でTeraTermなどを使ってOpenBlocksと接続しキーボード入力するとその文字をエコーバックします。
そして"who?"と入力すると"I am openblocks"と返事し、"exit!"と入力すると"!!!!!! STOPING prcess !!!!!"と返事してからプロセスを停止します。
※本番用デーモンとして起動する時は無限ループを抜ける仕組みがない方が良いです。

rs-serial.lua_org
function LOG_err(message)
LUA_sys_log (3, message)
end
function LOG_info(message)
LUA_sys_log (6, message)
end
function LOG_debug(message)
LUA_sys_log (7, message)
end
function LUA_main(void)
LOG_info("start rs-serial\n")
-- シリアルポートのオープン(RS485の時は"/dev/ttyMFD1"の部分を"/dev/ttyRS485"に変更)
local fd
fd = LUA_open_serial("/dev/ttyMFD1", 9600, 8, 1, false, false)
if (0 > fd) then
LOG_err("serial port open error\n")
return
end
-- メインループ
local read_buff = ""
local read_size = 0
while (1) do -- フォアグランドモードの場合、無限ループはCTRL-Cでも止まります。
local rbuff, rsize -- ローカル変数宣言
rbuff, rsize = LUA_read_serial(fd)
read_buff = read_buff .. rbuff -- 前にシリアル入力された文字列に今回分を連結
read_size = read_size + rsize -- 同上
-- データを受信した
if rsize > 0 then
LUA_send_serial(fd, rbuff, rsize) -- 入力文字のエコーバック
if nil ~= string.find(read_buff, "exit!") then -- 終了文字列の検索(exit!)
local res_str = "\n\r!!!!!! STOPING prcess !!!!!\n\r" -- レスポンス文字列
local res_len = string.len(res_str) -- レスポンス文字列の長さ
LUA_send_serial(fd, res_str, res_len) -- シリアルポートにレスポンスを送信
break; -- exit!で無限ループを抜ける
-- ※デーモン動作の時は無限ループから抜けないようにする
end
if nil ~= string.find(read_buff, "who%?") then -- 文字列検索(who?)
local res_str = "\n\rI am openblocks\n\r" -- レスポンス文字列
local res_len = string.len(res_str) -- レスポンス文字列の長さ
LUA_send_serial(fd, res_str, res_len) -- シリアルポートにレスポンスを送信
read_buff = ""
end
end
read_buff = string.sub(read_buff, -10) -- 最後に入力された10文字だけ残して捨てる
read_size = string.len(read_buff)
LUA_usleep (50000) -- 50msec程度スリープを入れとくと他の処理が軽くなります。
end
-- シリアルポートのクローズ
LUA_close_serial(fd)
LOG_info("stop rs-serial\n")
end

これは非常に簡単なプログラムで、もう少し色々試したい時はsample.luaを一度ダウンロードでして別名のluaのファイル名でアップロードして下さい。
IoTクラウド(PD Repeater)への送信サンプルなども用意されていますので参考にして下さい。

RS-SERIAL Lua のカスタマイズ(クラウドとの通信側)

もともとはシリアルポート側の処理系と一緒のソースコードとして作成しますが、わかりやすく解説するためにクラウド側の処理を分けて説明します。
以下は簡単なサンプルです。

クラウドとの通信側
function LOG_info(message) -- ログ出力用関数
LUA_sys_log (6, message)
end
local device_number = "userdev_0000001" -- WEB-UIでユーザー定義したデバイス。
function LUA_main(void)
local uds_fd
local rbuff
uds_fd = LUA_open_udomain(device_number) -- 待ち受けのユニックスドメインソケット
if (1 > uds_fd) then -- 戻り値0でオープンエラー
LOG_err("unix domain server open error\n")
return
end
-- メインループ ※基本的にはポーリング処理です。
while (1) do -- 無限ループはCTRL-Cでも止まります。
rbuff = LUA_read_udomain(uds_fd)
if (#rbuff > 0) then -- rbuff にメッセージ到着
-- 通常 rbuff 内の下流制御メッセージをデコードして実行する
-- ここでは PD-Handler のログへプリントしてクラウドへエコーバックする
LOG_info(rbuff)
LUA_post (device_number, rbuff) -- クラウドから受けたjsonをそのままエコーバック
end
LUA_usleep(1000000) -- 1秒スリープ
end
LUA_close_udomain(uds_fd) -- 使い終わったらクローズ
end

RS-SERIAL Lua で Device Shadow を使う

AWS IoTのDevice Shadowはクラウド上に仮想化したステートマシンを配置し、エッジ側の装置はそのステートマシンの状態を常に同期する仕組みを取ります。
例えばSwitchAがクラウド上のステートマシンで定義され、それがonになれば、エッジ側のマシンのSwitchAonのステータスへ移行します。
また、エッジ側のSwitchAが何らかのエッジ処理によってoffとされた場合、クラウド上のステートマシンに対してSwitchAoffになったステータスを通知します。
さらにエッジ側の自己処理でステータスが変わった場合もエッジ側が自発的にそのステータス変化を通知します。

こう言った一連の処理をMQTTプロトコルによって行います。
このAWS IoTのDevice Shadowの仮想ステートマシンは全てjson文字列で記述されています。

jsonで記述されたステートマシンの基本構造(参考例)

{
"state":{
"desired":{
...
},
"reported":{
...
},
}
}
↓ ここから下はDeviceShadowシステムがトピックに応じてメタ情報を書き込みます。

上記の形式で'...'の部分がユーザー定義部分です。 このユーザー定義の部分の使い方は自由ですが、基本的な操作の流れはクラウド(操作する側)からdesiredのメンバーでエッジデバイスに操作要求を送り、エッジデバイス(操作される側)がreportedのメンバでその結果をDeviceShadowに送ります。
例えばDeviceShadow仮想ステートマシンの状態が

{
"state":{
"desired":{
"SwitchA": "off",
"SwitchB": "off"
},
"reported":{
"SwitchA": "off",
"SwitchB": "off"
}
}
}

の所へクラウドから以下のjsonメッセージをDeviceShadowへパブリッシュすると

{
"state":{
"desired":{
"SwitchA": "on"
}
}
}

DeviceShadow仮想ステートマシンは

{
"state":{
"desired":{
"SwitchA": "on",
"SwitchB": "off"
},
"reported":{
"SwitchA": "off",
"SwitchB": "off"
}
}
}

と、このようになり、この変化がDeviceShadow配下のトピックにその状態変化を通知するメタ情報がパブリッシュされます。
OpenBlocksのPD Repeaterでは、このトピックの全てをサブスクライブしているため、すべてのメタ情報が取り込まれ読み出すことができます。

実際のDeviceShadowはこの状態変化が起こると、documents,delta,acceptedの3つのトピックにパブリッシュしてきます。
基本的にはどのトピックを参照しても現在のステータスを得ることができますが、エッジ処理ではdeltaトピックの内容を参照すると、その状態変化が容易にわかります。
deltaトピックはdesiredreportedのメンバーの差分をパブリッシュしてくるので次の様になります。

{
"state":{
"desired":{
"SwitchA": "on"
}
}
}
↓ ここから下はDeviceShadowシステムがトピックに応じてメタ情報を書き込みます。

このようにステータスに変異のあったメンバのみ通知されるので、この変異に同期するようにエッジ側の処理を実行します。
ここではSwitchAonにするためのエッジ処理を実行します。
エッジ処理を実行して実際のSwitchAonした後は、クラウドにそのリポートを通知します。

{
"state":{
"reported":{
"SwitchA": "on"
}
}
}

以上のようにクラウドにパブリッシュして処理の完了となります。
DeviceShadowはこの変異に対して同じようにdocuments,delta,acceptedの3つのトピックにパブリッシュしてきます。
これに対してもOpenBlocksはメッセージを受け取ることができますが、自発のレポートの変異であるためこのメッセージは無視します。

最終的にクラウドの仮想ステートマシンの状態は以下の通りとなります。

{
"state":{
"desired":{
"SwitchA": "on"
"SwitchB": "off"
},
"reported":{
"SwitchA": "on"
"SwitchB": "off"
}
}
}

仮想ステートマシンの特定のメンバを消すときには次の様にパブリッシュします。

{
"state":{
"desired":{
"SwitchA": null
}
}
}

仮想ステートマシンのdesired全部を消す場合は次の様にパブリッシュします。

{
"state":{
"desired": null
}
}

Luaスクリプトの実際

以下のLuaスクリプトは上記の手順を追ったものです。

Device Shadow を扱う Lua スクリプト
package.cpath = package.cpath .. ';' .. "/opt/pd/lua/util/luarocks/lib/lua/5.3/?.so;" -- cjsonなど置いている場所
package.cpath = package.cpath .. ';' .. "/opt/pd/lua/util/luarocks/share/lua/5.3/?/?.lua;"
json = require "cjson" -- jsonのエンコーダーデコーダーライブラリのLoad
DeviceShadow = require "DeviceShadow" -- DeviceShadowアクセスライブラリのLoad
function LOG_err(message)
LUA_sys_log (3, message)
end
function LOG_info(message)
LUA_sys_log (6, message)
end
function LOG_debug(message)
LUA_sys_log (7, message)
end
local device_number = "userdev_0000001" -- WEB-UIでユーザー定義したデバイス。
function LUA_main(void)
local uds_fd -- ファイルデスクリプタ
local jo -- json文字列をデコードしたあとに保存される連想配列テーブル
uds_fd = LUA_open_udomain(device_number) -- 待ち受けのユニックスドメインソケット
if (1 > uds_fd) then -- 戻り値0でオープンエラー
LOG_err("unix domain server open error\n")
return
end
-- メインループ ※基本的にはポーリング処理です。
while (1) do -- 無限ループはCTRL-Cでも止まります。
jo = DeviceShadow.receive(uds_fd) -- DeviceShadowのステータス変異を受け取る
if nil ~= jo then -- ステータス変異を受信
if jo.type == "delta" then -- deltaトピックだけ受け付ける
if jo.data ~= nil then -- 有効なペイロードがある場合
if jo.data.state.desired.SwitchA ~= nil then
local rep = {} -- クラウドにレポートを送るための連想配列テーブル
jo.data.state.desired.SwitchA == "on" then
-- ここへSwitchAをonするルーチンを入れる
rep.state.reported.SwitchA == "on" -- reportedへ変更後のステータスを入れておく
else
-- ここへSwitchAをoffするルーチンを入れる
rep.state.reported.SwitchA == "off" -- reportedへ変更後のステータスを入れておく
end
-- rep.state.desired = json.null << クラウドの"desired"の中身を全部消したい場合
DeviceShadow.update(uds_fd, rep) -- DeviceShadowへレポートする
end
end
end
end
LUA_usleep(1000000) -- 1秒スリープ(適当に!)
end
LUA_close_udomain(uds_fd) -- 使い終わったらクローズ
end

RS485半二重通信での注意点

RS485の場合、送信線と受信線を共有して4ワイヤ(実質2ワイヤでも通信可能)の半二重接続が主流の使い方になっています。
ここでよく問題となるのは、送信と受信を切り替えるタイミングで発生するデータ化け問題です。
例えば通信フレームに対してのエラーチェックでCRCを付与している時やパリティチェックしている時など、この切替の微妙なタイミングでエラーを発生します。
これを通信回路で発生するノイズと勘違いされている方が非常に多いです。

この図で示される切替のタイミングはプライマリが送信側としてセカンダリが受信機側としている場合、プライマリが送信終わって送受信を切り替えても即時切り替わるわけではなく多少の遅延が発生します。
このため、低いボーレートでは気にならなかった送受信線の切替が、高いボーレートではセカンダリのデバイスがデータを受けきらないタイミングで切り替わってしまい、データが化けるという現象が起こってしまいます。
この逆のパターンでは、セカンダリデバイスが送信し始めたタイミングをうまく検出できなくて、データ化けが起こるケースもあります。
これはデバイスがRS485半二重に対応したLSIを採用している場合にはデータ化けの無い通信が可能ですが、元々全二重のRS232Cの回路をRS485半二重にハードウェアロジックなどでコンバートした回路を採用しているデバイスにはこの切替の遅延が発生します。
(旧版のOpenBlocks IoT EX1やBXシリーズも対象になります。)
この場合には通信速度を遅くすることで対処も可能ですが、ソフトウェア的に解決するには、RS485に流すデータのフレームの前後にダミーデータを置く方法があります。

ダミーデータ付与の例

元となるデータペイロード
1234567890
ダミーを付けたデータ
・・・S1234567890CCE・・・ < Sはスタートコード Eはエンドコード CはCRCデータ
xxxxooooooooooooxxxx
xxxx部分のデータは無視してSの次のデータからEの前のoとc部分データを有効として使いCC部分でエラーチェック
・・・のダミーデータは0xaaや0x55などを使うことを推奨。

こういった感じのプロトコルで有効部分のデータの最後にCRCを付けて転送します。
受信側はダミー部分を捨てて有効部分のデータのみ使います。
また、スタートコードやエンドコードは1バイトの場合、使用制限が付きやすいので2バイトでスタート・エンドコードを設定するやり方が一般的です。

RS-SERIALのLua用関数

以下はOpenBlocksのLua言語用に実装している関数です。
sample.luaではこれら関数を一通り使っているので参考にして下さい。

LUA_open_serial()

シリアルポートを指定の通信パラメータでオープンする。

LUA_open_serial (devfile, speed, databit, stopbit, enable_parity, odd_parity)
引数
devfile (string)
対象のシリアルポートのデバイスファイル名を設定(例:"/dev/ttyMFD1")
speed (int)
シリアルポートの通信速度を指定(110~921600の範囲で14400はサポート外)
databit (int)
データのビット長(5~8 範囲外の値は8とする)
stopbit (int)
ストップビット長(1 or 2、2以外は全部1とする)
enable_parity (bool)
パリティを有効にする(true or false )
odd_parity (bool)
enable_parityがtrueの時にこの設定が有効になる。
trueの時、奇数パリティになる。
falseの時、偶数パリティになる。
戻り値
ファイルデスクリプタ(int)、オープン失敗時 マイナス値
LUA_close_serial()

シリアルポートをクローズする。

LUA_close_serial (fd)
引数
fd (int)
シリアルポートオープン時のファイルデスクリプタ
戻り値 無し
LUA_send_serial()

シリアルポートからバイナリーデータを送信する。

LUA_send_serial (fd, buffer, size)
引数
fd
ファイルデスクリプタ(int)
buffer
送信するバイナリーデータ(data ※ポインタではなく実体)
※送信データにNULL(0)などのバイナリーも含む事ができます。
size
バイナリデータの長さ
戻り値
送信できたバイト数
LUA_read_serial()

シリアルポートからバイナリーデータを受信する。

LUA_read_serial (fd, buffer, size)
引数
fd
ファイルデスクリプタ(int)
戻り値1
バイナリーデータ列の実体(NULLを含むがLUAではstring型)
※受信データにNULL(0)などのバイナリーも含む事ができます。
戻り値2
バイナリデータの長さ
データが無い時 0 ※C言語内では -1(使いにくいので0に置き換え)
LUA_post()

ユニックスドメインソケット経由で PD Repeater にメッセージを送信する。
PD Repeater はそのメッセージをWEB-UIの設定に従いクラウドへ送信する。

LUA_post (device_number, message)
引数
device_number
openblocksので確保したソケットの名前(string)
ソケットはWEB-UIにある 基本>Userデバイス登録 タブで登録された
デバイス番号を使う。
message
一般的にはjsonの文字列を指定するが、文字列であれば何でもかまわない。
LUA_open_udomain()

ユニックスドメインソケットの待ち受けサーバーを起動する。
PD Repeater はWEB-UIの設定に従いクラウドからの下流制御メッセージを受け取り、 このコマンドでオープンされたユニックスドメインソケットにjsonメッセージを送信する。

LUA_open_udomain(device_number)
引数
device_number
openblocksので確保したソケットの名前(string)
ソケットはWEB-UIにある 基本>Userデバイス登録 タブで登録された
デバイス番号を使う。
戻り値
ファイルデスクリプタ(int) 戻り値が0の時はオープンエラー
LUA_read_udomain()

ユニックスドメインソケットの待ち受けからメッセージを受け取る。

LUA_read_udomain(uds_fd)
引数
uds_fd
LUA_open_udomain関数で得たファイルディスクリプタ
戻り値
受信バッファデータ
注意:
送信元のデータが長い場合、1回のreadで読み切れない場合があるので
受信バッファデータが空になるまで読み込む。
LUA_close_udomain()

ユニックスドメインソケットの待ち受けをクローズする。

LUA_close_udomain(uds_fd)
引数
uds_fd
LUA_open_udomain関数で得たファイルディスクリプタ
LUA_dump_byte()

バイナリーのバイト列をダンプする。(デバッグメッセージとして出力)
※デバッグのために用意された関数です。

LUA_dump_byte (buffer, size)
引数
buffer
ダンプするバイナリーデータ(data ※ポインタではなく実体)
size
ダンプするバイナリデータの長さ
戻り値
無し
LUA_usleep()

プロセスを指定の時間停止させる。

LUA_usleep (microsec)
引数
microsec(int)
マイクロ秒単位で停止させる時間を設定。(互換性のためなるべく1000000未満(1秒)を設定)
戻り値
無し
LUA_sleep()

プロセスを指定の時間停止させる。

LUA_sleep (sec)
引数
microsec(int)
秒単位で停止させる時間を設定。
戻り値
無し
LUA_sys_log()

シスログにメッセージを送る。フォアグランドでは標準出力も一緒に行われる。

LUA_sys_log (level, message)
引数
level(int)
ログのレベル。(0~7)
message(string)
出力されるメッセージ。
※printf的に使う場合 string.format関数 を使います。(サンプル参照)
LUA_get_iso_datetime()

IoTクラウドに基本的に用いられるISO8601形式の日付時間を得ます。

LUA_get_iso_datetime (order_msec)
引数
order_msec(bool)
時間をミリ秒単位まで欲しい時trueにします。それ以外はfalse
戻り値
ISO8601形式の日付時間文字列

アップロード/ダウンロード操作

SERIAL LuaのスクリプトファイルはIoTデータ⇒SERIAL Luaタブの操作画面からアップロードやダウンロードすることができます。

設定項目説明
更新Luaファイルのアップロード先のファイル一覧表示を更新します。
削除ファイルを選択後削除ボタンで削除します。
ダウンロードファイルを選択後ダウンロードボタンでクライアントパソコンのダウンロードフォルダにファイルをダウンロードします。
実行権付与ファイルを選択後実行権付与ボタンで実行権を付与します。通常では不要です。
アップロードファイルを選択ボタンを押すと、クライアントパソコンのファイル一覧から指定のファイルを選択してアップロードできます。*1
info
  1. アップロード後に、プロセスの再起動が必要となります。
  2. 別名のファイルでアップロード指定した場合、IoTデータ⇒モジュール設定タブのPD Handler RS-SERIALの指定Luaファイルにて使用するLuaファイルを変更して下さい。

HVSMC Lua

アップロード/ダウンロード操作

作成したLuaハンドラのアップロードや、参考にするLuaハンドラのダウンロード操作は以下の通りです。

HVSMC Lua メニューの操作項目

設定項目説明
更新Luaファイルのアップロード先のファイル一覧表示を更新します。
削除ファイルを選択後削除ボタンで削除します。
削除可能なファイルはカスタム用ディレクトリ配下のファイルです。
ダウンロードファイルを選択後ダウンロードボタンでクライアントパソコンのダウンロードフォルダにファイルをダウンロードします。
実行権付与ファイルを選択後実行権付与ボタンで実行権を付与します。通常では不要です。
アップロード先アップロードしたいディレクトリをプルダウンから選択して下さい。
対象ディレクトリ配下がプルダウン対象です。
アップロードファイルを選択ボタンを押すと、クライアントパソコンのファイル一覧から指定のファイルを選択してアップロードできます。*1
info
  1. アップロード後にプロセスの再起動が必要なので、ダッシュボードから停止・起動の操作を行って下さい。
caution

アップロード先にLuaファイル以外が存在している場合には正常に動作しません。