はじめに
Wiki IoT/Bot Computing で、裏庭に来た動物を、昼も夜も、撮影できたので紹介します。
どうも最近鹿がきているらしいので、それが撮影できたら嬉しいな、と思ってやっているのですが、今の所撮影できたのは猫ばっかりです。
昼間は上のような感じで撮影できました。
夜は上のような感じで撮影できました。
ここで今回動物撮影に成功したシステムを、「動物撮影システム」と呼ぶことにします。
Wiki IoT/Bot Computing の概要
Bot Computing/Wiki IoT は、PukiWikiのページにかかれたスクリプトをIoTのEdge端末が実行し、実行結果をスクリプトが書かれたPukiWikiのページに書き加える、というものです。
Edge端末がWikiのページの記述で遠隔操作されるBot となります。これをWiki Botと呼んでいます。
Wiki Bot が読み込むPukiWiki のページを途中で変更したり、Wiki Bot が実行するWiki ページに、別のPukiWikiのページの内容を挿入したりすることもできます。
Wiki Bot の PukiWiki のAPIとしての機能
Wiki Bot は、PukiWiki のAPI として機能します。
Read, Update, Delete
Wiki Bot によって、PukiWiki の、ページの読み込みとページの編集を行うことができます。
したがって、Wiki Botにより、Webページ操作の C (Create) R (Read) U (Update) D (Delete)のうち、RUDを行うことができることになります。このとき、ページの削除は、ページの編集によって可能です。
C (Create) は実装していないので、あらかじめ使用するページを人手によって準備しておく必要があります。
添付ファイルの操作
PukiWiki では各ページに画像や音声などのファイルを添付することができるのですが、Wiki Bot により、添付ファイルのページへのUpload、ページに添付されたファイルの名前の一覧の獲得、ページから指定したファイルの削除を行うことができます。
特定のユーザによるアクセス制御
PukiWiki は、その設定ファイルの pukiwiki.ini.php ファイルの設定により、特定のユーザによる、特定のページの読み書きの制御を行うことができますが、Wiki Bot では、特定のユーザによる、(すべての)ページの読み書きの制御を行うことができます。特定のページのみの読み書きの制御はまだ行うことができません。
システム概要
今回動物撮影システムを制作するにあたり、もともとあった、Wiki Bot による以下のIoTシステム(202503システム)のカメラを普通のRaspberry Pi カメラから、赤外線カメラに置き換え、赤外線LEDランプを加えました。
また、赤外線LEDランプをOn/Off するため、モータドライバ DRV8853
を加えました。
動きを検知したら、赤外線LEDランプをOnにして、撮影し、撮影が終わったら赤外線LEDランプをOffにする訳です。画像ファイルの数を制限するため、動きを検知して撮影したら、10分間、撮影は行わないようにしています。
使った赤外線カメラと赤外線LEDランプ。
ハードウェア
今回の動物撮影システムのハードウェア(センサBox)の外観を以下に示します。
センサBox の中身は以下のようになっています。
すでについていたPIR動きセンサのOn/Off の情報を、センサデータ取得のために使っている Raspberry Pi Pico だけでなく、映像データをUploadするために使っている Raspberry Pi (カメラRasPi)でも使うように、PIR動きセンサの出力を、カメラRasPi のGPIO端子の、GPIO18番端子にも配線しています。
赤外線LEDランプをON/Off するために、カメラRasPi の、GPIO17番端子から、DRV8853のA-in へ配線を行い、DRV8853のA-out1 とA-out2を赤外線LEDランプの+端子と-端子に接続しています。赤外線LEDランプは2つあるので、並列接続しています。
ソフトウェア
動物撮影システムは、既存の202503システムの、class-daily ファイルのPython プログラムを書き換えて実現しています。
オブジェクトページ
動物撮影システムのオブジェクトページは、既存の202503システムのものと同じです。以下の図は、12日に得られた様々なセンサから得られたデータの、1時間ごとの平均を格納した、オブジェクトページです。なお、このデータは、次の月のデータで上書きされるようになっています。
このページの、上の方にある、
https://www**********/sensors-graph-01-daily.html
をクリックすると以下のような、202503システムによって獲得された、温度、湿度、明るさ、動き、二酸化炭素濃度、気圧、におい1、におい2、微粒子の、そのページに書かれた値の時系列変化をグラフで見ることができます。
元に戻って、オブジェクトページに下の方に、添付ファイルの一覧が表示されています。この一覧のなかで、"motion-<時間>_<分>"で表されているファイルは、動物や人間の動きを検知したとき(<時間>_<分>)に撮影された画像を表しています。
例えば、"motion-13_21.jpg"のリンクをクリックすると、以下のような画像が表示されます。
動きを検知したけど、なにも撮影されていない場合も結構あります。
クラスページ
オブジェクトページの上の方に
include <URI-1> or <URI-2>
で表された、クラスページを挿入することを示した行があります。
この <URI-1>または <URI-2>に、動物撮影システムの class-daily のページが格納されています。
以下、class-daily のページの、上の方の一部です。
このクラスページ全体は以下のようになっています。
command: set read_interval=1800000
command: set exec_interval=0
command: set report_length=216
command: py daily
py: import subprocess
py: import time
py: import RPi.GPIO as GPIO
py: date=self.Pico_Time.get_now()
py: #date=2024-12-31 14:25:16.61944
py: time_x=(date.split())[1]
py: #print('time_x='+time_x)
py: time_xx=time_x.split(':')
py: hm=time_xx[0]+'_'+time_xx[1]
py: ms=time_xx[1]
py: ss=time_xx[2]
py: dec_sec=ss[0]
py: this_hour=time_xx[0]
py: #print('hm='+hm+' ms='+ms+' ss='+ss+' desec='+dec_sec)
py: print('time='+hm)
py: attach_dir='/home/pi/python/pictures/'
py: attach_name=hm+'.jpg'
py: print('attach_name='+attach_name)
py: #
py: GPIO.setmode(GPIO.BCM)
py: #
py: GPIO.setup(17,GPIO.OUT)
py: GPIO.setup(18,GPIO.IN)
py: #
py: GPIO.output(17,GPIO.LOW)
py: #
py: GPIO.output(17,GPIO.HIGH)
py: #
py: cmd_line="/usr/bin/rpicam-jpeg -o "+attach_dir+attach_name+" -t 1000 --width 640 --height 480"
py: print(cmd_line)
py: cmd_line_list=cmd_line.split()
py: subprocess.run(cmd_line_list)
py: #
py: GPIO.output(17,GPIO.LOW)
py: #
py: list=self.get_attachment_list()
py: if attach_name in list:
py: self.delete_attachment(attach_name)
py: self.upload_file(attach_dir, attach_name)
py: #line="device=camera, Date="+self.Pico_Time.get_now()+", v="+attach_name
py: #self.write_line(line)
py: #time.sleep(6)
py: # self.send_result()
py: # time.sleep(1)
py: import re
py: #print('start class_daily')
py: hours=[ '00','01','02','03','04','05','06','07','08','09',
py: '10','11','12','13','14','15','16','17','18','19',
py: '20','21','22','23']
py: days=['01','02','03','04','05','06','07','08','09','10',
py: '11','12','13','14','15','16','17','18','19','20',
py: '21','22','23','24','25','26','27','28','29','30',
py: '31']
py: dataTable={}
py: columnLabel=[]
py: rowLabel=[]
py: devnames=['motion','lx','temp','humidity','co2','pressure','smell','smell2','particle']
py: sum={}
py: ave={}
py: avemax={}
py: avemin={}
py: #print('clear_report() start')
py: self.clear_report()
py: output=""
py: url="https://www***************/"
py: regex = r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?'
py: sensor_driver=self.Pico_Wiki_Driver
py: #print('sensor_driver created.')
py: for hx in hours:
py: #print('hx='+hx)
py: page='h'+hx
py: #sensor_driver.set_url(url)
py: #sensor_driver.set_page(page)
py: uri=url+'?'+page
py: r=sensor_driver.get_result(uri)
py: #print('r='+r)
py: ra=r.splitlines()
py: #print('initialize dataTable, sum')
py: for dv in devnames:
py: ave[dv]=0
py: dataTable[dv]=[]
py: sum[dv]=0.0
py: avemax[dv]=0
py: #print('avemax[d]=0')
py: avemin[dv]=65337
py: #print('avemin[dn]=65337')
py: #print('aggregate lines have a device')
py: for line in ra:
py: #print('line='+line)
py: for dv in devnames:
py: #print('dv='+dv)
py: if 'device='+dv in line:
py: #print('dataTable[dv]=')
py: #print(dataTable[dv])
py: (dataTable[dv]).append(line)
py: #print('dataTable[dv]=')
py: #print(dataTable[dv])
py: #print('sum')
py: for dv in devnames:
py: #print('sum ... dv='+dv)
py: dev_lines=dataTable[dv]
py: for line in dev_lines:
py: #print('line='+line)
py: elements=line.split(',')
py: for e in elements:
py: if 'v=' in e:
py: lr=e.split('=')
py: r=lr[1]
py: #print('r='+r)
py: rna = re.findall(rf'({regex})', r)
py: #print(rna)
py: if len(rna)>0:
py: rn = rna[0]
py: #print('rn[0]='+rn[0])
py: fn=float(rn[0])
py: #print('fn='+str(fn))
py: sum[dv]=sum[dv]+fn
py: #print('sum['+dv+']='+str(sum[dv]))
py: #print('avemax')
py: if fn>float(avemax[dv]):
py: avemax[dv]=fn
py: #print('avemin')
py: if fn<float(avemin[dv]):
py: avemin[dv]=fn
py: #print('end of the dv')
py: if 'Date=' in e:
py: lr=e.split('=')
py: dx=lr[1]
py: #print('dx='+dx)
py: print('make output line')
py: for dv in devnames:
py: #print('dv='+dv)
py: ldt=len(dataTable[dv])
py: #print('ldt='+str(ldt))
py: if ldt>0:
py: try:
py: ave[dv]=sum[dv]/ldt
py: line= "device="+dv+", Date="+dx+", ave="+str(ave[dv])+",max="+str(avemax[dv])+",min="+str(avemin[dv])
py: #print(line)
py: self.write_line(line)
py: except Exception as e:
py: print('error in output page')
py: print(e)
py: traceback.print_stack()
py: #print('next hx')
py: #print('end hx loop')
py: self.send_result()
py: print('end send result')
py: #
py: # sensing
py: while True:
py: date=self.Pico_Time.get_now()
py: #date=2024-12-31 14:25:16.61944
py: time_x=(date.split())[1]
py: #print('time_x='+time_x)
py: time_xx=time_x.split(':')
py: hm=time_xx[0]+'_'+time_xx[1]
py: ms=time_xx[1]
py: ss=time_xx[2]
py: dec_sec=ss[0]
py: #
py: if this_hour!=time_xx[0]:
py: break
py: if GPIO.input(18)==1:
py: #print('hm='+hm+' ms='+ms+' ss='+ss+' desec='+dec_sec)
py: print('time='+hm)
py: attach_dir='/home/pi/python/pictures/'
py: attach_name='motion-'+hm+'.jpg'
py: print('attach_name='+attach_name)
py: #
py: GPIO.setmode(GPIO.BCM)
py: #
py: GPIO.setup(17,GPIO.OUT)
py: #
py: GPIO.output(17,GPIO.LOW)
py: #
py: GPIO.output(17,GPIO.HIGH)
py: #
py: cmd_line="/usr/bin/rpicam-jpeg -o "+attach_dir+attach_name+" -t 1000 --width 640 --height 480"
py: print(cmd_line)
py: cmd_line_list=cmd_line.split()
py: subprocess.run(cmd_line_list)
py: #
py: GPIO.output(17,GPIO.LOW)
py: #
py: list=self.get_attachment_list()
py: if attach_name in list:
py: self.delete_attachment(attach_name)
py: self.upload_file(attach_dir, attach_name)
py: time.sleep(600)
py: else:
py: time.sleep(1)
py:
command: end daily
command: run daily
command: set pageName="d<day>"
謝辞
本作品を制作するにあたり、その一部は、科研費 21K11858 の支援を受けています。感謝します。
その他の参考資料











