OS7以降
端末の機能をプログラムを書き加えることで拡張する機能がプラグインです。
obnizOSの動作はそのままに、OSに制御される形で小さなアプリとして端末上で実行させることができます。

これによりobnizの端末のみでオフライン時の動作や通信データの圧縮など高度な最適化が可能となります。
Lua Pluginでできること
- 起動した瞬間からデータの記録を開始する
- オンライン・オフライン関係なく決まった動作を行う
- 特定のデータだけサーバーに送ったり圧縮を行う
- 特定のデータを受け取ったり、定期的なタイミングで接点操作を行う
- オフラインで状態を監視して、スリープし送るべきタイミングのみインターネット接続を行う。
書き込みと実行
Luaはネットワーク接続と関係なく、OSの起動時からOSにより即座に実行が開始されます。
シリアルコンソールではなくobniz.jsからLuaを書き込むには以下のように文字列を送信し保存します。
obniz.storage.savePluginLua()を利用することで内部のフラッシュを上書きします。こちらは上書きするだけで実際の実行は再起動後となります。即座に適用するにはobniz.plugin.reloadLua()を呼び出します。
// Javascript Example
obniz.storage.savePluginLua(`
os.log("Hello World")
`);
obniz.plugin.reloadLua();
二重書き込みの阻止
接続ごとにPluginを書き換えると内部のフラッシュメモリの寿命に影響が出ます。
そこで、期待するLuaが入っていたら書き込まないような処理が必要です。obnizOSは起動したときに読み出したLuaの名称を送信するため、これを比較することで防ぐことができます。obniz.plugin_nameには起動時に利用されたpluginの名前が入ります。pluginの名前はLuaの中でplugin_name()関数の返り値として指定できます。
// Javascript Example
if (obniz.plugin_name !== 'my_plugin') {
console.log("Must Load Plugin");
obniz.storage.savePluginLua(`
function plugin_name()
return "my_plugin" -- max 30 chars
end
`);
obniz.plugin!.reloadLua();
obniz.reboot(); // Not necessary on every case.
} else {
console.log("No Need to Load Plugin");
}
随時実行
Luaスクリプトは本体に保存せずともSDK経由で随時実行することが可能です。
// Javascript Example
obniz.plugin!.execLua(`
x=1;
x=x+2;
os.log(x.."\\n")
`); // will print "3" on obnizOS console(need console enabled)
obniz.plugin!.execLua(`os.log(os.getTick().."\\n")`);
obniz.plugin!.execLua(`os.log(os.getTick().."\\n")`);
この機能を利用することで、端末を再起動することなく必要な関数・変数を随時更新することができます。
これによりダウンタイムを発生させることなく高速に端末の挙動を最適化し、通信量を最大限圧縮することが可能となります。
通信頻度を値によってクラウドから変更する例
// Javascript Example
if (temperature > 26) {
obniz.plugin!.execLua(`
interval = 1;
`);
} else {
obniz.plugin!.execLua(`
interval = 60;
`);
}
送信されるデータの種類をモードによって切り替える例
if (mode == 'temperature') {
obniz.plugin!.execLua(`
function filter(data)
if data.temperature == nil then
return nil
end
return data
end
`);
} else {
obniz.plugin!.execLua(`
function filter(data)
return data
end
`);
}
obniz.plugin.onError
OS7.1.0以降
このイベントハンドラは、端末のLuaプラグインが起こしたエラーを受け取ります。スクリプトが失敗するたびに呼ばれます。execLua()で開始したスクリプトや、起動時に読み込んだプラグインも対象です。
// Javascript Example
obniz.plugin.onError = (error) => {
console.log(`error occurred: ${error.message}`);
};
// 失敗するスクリプトがonErrorを呼ぶ
obniz.plugin.execLua(`MUST FAILED`);
error引数は、Luaのエラー内容を示すmessageプロパティを持つオブジェクトです。
on_low_memory(largestFreeBlock)
OS7.1.0以降
端末の空きメモリが少なくなると、OSはメモリ確保が失敗する前にon_low_memory()を呼びます。引数は連続した空き領域の最大サイズ(バイト)です。これは次の確保に使える実際の余裕を表します。
このハンドラを定義し、不要になったテーブルやバッファなどを解放してください。これにより、プラグインは異常終了せずに回復できます。
function on_low_memory(largestFreeBlock)
os.log("low memory: " .. tostring(largestFreeBlock));
-- 手放せるものを解放する
cache = nil;
end
OSはこのハンドラを2つの場面で呼びます。1つはガベージコレクションでも十分なメモリを取り戻せないときに先回りで呼ぶ場面、もう1つはメモリ確保がすでに失敗したときに最後の手段として呼ぶ場面です。ハンドラの実行中に再び呼ぶことはありません。これにより再帰を防ぎます。
対応している全Lua向けのobniz関数
-- System Event Handler
function on_event(event)
if event == "power_on" then
elseif event == "setting_mode" then
elseif event == "connecting_to_network" then
elseif event == "connecting_to_cloud" then
elseif event == "online" then
end
end
-- func(int), module(int), data(string(uint8 array))
function on_upstream(func, module, data)
if necessary then
return 1;
elseif rightnow then
return 2;
elseif mustDrop then
cloud.upstreamEnqueue(1, 2, str); -- change to differenct command
cloud.upstreamFlush(); -- optional
return 0;
end
end
-- received from obnis.js
function on_command(command)
cloud.pluginSend("123");
end
-- called while online every around 1msec
function on_online_loop()
-- os.log(\"online_loop\");
end
-- called while offline every around 1msec
-- You should care about communication because this function will be called
-- different thread.
function on_offline_loop()
-- os.log(\"offline_loop\");
end
function on_self_check()
os.log(" - self check ok");
end
-- called when free memory runs low (OS7.1.0+).
-- The argument is the largest contiguous free block in bytes.
-- Release resources here to avoid an out-of-memory crash.
function on_low_memory(largestFreeBlock)
os.log("low memory: " .. tostring(largestFreeBlock));
end
function plugin_name() -- will be get by obniz.plugin_name
return "my_plugin"
end
-- Available Functions
-- OS
os.getTick(); -- system tick in msec
os.getUnixTime(); -- get seconds from 1970/1/1. you must adjust time by calling pingWait();
os.log("hello world");
os.wait(1000); -- 1,000msec wait
os.reboot();
os.resetOnDisconnect(false); -- It will never reset after disconnection. And queued data never lost when offline.
os.sleep(5 * 1000, 0); -- deep sleep + external module sleep.
os.sleep(5 * 1000, 1); -- deep sleep + without external module sleep.
os.sleep(5 * 1000, 2); -- light sleep + external module sleep.
os.sleep(5 * 1000, 3); -- light sleep + without external module
os.getVersion();
os.getHW();
-- storage file
storage.fileOpen("text.txt")
storage.fileWrite("ABC");
storage.fileAppend("abcdefg");
length = storage.fileGetSize();
data = storage.fileRead(0, length);
storage.fileClose();
-- storage kvs
error = storage.kvsSave({ name = "Yuki", attr = { engineer = true } })
data = storage.kvsLoad();
-- Cloud
cloud.connect();
cloud.disconnect();
cloud.upstreamEnqueue(0, 1, data) -- module func data
cloud.pluginSend(data);
cloud.pluginSendFrameStart(frame_id, length); -- framing
cloud.pluginSendFrameEnd(); -- framing
cloud.enqueueTimestampByTick(os.getTick()-1000); -- it will enqueue unix timestamp
-- cloud transaction (OS7.1.0+, must run inside a coroutine / callWait)
local success, result = cloud.transactionWait("ping", 5000); -- data, [callback], [timeout_ms]
-- io
io.retain(1, true);
io.output(1, true);
local val = io.input(1);
io.drive(1, "5v"); -- OS7.1.0+ : "push-pull3v"(default) / "5v" / "open-drain"
io.pull(1, "3v"); -- OS7.1.0+ : "float"(default) / "5v" / "3v" / "0v"
-- ad
local val = ad.get(1); -- 0.0v~3.3v
-- pwm (OS7.1.0+)
pwm.start(1); -- start PWM on a logical obniz IO
pwm.freq(1, 1000); -- frequency in Hz
pwm.duty(1, 50); -- duty cycle 0-100%
pwm.stop(1); -- stop and release the IO
-- uart
uart.start(1, 2, 9600) -- tx:io1 rx:io2 baud:9600
uart.send("hello");
local data = uart.recv() -- uart.recv(1) limit to 1byte max
if #data > 1 then
os.log(data); -- received
end
local used = uart.isUsed(); -- OS7.1.0+ : true once uart.start() succeeded
-- spi
local err = spi.start(20, 21, 19, 8, 100 * 1000); -- MOSI, MISO, CLK, CS(could be null for non shared SPI), baudrate
if err > 0 then
os.log("SPI Error: " .. err);
return
end
let result = spi.write(string.char(0x40, 0x00, 0xC0)); -- result must be 3bytes if success.
local used = spi.isUsed(); -- OS7.1.0+ : true once spi.start() succeeded
-- display (OS7.1.0+, no-op on products without a display)
display.print("Hello"); -- append a string at the cursor
display.clear(); -- clear the screen
display.raw({0x00, 0xFF}); -- draw a raw vertical-byte framebuffer
-- lte (OS7.1.0+, cellular products)
local resp, status = lte.at("AT+CSQ", 10000); -- command, [timeout_ms]; status 0=OK 1=ERROR 2=TIMEOUT 3=FATAL
-- net (talk to your own server, bypassing obniz Cloud; blocks until done. plain http only, no TLS.
-- use one socket at a time. http/tcp: wifi/lte/ethernet, udp: wifi/ethernet only (no LTE))
local status, body, headers = net.http.get("http://example.com");
local status, body, headers = net.http.post(url, body, "application/json");
local status, body, headers = net.http.request({ url = url, method = "GET", headers = {}, body = "", timeout = 30000 });
local id = net.tcp.connect("example.com", 80); -- host, port
local written = net.tcp.write(id, "data");
local data = net.tcp.read(id, 1024); -- "" = no data yet, nil = closed/error
net.tcp.close(id);
local uid = net.udp.open(0); -- [localPort]
local written = net.udp.sendTo(uid, "192.168.0.2", 5000, "data");
local data, ip, port = net.udp.receive(uid, 1024); -- nil = nothing waiting
net.udp.close(uid);
-- wifi
wifi.on();
wifi.off();
wifi.sniffStart(callback);
wifi.sniffSetChannel(2);
wifi.sniffStop();
-- ble
ble.on();
ble.off();
ble.scanStart(onFind, { -- default option values
active=true,
interval=16,
window=16,
phy1m=true,
phyCoded=true,
duplicate=true
});