Skip to content

Commit 9a6f59e

Browse files
committedMar 30, 2022
機能追加: AtomSwing
- クルーズ機能の追加
1 parent 578719c commit 9a6f59e

File tree

6 files changed

+297
-22
lines changed

6 files changed

+297
-22
lines changed
 

‎README.md

+48-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@
5959
但し自動追跡をonにしていると映像が動くことで物体認識して取り合いになります。
6060

6161
- pan/tilt座標系の初期化のためのリセット動作をするボタンをメンテナンスに追加
62+
63+
- AtomSwingのクルーズシーケンス制御(experimental)
64+
65+
- WebUIからのpan/tiltと待ち時間の登録
66+
- 待ち時間中の動体検知、動体追尾の選択
6267
## セキュリティに関わる重要事項
6368
上記項目に書いてある各ポートが利用可能となります。
6469
現時点ではこのポートはセキュリティ上の懸念材料となりますので、
@@ -163,7 +168,7 @@ ATOMCamアプリの「録画およびストレージ管理」の「ローカル
163168

164169
### 記録メディア
165170

166-
#### SD-Card
171+
#### SD-Card録画/SD-Cardモーション検知録画
167172

168173
offにするとATOMCamアプリの「Micro SDへのローカル録画」がonでもSD-Cardに記録されなくなります。
169174

@@ -203,11 +208,11 @@ SD Cardのボタンを押すとSD-Cardの録画ファイルのディレクトリ
203208

204209
ファイルをクリックすることでmp4, jpegファイルを開くことができます。
205210

206-
#### NAS
211+
#### NAS録画/NASモーション検知録画
207212

208213
ATOMCamアプリの「Micro SDへのローカル録画」、この設定画面の「検出通知のローカル録画」の映像をNASにも記録します。
209214

210-
##### - ネットワークパス
215+
##### - ネットワークPATH
211216

212217
NASのホスト名+フォルダー名を//\[ホスト名]/[フォルダー名] の形式で指定します。
213218

@@ -321,6 +326,46 @@ WebHookを受け取るURLを指定します。今のところ実験的な実装
321326

322327
設定変更時には反映するために再起動されます。
323328

329+
### クルーズ(AtomSwingのみ)
330+
331+
#### クルーズ動作
332+
333+
クルーズ動作のOn/Offの設定とシーケンス登録
334+
335+
以下の項目を登録順に動作し、最後までいくと最初に戻って繰り返します。
336+
337+
シーケンスの1項目を選択中は薄緑の色がつきます。色がついてる項目を編集できます。
338+
339+
右端のー、+マークで項目の削除、追加ができます。
340+
341+
編集するときは、一旦クルーズをOffにして設定してからにしないとカメラの方向が動いてやりにくいです。
342+
343+
#### pan, tilt
344+
345+
カメラを向ける方向を指定します。
346+
347+
数値を直接入力するか、Jpeg表示の左、下にあるスライダーで方向を制御しても入力できます。
348+
349+
#### 動作後待機時間
350+
351+
カメラの移動が終わってから、次の移動までの待機時間を秒数で指定します。
352+
353+
#### 検知
354+
355+
待機時間中に動体検知すると、待機時間を延長します。
356+
357+
動体検知が終わってからの待機時間は下の項目の検知後待機時間になります。
358+
359+
#### 追尾
360+
361+
待機時間中に動体検知すると、動体を追尾し、待機時間を延長します。
362+
363+
検知後待機時間は上記の検知と同様です。
364+
365+
#### 検知後待機時間
366+
367+
検知後の待機時間中も動体検知は働いて、待機時間の間検知しないと次のシーケンスに進みます。
368+
324369
### メンテナンス
325370

326371
#### Swing座標初期化

‎configs/overlay_rootfs/etc/init.d/S39webcontrol

+2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ case "$1" in
77
start)
88
/scripts/webhook.sh &
99
/scripts/webcmd.sh &
10+
/scripts/cruise.sh &
1011
;;
1112
stop)
1213
kill `pidof webhook.sh`
1314
kill `pidof webcmd.sh`
15+
kill `pidof cruise.sh`
1416
;;
1517
restart|reload)
1618
$0 stop
+20-13
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,46 @@
11
#!/bin/sh
22

33
# ex.
4-
# CRUISE_LIST="move 180 90;detect 10;move 210 90;follow 10;move 240 90;sleep 10;"
4+
# CRUISE_LIST="move 180 90;detect 10 10;move 210 90;follow 10 10;move 240 90;sleep 10;"
55
# move <pan> <tilt> Point the camera.
6-
# detect <timeout> If an object is detected, wait.
7-
# follow <timeout> If an object is detected, follow it.
8-
# sleep <timeout> Sleep.
6+
# detect <wait> <timeout> If an object is detected, wait.
7+
# follow <wait> <timeout> If an object is detected, follow it.
8+
# sleep <wait> Sleep.
99

1010
HACK_INI=/tmp/hack.ini
1111
CRUISE=$(awk -F "=" '/CRUISE *=/ {print $2}' $HACK_INI)
12-
CRUISE_LIST=$(awk -F "=" '/CRUISE_LIST *=/ {gusb(/^ */, "", $2);gsub(/ *; */, ";", $2);print $2}' $HACK_INI)
13-
if [ "$CRUISE" != "on" ] ; then
14-
exit 0
15-
fi
12+
CRUISE_LIST=$(awk -F "=" '/CRUISE_LIST *=/ {gsub(/^ */, "", $2);gsub(/ *; */, ";", $2);print $2}' $HACK_INI)
13+
[ "$CRUISE" != "on" ] && exit 0
14+
15+
PRODUCT_CONFIG=/atom/configs/.product_config
16+
PRODUCT_MODEL=$(awk -F "=" '/PRODUCT_MODEL *=/ {print $2}' $PRODUCT_CONFIG)
17+
[ "ATOM_CAKP1JZJP" != "$PRODUCT_MODEL" ] && exit 0
1618

1719
while : ; do
1820
IFS=";"
1921
for str in $CRUISE_LIST ; do
2022
cmd=${str%% *}
2123
param=${str#* }
22-
echo "$cmd : $param"
24+
echo `TZ=JST-9 date +"%Y/%m/%d %H:%M:%S"` : $cmd : $param
2325
if [ "$cmd" = "move" ] ; then
2426
echo "$cmd $param" | /usr/bin/nc localhost 4000
2527
fi
2628
if [ "$cmd" = "detect" ] ; then
29+
wait=${param%% *}
2730
while : ; do
2831
IFS=" "
29-
motion=`echo "waitMotion $param" | /usr/bin/nc localhost 4000`
32+
motion=`echo "waitMotion $wait" | /usr/bin/nc localhost 4000`
3033
if [ "$motion" = "timeout" ] ; then
3134
break;
3235
fi
36+
wait=${param##* }
3337
done
3438
fi
3539
if [ "$cmd" = "follow" ] ; then
40+
wait=${param%% *}
3641
while : ; do
3742
IFS=" "
38-
motion=`echo "waitMotion $param" | /usr/bin/nc localhost 4000`
43+
motion=`echo "waitMotion $wait" | /usr/bin/nc localhost 4000`
3944
if [ "$motion" = "timeout" ] ; then
4045
break;
4146
fi
@@ -44,11 +49,13 @@ while : ; do
4449
echo "move $6 $7"
4550
echo "move $6 $7" | /usr/bin/nc localhost 4000
4651
fi
52+
wait=${param##* }
4753
done
4854
IFS=";"
4955
fi
5056
if [ "$cmd" = "sleep" ] ; then
51-
sleep $param
57+
wait=${param%% *}
58+
sleep $wait
5259
fi
5360
done
54-
done
61+
done >> /tmp/log/cruise.log

‎configs/overlay_rootfs/scripts/webcmd.sh

+6
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ EOF
5050
echo "$cmd $params OK" >> /var/run/webres
5151
cmd=""
5252
fi
53+
if [ "$cmd" = "cruise" ]; then
54+
kill -9 `pidof cruise.sh`
55+
/scripts/cruise.sh &
56+
echo "$cmd $params OK" >> /var/run/webres
57+
cmd=""
58+
fi
5359
if [ "$cmd" = "lighttpd" ]; then
5460
echo "$cmd OK" >> /var/run/webres
5561
sleep 3

‎web/source/vue/Setting.vue

+87-6
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
<div class="well-transparent container">
2222
<div class="image-frame" :style="imageFrameStyle">
2323
<div class="image-frame-inner1">
24-
<ElSlider v-if="isSwing" class="tilt-slider" v-model="tilt" :min="0" :max="180" vertical :show-input-controls="false" height="100%" @input="Move" />
24+
<ElSlider v-if="isSwing" class="tilt-slider" v-model="tilt" :min="0" :max="180" vertical :show-input-controls="false" height="100%" @change="Move" @input="Move" />
2525
<ElTooltip :tabindex="-1" placement="top" :content="stillFullView?'clickで縮小します':'clickで拡大します'" effect="light" :open-delay="500">
2626
<img class="still-image" :src="stillImage" @click="stillFullView=!stillFullView">
2727
</ElTooltip>
2828
</div>
2929
<div v-if="isSwing" class="image-frame-inner2">
30-
<ElSlider class="pan-slider" v-model="pan" :min="0" :max="355" :show-input-controls="false" @input="Move" />
30+
<ElSlider class="pan-slider" v-model="pan" :min="0" :max="355" :show-input-controls="false" @change="Move" @input="Move" />
3131
</div>
3232
</div>
3333

@@ -86,6 +86,14 @@
8686
<h3>動体検知</h3>
8787
<SettingSwitch title="動体検知周期の短縮" tooltip="Alarmの無検知時間5分を30秒に短縮します" v-model="config.MINIMIZE_ALARM_CYCLE" comment="※ 変更すると設定ボタンで再起動します" />
8888

89+
<div v-if="isSwing" @click="ClearCruiseSelect">
90+
<h3>クルーズ</h3>
91+
<SettingSwitch title="クルーズ動作" tooltip="クルーズ軌道を設定し、巡回動作をします" v-model="config.CRUISE" @change="(config.CRUISE === 'on') && !cruiseList.length && AddCruise()" @click.native.stop />
92+
<div>
93+
<SettingCruise v-for="(cruise, idx) of cruiseList" :key="'timetable'+idx" v-model="cruiseList[idx]" :pan="pan" :tilt="tilt" :selected="cruiseSelect === idx" @add="AddCruise" @remove="DeleteCruise(idx)" @pan="pan=$event" @tilt="tilt=$event" @click="CruiseSelect(idx)" />
94+
</div>
95+
</div>
96+
8997
<h3>メンテナンス</h3>
9098
<SettingButton v-if="isSwing" title="Swing座標初期化" :titleOffset="2" :span="4" tooltip="Swingの座標を両側の端点当てで修正します" label="初期化" @click="MoveInit" />
9199
<SettingSwitch title="定期リスタート" tooltip="定期的に再起動する設定をします" v-model="config.REBOOT" />
@@ -121,6 +129,7 @@
121129
import SettingButton from './SettingButton.vue';
122130
import SettingDangerButton from './SettingDangerButton.vue';
123131
import SettingSchedule from './SettingSchedule.vue';
132+
import SettingCruise from './SettingCruise.vue';
124133
125134
import 'element-ui/lib/theme-chalk/tooltip.css';
126135
import 'element-ui/lib/theme-chalk/drawer.css';
@@ -137,6 +146,7 @@
137146
SettingButton,
138147
SettingDangerButton,
139148
SettingSchedule,
149+
SettingCruise,
140150
},
141151
data() {
142152
return {
@@ -177,6 +187,8 @@
177187
WEBHOOK_RECORD_EVENT: 'off',
178188
WEBHOOK_TIMELAPSE_EVENT: 'off',
179189
WEBHOOK_TIMELAPSE_FINISH: 'off',
190+
CRUISE: 'off',
191+
CRUISE_LIST: '',
180192
MINIMIZE_ALARM_CYCLE: 'off',
181193
},
182194
loginAuth: 'off',
@@ -192,6 +204,8 @@
192204
storage_cifs_record: false,
193205
storage_cifs_alarm: false,
194206
schedule: [],
207+
cruiseList: [],
208+
cruiseSelect: -1,
195209
reboot: {
196210
startTime: '02:00',
197211
endTime: '02:00',
@@ -317,6 +331,29 @@
317331
}, []);
318332
}
319333
334+
this.cruiseList = (this.config.CRUISE_LIST || '').split(';').reduce((array, cmd) => {
335+
const args = cmd.trim().split(' ');
336+
if(args[0] === 'move') {
337+
array.push({
338+
pan: parseInt(args[1]),
339+
tilt: parseInt(args[2]),
340+
});
341+
return array;
342+
}
343+
const last = array[array.length - 1];
344+
if(!last) return array;
345+
if(['detect', 'follow', 'sleep'].indexOf(args[0]) < 0) return array;
346+
last.wait = parseInt(args[1]);
347+
last.timeout = parseInt(args[2]);
348+
last.detect = true;
349+
last.follow = true;
350+
if(args[0] === 'follow') return array;
351+
last.follow = false;
352+
if(args[0] === 'detect') return array;
353+
last.detect = false;
354+
return array;
355+
}, []);
356+
320357
const status = (await axios.get('./cgi-bin/cmd.cgi').catch(err => {
321358
// eslint-disable-next-line no-console
322359
console.log(err);
@@ -328,8 +365,8 @@
328365
329366
this.latestVer = status.LATESTVER;
330367
const pos = status.MOTORPOS.split(' ');
331-
this.pan = parseFloat(pos[0]);
332-
this.tilt = parseFloat(pos[1]);
368+
this.pan = Math.round(parseFloat(pos[0]));
369+
this.tilt = Math.round(parseFloat(pos[1]));
333370
this.posValid = true;
334371
335372
if(this.config.REBOOT_SCHEDULE) {
@@ -366,6 +403,7 @@
366403
this.moving = true;
367404
await this.Exec(`move ${this.pan} ${this.tilt}`, 'socket');
368405
this.moving = false;
406+
this.moved = true;
369407
this.StillImageInterval();
370408
if(this.moveTimeout) clearTimeout(this.moveTimeout);
371409
this.moveTimeout = setTimeout(() => {
@@ -385,8 +423,15 @@
385423
}, {});
386424
if(status.MOTORPOS) {
387425
const pos = status.MOTORPOS.split(' ');
388-
this.pan = parseFloat(pos[0]);
389-
this.tilt = parseFloat(pos[1]);
426+
const pan = Math.round(parseFloat(pos[0]));
427+
const tilt = Math.round(parseFloat(pos[1]));
428+
if(this.moved) {
429+
this.moved = false;
430+
if((pan !== this.pan) || (tilt !== this.tilt)) this.Move();
431+
} else {
432+
this.pan = pan;
433+
this.tilt = tilt;
434+
}
390435
}
391436
}
392437
if(this.getposTimeout) clearTimeout(this.getposTimeout);
@@ -413,6 +458,30 @@
413458
this.schedule.splice(i, 1);
414459
if(!this.schedule.length) this.config.RECORDING_LOCAL_SCHEDULE = false;
415460
},
461+
AddCruise() {
462+
this.cruiseList.push({
463+
pan: this.pan,
464+
tilt: this.tilt,
465+
wait: 10,
466+
timeout: 10,
467+
detect: false,
468+
follow: false,
469+
});
470+
this.cruiseSelect = this.cruiseList.length - 1;
471+
},
472+
DeleteCruise(i) {
473+
this.cruiseList.splice(i, 1);
474+
if(!this.cruiseList.length) this.config.CRUISE = false;
475+
if(this.cruiseSelect === i) this.cruiseSelect = -1;
476+
},
477+
CruiseSelect(idx) {
478+
this.cruiseSelect = idx;
479+
this.pan = this.cruiseList[idx].pan;
480+
this.tilt = this.cruiseList[idx].tilt;
481+
},
482+
ClearCruiseSelect() {
483+
this.cruiseSelect = -1;
484+
},
416485
FixPath(label) {
417486
this.config[label] = this.config[label].replace(/\\/g, '/');
418487
},
@@ -485,6 +554,14 @@
485554
this.config.STORAGE_CIFS = 'off';
486555
}
487556
557+
this.config.CRUISE_LIST = this.cruiseList.reduce((str, cruise) => {
558+
str += `move ${cruise.pan} ${cruise.tilt};`;
559+
const waitMode = cruise.detect ? (cruise.follow ? 'follow' : 'detect') : 'sleep';
560+
str += `${waitMode} ${cruise.wait} ${cruise.timeout};`;
561+
return str;
562+
}, '');
563+
this.ClearCruiseSelect();
564+
488565
str = parseInt(this.reboot.startTime.slice(-2)) + ' ';
489566
str += parseInt(this.reboot.startTime.slice(0, 2)) + ' * * ';
490567
str += this.weekDays.flatMap((v, i) => this.reboot.dayOfWeekSelect.indexOf(v) < 0 ? [] : [(i + 1) % 7]).sort((a, b) => a - b).reduce((s, d) => s += (s.length ? ':' : '') + d.toString() , '');
@@ -531,6 +608,10 @@
531608
if(Object.keys(this.config).some(prop => (prop.search(/WEBHOOK/) === 0) && (this.config[prop] !== this.oldConfig[prop]))) {
532609
execCmds.push('setwebhook');
533610
}
611+
if((this.config.CRUISE !== this.oldConfig.CRUISE) ||
612+
(this.config.CRUISE_LIST !== this.oldConfig.CRUISE_LIST)) {
613+
execCmds.push('cruise restart');
614+
}
534615
}
535616
if(this.config.DIGEST !== this.oldConfig.DIGEST) execCmds.push('lighttpd');
536617

‎web/source/vue/SettingCruise.vue

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<template>
2+
<ElRow>
3+
<ElCol :offset="9" :span="14" @click.native.stop="$emit('click')">
4+
<div class="well cruise" :class="{ selected: selected }">
5+
<ElTooltip :tabindex="-1" placement="top" content="カメラの向きを設定。jpeg windowでも調整できます" effect="light" :open-delay="500">
6+
<ElRow>
7+
<ElCol :span="10">
8+
pan
9+
<ElInputNumber v-model="innerValue.pan" :min="0" :max="355" :step="5" :precision="0" size="mini" @input="Pan" />
10+
</ElCol>
11+
<ElCol :span="10">
12+
tilt
13+
<ElInputNumber v-model="innerValue.tilt" :min="0" :max="180" :step="5" :precision="0" size="mini" @input="Tilt" />
14+
</ElCol>
15+
<ElCol :offset="3" :span="1">
16+
<ElButton class="list-button" type="text" size="small" circle icon="el-icon-minus" @click="$emit('remove', $event)" />
17+
</ElCol>
18+
</ElRow>
19+
</ElTooltip>
20+
<ElRow>
21+
<ElTooltip :tabindex="-1" placement="top" content="カメラ動作後の待機時間を設定します" effect="light" :open-delay="500">
22+
<ElCol :span="14">
23+
動作後待機時間
24+
<ElInputNumber v-model="innerValue.wait" :min="10" :step="5" :precision="0" size="mini" @input="Submit" />
25+
26+
</ElCol>
27+
</ElTooltip>
28+
<ElCol :span="9">
29+
<ElTooltip :tabindex="-1" placement="top" content="待機中に動体検知すると待機時間を延長します" effect="light" :open-delay="500">
30+
<ElCheckbox v-model="innerValue.detect" @change="Submit">検知</ElCheckbox>
31+
</ElTooltip>
32+
<ElTooltip :tabindex="-1" placement="top" content="待機中に動体検知すると追尾し、待機時間を延長します" effect="light" :open-delay="500">
33+
<ElCheckbox v-model="innerValue.follow" :disabled="!innerValue.detect" @change="Submit">追尾</ElCheckbox>
34+
</ElTooltip>
35+
</ElCol>
36+
<ElCol v-if="!innerValue.detect" :span="1">
37+
<ElButton class="list-button" type="text" size="small" circle icon="el-icon-plus" @click.stop="$emit('add', $event)" />
38+
</ElCol>
39+
</ElRow>
40+
<ElRow v-if="innerValue.detect">
41+
<ElTooltip :tabindex="-1" placement="top" content="動体検知終了後の待機時間を設定します" effect="light" :open-delay="500">
42+
<ElCol :span="14">
43+
検知後待機時間
44+
<ElInputNumber v-model="innerValue.timeout" :min="10" :step="5" :precision="0" size="mini" @input="Submit" />
45+
46+
</ElCol>
47+
</ElTooltip>
48+
<ElCol :offset="9" :span="1">
49+
<ElButton class="list-button" type="text" size="small" circle icon="el-icon-plus" @click.stop="$emit('add', $event)" />
50+
</ElCol>
51+
</ElRow>
52+
</div>
53+
</ElCol>
54+
</ElRow>
55+
</template>
56+
57+
<script>
58+
import { Tooltip, InputNumber, Checkbox } from 'element-ui';
59+
import 'element-ui/lib/theme-chalk/tooltip.css';
60+
import 'element-ui/lib/theme-chalk/switch.css';
61+
import 'element-ui/lib/theme-chalk/checkbox.css';
62+
import 'element-ui/lib/theme-chalk/input-number.css';
63+
64+
export default {
65+
components: {
66+
ElTooltip: Tooltip,
67+
ElCheckbox: Checkbox,
68+
ElInputNumber: InputNumber,
69+
},
70+
props: {
71+
pan: {
72+
type: Number,
73+
required: true,
74+
},
75+
tilt: {
76+
type: Number,
77+
required: true,
78+
},
79+
value: {
80+
type: Object,
81+
required: true,
82+
},
83+
selected: {
84+
type: Boolean,
85+
default: false,
86+
},
87+
},
88+
data() {
89+
return {
90+
innerValue: this.value,
91+
};
92+
},
93+
watch: {
94+
value(v) {
95+
this.innerValue = { ...v };
96+
},
97+
pan(p) {
98+
if(this.selected) this.innerValue.pan = Math.round(p);
99+
},
100+
tilt(t) {
101+
if(this.selected) this.innerValue.tilt = Math.round(t);
102+
},
103+
},
104+
methods: {
105+
Pan() {
106+
if(this.selected) this.$emit('pan', this.innerValue.pan);
107+
this.Submit();
108+
},
109+
Tilt() {
110+
if(this.selected) this.$emit('tilt', this.innerValue.tilt);
111+
this.Submit();
112+
},
113+
Submit() {
114+
this.$emit('input', this.innerValue);
115+
},
116+
},
117+
};
118+
</script>
119+
120+
<style scoped>
121+
.cruise {
122+
display: inline-flex;
123+
flex-direction: column;
124+
width: 95%;
125+
}
126+
.selected {
127+
background-color: #f1f6f0;
128+
}
129+
.list-button {
130+
padding: 0;
131+
text-alin:right;
132+
float:right;
133+
}
134+
</style>

0 commit comments

Comments
 (0)