私的AI研究会 > 2軸カメラ台
● サーボモーターSG-90 とドライバー基板PCA-9865 を準備する。
● サーボモーターの電源は別途5Vを供給する。百均のUSB充電ケーブルを利用した。
pi@raspberrypi:~ $ sudo i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: 70 -- -- -- -- -- -- --
40 に接続。
pi@raspberrypi:~ $ sudo vi /etc/webiopi/config : [SCRIPTS] # Load custom scripts syntax : # name = sourcefile # each sourcefile may have setup, loop and destroy functions and macros #myscript = /home/pi/webiopi/examples/scripts/macros/script.py #myscript = /home/pi/webiopi/callmacro_leds/script.py #myscript = /home/pi/webiopi/bb/06/script.py #myscript = /home/pi/webiopi/my06/script.py : [HTTP] # Use doc-root to change default HTML and resource files location #doc-root = /home/pi/webiopi/examples/scripts/macros #doc-root = /home/pi/webiopi/callmacro_leds/ #doc-root = /home/pi/webiopi/bb/06/ #doc-root = /home/pi/webiopi/my06/ : [DEVICES] pwm0 = PCA9685
pi@raspberrypi:~ $ sudo service webiopi start
pi@raspberrypi:~ $ ps ax |grep webiopi 1612 ? Ssl 0:00 /usr/bin/python3 -m webiopi -l /var/log/webiopi -c /etc/webiopi/config 1619 pts/0 S+ 0:00 grep --color=auto webiopi
● 表皮された画面から「Device Monitor」を選択する。
● 16チャネルのサーボモーターの操作スライダーが表皮され操作できることを確認。
pi@raspberrypi:~ $ sudo pip install adafruit-pca9685 Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple Collecting adafruit-pca9685 : : Successfully installed Adafruit-GPIO-1.0.3 adafruit-pca9685-1.0.1 adafruit-pureio-1.0.1
pi@raspberrypi:~ $ sudo pip3 install Adafruit_PCA9685 Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple Collecting Adafruit_PCA9685 : : Successfully installed Adafruit-GPIO-1.0.3 Adafruit-PCA9685-1.0.1 adafruit-pureio-1.0.1
pi@raspberrypi:~ $ pip freeze Adafruit-GPIO==1.0.3 Adafruit-PCA9685==1.0.1 Adafruit-PureIO==1.0.1 :
pi@raspberrypi:~ $ python Python 2.7.16 (default, Oct 10 2019, 22:02:15) [GCC 8.3.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import Adafruit_PCA9685 >>> pwm = Adafruit_PCA9685.PCA9685() >>> pwm.set_pwm_freq(60) >>> pwm.set_pwm(0, 0, 120) >>> pwm.set_pwm(0, 0, 480) >>> pwm.set_pwm(0, 0, 400) >>> pwm.set_pwm(0, 0, 390) >>> pwm.set_pwm(0, 0, 420) >>> pwm.set_pwm(0, 0, 500) >>> pwm.set_pwm(0, 0, 0) >>> quit() pi@raspberrypi:~ $
pi@raspberrypi:~ $ python3 Python 3.7.3 (default, Jul 25 2020, 13:03:44) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import Adafruit_PCA9685 >>> pwm = Adafruit_PCA9685.PCA9685() >>> pwm.set_pwm_freq(60) >>> pwm.set_pwm(0, 0, 500) >>> pwm.set_pwm(0, 0, 300) >>> pwm.set_pwm(0, 0, 390) >>> pwm.set_pwm(0, 0, 550) >>> pwm.set_pwm(0, 0, 0) >>> ←Ctrl+D pi@raspberrypi:~ $
pi@raspberrypi:~/Programs $ vi servo.py #! /usr/bin/env python import time import Adafruit_PCA9685 pwm = Adafruit_PCA9685.PCA9685() pwm.set_pwm_freq(60) while True: pwm.set_pwm(0, 0, 300) time.sleep(1) pwm.set_pwm(1, 0, 300) time.sleep(1) pwm.set_pwm(1, 0, 450) time.sleep(1) pwm.set_pwm(0, 0, 450) time.sleep(1) pwm.set_pwm(1, 0, 300) time.sleep(1) pwm.set_pwm(1, 0, 450) time.sleep(1)
pi@raspberrypi:~/Programs $ vi servo_pca9685.py #!/usr/bin/env python # -*- coding: utf-8 -*- import Adafruit_PCA9685 import time class servo_Class: #ChannelはPCA9685のサーボモータの接続チャンネル #ZeroOffsetはサーボモータの基準位置調節用パラメータ def __init__(self, Channel, ZeroOffset): self.Channel = Channel self.ZeroOffset = ZeroOffset #Adafruit_PCA9685の初期化 self.pwm = Adafruit_PCA9685.PCA9685(address=0x40) self.pwm.set_pwm_freq(60) # 角度設定 def SetPos(self,pos): #PCA9685はパルスで角度を制御しており、パルス150~650が角度0~180に対応 pulse = int((650-150)*pos/180+150+self.ZeroOffset) self.pwm.set_pwm(self.Channel, 0, pulse) # 終了処理 def Cleanup(self): #サーボを90°にセット self.SetPos(90) if __name__ == '__main__': Servo0 = servo_Class(Channel=0, ZeroOffset=0) Servo1 = servo_Class(Channel=1, ZeroOffset=0) try: while True: print ('30') Servo0.SetPos(30) Servo1.SetPos(30) time.sleep(1) print ('90') Servo0.SetPos(90) Servo1.SetPos(90) time.sleep(1) print ('150') Servo0.SetPos(150) Servo1.SetPos(150) time.sleep(1) print ('90') Servo0.SetPos(90) Servo1.SetPos(90) time.sleep(1) except KeyboardInterrupt: print("\nCtl+C") except Exception as e: print(str(e)) finally: Servo0.Cleanup() Servo1.Cleanup() print("\nexit program")
pi@raspberrypi:~ $ sudo ./webiopi/streamer-start.sh mjpg_streamer started
pi@raspberrypi:~ $ sudo ./webiopi/streamer-stop.sh 1010 mjpg_streamer stopped
pi@raspberrypi:~ $ ps ax |grep mjpg_streamer 3523 pts/0 S+ 0:00 grep --color=auto mjpg_streamer
pi@raspberrypi:~ $ sudo service webiopi start
pi@raspberrypi:~ $ sudo service webiopi stop
pi@raspberrypi:~ $ ps ax |grep webiopi
1612 ? Ssl 0:00 /usr/bin/python3 -m webiopi -l /var/log/webiopi -c /etc/webiopi/config 1619 pts/0 S+ 0:00 grep --color=auto webiopi
http://192.168.xxx.xxx:8000
webiopi.setDebug()
pi@raspberrypi:~ $ sudo webiopi -c /etc/webiopi/config -d※ 終了は「CTRL+C」の後、「CTRL+Z」
● 前節の回路を使用する。
● サーボモーターの電源は別途5Vを供給する。
● カメラと上記の回路以外のハードウェアは外しておく。
pi@raspberrypi:~ $ cd /home/pi/webiopi/my08 pi@raspberrypi:~/webiopi/my08 $ ls index.html javascript.js js script.py styles.css
pi@raspberrypi:~/webiopi/my08 $ vi index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no"> <title>6 - クローラーの操作 (2軸コントロールカメラつき)</title> <!-- jQuery UI --> <link rel="stylesheet" href="js/jquery-ui.min.css"> <script src="js/jquery-1.12.4.min.js"></script> <script src="js/jquery-ui.min.js"></script> <script src="js/jquery.ui.touch-punch.js"></script> <!-- WebIOPi --> <script src="js/require.js"></script> <script> require(["/webiopi.js", "javascript.js"], function(){ webiopi().ready( initialize_webiopi ); }); </script> </head> <body> <div align="center"> <div id="box"> <span id="touchArea"><canvas id="canvas" oncontextmenu="return false;"></canvas></span> <span id="slider1"></span> </div> <div id="slider0"></div> </div> <div align="center"> <p>【タッチ操作】→ タッチしている間だけモーターは回転し、タッチを離すと静止</p> <p>【マウス操作】→ マウスをクリックした位置により、モーターが回転を開始。中央付近のクリックでモーター停止。</p> </dev> </body> </html>
pi@raspberrypi:~/webiopi/my08 $ vi stile.css #touchArea{ -ms-touch-action: none; touch-action: none; } #slider1 { display: inline-block; } .ui-slider .ui-slider-handle { width: 30px; height: 30px; }
pi@raspberrypi:~/webiopi/my08 $ vi javascript.js // タッチのサポート状況のチェック用変数 var support = { pointer: window.navigator.pointerEnabled, mspointer: window.navigator.msPointerEnabled, touch: 'ontouchstart' in window }; // タッチの場合わけ。pointer系:IE11以降、MSPointer系:IE10、touch系:android、iPhone、iPad var touchStart = support.pointer ? 'pointerdown' : support.mspointer ? 'MSPointerDown' : 'touchstart'; var touchMove = support.pointer ? 'pointermove' : support.mspointer ? 'MSPointerMove' : 'touchmove'; var touchEnd = support.pointer ? 'pointerup' : support.mspointer ? 'MSPointerUp' : 'touchend'; function initialize_webiopi(){ // webiopiの準備が終わってからstyles.cssを適用する applyCustomCss('styles.css'); mCanvas = document.getElementById("canvas"); mCtx = mCanvas.getContext('2d'); resize_canvas(); // タッチエリアの設定 var touchArea = $("#touchArea")[0]; // タッチイベントのイベントリスナーの登録 touchArea.addEventListener(touchStart, touchEvent, false); touchArea.addEventListener(touchMove, touchEvent, false); touchArea.addEventListener(touchEnd, touchEndEvent, false); // クリックイベントのイベントリスナーの登録 touchArea.addEventListener("click", clickEvent, false); // iOSでcanvas上のスクロールを抑制 document.addEventListener("touchstart", preventScroll, false); document.addEventListener("touchmove", preventScroll, false); document.addEventListener("touchend", preventScroll, false); // Firefoxで画面の回転を検出 var mqOrientation = window.matchMedia("(orientation: portrait)"); mqOrientation.addListener(function() { resize_canvas(); }); // ウインドウサイズの変更を検出 window.addEventListener('resize', function (event) { resize_canvas(); }); // GPIOの状態を監視しない webiopi().refreshGPIO(false); } // iOSで画面の回転を検出 window.onorientationchange = function() { var iR = Math.abs( window.orientation ); if ( iR == 0 || iR == 90 ){ resize_canvas(); } } // スライダの最小値、最大値、刻み幅、初期値 var sliderMin = 0; var sliderMax = 20; var sliderStep = 1; var sliderValue = sliderMax/2; $(function() { var sliderHandler1 = function(e, ui){ var ratio = ui.value/sliderMax; // サーボの回転の向きを逆にしたい場合次の行を無効に //ratio = 1.0 - ratio; webiopi().callMacro("setPCA9685PWM", [1, ratio, commandID++]); }; var sliderHandler0 = function(e, ui){ var ratio = ui.value/sliderMax; // サーボの回転の向きを逆にしたい場合次の行を無効に //ratio = 1.0 - ratio; webiopi().callMacro("setPCA9685PWM", [0, ratio, commandID++]); }; $( "#slider1" ).slider({ orientation: "vertical", min: sliderMin, max: sliderMax, step: sliderStep, value: sliderValue, change: sliderHandler1, slide: sliderHandler1 }); $( "#slider0" ).slider({ orientation: "horizontal", min: sliderMin, max: sliderMax, step: sliderStep, value: sliderValue, change: sliderHandler0, slide: sliderHandler0 }); }); // 前に送信したデューティー比を覚えておく var rate25Prev = 0; var rate24Prev = 0; var rate23Prev = 0; var rate22Prev = 0; // デューティー比がth (0.0~1.0) 以上変化した時のみ値を送信 var th = 0.1; // モーターの最大速度 (0.0~1.0)。モーターを保護する意味で1.0にはしない方が良い var maxSpeed = 0.7; // 命令送信ごとに増加するIDを作成(iOSのSafariでPOSTがキャッシュされることの対策) var commandID = 0; var mCount = 0; var mCanvas; var mCtx; var mImg1; var mImg2; var mImgArrow; var mWidth = 640; var mHeight = 480; var host = location.host; var hostname = host.split(":")[0]; var port= 9000; var URL1 = 'http://' + hostname + ':' + port + '/?action=snapshot'; //var URL2 = 'http://' + hostname + ':8000/bb/06/img/CrawlerControllerTrans.png'; var URL2 = 'http://' + hostname + ':8000/img/CrawlerControllerTrans.png'; var mTouchWidth; var mTouchHeight; var mTouchOffsetTop; var mTouchOffsetLeft; function imageSetup(){ mImg1 = new Image(); mImg2 = new Image(); mImgArrow = new Image(); mImg1.src = URL1 +'&'+(mCount++); mImgArrow.src = URL2; mImg1.onload = function() { mImg2.src = URL1 + '&' + (mCount++); mCtx.drawImage(mImg1, 0, 0, mWidth, mHeight); mCtx.drawImage(mImgArrow, 0, 0, mWidth, mHeight); }; mImg2.onload = function() { mImg1.src =URL1 + '&' + (mCount++); mCtx.drawImage(mImg2, 0, 0, mWidth, mHeight); mCtx.drawImage(mImgArrow, 0, 0, mWidth, mHeight); }; } function touchEvent(e){ e.preventDefault(); // タッチ中のイベントのみ捕捉(IE) if(support.pointer || support.mspointer){ if(e.pointerType != 'touch' && e.pointerType != 2){ return; } } var touch = (support.pointer || support.mspointer) ? e : e.touches[0]; // エリア外のタッチを無視 if(touch.pageX < mTouchOffsetLeft || touch.pageX >= mTouchOffsetLeft + mTouchWidth || touch.pageY < mTouchOffsetTop || touch.pageY >= mTouchOffsetTop + mTouchHeight){ return; } if(touch.pageX < mTouchOffsetLeft + mTouchWidth/3){ // 左旋回 var rate = maxSpeed*(mTouchOffsetLeft + mTouchWidth/3-touch.pageX)/(mTouchWidth/3); // 前回送信時と値が大きく違うときのみ送信 if(Math.abs(rate-rate24Prev)>th || Math.abs(rate-rate23Prev)>th){ webiopi().callMacro("pwm4Write", [0, rate, rate, 0, commandID++]); rate25Prev = 0; rate24Prev = rate; rate23Prev = rate; rate22Prev = 0; } }else if(touch.pageX < mTouchOffsetLeft + 2*mTouchWidth/3){ // 前後移動 // 左右の車輪の速さの違いの補正 var modL = (1.2-0.8)*(touch.pageX - mTouchOffsetLeft -mTouchWidth/3)/(mTouchWidth/3) + 0.8; var modR = (0.8-1.2)*(touch.pageX - mTouchOffsetLeft - mTouchWidth/3)/(mTouchWidth/3) + 1.2; if(touch.pageY < mTouchOffsetTop + mTouchHeight/2){ var rate = maxSpeed*(mTouchHeight/2 + mTouchOffsetTop - touch.pageY)/(mTouchHeight/2); modL *= rate; modR *= rate; if(modL > 1.0){ modL = 1.0; } if(modR > 1.0){ modR = 1.0; } // 前回送信時と値が大きく違うときのみ送信 if(Math.abs(modL-rate25Prev)>th || Math.abs(modR-rate23Prev)>th){ webiopi().callMacro("pwm4Write", [modL, 0, modR, 0, commandID++]); rate25Prev = modL; rate24Prev = 0; rate23Prev = modR; rate22Prev = 0; } }else{ var rate = maxSpeed*(touch.pageY - mTouchOffsetTop - mTouchHeight/2)/(mTouchHeight/2); modL *= rate; modR *= rate; if(modL > 1.0){ modL = 1.0; } if(modR > 1.0){ modR = 1.0; } // 前回送信時と値が大きく違うときのみ送信 if(Math.abs(modL-rate24Prev)>th || Math.abs(modR-rate22Prev)>th){ webiopi().callMacro("pwm4Write", [0, modL, 0, modR, commandID++]); rate25Prev = 0; rate24Prev = modL; rate23Prev = 0; rate22Prev = modR; } } }else{ // 右旋回 var rate = maxSpeed*(touch.pageX - mTouchOffsetLeft - 2*mTouchWidth/3)/(mTouchWidth/3); // 前回送信時と値が大きく違うときのみ送信 if(Math.abs(rate-rate25Prev)>th || Math.abs(rate-rate22Prev)>th){ webiopi().callMacro("pwm4Write", [rate, 0, 0, rate, commandID++]); rate25Prev = rate; rate24Prev = 0; rate23Prev = 0; rate22Prev = rate; } } } // タッチ終了時のイベントリスナー function touchEndEvent(e){ e.preventDefault(); webiopi().callMacro("pwm4Write", [0, 0, 0, 0, commandID++]); rate25Prev = 0; rate24Prev = 0; rate23Prev = 0; rate22Prev = 0; } // クリック時のイベントリスナー(主にPC用) function clickEvent(e){ e.preventDefault(); // タッチによるクリックは無視(IE) if(support.pointer || support.mspointer){ if(e.pointerType == 'touch' || e.pointerType == 2){ return; } } // エリア外のクリックを無視 if(e.pageX < mTouchOffsetLeft || e.pageX >= mTouchOffsetLeft + mTouchWidth || e.pageY < mTouchOffsetTop || e.pageY >= mTouchOffsetTop + mTouchHeight){ return; } if(e.pageX < mTouchOffsetLeft + mTouchWidth/3){ // 左旋回 var rate = maxSpeed*(mTouchOffsetLeft + mTouchWidth/3 - e.pageX)/(mTouchWidth/3); webiopi().callMacro("pwm4Write", [0, rate, rate, 0, commandID++]); rate25Prev = 0; rate24Prev = rate; rate23Prev = rate; rate22Prev = 0; }else if(e.pageX < mTouchOffsetLeft + 2*mTouchWidth/3){ // 前後移動 // 左右の車輪の速さの違いの補正 var modL = (1.2-0.8)*(e.pageX - mTouchOffsetLeft -mTouchWidth/3)/(mTouchWidth/3) + 0.8; var modR = (0.8-1.2)*(e.pageX - mTouchOffsetLeft - mTouchWidth/3)/(mTouchWidth/3) + 1.2; if(e.pageY >= mTouchOffsetTop + 2*mTouchHeight/5 && e.pageY < mTouchOffsetTop + 3*mTouchHeight/5){ webiopi().callMacro("pwm4Write", [0, 0, 0, 0, commandID++]); rate25Prev = 0; rate24Prev = 0; rate23Prev = 0; rate22Prev = 0; }else if(e.pageY < mTouchOffsetTop + mTouchHeight/2){ var rate = maxSpeed*(mTouchOffsetTop + mTouchHeight/2 - e.pageY)/(mTouchHeight/2); modL *= rate; modR *= rate; if(modL > 1.0){ modL = 1.0; } if(modR > 1.0){ modR = 1.0; } webiopi().callMacro("pwm4Write", [modL, 0, modR, 0, commandID++]); rate25Prev = modL; rate24Prev = 0; rate23Prev = modR; rate22Prev = 0; }else{ var rate = maxSpeed*(e.pageY - mTouchOffsetTop - mTouchHeight/2)/(mTouchHeight/2); modL *= rate; modR *= rate; if(modL > 1.0){ modL = 1.0; } if(modR > 1.0){ modR = 1.0; } webiopi().callMacro("pwm4Write", [0, modL, 0, modR, commandID++]); rate25Prev = 0; rate24Prev = modL; rate23Prev = 0; rate22Prev = modR; } }else{ // 右旋回 var rate = maxSpeed*(e.pageX - mTouchOffsetLeft - 2*mTouchWidth/3)/(mTouchWidth/3); webiopi().callMacro("pwm4Write", [rate, 0, 0, rate, commandID++]); rate25Prev = rate; rate24Prev = 0; rate23Prev = 0; rate22Prev = rate; } } function resize_canvas(){ if($(window).width() < 4*$(window).height()/3){ isPortrait = true; }else{ isPortrait = false; } if(isPortrait){ mTouchWidth = 0.9*$(window).width(); mTouchHeight = 3*mTouchWidth/4; }else{ mTouchHeight = 0.9*$(window).height(); mTouchWidth = 4*mTouchHeight/3; } mWidth = mTouchWidth; mHeight = mTouchHeight; mCanvas.width = mWidth; mCanvas.height = mHeight mTouchOffsetLeft = $("#canvas").offset().left; mTouchOffsetTop = $("#canvas").offset().top; $( "#slider1" ).height(mHeight); $( "#slider0" ).width(mWidth); imageSetup(); } function applyCustomCss(custom_css){ var head = document.getElementsByTagName('head')[0]; var style = document.createElement('link'); style.rel = "stylesheet"; style.type = 'text/css'; style.href = custom_css; head.appendChild(style); }
pi@raspberrypi:~/webiopi/my08 $ vi script.py #!/usr/bin/env python # -*- coding: utf-8 -*- import webiopi import time import subprocess import Adafruit_PCA9685 # デバッグ出力を有効に webiopi.setDebug() # GPIOライブラリの取得 GPIO = webiopi.GPIO # PCA9685サーボモーターコントロール class servo_Class: #ChannelはPCA9685のサーボモータの接続チャンネル #ZeroOffsetはサーボモータの基準位置調節用パラメータ def __init__(self, Channel, ZeroOffset): self.Channel = Channel self.ZeroOffset = ZeroOffset #Adafruit_PCA9685の初期化 self.pwm = Adafruit_PCA9685.PCA9685(address=0x40) self.pwm.set_pwm_freq(60) # 角度設定 def SetPos(self,pos): #PCA9685はパルスで角度を制御しており、パルス150~650が角度0~180に対応 pulse = int((650-150)*pos/180+150+self.ZeroOffset) self.pwm.set_pwm(self.Channel, 0, pulse) # 終了処理 def Cleanup(self): #サーボを90°にセット self.SetPos(90) Servo0 = servo_Class(Channel=0, ZeroOffset=0) # Pan Servo1 = servo_Class(Channel=1, ZeroOffset=0) # Tilt PWM1 = 25 # R AIN1 PWM2 = 24 # AIN2 PWM3 = 23 # L BIN1 PWM4 = 22 # BIN2 LED1 = 21 # SW LED2 = 12 # R LED LED3 = 13 # L LED led2_mode = 0 # 右LED 0 = OFF, 1 = ON led3_mode = 0 # 左LED 0 = OFF, 1 = ON # WebIOPiの起動時に呼ばれる関数 def setup(): webiopi.debug("Script with macros - Setup") subprocess.call("aplay -q /home/pi/Voice/webiopi-start.wav", shell=True) # GPIOのセットアップ GPIO.setFunction(PWM1, GPIO.PWM) GPIO.setFunction(PWM2, GPIO.PWM) GPIO.setFunction(PWM3, GPIO.PWM) GPIO.setFunction(PWM4, GPIO.PWM) # 初期のデューティー比を0%に(静止状態) GPIO.pwmWrite(PWM1, 0) GPIO.pwmWrite(PWM2, 0) GPIO.pwmWrite(PWM3, 0) GPIO.pwmWrite(PWM4, 0) # LED GPIO.setFunction(LED1, GPIO.OUT) GPIO.setFunction(LED2, GPIO.OUT) GPIO.setFunction(LED3, GPIO.OUT) # LED 初期設定 GPIO.digitalWrite(LED1, 1) GPIO.digitalWrite(LED2, 0) GPIO.digitalWrite(LED3, 0) # WebIOPiにより繰り返される関数 def loop(): global led2_mode, led3_mode for num in range(10): if led2_mode >= 0 and led3_mode >= 0: if led2_mode == 0: GPIO.digitalWrite(LED2, 0) else: GPIO.digitalWrite(LED2, 1) if led3_mode == 0: GPIO.digitalWrite(LED3, 0) else: GPIO.digitalWrite(LED3, 1) webiopi.sleep(0.25) if led2_mode >= 0 and led3_mode >= 0: if led2_mode == 1: GPIO.digitalWrite(LED2, 1) else: GPIO.digitalWrite(LED2, 0) if led3_mode == 1: GPIO.digitalWrite(LED3, 1) else: GPIO.digitalWrite(LED3, 0) webiopi.sleep(0.25) # WebIOPi終了時に呼ばれる関数 def destroy(): global led2_mode, led3_mode webiopi.debug("Script with macros - Destroy") subprocess.call("aplay -q /home/pi/Voice/webiopi-stop.wav", shell=True) # GPIO関数のリセット(入力にセットすることで行う) GPIO.setFunction(PWM1, GPIO.IN) GPIO.setFunction(PWM2, GPIO.IN) GPIO.setFunction(PWM3, GPIO.IN) GPIO.setFunction(PWM4, GPIO.IN) led2_mode = -1 led3_mode = -1 GPIO.setFunction(LED1, GPIO.IN) GPIO.setFunction(LED2, GPIO.IN) GPIO.setFunction(LED3, GPIO.IN) # カメラを中央に戻す Servo0.Cleanup() Servo1.Cleanup() # 4つのPWMにデューティー比をまとめてセットするためのマクロ # commandIDは、iOSのSafariでPOSTがキャッシュされることへの対策 @webiopi.macro def pwm4Write(duty1, duty2, duty3, duty4, commandID): global led2_mode, led3_mode GPIO.pwmWrite(PWM1, float(duty1)) GPIO.pwmWrite(PWM2, float(duty2)) GPIO.pwmWrite(PWM3, float(duty3)) GPIO.pwmWrite(PWM4, float(duty4)) d1 = int(float(duty1) * 100.0) d2 = int(float(duty2) * 100.0) d3 = int(float(duty3) * 100.0) d4 = int(float(duty4) * 100.0) if d1 == 0 and d2 == 0: led2_mode = 0 led3_mode = 0 GPIO.digitalWrite(LED2, 0) GPIO.digitalWrite(LED3, 0) else: if d1 > 0 and d2 == 0: # R 前進 led2_mode = 1 GPIO.digitalWrite(LED2, 1) elif d1 == 0 and d2 > 0: # R 後退 led2_mode = 2 if d3 > 0 and d4 == 0: # L 前進 led3_mode = 1 GPIO.digitalWrite(LED3, 1) elif d3 == 0 and d4 > 0: # L 後退 led3_mode = 2 wav_file = "aplay -q /home/pi/Voice/" wav_mode = 0 if led2_mode == 0 and led3_mode == 0: wav_file += "stop.wav" wav_mode = 1 elif led2_mode == 1 and led3_mode == 1: wav_file += "foward.wav" wav_mode = 1 elif led2_mode == 2 and led3_mode == 2: wav_file += "back.wav" wav_mode = 1 elif led2_mode == 1 and led3_mode == 2: wav_file += "right.wav" wav_mode = 1 elif led2_mode == 2 and led3_mode == 1: wav_file += "left.wav" wav_mode = 1 if wav_mode == 1: subprocess.call(wav_file, shell=True) webiopi.debug("led2_mode = " + str(led2_mode)) webiopi.debug("led3_mode = " + str(led3_mode)) # 2軸のサーボモーターコントロールのためのマクロ # servoID: 0 = Horizontal, 1 = Vertical # duty: 0.0 ~ 0.5 ~ 1.0 # degr: 40 ~ 90 ~ 140 (角度) @webiopi.macro def setPCA9685PWM(servoID, duty, commandID): degr = (140-40)*float(duty) + 40 # webiopi.debug("servoID = " + str(servoID) + " duty = " + str(float(duty)) + " degr = " + str(degr)) if int(servoID) == 0: # webiopi.debug(">> Pan degr = " + str(degr)) Servo0.SetPos(degr) elif int(servoID) == 1: # webiopi.debug(">> Tilt degr = " + str(degr)) Servo1.SetPos(degr)
pi@raspberrypi:~ $ sudo vi /etc/webiopi/config : [SCRIPTS] myscript = /home/pi/webiopi/my08/script.py : [HTTP] doc-root = /home/pi/webiopi/my08/ : [DEVICES] #pwm0 = #PCA9685
pi@raspberrypi:~ $ sudo ./webiopi/streamer-start.sh mjpg_streamer started
pi@raspberrypi:~ $ sudo webiopi -c /etc/webiopi/config -d※ 終了は「CTRL+C」の後、「CTRL+Z」
http://192.168.xxx.xxx:8000/
pi@raspberrypi:~ $ sudo service webiopi start
pi@raspberrypi:~/webiopi/my08 $ cp script.py script_adafruit.py pi@raspberrypi:~/webiopi/my08 $ vi script.py #!/usr/bin/env python # -*- coding: utf-8 -*- import webiopi from webiopi import deviceInstance import time import subprocess # デバッグ出力を有効に webiopi.setDebug() # GPIOライブラリの取得 GPIO = webiopi.GPIO # PCA9685ライブラリの取得 pwm0 = deviceInstance("pwm0") PWM1 = 25 # R AIN1 PWM2 = 24 # AIN2 PWM3 = 23 # L BIN1 PWM4 = 22 # BIN2 LED1 = 21 # SW LED2 = 12 # R LED LED3 = 13 # L LED led2_mode = 0 # 右LED 0 = OFF, 1 = ON led3_mode = 0 # 左LED 0 = OFF, 1 = ON # WebIOPiの起動時に呼ばれる関数 def setup(): webiopi.debug("Script with macros - Setup") subprocess.call("aplay -q /home/pi/Voice/webiopi-start.wav", shell=True) # GPIOのセットアップ GPIO.setFunction(PWM1, GPIO.PWM) GPIO.setFunction(PWM2, GPIO.PWM) GPIO.setFunction(PWM3, GPIO.PWM) GPIO.setFunction(PWM4, GPIO.PWM) # 初期のデューティー比を0%に(静止状態) GPIO.pwmWrite(PWM1, 0) GPIO.pwmWrite(PWM2, 0) GPIO.pwmWrite(PWM3, 0) GPIO.pwmWrite(PWM4, 0) # LED GPIO.setFunction(LED1, GPIO.OUT) GPIO.setFunction(LED2, GPIO.OUT) GPIO.setFunction(LED3, GPIO.OUT) # LED 初期設定 GPIO.digitalWrite(LED1, 1) GPIO.digitalWrite(LED2, 0) GPIO.digitalWrite(LED3, 0) # WebIOPiにより繰り返される関数 def loop(): global led2_mode, led3_mode for num in range(10): if led2_mode >= 0 and led3_mode >= 0: if led2_mode == 0: GPIO.digitalWrite(LED2, 0) else: GPIO.digitalWrite(LED2, 1) if led3_mode == 0: GPIO.digitalWrite(LED3, 0) else: GPIO.digitalWrite(LED3, 1) webiopi.sleep(0.25) if led2_mode >= 0 and led3_mode >= 0: if led2_mode == 1: GPIO.digitalWrite(LED2, 1) else: GPIO.digitalWrite(LED2, 0) if led3_mode == 1: GPIO.digitalWrite(LED3, 1) else: GPIO.digitalWrite(LED3, 0) webiopi.sleep(0.25) # WebIOPi終了時に呼ばれる関数 def destroy(): global led2_mode, led3_mode webiopi.debug("Script with macros - Destroy") subprocess.call("aplay -q /home/pi/Voice/webiopi-stop.wav", shell=True) # GPIO関数のリセット(入力にセットすることで行う) GPIO.setFunction(PWM1, GPIO.IN) GPIO.setFunction(PWM2, GPIO.IN) GPIO.setFunction(PWM3, GPIO.IN) GPIO.setFunction(PWM4, GPIO.IN) led2_mode = -1 led3_mode = -1 GPIO.setFunction(LED1, GPIO.IN) GPIO.setFunction(LED2, GPIO.IN) GPIO.setFunction(LED3, GPIO.IN) # カメラを中央に戻す pwm0.pwmWriteAngle(0, 0) time.sleep(1) pwm0.pwmWriteAngle(1, 0) # 4つのPWMにデューティー比をまとめてセットするためのマクロ # commandIDは、iOSのSafariでPOSTがキャッシュされることへの対策 @webiopi.macro def pwm4Write(duty1, duty2, duty3, duty4, commandID): global led2_mode, led3_mode GPIO.pwmWrite(PWM1, float(duty1)) GPIO.pwmWrite(PWM2, float(duty2)) GPIO.pwmWrite(PWM3, float(duty3)) GPIO.pwmWrite(PWM4, float(duty4)) d1 = int(float(duty1) * 100.0) d2 = int(float(duty2) * 100.0) d3 = int(float(duty3) * 100.0) d4 = int(float(duty4) * 100.0) if d1 == 0 and d2 == 0: led2_mode = 0 led3_mode = 0 GPIO.digitalWrite(LED2, 0) GPIO.digitalWrite(LED3, 0) else: if d1 > 0 and d2 == 0: # R 前進 led2_mode = 1 GPIO.digitalWrite(LED2, 1) elif d1 == 0 and d2 > 0: # R 後退 led2_mode = 2 if d3 > 0 and d4 == 0: # L 前進 led3_mode = 1 GPIO.digitalWrite(LED3, 1) elif d3 == 0 and d4 > 0: # L 後退 led3_mode = 2 wav_file = "aplay -q /home/pi/Voice/" wav_mode = 0 if led2_mode == 0 and led3_mode == 0: wav_file += "stop.wav" wav_mode = 1 elif led2_mode == 1 and led3_mode == 1: wav_file += "foward.wav" wav_mode = 1 elif led2_mode == 2 and led3_mode == 2: wav_file += "back.wav" wav_mode = 1 elif led2_mode == 1 and led3_mode == 2: wav_file += "right.wav" wav_mode = 1 elif led2_mode == 2 and led3_mode == 1: wav_file += "left.wav" wav_mode = 1 if wav_mode == 1: subprocess.call(wav_file, shell=True) webiopi.debug("led2_mode = " + str(led2_mode)) webiopi.debug("led3_mode = " + str(led3_mode)) # 2軸のサーボモーターコントロールのためのマクロ # servoID: 0 = Horizontal, 1 = Vertical # duty: 0.0 ~ 0.5 ~ 1.0 # degr: -50 ~ 0 ~ 50 (角度) @webiopi.macro def setPCA9685PWM(servoID, duty, commandID): degr = int(100*float(duty) - 50) if (int(servoID) == 0): degr = -degr pwm0.pwmWriteAngle(int(servoID), degr) # webiopi.debug("servoID = " + str(servoID) + " duty = " + str(float(duty)) + " degr = " + str(degr))
pi@raspberrypi:~ $ sudo vi /etc/webiopi/config : [SCRIPTS] myscript = /home/pi/webiopi/my08/script.py : [HTTP] doc-root = /home/pi/webiopi/my08/ : [DEVICES] pwm0 = PCA9685
ブラウザからGPIO を操作3-WebIOPi応用 で完成した喋るキャタピラーロボットを解体して2軸カメラ台を搭載する。
● 上部の箱を半分に切断、逆さにしてスピーカー・オーディオ基板・電池ボックスを組み込む。
● 箱を2段にしてRaspberryPi基板・カメラ台を取り付けその上にサーボドライバー基板・を搭載。
● IPアドレスを表示するLCD基板とシャットダウンスイッチを組み込む。
● 駆動部は分離できて、本体部分のみで動作可能なのでこのままプログラム開発に利用できる。
● 蓋と本体の留め具と取っ手を付けて完成。
$ vi ~/webiopi/sw-poweroff2.py import RPi.GPIO as GPIO from time import sleep import subprocess # 2020/07/16 GPIO 20 ext-pulldown SWITCH=20 state = 0 GPIO.setmode(GPIO.BCM) GPIO.setup(SWITCH, GPIO.IN) subprocess.call("aplay -q /home/pi/Voice/boot.wav", shell=True) try: while True: if GPIO.input(SWITCH)==GPIO.HIGH: if state == 2: state = 0 subprocess.call("aplay -q /home/pi/Voice/shutdown.wav", shell=True) cmd = "python /home/pi/webiopi/i2c-lcd.py Shutdown" subprocess.call(cmd.split()) args = ['sudo', 'poweroff'] subprocess.Popen(args) else: state += 1 else: state = 0 sleep(0.5) except KeyboardInterrupt: pass GPIO.cleanup()
$ vi ~/webiopi/i2c-lcd.py # -*- coding: utf-8 -*- import smbus import sys from time import sleep def setup_st7032(): trials = 5 for i in range(trials): try: c_lower = (contrast & 0xf) c_upper = (contrast & 0x30)>>4 bus.write_i2c_block_data(address_st7032, register_setting, [0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]) sleep(0.2) bus.write_i2c_block_data(address_st7032, register_setting, [0x38, 0x0d, 0x01]) sleep(0.001) break except IOError: if i==trials-1: sys.exit() def clear(): global position global line position = 0 line = 0 bus.write_byte_data(address_st7032, register_setting, 0x01) sleep(0.001) def newline(): global position global line if line == display_lines-1: clear() else: line += 1 position = chars_per_line*line bus.write_byte_data(address_st7032, register_setting, 0xc0) sleep(0.001) def write_string(s): for c in list(s): write_char(ord(c)) def write_char(c): global position byte_data = check_writable(c) if position == display_chars: clear() elif position == chars_per_line*(line+1): newline() bus.write_byte_data(address_st7032, register_display, byte_data) position += 1 def check_writable(c): if c >= 0x06 and c <= 0xff : return c else: return 0x20 # 空白文字 bus = smbus.SMBus(1) address_st7032 = 0x3e register_setting = 0x00 register_display = 0x40 contrast = 32 # 0から63のコントラスト。30から40程度を推奨 chars_per_line = 8 # LCDの横方向の文字数 display_lines = 2 # LCDの行数 display_chars = chars_per_line*display_lines position = 0 line = 0 setup_st7032() if len(sys.argv)==1: # アルファベットと記号は「''」でくくってそのまま表示可能 write_string('Hello World') # カタカナや特殊記号は文字コードを一文字ずつ入力 # 以下は「ラズベリー パイ」と表示する例 #s = chr(0xd7)+chr(0xbd)+chr(0xde)+chr(0xcd)+chr(0xde)+chr(0xd8)+chr(0xb0)+' '+chr(0xca)+chr(0xdf)+chr(0xb2) #write_string(s) else: write_string(sys.argv[1])
$ vi ~/webiopi/streamer-start.py #!/bin/bash if pgrep mjpg_streamer > /dev/null then echo "mjpg_streamer already running" else LD_LIBRARY_PATH=/opt/mjpg-streamer/ /opt/mjpg-streamer/mjpg_streamer -i "input_raspicam.so -fps 15 -q 50 -x 640 -y 480" -o "output_http.so -p 9000 -w /opt/mjpg-streamer/www" > /dev/null 2>&1& echo "mjpg_streamer started" fi
$ sudo systemctl enable webiopi
$ sudo vi /etc/rc.local #!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. sleep 10 # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi python3 /home/pi/webiopi/i2c-lcd.py $_IP python3 /home/pi/webiopi/sw-poweroff2.py & sh /home/pi/webiopi/streamer-start.sh exit 0