見出し画像

ドローンレースの電飾

TinyViewPlusが浅野様のおかげでラップやレースの開始終了時にOSCの信号を出してくれるようになったので、その信号を受信して電飾する実験を行いました

TinyViewPlus

Macの場合ターミナルで
xattr -rc /Applications/Tiny\ View\ Plus.app
としておきます

TinyViewPlusの設定

Macの場合
/Applications/Tiny\ View\ Plus.app/Contents/Resources/data/settings.xml
のファイルを編集します
どのデバイスでも受信したいのでアドレスは使っているLANの最下位を255したものをセットします

<oscMonitor>
    <enabled>1</enabled>
    <host>xxx.xxx.xxx.255</host>
    <port>4001</port>
</oscMonitor>

OSC

TVPからはOSCを使って信号が来るのでTVPと同じLANの中にいる必要があります。

スタートシグナル

機材

ケーブルを長く伸ばせないケースが多いのでWiFiが使えるものがいいと思います。今回は手元にあったESP32C3 miniを使いました、WiFiが標準で付いているESP32系が使いやすいと思います
LEDはシリアルLEDを5列使用します

配線

PIN5,6,7,8,9にLEDを接続します

LEDの個数によっては別電源にします

ライブラリ

ArduinoOSCが簡単に使えてお勧めです

コード

////////////////////////////////////
//
////////////////////////////////////
#include <ArduinoOSCWiFi.h>
#include <ESPmDNS.h>
#include <FastLED.h>
#define NUM_LEDS 16

#define PILOT1_PIN 5
#define PILOT2_PIN 6
#define PILOT3_PIN 7
#define PILOT4_PIN 8
#define PILOT5_PIN 9

CRGB pilot1_LED[NUM_LEDS];
CRGB pilot2_LED[NUM_LEDS];
CRGB pilot3_LED[NUM_LEDS];
CRGB pilot4_LED[NUM_LEDS];
CRGB pilot5_LED[NUM_LEDS];

volatile float raceStartTime = 0;
volatile float pilot1 = 0;
volatile float pilot2 = 0;
volatile float pilot3 = 0;
volatile float pilot4 = 0;
volatile float pilot5 = 0;
volatile bool onrace = false;

const char* ssid = "wtwshikoku";
const char* pwd = "password";

const int recv_port = 4001;
const char* hostname = "startSignal";

void onRace(const OscMessage& m) {
  for (int i = 0; i < NUM_LEDS; i++) {
    pilot1_LED[i].setRGB(0, 0, 0);
    pilot2_LED[i].setRGB(0, 0, 0);
    pilot3_LED[i].setRGB(0, 0, 0);
    pilot4_LED[i].setRGB(0, 0, 0);
    pilot5_LED[i].setRGB(0, 0, 0);
  }
  FastLED.show();
  pilot1 = 0;
  pilot2 = 0;
  pilot3 = 0;

  if (m.arg<String>(0) == "started") {
    raceStartTime = millis();
    onrace = true;
  } else {
    onrace = false;
  }
}

void onLap1(const OscMessage& m) {
  pilot1 = millis();
}

void onLap2(const OscMessage& m) {
  pilot2 = millis();
}

void onLap3(const OscMessage& m) {
  pilot3 = millis();
}

//スタートシグナル
void startFase() {
  int keikaSec = (millis() - raceStartTime) / 1000;
  switch (keikaSec) {
    case 1:
      for (int i = 0; i < NUM_LEDS; i++) {
        pilot1_LED[i].setRGB(0, 255, 0);
      }
      FastLED.show();
      break;
    case 2:
      for (int i = 0; i < NUM_LEDS; i++) {
        pilot2_LED[i].setRGB(0, 255, 0);
      }
      FastLED.show();
      break;
    case 3:
      for (int i = 0; i < NUM_LEDS; i++) {
        pilot3_LED[i].setRGB(0, 255, 0);
      }
      FastLED.show();
      break;
    case 4:
      for (int i = 0; i < NUM_LEDS; i++) {
        pilot4_LED[i].setRGB(0, 255, 0);
      }
      FastLED.show();
      break;
    case 5:
      for (int i = 0; i < NUM_LEDS; i++) {
        pilot5_LED[i].setRGB(0, 255, 0);
      }
      FastLED.show();
      break;
    case 6:
      for (int i = 0; i < NUM_LEDS; i++) {
        pilot1_LED[i].setRGB(1, 1, 1);
        pilot2_LED[i].setRGB(0, 0, 0);
        pilot3_LED[i].setRGB(0, 0, 0);
        pilot4_LED[i].setRGB(0, 0, 0);
        pilot5_LED[i].setRGB(1, 1, 1);
      }
      raceStartTime = 0;
      FastLED.show();
      break;
  }
}

////レース中
void racing() {
  if (pilot1 > 0) {
    for (int i = 0; i < NUM_LEDS; i++) {
      pilot2_LED[i].setRGB((255 * ((pilot1 + 3000) - millis()) / 3000), 0, 0);
    }
    FastLED.show();
    pilot1 = ((pilot1 + 3000) > millis()) ? pilot1 : 0;
  }
  if (pilot2 > 0) {
    for (int i = 0; i < NUM_LEDS; i++) {
      pilot3_LED[i].setRGB(0, (255 * ((pilot2 + 3000) - millis()) / 3000), 0);
    }
    FastLED.show();
    pilot2 = ((pilot2 + 3000) > millis()) ? pilot2 : 0;
  }
  if (pilot3 > 0) {
    for (int i = 0; i < NUM_LEDS; i++) {
      pilot4_LED[i].setRGB(0, 0, (255 * ((pilot3 + 3000) - millis()) / 3000));
    }
    FastLED.show();
    pilot3 = ((pilot3 + 3000) > millis()) ? pilot3 : 0;
  }
}

////休憩中
void offRacing() {
  int rgb1 = random(2);
  int rgb2 = random(2);
  int rgb3 = random(2);

  for (int i = 0; i < NUM_LEDS; i++) {
    pilot1_LED[i].setRGB(rgb1, rgb2, rgb3);
    pilot2_LED[i].setRGB(rgb3, rgb1, rgb2);
    pilot3_LED[i].setRGB(rgb2, rgb3, rgb1);
    pilot4_LED[i].setRGB(rgb1, rgb3, rgb2);
    pilot5_LED[i].setRGB(rgb2, rgb1, rgb3);
  }
  FastLED.show();
}

void setup() {
  Serial.begin(115200);
  WiFi.begin(ssid, pwd);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  if (!MDNS.begin(hostname)) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }

  Serial.print("WiFi connected, IP = ");
  Serial.println(WiFi.localIP());

  OscWiFi.subscribe(recv_port, "/v1/race/event", onRace);
  OscWiFi.subscribe(recv_port, "/v1/camera/1/lap", onLap1);
  OscWiFi.subscribe(recv_port, "/v1/camera/2/lap", onLap2);
  OscWiFi.subscribe(recv_port, "/v1/camera/3/lap", onLap3);

  FastLED.addLeds<WS2812B, PILOT1_PIN, GRB>(pilot1_LED, NUM_LEDS);
  FastLED.addLeds<WS2812B, PILOT2_PIN, GRB>(pilot2_LED, NUM_LEDS);
  FastLED.addLeds<WS2812B, PILOT3_PIN, GRB>(pilot3_LED, NUM_LEDS);
  FastLED.addLeds<WS2812B, PILOT4_PIN, GRB>(pilot4_LED, NUM_LEDS);
  FastLED.addLeds<WS2812B, PILOT5_PIN, GRB>(pilot5_LED, NUM_LEDS);
}

void loop() {
  OscWiFi.parse();
  if (raceStartTime > 0) {
    startFase();
  } else {
    if (onrace) {
      racing();
    } else {
      offRacing();
    }
  }
}

コーナーポールLED

コースのコーナーに設置するLEDポールをスタートシグナルと連動します
色はブラウザでpole-01.localのようにアクセスして変更します

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <ArduinoOSCWiFi.h>
#include <FastLED.h>
#include <EEPROM.h>

const char* ssid = "wtwshikoku";
const char* pass = "password";
WebServer Server(80);
#define NUM_LEDS 64

#define LED_PIN 5

CRGB pole_LED[NUM_LEDS];

volatile float raceStartTime = 0;
int rgbR = 0;
int rgbG = 0;
int rgbB = 0;

volatile bool onrace = false;

const int recv_port = 4001;
const char* hostname = "pole-01";

void SendMessage() {
  String message = "<html>";
  message += "<h1>";
  message += hostname;
  message += "</h1><form method='post' action='/submit'>";
  message += "<input type='radio' name='color' value='red'>Red<br>";
  message += "<input type='radio' name='color' value='green'>Green<br>";
  message += "<input type='radio' name='color' value='blue'>Blue<br>";
  message += "<input type='radio' name='color' value='white'>White<br>";
  message += "<input type='submit' value='submit'>";
  message += "</form></html>\n";
  Server.send(200, "text/html", message);
}

void submitMessage() {
  rgbR = 0;
  rgbG = 0;
  rgbB = 0;
  String theColor = Server.arg(0);
  if (theColor == "red") {
    rgbR = 255;
  } else if (theColor == "green") {
    rgbG = 255;
  } else if (theColor == "blue") {
    rgbB = 255;
  } else if (theColor == "white") {
    rgbR = 255;
    rgbG = 255;
    rgbB = 255;
  }
  EEPROM.write(0, rgbR);
  EEPROM.write(1, rgbG);
  EEPROM.write(2, rgbB);
  EEPROM.commit();
  SendMessage();
}

void SendNotFound() {
  Serial.println("SendNotFound");
  Server.send(404, "text/plain", "404 not found...");
}
void onRace(const OscMessage& m) {
  for (int i = 0; i < NUM_LEDS; i++) {
    pole_LED[i].setRGB(0, 0, 0);
  }
  FastLED.show();

  if (m.arg<String>(0) == "started") {
    raceStartTime = millis();
    onrace = true;
  } else {
    onrace = false;
  }
}

//スタートシグナル
void startFase() {
  float nowTime = millis();
  if ((raceStartTime < nowTime) && (raceStartTime > nowTime - 1000)) {
    for (int i = 0; i < NUM_LEDS; i++) {
      pole_LED[i].setRGB(rgbR, rgbG, rgbB);
    }
    FastLED.show();
  }
  if ((raceStartTime < nowTime - 1000) && (raceStartTime > nowTime - 2000)) {
    for (int i = 17; i < NUM_LEDS; i++) {
      pole_LED[i].setRGB(0, 0, 0);
    }
    FastLED.show();
  }
  if ((raceStartTime < nowTime - 2000) && (raceStartTime > nowTime - 3000)) {
    for (int i = 11; i < 17; i++) {
      pole_LED[i].setRGB(0, 0, 0);
    }
    FastLED.show();
  }
  if ((raceStartTime < nowTime - 3000) && (raceStartTime > nowTime - 4000)) {
    for (int i = 5; i < 11; i++) {
      pole_LED[i].setRGB(0, 0, 0);
    }
    FastLED.show();
  }
  if ((raceStartTime < nowTime - 4000) && (raceStartTime > nowTime - 5000)) {
    for (int i = 0; i < 5; i++) {
      pole_LED[i].setRGB(0, 0, 0);
    }
    FastLED.show();
  }
  if (raceStartTime <= nowTime - 5000) {
    for (int i = 0; i < NUM_LEDS; i++) {
      pole_LED[i].setRGB(rgbR, rgbG, rgbB);
    }
    raceStartTime = 0;
  }
  FastLED.show();
}

////レース中
void racing() {
  for (int i = 0; i < NUM_LEDS; i++) {
    pole_LED[i].setRGB(rgbR, rgbG, rgbB);
  }
  FastLED.show();
}

////休憩中
void offRacing() {
  int rgb1 = random(20) * rgbR / 200;
  int rgb2 = random(20) * rgbG / 200;
  int rgb3 = random(20) * rgbB / 200;

  for (int i = 0; i < NUM_LEDS; i++) {
    pole_LED[i].setRGB(rgb1, rgb2, rgb3);
  }
  FastLED.show();
}
void setup() {
  EEPROM.begin(5);
  rgbR = EEPROM.read(0);
  rgbG = EEPROM.read(1);
  rgbB = EEPROM.read(2);
  // rgbR = 255;
  // rgbG = 0;
  // rgbB = 0;
  Serial.begin(115200);
  delay(100);
  Serial.println("\n*** Starting ***");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  Serial.println("Connecting...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    if (WiFi.status() == WL_CONNECT_FAILED) {
      Serial.println("Can't connect");
    }
  }
  Serial.println("Connected");
  Serial.println(WiFi.localIP());
  if (!MDNS.begin(hostname)) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  //  ウェブサーバの設定
  Server.on("/", SendMessage);
  Server.on("/submit", submitMessage);
  Server.onNotFound(SendNotFound);  //  不正アクセス時の応答
  Server.begin();                   //  ウェブサーバ開始

  OscWiFi.subscribe(recv_port, "/v1/race/event", onRace);

  FastLED.addLeds<WS2812B, LED_PIN, GRB>(pole_LED, NUM_LEDS);
}
void loop() {
  Server.handleClient();
  OscWiFi.parse();
  if (raceStartTime > 0) {
    startFase();
  } else {
    if (onrace) {
      racing();
    } else {
      offRacing();
    }
  }
}

OSC送信機


テストするのにいちいちTinyViewPlusを起動するのがめんどうなので、簡単に信号を送れるように手持ちのM5StickPlusで作成しました。付いているボタンを使うので配線はありません。Bボタンでレースの開始終了、AボタンでPilot1から3までトグルしながらラップを刻みます。ラップタイムはランダムで適当に出しています。

コード

//
#include <ArduinoOSCWiFi.h>
#include "M5StickCPlus.h"
#include <ESPmDNS.h>

const char* ssid = "wtwshikoku";
const char* pwd = "genishii";
String host = "10.0.1.255";
const int publish_port = 4001;
const char* hostname = "sendTester";
String raceStatus = "finished";
int pilot = 0;
int lap1 = 0;
int lap2 = 0;
int lap3 = 0;
bool race = true;
float f;

int split(String data, char delimiter, String* dst) {
  int index = 0;
  int arraySize = (sizeof(data)) / sizeof((data[0]));
  int datalength = data.length();

  for (int i = 0; i < datalength; i++) {
    char tmp = data.charAt(i);
    if (tmp == delimiter) {
      index++;
      if (index > (arraySize - 1)) return -1;
    } else dst[index] += tmp;
  }
  return (index + 1);
}
void setup() {
  Serial.begin(115200);
  delay(1000);
  M5.begin();
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);
  WiFi.begin(ssid, pwd);

  while (WiFi.status() != WL_CONNECTED) {
    M5.Lcd.print(".");
    delay(500);
  }
  M5.Lcd.setCursor(10, 20);
  M5.Lcd.println("connected, IP = ");
  M5.Lcd.println(WiFi.localIP());
  String cmds[4] = { "" };
  int index = split(WiFi.localIP().toString(), '.', cmds);
  host = cmds[0] + "." + cmds[1] + "." + cmds[2] + ".255";
}

void loop() {
  OscWiFi.update();
  M5.update();
  if (M5.BtnA.wasPressed()) {
    pilot = (pilot < 3) ? pilot + 1 : 1;
    f = random(900)/100.0;
    switch (pilot) {
      case 1:
        lap1++;
        OscWiFi.send(host, publish_port, "/v1/camera/1/lap", lap1, f, " Pilot1");
        M5.Lcd.setCursor(10, 150);
        M5.Lcd.println("Pilot 1");
        M5.Lcd.println(" Lap  " + String(lap1));
        M5.Lcd.println(" time " + String(f));
        break;
      case 2:
        lap2++;
        OscWiFi.send(host, publish_port, "/v1/camera/2/lap", lap1, f, "ぱいろっと2");
        M5.Lcd.setCursor(10, 150);
        M5.Lcd.println("Pilot 2");
        M5.Lcd.println(" Lap  " + String(lap2));
        M5.Lcd.println(" time " + String(f));
        break;
      case 3:
        lap3++;
        OscWiFi.send(host, publish_port, "/v1/camera/3/lap", lap1, f, "パイロット3");
        M5.Lcd.setCursor(10, 150);
        M5.Lcd.println("Pilot 3");
        M5.Lcd.println(" Lap  " + String(lap3));
        M5.Lcd.println(" time " + String(f));
        break;
    }
  }

  if (M5.BtnB.wasPressed()) {
    lap1 = 0;
    lap2 = 0;
    lap3 = 0;
    raceStatus = (race) ? "started" : "finished";
    M5.Lcd.setCursor(10, 110);
    M5.Lcd.print(raceStatus);
    OscWiFi.send(host, publish_port, "/v1/race/event", raceStatus);
    race = !race;
  }
}


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