サーボモーターの扱いにほんの少しですが慣れてきましたので、遠隔操作で視点を調整できるネットワークカメラを作成いたしました。
カメラとWEBサーバーはunitv2、サーボモーターの制御にRaspberry Pi picoを使用しました。※WEBサーバーはpython http.serverを使います。
流れ的には、
unitV2→uart→Raspberry Pi pico→サーボモーター
となります。
プログラムはサーバー側にhtml/css,js,cgi(python), サーボモーター側にmicropythonとなっています。
ブラウザでunitv2のサーバにアクセスして、そこにカメラストリーミング表示とボタンをを設置し、遠隔操作出来るようになっています。
簡単なものになりますが、備忘録のためにまとめておこうと思います。
目次
初めに
使用するもの
・unitv2
・raspberry pi pico
・サーボモーター(SG90) × 2
・ジャンピングワイヤー多数
・ブレッドボード
・適当な土台
遠隔から左右上下にカメラを動かせるものを作るので、適当な土台が必要です。
土台作成
我ながらセンスのかけらも見当たりませんが、とりあえずサーボモーターを左右と上下に振れるように細工します。
OKOK、何となく形になればいいんですよ。
続いてプログラムを書きます。
unitv2
html/css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>テスト</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="style.css"> </head> <body> <div class="cam"> <img src="http://unitv2のIP/video_feed"> </div> <main> <ul> <div class="a"> <li id="forward" class="ledoff">前進</li> </div> <div class="bc"> <div class="b"> <li id="left" class="ledoff">左旋回</li> </div> <div class="n"> <li></li> </div> <div class="c"> <li id="right" class="ledoff">右旋回</li> </div> </div> <div class="d"> <li id="backward" class="ledoff">後退</li> </div> </ul> </main> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script> <script src="main.js"></script> </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
* { margin: 0px; padding: 0px; } body { max-width: 600px; font-size: 25px; width: 100%; -webkit-touch-callout: none; -webkit-user-select: none; } img { width: 100%; } main { height: 40vh; background: skyblue; } ul { display: block; height: 40vh; list-style: none; padding-top: 10px; } .bc { display: flex; } .a ,.bc, .d { height: 12vh; } li { width: 100px ; height: 90% ; margin-left: auto; margin-right: auto; background: yellow; } .b { margin-left: auto; } .c { margin-right: auto; } ul li { text-align: center; } .a li, .b li, .c li, .d li { border: solid 1px; } .ledon { background: #f88888; } .n li { background: skyblue; } a:active { color: #ff2020; } |
javascript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
$(function(){ let motor = "STOP"; // 関数:モーターを動かすマクロ呼び出し function change_motor(typee) { motor = typee; if(typee == "FOWARD") { // 前進 //w().callMacro('FW'); console.log(typee); $.ajax({ url: 'cgi-bin/recieve.py', type: 'post', data: {name: 'hhhhh' } }).done(function(data){ console.log(data); }).fail(function(){ console.log('failed'); }); } else if(typee == "BACKWARD") { //w().callMacro('BK'); console.log(typee); $.ajax({ url: 'cgi-bin/recieve.py', type: 'post', data: {name: 'iiii' } }).done(function(data){ console.log(data); }).fail(function(){ console.log('failed'); }); } else if(typee == "RIGHT") { //w().callMacro('RT'); console.log(typee); $.ajax({ url: 'cgi-bin/recieve.py', type: 'post', data: {name: 'jjjj' } }).done(function(data){ console.log(data); }).fail(function(){ console.log('failed'); }); } else if(typee == "LEFT") { //w().callMacro('LT'); console.log(typee); $.ajax({ url: 'cgi-bin/recieve.py', type: 'post', data: {name: 'kkkk' } }).done(function(data){ console.log(data); }).fail(function(){ console.log('failed'); }); } else if(typee == "STOP") { //w().callMacro('ST'); console.log(typee); $.ajax({ url: 'cgi-bin/recieve.py', type: 'post', data: {name: 'ssss' } }).done(function(data){ console.log(data); }).fail(function(){ console.log('failed'); }); } } // 「前進」ボタンが押されたときのイベント処理 $('#forward').bind('touchstart', function() { // 押されたとき if(motor == 'STOP') { $(this).addClass('ledon'); change_motor('FOWARD'); } }).bind('touchend', function() { // 離したとき $(this).removeClass('ledon'); change_motor('STOP'); }); // 「後退」ボタンが押されたときのイベント処理 $('#backward').bind('touchstart', function() { if(motor == "STOP") { $(this).addClass('ledon'); change_motor('BACKWARD'); } }).bind('touchend', function() { $(this).removeClass('ledon'); change_motor('STOP'); }); // 「右」ボタンが押されたときのイベント処理 $('#right').bind('touchstart', function() { if(motor == "STOP") { $(this).addClass('ledon'); change_motor('RIGHT'); } }).bind('touchend', function() { $(this).removeClass('ledon'); change_motor('STOP'); }); // 「左」ボタンが押されたときのイベント処理 $('#left').bind('touchstart', function() { if(motor == "STOP") { $(this).addClass('ledon'); change_motor('LEFT'); } }).bind('touchend', function() { $(this).removeClass('ledon'); change_motor('STOP'); }); }); |
jsからpythonに値を送りたいので、ajaxの非同期通信を行っています。これでpythonのCGIスクリプトが動かせます。
※CGIについては過去記事で詳細をまとめています。↓
CGI(python)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/python3 import serial import cgi import time form = cgi.FieldStorage() recieve = form.getvalue('name') moji= recieve + '\r\n' uart_gpio = serial.Serial('/dev/ttyS1', 115200, timeout=0.5) uart_gpio.write(moji.encode('utf-8')) |
jsからpostされた値を受け取って、uartでpicoに転送しています。
uartについても詳細は過去記事でまとめています。↓
Raspberry Pi pico
micropython
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
import utime from machine import UART, Pin from machine import PWM, Pin led = machine.Pin(25, machine.Pin.OUT) servo1 = PWM(Pin(0)) servo2 = PWM(Pin(2)) servo1.freq(50) servo2.freq(50) max_duty = 65025 dig_0 = 0.0725 #0° dig_90 = 0.12 #90° dig_180 = 0.025 #-90 x = 0.0725 y = 0.0725 servo1.duty_u16(int(max_duty*dig_0)) servo2.duty_u16(int(max_duty*dig_0)) uart1 = UART(1, 115200, tx=Pin(4), rx=Pin(5)) while True: rxData = uart1.readline() if rxData == None: break utime.sleep(0.005) while True: rxData = uart1.readline() print(rxData) if rxData != None: rxData2 = rxData.strip().decode('utf-8') print(rxData2) if rxData2 == "hhhhh": print("上") while y >= 0.025: servo2.duty_u16(int(max_duty*y)) y -= 0.0001 utime.sleep(0.003) rxData = uart1.readline() if rxData != None: rxData2 = rxData.strip().decode('utf-8') break elif rxData2 == "iiii": print("下") while y <= 0.12: servo2.duty_u16(int(max_duty*y)) y += 0.0001 utime.sleep(0.003) rxData = uart1.readline() if rxData != None: rxData2 = rxData.strip().decode('utf-8') break elif rxData2 == "jjjj": print("右") while x <= 0.12: servo1.duty_u16(int(max_duty*x)) x += 0.0001 utime.sleep(0.003) rxData = uart1.readline() if rxData != None: rxData2 = rxData.strip().decode('utf-8') break elif rxData2 == "kkkk": print("左") while x >= 0.025: servo1.duty_u16(int(max_duty*x)) x -= 0.0001 utime.sleep(0.003) rxData = uart1.readline() if rxData != None: rxData2 = rxData.strip().decode('utf-8') break elif rxData2 == "ssss": print("止") utime.sleep(0.005) |
先頭の方でサーボモーターの設定、uartの受信設定を行っています。
1つ目のwhile文ですが、一見いらないように見えますが、ここがハマりどころで1日悩みました。
leadlineでunitv2からの値を受け取っているのですが、どうもpicoをスタンドアロンで起動した場合、入力が無い状態の一発目の値が空である「None」にならず、何等かの値が転送されてきているみたいです。
ここのループ処理がなければ次のループ内にある「decode」のところでループ処理がストップしてしまう挙動になってしまい、「IDEではスタート出来るがスタンドアロンだとプログラムが起動しない」という頭が混乱する症状を発生してしまうので、とにかく一発目のゴミをループで除去しました。(他にもっと良いやり方あるんでしょうけど。。)
下半分は、押してる間だけボタンの方向にサーボが動かし、現在の位置は変数を使ってキープさせています。
プログラムは以上です。
ハードウェア
回路組立て
汚い配線図で申し訳ないですが、図のような感じです。(ブレッドボードを使えばこんな配線にはなりませんが。。)
電源は5V2A位のモバイルバッテリー1個で大丈夫だと思います。
これで完成になります。
完成
配線丸見えだし、少しタイムラグが気になりますが、初めにしては上出来だという事にしておきます。