ドローンレースの電飾
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を接続します
ライブラリ
コード
////////////////////////////////////
//
////////////////////////////////////
#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;
}
}