2021年5月29日土曜日

<備忘録>CTU:ムクドリ追払いマシーン

 えー、プロトタイプですが、監視カメラで動きを検知したらドローンを出動させることに成功したので、簡単にメモ書いておきます。


<本当にやりたいこと>
 家の電線にムクドリがとまったら、ドローンを出動させて即座に追い払いたい。

<動機>
 およそ1月〜3月の夕方18時頃、家の電線に大量のムクドリが飛来します(以下、写真)。立つ鳥跡を濁しまくりで、周辺には大量の糞が投下され困っております。昔、東電に鳥害対策をしてもらったが効果はイマイチなようです。

<考えたこと>
 鳥が電線にとまったことを検知したら、ドローンを出動させて即座に追い払うソリューションを考えてみました。名付けて”CTU(Counter Tori Unit:トリ対策ユニット)”。

<構成イメージ>
 ①ムクドリが電線にとまる
 ②カメラで動きを検知する
 ③ドローンに指令を送る
 ④ドローンが浮上しムクドリを威嚇する
 ⑤ムクドリが逃げる

<作り方>
0)デバイスを購入
 ・RaspberryPi 4 4GB  
4)監視カメラとドローンを連動させる
4−1)ドローン起動用のシェルスクリプトを作る(event_start.sh)
・event_start.shという名前のシェルを作る

pi@raspberrypi:~ $ nano event_start.sh 


・シェルの中身はこんな雰囲気

  GNU nano 3.2                                                         event_start.sh                                                                   


#!/bin/bash


#本シェルスクリプトの置いてあるディレクトリに移動(pythonがはくlogが適切に吐き出されるように。)

cd $(dirname $0)


#トークンを記述

token="xxxご自身のtokenを記載xxx"

#LINEにメッセージを送信

curl -X POST -H "Authorization: Bearer ${token}" -F "message = ラズパイからのメッセージstart" https://notify-api.line.me/api/notify


#ドローンを動かす。result.txtに直近実行のlogを吐けるようにしとく。

python /home/pi/Tello-Python/Single_Tello_Test/tello_test.py /home/pi/Tello-Python/Single_Tello_Test/flight_test2.txt 2>&1 | tee /home/pi/result.txt


#motionを一時停止。暴走しないように。後述。

curl http://localhost:8080/0/detection/pause



・control+O(保存)、control+X(終了)でシェルを抜けた後、シェルの実行権限をつける

pi@raspberrypi:~ $ chmod +x event_start.sh 


4−2)モーションのイベント終了時のシェルスクリプトを作る(event_end.sh)
・event_end.shという名前のシェルを作る

pi@raspberrypi:~ $ nano event_end.sh 


・シェルの中身はこんな雰囲気

  GNU nano 3.2                                                          event_end.sh                                                                    


#!/bin/bash

#トークンを記述

token="xxxご自身のtokenを記載xxx"

#LINEにメッセージを送信

curl -X POST -H "Authorization: Bearer ${token}" -F "message = ラズパイからのメッセージend" https://notify-api.line.me/api/notify



・control+O(保存)、control+X(終了)でシェルを抜けた後、シェルの実行権限をつける

pi@raspberrypi:~ $ chmod +x event_end.sh 

 
4−2)motion.confに作ったシェルスクリプト組み込む
・motionの設定ファイルを開く

pi@raspberrypi:~ $ sudo nano /etc/motion/motion.conf


・control+W(検索)で、on_event と検索し設定近くに飛ぶ
・on_event_startとon_event_endの部分にシェルスクリプトを組み込む(以下、黄色)

  GNU nano 3.2                                                    /etc/motion/motion.conf                                                               


# %v = event, %q = frame number, %t = camera id number,

# %D = changed pixels, %N = noise level,

# %i and %J = width and height of motion area,

# %K and %L = X and Y coordinates of motion center

# %C = value defined by text_event

# %f = filename with full path

# %n = number indicating filetype

# Both %f and %n are only defined for on_picture_save,

# on_movie_start and on_movie_end

# Quotation marks round string are allowed.

############################################################


# Do not sound beeps when detecting motion (default: on)

# Note: Motion never beeps when running in daemon mode.

quiet on


# Command to be executed when an event starts. (default: none)

# An event starts at first motion detected after a period of no motion defined by event_gap

#; on_event_start value

on_event_start /home/pi/event_start.sh


# Command to be executed when an event ends after a period of no motion

# (default: none). The period of no motion is defined by option event_gap.

#; on_event_end value

on_event_end /home/pi/event_end.sh


# Command to be executed when a picture (.ppm|.jpg) is saved (default: none)

# To give the filename as an argument to a command append it with %f

; on_picture_save value



4−3)シェルと同じ場所にlogフォルダを作っておく
・python実行後のログが格納されます。

pi@raspberrypi:~ $ mkdir log


5)ドローンの電源を入れて数秒待って、motionを実行する
  監視カメラが動きを検知したら、ドローンが動きはじめる

pi@raspberrypi:~ $ sudo motion


6)ドローン動作終了後、実行ログを確認(動作エラー時の確認用)
・直近のログは直下のresult.txtを確認

pi@raspberrypi:~ $ cat result.txt 


id: 0

command: command

response: ok

start time: 2021-05-29 13:08:52.257217

end_time: 2021-05-29 13:08:52.315474

duration: 0.058257


id: 1

command: takeoff

response: ok

start time: 2021-05-29 13:08:52.315591

end_time: 2021-05-29 13:08:57.701102

duration: 5.385511


id: 2

command: flip b

response: ok

start time: 2021-05-29 13:08:59.703400

end_time: 2021-05-29 13:09:03.325487

duration: 3.622087


id: 3

command: flip f

response: ok

start time: 2021-05-29 13:09:05.327844

end_time: 2021-05-29 13:09:08.921326

duration: 3.593482


id: 4

command: flip l

response: ok

start time: 2021-05-29 13:09:10.923559

end_time: 2021-05-29 13:09:14.479949

duration: 3.55639


id: 5

command: flip r

response: ok

start time: 2021-05-29 13:09:16.482158

end_time: 2021-05-29 13:09:20.089971

duration: 3.607813


id: 6

command: land

response: ok

start time: 2021-05-29 13:09:22.092242

end_time: 2021-05-29 13:09:25.904518

duration: 3.812276



・logフォルダ内のファイルで過去履歴も残っている

pi@raspberrypi:~/log $ ls

'2021-05-23 16:42:56.109620.txt'  '2021-05-23 18:13:02.157302.txt'

'2021-05-23 17:30:33.422215.txt'  '2021-05-23 18:13:52.863802.txt'

'2021-05-23 17:55:20.817894.txt'  '2021-05-23 18:14:50.656453.txt'

'2021-05-23 17:58:11.359075.txt'  '2021-05-29 13:07:00.454953.txt'

'2021-05-23 17:59:02.850064.txt'  '2021-05-29 13:08:52.256574.txt'

'2021-05-23 17:59:53.879250.txt'  '2021-05-30 12:15:42.879107.txt'

'2021-05-23 18:11:40.650572.txt'


<確認・工夫したこと>
・シェルを書く前にコマンドが単体で動くことを確認した。
 当たり前かも知れんが、event_start.shの中身は一気に書けるほど分かっているわけではなく、、コマンド単体で動くかは動作確認した。試行錯誤の連続です。

・ドローン起動のトリガーに、motionのon_event_startを使った。
 最初適当にon_picture_saveにシェルスクリプトを組み込んだところドローンが暴走した(汗)。LINEに写真を送るサンプルがここだったので。on_picture_saveはカメラで写真を保存するタイミングをトリガーとする設定らしい。動きがあると連続して複数枚の写真が連続で撮影され、次々にドローンに指令が入ったため暴走したと思われる。motionの公式ページを見てみると、on_event_startはイベント(動き)がスタートしたタイミングで1度トリガーとなる。一連の動きが終わればon_event_endでクローズ(一連の動き終了)となる。また動きがあればon_event_startが実行される。一旦これを採用してみる。

・ドローンが暴走しないようにドローン起動後にmotion一時中止を入れた。
 on_event_startもどのタイミングで再実行になるかは動き次第(いつon_event_endが実行されるかは動き次第)。ドローン飛行中に再実行されると暴走の危険性があるので、苦肉の策でシェルスクリプトにmotion自体の動きを一時中止するコマンドを入れておいた。
 ドローンが無事着陸した後も連続で動くようにするには、cronで数分後にmotion再開するとか、着陸後に再開コマンド叩くとかすれば良いと思われる(未実装)。

pi@raspberrypi:~ $ curl http://localhost:8080/0/detection/pause   #motion一時停止

pi@raspberrypi:~ $ curl http://localhost:8080/0/detection/start  #motion再開


<今後>
 実はフィールドテストは未実施。ラボで動作確認しただけ。まず3月の構想から約2ヶ月経ち、ターゲットのムクドリが飛来しなくなりました…。またターゲットが現れたとしても、カメラを屋外に設置して常時鳥の動きを観察したり、監視カメラの位置・角度やmotionの設定をチューニングをしたり、お庭でドローンを電柱の高さ約10m位まで上昇させてみたりと、やることは盛り沢山。安心・安全かつ完全自動運転までは、まだまだ道のりが長そうです。動物と自然との戦いはまだまだ続く。。。

<参考>

2021年5月23日日曜日

<備忘録>プロペラガードを装着

ある程度pythonでコントロールできるようになってきたドローンですが、部屋が狭いせいか実験中に壁に激突して危険です。最初からプロペラガードは付いているものの、転倒には弱いです。ということで、ひっくり返っても安心なように全体を覆うプロペラガードを買ってみました。


<感想>
軽くて良い!計量したところ92.5gでした。このプロペラガードがつくことでホバリングが気持ち安定した気がします(フラフラと横にずれなくなった気がする)。amazonの説明にバク転はできません、って書いてあったけど普通にできました。安全面を考えても購入した方がいいですね。

<参考>

2021年5月9日日曜日

<備忘録>飛行計画通りにドローンが動くようになった。

前回ラズパイでドローンを動かせるようになりました。さらに一歩進んでDJI社公式SDKで飛行計画を立ててみようと思います。細かいやり方は、こちら参照。ほぼここのページの通りそのままやっただけ。

1. gitで公開されているSDKサンプルをダウンロード。
2. (必要に応じて)今回自宅無線LANでStationモードでTello EDUが設定されている。そのため、tello.pyの17,18行目あたりを自分用にいじる。

  GNU nano 3.2                        tello.py                                  


import socket

import threading

import time

from stats import Stats


class Tello:

    def __init__(self):

        self.local_ip = ''

        self.local_port = 8889

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # socke$

        self.socket.bind((self.local_ip, self.local_port))


        # thread for receiving cmd ack

        self.receive_thread = threading.Thread(target=self._receive_thread)

        self.receive_thread.daemon = True

        self.receive_thread.start()


#        self.tello_ip = '192.168.10.1'  #Tello EDUのデフォルトのIPはコメントアウトしておく。

        self.tello_ip = '192.168.xx.cc'  #ここをTello EDUに振られたIPアドレスにしておく。

        self.tello_port = 8889

        self.tello_adderss = (self.tello_ip, self.tello_port)

        self.log = []


        self.MAX_TIME_OUT = 15.0



3. 飛行計画用のファイルを何個か準備する。今回5個準備した。
  デフォルトでは、command.txtがあるのでこれをいじる感じ。

3-1. 離陸して5秒待って着陸する飛行計画

  GNU nano 3.2                      command.txt                                 


command

takeoff

delay 5

land



3−2. 離陸して2秒待って着陸する飛行計画

  GNU nano 3.2                      command2.txt                                


command

takeoff

delay 2

land



3−3. 離陸して左に20cm、後ろに20cm、右に20cm、前に20cm移動し着陸する飛行計画

  GNU nano 3.2                    flight_test.txt                               


command

takeoff

delay 2

left 20

delay 2

back 20

delay 2

right 20

delay 2

forward 20

delay 2

land


3−4. 離陸してバク転、前転、左回転、右回転し着陸する飛行計画

  GNU nano 3.2                    flight_test2.txt                              


command

takeoff

delay 2

flip b

delay 2

flip f

delay 2

flip l

delay 2

flip r

delay 2

land



3−5. 離陸して50cm上昇、360度時計回り旋回、50cm下降、360度反時計周り旋回し着陸する飛行計画

  GNU nano 3.2                    flight_test3.txt                    変更済み  


command

takeoff

delay 2

up 50

delay 2

cw 360

delay 2

down 50

delay 2

ccw 360

delay 2

land



4. Single_Tello_Testを動かしてみる。引数ファイルを指定しPythonを実行する。一応成功。
  実行後ログ等出力されるが詳細割愛。
  ※このSDKはPython2系のようだ。前回のサンプルはPython3系だったが。

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ python tello_test.py command.txt

(詳細割愛)

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ python tello_test.py command2.txt



pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ python tello_test.py flight_test.txt



pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ python tello_test.py flight_test2.txt



pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ python tello_test.py flight_test3.txt



<所感>
・計画通りにドローンを操作できた。
・結構本体が熱を持つ。冷却が必要か??
・狭い部屋でやってたのでたまに壁に激突し、本体が壊れるんじゃないかと超焦る。ホバリングがなぜか安定しない。その場合プログラムが最後まで実行できていないため次回実行時にソケットエラーが出るので、killしないと実行できない。

・ソケットエラー

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ python tello_test.py command2.txt

Traceback (most recent call last):

  File "tello_test.py", line 13, in <module>

    tello = Tello()

  File "/home/pi/Tello-Python/Single_Tello_Test/tello.py", line 11, in __init__

    self.socket.bind((self.local_ip, self.local_port))

  File "/usr/lib/python2.7/socket.py", line 228, in meth

    return getattr(self._sock,name)(*args)

socket.error: [Errno 98] Address already in use


・プロセスIDを調べてプロセスを強制killする(今回20201)

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ ps -fA | grep python

pi         685     1  0  5月02 ?      00:00:00 /usr/bin/python3 /usr/share/system-config-printer/applet.py

pi       20201 20030 14 16:33 pts/0    00:00:33 python tello_test.py command2.txt

pi       20259 20030  0 16:36 pts/0    00:00:00 grep --color=auto python

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ sudo lsof -i:8889

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME

python  20201   pi    4u  IPv4 469376      0t0  UDP *:8889 

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ 

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ kill -9 20201

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ ps -fA | grep python

pi         685     1  0  5月02 ?      00:00:00 /usr/bin/python3 /usr/share/system-config-printer/applet.py

pi       20268 20030  0 16:38 pts/0    00:00:00 grep --color=auto python

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ 

pi@raspberrypi:~/Tello-Python/Single_Tello_Test $ sudo lsof -i:8889

[1]+  強制終了            python tello_test.py command2.txt



<参考>

2021年5月8日土曜日

<備忘録>ラズパイはドローンを動かせるようになった。

 ドローンを買っちゃいました。きっかけはsora:share事業を頑張っている同期に影響を受けたのと、最近ラズパイをゲットしたんでなんか動かしてみようかと。感想、これはヤバイ、くそオモロイ!

 ドローンは素人でしたが、ググってみてScratchPythonでもコントロールできそうな「DJI社 Tello EDU」にしてみました(中国製排除の波は無視)。そしてTelloシリーズの中でも、TelloとTello EDUってのがあって、主な違いは後者の方が最新のSDKに対応しているのと、自宅の無線APに接続できること。前者はTello本体が無線AP(親機)になり自宅ネットでは使えない。


以下、実験的にやってみたこと。忘れずにメモメモ。

<構成イメージ>

<実験1. iPhoneで動かしてみる>
 詳細割愛。アプリインストール後、iPhoneで簡単に操作できます。

<実験2. iMacで動かしてみる>
 2.1 Python3をダウンロード、インストール。
 2.2 公式サイトのSDK資料を見て、サンプルPythonコードをダウンロード。
 2.3 Tello EDUの電源を入れる。
 2.4 iMacのWi-Fiより「TELLO-xxxxx」(xxxxは数字)を探して接続する。
   ここでiMacとTello EDUはスタンドアロン状態になる。
 2.5 ターミナルを開いて、サンプルPythonコードを動かす。
   コマンドを打ってインタラクティブにドローンを操れます。
$ python3 --version   #一応Pythonのバージョン確認

Python 3.9.5

$ python3 Tello3.py   #サンプルコード実行



Tello Python3 Demo.


Tello: command takeoff land flip forward back left right 

       up down cw ccw speed speed?


end -- quit demo.


command        #接続コマンド。まずcommandと打って、本体と接続する

ok        #成功

takeoff     #takeoffを打つとドローンが離陸!

ok        #成功

land       #landを打つとドローンが着陸!

ok        #成功

end       #終了コマンド

...


<実験3. ラズパイで動かしてみる>
 3.1 iMacでTello EDUを無線クライアント(子機)に変更(Stationモード)。

$ python3 Tello3.py    #サンプルコード実行



Tello Python3 Demo.


Tello: command takeoff land flip forward back left right 

       up down cw ccw speed speed?


end -- quit demo.


command        #接続コマンド

ok        #成功

ap xxxxx yyyyy  #xxxxxには自宅無線APのSSIDを入れる。yyyyyにはパスワードを入れる。

OK,drone will reboot in 3s  #Tello本体が3秒後にリブートする。

end       #終了コマンド

...

  ※Stationモードは本体の電源を切っても保存されたままになる。
  ※もし初期状態(本体が無線AP(親機))に戻したい場合は、電源を5秒長押しすれば元に戻る。

 3.2 自宅無線ルータにログインし、Tello EDUに振られたIPアドレスをメモる。
 3.3 ラズパイにSSHする。
 3.4 ラズパイ上でサンプルPythonコードを取得。別名コピー。メモったIPアドレスに書き換え保存。

pi@raspberrypi:~ $ curl -OL https://dl-cdn.ryzerobotics.com/downloads/tello/20180222/Tello3.py

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

100  1296  100  1296    0     0   1001      0  0:00:01  0:00:01 --:--:--  1000

pi@raspberrypi:~ $ 

pi@raspberrypi:~ $ cp Tello3.py Tello3-stmode.py

pi@raspberrypi:~ $ 

pi@raspberrypi:~ $ nano Tello3-stmode.py 


  GNU nano 3.2                    Tello3-stmode.py                              


#

# Tello Python3 Control Demo 

#

# http://www.ryzerobotics.com/

#

# 1/1/2018


import threading 

import socket

import sys

import time



host = ''

port = 9000

locaddr = (host,port) 



# Create a UDP socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)


tello_address = ('192.168.10.1', 8889)    #ここのIPアドレスをメモったIPアドレスに書き換え保存。


sock.bind(locaddr)

・・・(以下略)



 3.5 保存したPythonコードを動かす。
   コマンドを打ってインタラクティブにドローンを操れます。

pi@raspberrypi:~ $ python3 Tello3-stmode.py #Stationモードのサンプルコード実行



Tello Python3 Demo.


Tello: command takeoff land flip forward back left right 

       up down cw ccw speed speed?


end -- quit demo.


command        #接続コマンド

ok        #成功

takeoff     #takeoffを打つとドローンが離陸!

ok        #成功

land       #landを打つとドローンが着陸!

ok        #成功

end       #終了コマンド

...


<感想と今後>
・これはヤバイ、くそオモロイ。
・Telloのバッテリーは10分位で切れます。長時間利用の場合は予備バッテリーが必要かも。
・自宅WiFiの届く範囲で屋外実験を予定。国土交通省(航空法)と一般常識の範囲内で。(Tello EDU=87g)

<参考>