obniz x Kintone x sony MESHでドミノ倒しIoTしてみた
SONY MESHさんが技術資料を公開し、obnizのパーツライブラリにMESHが追加されました
これはなにか作ってみよう!ということで、MESHを見つめていると、ドミノ倒ししかイメージができなくなったので、MESHとobnizを使ってドミノ倒しをしてみました
登壇したときの資料はこちら
つくったもの
ドミノ倒しにMESHブロックを混ぜると、
- ドミノ倒しタイムアタックをしたり
- 遠隔でドミノ倒しの状況を知ったり
- ドミノ倒しの記録をとったり
ができます!
(何の役に立つのかは不明)
つかったMESHブロック
MESHとドミノを混ぜても違和感ありません笑
ドミノといえばやはり倒れるもの、ということで倒れたことを検知するようにします。
倒れたことを検知するには、最初に思いつくのは 動きブロックです
動きブロックはそのまま、ブロックの姿勢を取ることができるので、シンプルにわかりやすくできます。
ただ、動きブロックをたくさん持っているわけではないのでこれだと1個だけになってしまいます。
それだと物足りないので、明るさブロックも使うことにしました。
倒れたら明るさのセンサ部分が暗くなるのでは!? ってことで使ってみたらちょうど良かったのでそのまま使ってます
obniz
今回は8月に発売されたばかりの obniz BLE/Wi-Fi Gateway Gen2.0を使いました
といっても、この機種特有の機能は使ってないのでobnizBoardでも動く(はず)です
obniz.js@3.23.0よりMESHブロックが使えるようになっています。
npm install obniz@3.23.0
kintone
せっかく計測するなら記録したいので、記録としてkintoneを使っています。
kintoneはデータも保存できるしグラフかもできるので便利ですね
開発者用のサンプルアカウントを作ることができるので、お試しにも最適です
プログラム
node.js/Typescript で書いています
大きく分けて
- セットアップ(MESH↔obnizの接続)
- ドミノ倒し実行
- Kintoneへのアップロード
の3つで構成しています。
セットアップ(MESH↔obnizの接続)
動きブロックと明るさブロックを見つけるまでずっとスキャン
→両方見つけたらそれぞれconnect
→セットアップ完了!
obniz.onconnect = async () => {
log("obniz connected");
await obniz.ble!.initWait();
const SonyMeshFilter = [0x4d, 0x45, 0x53, 0x48, 0x2d, 0x31, 0x30, 0x30]; // MESH-100
const MESH_100AC = Obniz.getPartsClass("MESH_100AC");
const MESH_100PA = Obniz.getPartsClass("MESH_100PA");
obniz.ble!.scan.onfind = async (peripheral) => {
log("name:", peripheral.localName);
if (!devices.ac && MESH_100AC.isDevice(peripheral)) {
devices.ac = new MESH_100AC(peripheral);
} else if (!devices.pa && MESH_100PA.isDevice(peripheral)) {
devices.pa = new MESH_100PA(peripheral);
}
if (devices.ac && devices.pa) {
await obniz.ble!.scan.endWait();
}
};
await obniz.ble!.scan.startWait(
{ binary: [SonyMeshFilter] },
{ duration: null, filterOnDevice: true }
);
obniz.onloop = async () => {
await wait(1000);
if (obniz.ble!.scan.state === "stopped" && devices.ac && devices.pa) {
await connectAndSetup();
if (
!devices.pa.peripheral.connected ||
!devices.ac.peripheral.connected
) {
return;
}
}
};
};
ble.onfind
の中でブロックを両方見つけたら、connectAndSetup
を呼んでいます。
connectAndSetup
では、各デバイスの設定をしています
ドミノ倒し実行
センサからデータが来たら倒れているかチェック
→倒れていたらスタート! もしくはフィニッシュ!として記録しています
センサのデータが来たときのコールバックをきっかけに、スタートとフィニッシュの時間を記録しています。フィニッシュ時はkintoneにデータを送る sendToKintone()
を呼んでいます。
const onMaybeStart = async () => {
if (!measure.startUnixTime) {
console.log("START!");
measure.startUnixTime = new Date().getTime();
}
};
const onMaybeFinish = async () => {
if (measure.startUnixTime && !measure.finishUnixTime) {
measure.finishUnixTime = new Date().getTime();
const time = (measure.finishUnixTime - measure.startUnixTime) / 1000;
console.log(`FINISH! ${time}ms`);
await sendToKintone(time);
}
};
Kintoneへのアップロード
記録をアップロードするだけですね
kintoneさんの公式SDKがあるのでそれを使っていきます。
ドキュメントも充実してるのでわかりやすかったです。
const { KintoneRestAPIClient } = require("@kintone/rest-api-client");
const client = new KintoneRestAPIClient({
baseUrl: config.kintone.baseUrl,
auth: {
apiToken: config.kintone.apiToken,
},
});
const sendToKintone = async (time: number) => {
// 追加方法についてはこちら
// https://developer.cybozu.io/hc/ja/articles/202166160-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E7%99%BB%E9%8C%B2-POST-
await client.record.addRecord({
app: config.kintone.appId,
record: {
time: { value: time },
},
});
};
プログラム
プログラム全体はこちら
import Obniz, { Parts } from "obniz";
import MESH_100PA from "obniz/dist/src/parts/Ble/MESH_100PA";
const { KintoneRestAPIClient } = require("@kintone/rest-api-client");
const config = {
kintone: {
apiToken: "Kintoneのアプリトークン",
baseUrl: "KintoneのURL",
appId: 2, //kintoneのアプリID
},
obnizId: "obnizのID"
};
const client = new KintoneRestAPIClient({
baseUrl: config.kintone.baseUrl,
auth: {
apiToken: config.kintone.apiToken,
},
});
const sendToKintone = async (time: number) => {
// 追加方法についてはこちら
// https://developer.cybozu.io/hc/ja/articles/202166160-%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E7%99%BB%E9%8C%B2-POST-
await client.record.addRecord({
app: config.kintone.appId,
record: {
time: { value: time },
},
});
};
const obniz = new Obniz(config.obnizId);
const devices: {
pa?: Parts<"MESH_100PA">;
ac?: Parts<"MESH_100AC">;
} = {};
const measure: {
startUnixTime: number | null;
finishUnixTime: number | null;
} = { startUnixTime: null, finishUnixTime: null };
obniz.onconnect = async () => {
log("obniz connected");
await obniz.ble!.initWait();
const SonyMeshFilter = [0x4d, 0x45, 0x53, 0x48, 0x2d, 0x31, 0x30, 0x30]; // MESH-100
const MESH_100AC = Obniz.getPartsClass("MESH_100AC");
const MESH_100PA = Obniz.getPartsClass("MESH_100PA");
obniz.ble!.scan.onfind = async (peripheral) => {
log("name:", peripheral.localName);
if (!devices.ac && MESH_100AC.isDevice(peripheral)) {
devices.ac = new MESH_100AC(peripheral);
} else if (!devices.pa && MESH_100PA.isDevice(peripheral)) {
devices.pa = new MESH_100PA(peripheral);
}
if (devices.ac && devices.pa) {
await obniz.ble!.scan.endWait();
}
};
await obniz.ble!.scan.startWait(
{ binary: [SonyMeshFilter] },
{ duration: null, filterOnDevice: true }
);
obniz.onloop = async () => {
await wait(1000);
if (obniz.ble!.scan.state === "stopped" && devices.ac && devices.pa) {
await connectAndSetup();
if (
!devices.pa.peripheral.connected ||
!devices.ac.peripheral.connected
) {
return;
}
}
};
};
const connectAndSetup = async () => {
if (!devices.ac || !devices.pa) {
return;
}
if (!devices.pa.peripheral.connected) {
await devices.pa.connectWait();
// Set event handler
devices.pa.onSensorEvent = (proximity, brightness) => {
if (!measure.startUnixTime) {
console.log("proximity: " + proximity + ", brightness: " + brightness);
if (proximity > 100) {
onMaybeStart();
}
}
};
// Prepare params (See the linked page below for more information.)
const notifyMode = MESH_100PA.NotifyMode.ALWAYS;
// Write
devices.pa.setMode(notifyMode);
if (devices.pa.peripheral.connected && devices.ac.peripheral.connected) {
console.log("Ready");
}
}
if (!devices.ac.peripheral.connected) {
await devices.ac.connectWait();
devices.ac.onOrientationChanged = (orientation, accele) => {
if (measure.startUnixTime && !measure.finishUnixTime) {
console.log(
"orientation changed! " +
orientation +
", (ax, ay, az) = (" +
accele.x +
", " +
accele.y +
"," +
accele.z +
")"
);
if (![5, 2].includes(orientation)) {
onMaybeFinish();
}
}
};
}
};
const onMaybeStart = async () => {
if (!measure.startUnixTime) {
console.log("START!");
measure.startUnixTime = new Date().getTime();
}
};
const onMaybeFinish = async () => {
if (measure.startUnixTime && !measure.finishUnixTime) {
measure.finishUnixTime = new Date().getTime();
const time = (measure.finishUnixTime - measure.startUnixTime) / 1000;
console.log(`FINISH! ${time}ms`);
await sendToKintone(time);
}
};
const onMaybeReady = async () => {};
const wait = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const log = (...args: any[]) => {
console.log(new Date(), ...args);
};
log("start");
完成!
作ったら動かそうということで動かしました
3回しかやってないですが、ちゃんとkintoneにデータが来てました
Discussion