見出し画像

TinyViewPlus補助リモコン

TinyViewPlus

ドローンレースに欠かせない神ソフトTinyViewPlusですが、計測ゲートを斜めに入ったり画質が悪かったり、様々な原因で計測をミスることがあります。人間がFPVモニターを見ながら計測ゲート通過の時にボタンを押すことを併用すると計測ミスを軽減することができます。人間は必ずモニターかゴーグルのFPV画面を見て計測ゲート通過のタイミングでボタンを押します。目視でドローンを見ていると計測するべきドローンではないドローンの通過を計測してしまうミスを起こしますし、何かの影に入った時に見失うこともありますので、必ずFPV画面だけを注視します。また、TinyViewPlusが計測しようがしまいが、関係なくボタンを押す必要があります。TinyViewPlusが計測しなかったらボタンを押すのではなく、必ず人間とTinyViewPlusの双方とも計測しなければいけません。人間もうっかりして、ボタンを押し忘れることもあるはずです。最低計測秒数を設定しておけば二重に計測されることはありません。早い方が優先して計測されます。

TinyViewPlusには初めからこういうことを想定して手動計測の手段がいくつか装備されています(神か?)キー入力による計測と、OSCによる計測です。ここではBluetoothキーボードタイプとOSCタイプの2種類を紹介します

ハードウェア

使うハードはBluetoothキーボードタイプとOSCタイプ共通です、E1、F1、F4用に3個作成します

マイコン

バッテリーホルダ付きーESP32開発ボード、ここでは18650のバッテリーホルダが付いたESP32を使いますが、電池駆動できるようなESP32であれば何でも大丈夫だと思います。ライブラリを読み込めばM5StackやM5Stickも使えます。

パーツ

押しボタンスイッチ-----------2個(測定用、測定取り消し用)
LED---------------------------1個(必要に応じて)
18650電池--------------------1個
リード線----------------------適量
箱-----------------------------適当

配線

32pinに周回追加ボタン、33pinに周回減算ボタン、13pinにLED

ソフトウェア

arduinoIDEを使います

使用するマイコンに合わせてボードとポートを設定してください

Bluetooth版

下記のコードを、バンドに合わせてコメントアウト(3箇所)を調整し、Win or Macに合わせてコメントアウトを調整して書き込みます

ライブラリ

https://github.com/T-vK/ESP32-BLE-Keyboard
ここからzipをダウンロードして、スケッチメニューからライブラリマネージャのzip読み込みしてインストールしてください

CODE

#include <BleKeyboard.h>
#define LEDPIN 13

BleKeyboard bleKeyboard("Pilot3"); // 'Pilot1' or 'Pilot2'

void setup() {
  bleKeyboard.begin();
  pinMode(LEDPIN, OUTPUT);
  digitalWrite(LEDPIN, HIGH);
  pinMode(32, INPUT_PULLUP);
  pinMode(33, INPUT_PULLUP);
}

void loop() {
  if (digitalRead(32) == LOW) {
    bleKeyboard.press('7'); //Pilot1 --> '5' or Pilot2 --> '6'
    bleKeyboard.releaseAll();
    delay(300);
  }
  if (digitalRead(33) == LOW) {
    bleKeyboard.press(KEY_LEFT_GUI);
    bleKeyboard.press('7'); //Pilot1 --> '5' or Pilot2 --> '6'
    bleKeyboard.releaseAll();
    delay(300);
  }
}

Bluetooth版は以上です

OSC版

TinyViewPlusと同じLANになるようなWiFi環境が必要です。ハードコートになるので、現場での変更はできません。

ライブラリの追加

ライブラリにarduinoOSCを追加してください

CODE

WiFi環境に合わせて1行目と2行目を変更してください
TinyViewPlusに合わせて3行目を変更してください
チャンネルに合わせて4行目と10行目を変更してください

const char* ssid = "mySSID";    // Wi-FiルータのSSIDを記述します。
const char* password = "myPassword";  // Wi-Fiルータのパスワードを記述します。
const String oscHost = "TinyViewPlusHostName";     //TinyViewPlusのホスト名(.localなし)
const String channel = "Pilot1";        //"Pilot1" "Pilot2" "Pilot3"

#include <ArduinoOSCWiFi.h>
#include <ESPmDNS.h>

String ip4 = "";
String camera = "/v1/camera/1/lap";  //Pilot1 = 1 , Pilot2 = 2 , Pilot3 = 3

void setup() {
  pinMode(32, INPUT_PULLUP);
  pinMode(33, INPUT_PULLUP);
  if (channel == "Pilot1") {
    camera = "/v1/camera/1/lap";
  } else {
    if (channel == "Pilot2") {
      camera = "/v1/camera/2/lap";
    } else {
      if (channel == "Pilot3") {
        camera = "/v1/camera/3/lap";
      }
    }
  }

  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.print("WiFi connected, IP = ");
  Serial.println(WiFi.localIP());

  MDNS.begin("OSC_Ctrl");

  boolean hostConnect = true;

  while (hostConnect) {
    const IPAddress ip = MDNS.queryHost(oscHost);
    ip4 = String(ip[0]) + "." + String(ip[1]) + "." + String(ip[2]) + "." + String(ip[3]);
    Serial.println("Searching OSC host");
    if (ip[0] != 0) {
      hostConnect = false;
    }
  }

  Serial.print("OSC host IPAddress = ");
  Serial.println(ip4);
}

void loop() {

  if (digitalRead(32) == LOW) {
    OscWiFi.send(ip4, 4000, camera, "add");
    Serial.println("d32");
    delay(400);
  }

  if (digitalRead(33) == LOW) {
    OscWiFi.send(ip4, 4000, camera, "del");
    Serial.println("d33");
    delay(400);
  }
}

会場のWiFi環境を必ずチェックしてください

iOS版

自分しか使えませんがiOS版です
XCODEでStoryBoardを使って作りました
OSCを使うのでWiFi環境が必要です
TinyViewPlusはIPアドレスで指定します

import UIKit
import OSCKit
import Foundation

class ViewController: UIViewController {
    @IBOutlet weak var vtxChannel: UISegmentedControl!
    @IBOutlet weak var vtx: UILabel!
    @IBOutlet weak var addBtn: UIButton!
    @IBOutlet weak var hostIP: UITextField!

    let oscClient = OSCClient()
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            view.endEditing(true)
        }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else{
              fatalError("URL取得失敗")
          }
          let fullURL = docURL.appendingPathComponent("myPro.plist")
          let plist = NSDictionary(contentsOfFile: fullURL.path)
        guard let hostIPaddress = plist?["hostIP"] else {
                print("nilです")
            createPlist()
                return
            }
        print(hostIPaddress)
        hostIP.text = hostIPaddress as? String
        let ch = plist?["channel"] as! String
        print(ch)
        if( ch == "0"){
            addBtn.backgroundColor = UIColor(red: 1.0, green: 0, blue: 0, alpha: 0.5)
            vtx.text = "Pilot1"
            vtxChannel.selectedSegmentIndex = 0
        }else if(ch == "1"){
            addBtn.backgroundColor = UIColor(red: 0, green: 1.0, blue: 0, alpha: 0.5)
            vtx.text = "Pilot2"
            vtxChannel.selectedSegmentIndex = 1
        }else{
            addBtn.backgroundColor = UIColor(red: 0, green: 0, blue: 1.0, alpha: 0.5)
            vtx.text = "Pilot3"
            vtxChannel.selectedSegmentIndex = 2
        }
    }
    
    func createPlist(){
        let data: NSDictionary = ["hostIP": "192.168.1.2", "channel": "0"]
        guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else{
            fatalError("URL取得失敗")
        }
        let fullURL = docURL.appendingPathComponent("myPro.plist")
        
        data.write(to: fullURL,atomically: true)
        addBtn.backgroundColor = UIColor(red: 1.0, green: 0, blue: 0, alpha: 0.5)
        vtx.text = "Pilot1"
        vtxChannel.selectedSegmentIndex = 0
    }
    
    func savePlist(){
        let hostAdd = hostIP.text
        let chan = String(vtxChannel.selectedSegmentIndex)
        let data: NSDictionary = ["hostIP": hostAdd!, "channel": chan]
        guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else{
            fatalError("URL取得失敗")
        }
        let fullURL = docURL.appendingPathComponent("myPro.plist")
        
        data.write(to: fullURL,atomically: true)
    }
    
    @IBAction func addButton(_ sender: Any) {
        sendMessage(msg: "add")
    }
    
    @IBAction func decButton(_ sender: Any) {
        sendMessage(msg: "del")
    }
    func sendMessage(msg: String) {
        let vtx = String(vtxChannel.selectedSegmentIndex + 1)
        print(vtx)
        let hostAddress = hostIP.text ?? "0.0.0.0"
        let oscMessage = OSCMessage(
            "/v1/camera/" + vtx + "/lap",
            values: [ msg ]
        )
        try? oscClient.send(
            oscMessage,
            to: hostAddress,
            port: 4000
        )
    }

    @IBAction func band(_ sender: Any) {
        if(vtxChannel.selectedSegmentIndex == 0){
            addBtn.backgroundColor = UIColor(red: 1.0, green: 0, blue: 0, alpha: 0.5)
            vtx.text = "Pilot1"
        }else if(vtxChannel.selectedSegmentIndex == 1){
            addBtn.backgroundColor = UIColor(red: 0, green: 1.0, blue: 0, alpha: 0.5)
            vtx.text = "Pilot2"
        }else{
            addBtn.backgroundColor = UIColor(red: 0, green: 0, blue: 1.0, alpha: 0.5)
            vtx.text = "Pilot3"
        }
        savePlist()
    }
    
    @IBAction func ipEdited(_ sender: Any) {
        savePlist()
    }
    
}

いいなと思ったら応援しよう!