自宅のWi-Fiに居住者全員のデバイスが接続しなくなったらWebhookを叩いてルンバを起動する。
既にローカルで、ネットワークに接続している機器をスキャンする部分は以前に確認済みなので、
今回は、node.jsあたりで、ネットワーク接続状況を確認して、特定の機器が接続していなければ、Webhookを叩きに行くという処理を作ります。
もう完全に忘れてしまったな・・・と思って過去の日記を見に行く。
まず、プロジェクトのディレクトリを作成。
npm init コマンドでNode.jsプロジェクトを初期化→必要なパッケージ(arp-a)をインストールとのことで試すもエラーに・・・。Node.jsとnpmのバージョンを確認しろとのことなので試すも問題なく最新版とでる。。なぜだ・・・。
別のパッケージを提案されたのでそちらを試すもこっちは脆弱性ありとでる・・・内部ネットワークに閉じてれば大丈夫かなとも思いつつ、他の方法も探る。。
書き込み権限を付与したり、地道にエラー対応したり試行錯誤を重ねた結果、処理が走る!!!
この段階では一台のMACアドレスしか監視できていないので、2台監視するように修整。また、定期実行して一定時間接続端末が見つからなかった場合に作動するように修整。具体的には、15分ごとに実行し、2回連続でデバイスが見つからない場合にWebhookを実行するように修整。
これ、常にラズパイは15分おきにスキャンしなくちゃいけなくなるわけですが、電気代的にはどんなもんなんでしょうね。待機電力。大したものではないことはわかってるけど、具体的に知りたいなあ。
ちなみに、上で実行していたのはダミーのMACアドレスですが、本物のアドレスを使ってテストしてみると、そもそも正しくスキャンができていなかったことが判明。。。できたフリだった。。
ということで、ちゃんと見つけたらアドレスをコンソールに吐き出してもらい、スキャン結果を確認することに。すると、スキャン結果に表示されるにも関わらず、見つかりません。処理を実行します。(iPhoneが見つからないので不在と判断して掃除を始めます。と言っている。)となるのでこれはおかしい。
GPT先生に状況をお伝えすると、MACアドレスをすべて小文字に変換してから比較する方法の提案をいただく。しかしこれも空振り。
結局、MACアドレスの前後に余分な空白や制御文字が含まれていないことを確認するためのトリミング処理をしてもらうことと、nullのエントリが混じっている可能性を考慮してそれを無視することの対応をしてもらうことで期待した結果がようやく得られる。
nmapを使用してネットワークスキャンを行うにあたり管理者権限が都度必要になるのがネックですが、内部のネットワークに閉じた環境であればラズパイ上では管理者権限をハードコーディングするのもありかなという所感。自己責任ですね。。。
const { exec } = require('child_process');
const axios = require('axios');
// 監視するMACアドレスのリスト(すべて小文字で記載)
const targetMacAddresses = [
'00:11:22:33:44:55'.toLowerCase(),
'00:11:22:33:44:55'.toLowerCase()
];
// Webhook URL
const webhookUrl = 'IFTTTのURL';
// 状態管理変数
let consecutiveMisses = 0;
// スキャンするネットワーク範囲
const networkRange = 'XXX.XXX.XX.X/XX'; // 実際のネットワーク範囲に置き換えてください
// nmapスキャン
function scanNetwork() {
return new Promise((resolve, reject) => {
exec(`sudo nmap -sP ${networkRange}`, (error, stdout, stderr) => {
if (error) {
reject(`exec error: ${error}`);
return;
}
const entries = [];
const lines = stdout.split('\n');
let currentEntry = null;
lines.forEach(line => {
const ipMatch = line.match(/Nmap scan report for (.+)/);
if (ipMatch) {
if (currentEntry) {
entries.push(currentEntry);
}
currentEntry = { ip: ipMatch[1], mac: null };
}
const macMatch = line.match(/MAC Address: (.+) \((.+)\)/);
if (macMatch && currentEntry) {
currentEntry.mac = macMatch[1].toLowerCase().trim(); // MACアドレスを小文字に変換してトリム
}
});
if (currentEntry) {
entries.push(currentEntry);
}
resolve(entries);
});
});
}
// Webhookを叩く
function triggerWebhook() {
axios.post(webhookUrl)
.then(response => {
console.log('Webhook triggered:', response.data);
})
.catch(error => {
console.error('Error triggering webhook:', error);
});
}
// スキャン実行
async function checkNetwork() {
try {
console.log('ネットワークスキャン中...');
const networkEntries = await scanNetwork();
console.log('ネットワークスキャン結果:');
networkEntries.forEach(entry => {
console.log(`IPアドレス: ${entry.ip}, MACアドレス: ${entry.mac || '不明'}`);
});
// デバッグ出力:ターゲットMACアドレスとスキャン結果のMACアドレスを表示
targetMacAddresses.forEach(targetMac => {
console.log(`ターゲットMACアドレス: ${targetMac}`);
});
networkEntries.forEach(entry => {
console.log(`スキャン結果のMACアドレス: ${entry.mac}`);
});
// すべての監視対象MACアドレスが存在するか確認
const anyDeviceFound = targetMacAddresses.some(macAddress => {
const found = networkEntries.some(entry => entry.mac !== null && entry.mac === macAddress);
console.log(`MACアドレス ${macAddress} が見つかった: ${found}`);
return found;
});
if (!anyDeviceFound) {
consecutiveMisses++;
console.log(`すべてのデバイスが見つかりませんでした。連続回数: ${consecutiveMisses}`);
if (consecutiveMisses >= 2) {
console.log(`2回連続でデバイスが見つかりませんでした。Webhookを叩きます。`);
triggerWebhook();
consecutiveMisses = 0; // リセット
}
} else {
console.log(`少なくとも1つのデバイスがネットワークに存在します。`);
consecutiveMisses = 0; // リセット
}
} catch (error) {
console.error('エラー:', error);
}
}
// 15分ごとにネットワークチェックを実行
setInterval(checkNetwork, 15 * 60 * 1000);
// 初回実行
checkNetwork();
次回は、いよいよラズパイに実装です・・・!
ご覧いただきありがとうございます。とても嬉しいです。