はじめに
とあるIPカメラから検知イベントを受信する方法として、ONVIF/FTP/HTTP(マルチパート)などがある。
今回はHTTPマルチパートで受信する方法を採択したが、遭遇した問題と回避方法をまとめる。
”とあるIPカメラ”と書いたが、以下に記載したサンプルでどのカメラか予測は付くが、そのままとしておく。
最初の実装(Node+@koush/axios-digest-auth)
まず実装したのは以下。(利用しているNodeモジュールは@koush/axios-digest-auth)
if (!options['method']) options['method'] = "GET";
options['url'] = url;
options['responseType'] = 'stream';
const axiosDigest = new AxiosDigestAuth({
username: options['username'],
password: options['password'],
});
axiosDigest.request(options)
.then((res) => {
res.data.on("data", (chunk) => {
・
・
・
var result = bufferBoundary.match(/Code=(?<code>.*?);action=(?<action>.*?);index=(?<index>.*?);data=(?<data>\{([\s\S]*?)\})/);
if (result) {
try {
if (chunkCb) chunkCb({
Code: result.groups.code,
action: result.groups.action,
index: result.groups.index,
data: JSON.parse(result.groups.data),
});
}
catch (e) {
// ...
}
}
});
})
.catch((error) => {
// ...
});
今回のIPカメラは検知の情報(検知日時、開始/終了、index)がバウンダリ文字列で区切られて届く。
この実装でしばらくは問題なく検知イベントを受信できるのだが、30分ほどしたら受信しなくなる事象に遭遇した。
ChatGPTにサンプルを出してもらったらバウンダリ文字列の扱いが誤っており、2個目の検知イベントが届かないと1個目を検知イベントとして認識しない、などの不具合があったがそのレベルではない。
上記の
res.data.on("data", (chunk) => {
}
が呼ばれないのである。
ただし、
curl -g "http://CAMIP/cgi-bin/eventManager.cgi?action=attach&codes=[SmartMotionHuman%2CSmartMotionVehicle...]" -X GET --digest --user "ID:PASS"
では受信し続けていたのでIPカメラ側の問題ではなく、Nodeモジュールを含むNode側の何か、、、か。
理由を知りたかったが時間がなかったので、上記curlを利用する方式に変更することにした。
その実装が以下。
次の実装(Node+curl。その1)
var cmd = "curl";
var args = [];
var url = sclString.joinUrl(parentUrl, `cgi-bin/eventManager.cgi?action=attach&codes=[${SmartMotionHuman%2CSmartMotionVehicle...}]`);
args.push('-g', url);
args.push('-X', 'GET');
args.push('--digest');
args.push('--user', `${ID}:${PASS}`);
var chprocess = spawn(cmd, args);
chprocess.stdout.setEncoding('utf-8');
chprocess.stdout.on('data', (data) => {
・
・
・
var result = bufferBoundary.match(/Code=(?<code>.*?);action=(?<action>.*?);index=(?<index>.*?);data=(?<data>\{([\s\S]*?)\})/);
if (result) {
try {
if (chunkCb) chunkCb({
Code: result.groups.code,
action: result.groups.action,
index: result.groups.index,
data: JSON.parse(result.groups.data),
}, res.data);
}
catch (e) {
// ...
}
}
});
しかし何故か
chprocess.stdout.on('data', (data) => {
}
のdataが4KB程度にならないと呼ばれない事象に遭遇した。
検知を即座に扱いたいので困っていたが、どうやらこの方法はcurlの標準出力をpipeで受信する方法のため、curl側がある程度蓄積してから送る、という挙動になるようだ。
以下のようにstdbufを利用するようにすることでstdoutに出力がある度(=個々の検知イベントを受信する度)にchprocess.stdout.on('data', (data) => {}が呼ばれるようになった。
var cmd = "stdbuf";
var args = [];
var url = sclString.joinUrl(parentUrl, `cgi-bin/eventManager.cgi?action=attach&codes=[${SmartMotionHuman%2CSmartMotionVehicle...}]`);
args.push('-oL');
args.push("curl");
args.push('-g', url);
・
・
・