M5Stackとステッピングモーター、サーボでペンプロッタを作成しました
自分は手始めとして手に入れやすい部材を使いましたが応用すればもっと高精度なものも作れると思います
こんな感じで動作します
うまくいきました🤗
— もけ@ムギ㌠ (@coppercele) September 10, 2021
ただタイムラプスしないといけないくらい時間がかかるんで実用性は微妙かも🤣🤣🤣#M5Stack pic.twitter.com/MCSxDfefOw
使用するデバイス
M5Stack Basic - スイッチサイエンス
https://www.switch-science.com/catalog/3647/
Arduino互換なESP32がケースに入っててボタンとディスプレイとバッテリーがついててIOも豊富でWifiとBluetoothも使えるニクい奴
バリエーションによって9軸センサがついてたりするけどbasicは一番基本的な奴
M5Stack用GRBLモジュール - スイッチサイエンス
https://www.switch-science.com/catalog/6994/
ステッピングモーターを3個まで制御できるモジュール
スタックすると最大6個まで使えます
Gcodeが使えるので割とお手軽にステッピングモーターを動かせます
M5Stack用12チャンネル サーボドライバモジュール - スイッチサイエンス
https://www.switch-science.com/catalog/6060/
サーボを12個まで制御できるモジュール
現在は型落ちみたいなのでServo2モジュールを使用してください
M5Stack用Servo2モジュール - 16チャンネル サーボドライバ - スイッチサイエンス
https://www.switch-science.com/catalog/6737/
Amazon | 3DプリンタガイドレールセットT8リードスクリュー ピッチ1mmリード1mm +リニアシャフト8 * 100mm + KP08 SK8 SC8UU +ナットハウジング+カップリング+ステップモータ (200mm) | リニアガイド | 産業・研究開発用品 通販
https://www.amazon.co.jp/gp/product/B07D3P1YST/
自分はステッピングモータに関しては素人だったのでステッピングモーターとレールがセットになってるものを購入しました
Amazon.co.jp: マイクロスライディングテーブルステッピングモーターリニアモータースクリュー2相4線リニアステッピングモータースクリュースライドテーブル4‑9V駆動電圧18°ステップ角 : 産業・研究開発用品
https://www.amazon.co.jp/gp/product/B08LQ36RZM/
Y軸には簡易型のリニアレールを使いました
Stepperモジュールが9V~なので対応した電圧の物を用意しましょう
ただこのモーターは4~9Vなんですが短時間で過熱するので実用時は
上記の大きいステッピングモーターを使った方がいいと思います
ハードを構築する
ホームセンターでアクリル板を買ってきてドリルで穴を開けてX軸のレールをネジ止めしました
左側のステッピングモーターは高さが合わなかったのでアクリル板の外に出して高さを逃がしています
Y軸はX軸のアルミブロックのネジ穴の幅がちょうどよかったので
直接ネジ止めしました
リミットスイッチは工具箱にあったタクトスイッチを使いました
グルーガンでアルミブロックに当たるところに固定します
リミットスイッチはGNDがXYZ共用なので接続するための基板を作りました
サーボはServoモジュールから制御します
Y軸のステップモーターにサーボを設置します
台座はthingiverseで見つけたアクチュエーターを
Fusion360で切ったり貼ったりして3Dプリンタで出力しました
Linear Servo Actuators by potentprintables - Thingiverse
https://www.thingiverse.com/thing:3170748
これにペンを固定することでサーボを回して上下することができます
スケッチを書いていく
Githubのサンプルコードを見ながら書いていきます
以下の.inoと.cppと.hを同じフォルダに入れればコンパイルできると思います
M5Stack/xyz_control.ino at master · m5stack/M5Stack · GitHub
https://github.com/m5stack/MODULE_GRBL13.2/tree/master/examples/xyz_control
MODULE_GRBL13.2/src at master · m5stack/MODULE_GRBL13.2 · GitHub
https://github.com/m5stack/MODULE_GRBL13.2/tree/master/src
基本的にはG1の直線だけ使っています
_GRBL.sendGcode("G1 X5Y5Z5 F200");
ちなみに動作が遅かったので
初期化の引数とかCcodeのFRAMEを変えてみたんですが
_GRBL.Init(400, 400, 400, 30);
_GRBL.sendGcode("G1 X2 F100");
_GRBL.sendGcode("G1 X2 F200");
_GRBL.sendGcode("G1 X3 F300");
_GRBL.sendGcode("G1 X4 F400");
_GRBL.sendGcode("G1 X5 F500");
脱調するんですよね・・・(´・ω・`)
X,YそれぞれF100,F200,F300,F400,F500とスピード変えてテストしてみた
— もけ@ムギ㌠ (@coppercele) July 14, 2021
F100は脱調してるっぽいけどF200からはスムーズでF500でもまだ余裕ありそう
Y軸はアッチッチになるから遅くしようと思ってたけど素早く動かして放熱時間を取った方がいいかもね pic.twitter.com/fTuKPPgigJ
うまい設定方法があったら教えてください(´・ω・`)
ステップモーターを動かせるようになったのでこんな関数を作りました
void moveTo(float toX, float toY) {
String command = "G1 X";
command += String(toX);
command += " F200";
Serial.println(command.c_str());
_GRBL.Gcode((char *)(command.c_str()));
command = "G1 Y";
command += String(toY);
command += " F200";
Serial.println(command.c_str());
_GRBL.Gcode((char *)(command.c_str()));
_GRBL.WaitIdle();
}
C言語が分からないのでJavaライクな書き方です(´・ω・`)
原点戻しを実装する
リセットするごとに原点が変わっても困るので原点に戻す動作を実装します
まず1だけ+方向に動かしてからリミットスイッチが押されるまで-0.3ずつ動かし続けます
リミットスイッチが押されると_GRBL.InLock()がtrueになるので
Unlock()してから+に戻します
この時タクトスイッチが押されたままUnlock()するので即lock()されることがあり、
その対策のために動かしてから少し待ってリミットスイッチが押されたままだと再度動かすようにしています
void resetOrigin(char *axis) {
_GRBL.SetMode("distance"); // 相対移動モード
std::string str = "G1 1 F200";
str.insert(3, axis);
// USE Gcode
_GRBL.Gcode(const_cast<char *>(str.c_str())); // +1移動
str = "G1 -0.3 F200";
str.insert(3, axis);
while (!_GRBL.InLock()) {
_GRBL.Gcode(
const_cast<char *>(str.c_str())); // -0.2ずつロックされるまで移動
delay(100);
}
Serial.println("Locked");
Serial.println("Back to Origin");
str = "G1 0.2 F200";
str.insert(3, axis);
while (_GRBL.InLock()) {
_GRBL.UnLock(); // リミットスイッチのロックを解除する
_GRBL.SetMode("distance"); // absoluteに戻るので再設定
_GRBL.Gcode(const_cast<char *>(str.c_str()));
delay(300);
// スイッチが押しっぱなしだと即座にロックに戻るので移動してから少し待つ
}
_GRBL.SetMode("absolute");
Serial.println("Moved to Origin");
}
M5Stackペンプロッタの原点戻し動画(´・ω・`) pic.twitter.com/M3jWKmBsTY
— もけ@ムギ㌠ (@coppercele) September 15, 2021
画面をタップする
サーボを回すとペンを上下できるのでペンの上げ下げとタップで関数を作ります
定数は現物合わせで調整してください
int PEN_UP = 130;
int PEN_DOWN = 100;
void penUp() {
// servo(PEN_UP);
Servo_write_angle(0, PEN_UP);
delay(100);
}
void penDown() {
Servo_write_angle(0, PEN_DOWN);
delay(100);
}
void tap() {
penDown();
penUp();
}
void Servo_write_angle(uint8_t number, uint8_t angle) {
Wire.beginTransmission(SERVO_ADDR);
Wire.write(0x10 | number);
Wire.write(angle);
Wire.endTransmission();
}
そうしたら画面の上を動かしてタップする場所を定数にしていきます
今回はデモのために某FG●を操作することにしました
自分が動かした感じでは横の1マスが10,縦が1くらいになるのでタップする場所を列挙していきます
長くなるので折りたたみ
float SKILL_1_1_X = 0;
float SKILL_1_1_Y = 0.7;
float SKILL_1_2_X = 4;
float SKILL_1_2_Y = 0.7;
float SKILL_1_3_X = 7.5;
float SKILL_1_3_Y = 0.7;
float SKILL_2_1_X = 13.5;
float SKILL_2_1_Y = 0.7;
float SKILL_2_2_X = 17.5;
float SKILL_2_2_Y = 0.7;
float SKILL_2_3_X = 21;
float SKILL_2_3_Y = 0.7;
float SKILL_3_1_X = 27.5;
float SKILL_3_1_Y = 0.7;
float SKILL_3_2_X = 31;
float SKILL_3_2_Y = 0.7;
float SKILL_3_3_X = 34.5;
float SKILL_3_3_Y = 0.7;
float OPPONENT_1_X = 17;
float OPPONENT_1_Y = 1.5;
float OPPONENT_2_X = 29;
float OPPONENT_2_Y = 1.5;
float OPPONENT_3_X = 39;
float OPPONENT_3_Y = 1.5;
float ATTACK_X = 52;
float ATTACK_Y = 0.5;
float CARD_1_X = 8;
float CARD_1_Y = 0.7;
float CARD_2_X = 17;
float CARD_2_Y = 0.7;
float CARD_3_X = 29;
float CARD_3_Y = 0.7;
float CARD_4_X = 39;
float CARD_4_Y = 0.7;
float CARD_5_X = 50;
float CARD_5_Y = 0.7;
float HOUGU_1_X = 17;
float HOUGU_1_Y = 3.9;
float HOUGU_2_X = 29;
float HOUGU_2_Y = 3.9;
float HOUGU_3_X = 38;
float HOUGU_3_Y = 3.9;
float MASTER_SKILL_X = 54;
float MASTER_SKILL_Y = 2.6;
float MASTER_SKILL_1_X = 42;
float MASTER_SKILL_1_Y = 2.6;
float MASTER_SKILL_2_X = 46;
float MASTER_SKILL_2_Y = 2.6;
float MASTER_SKILL_3_X = 50;
float MASTER_SKILL_3_Y = 2.6;
float ORDER_CHANGE_1_X = 7;
float ORDER_CHANGE_1_Y = 2.6;
float ORDER_CHANGE_2_X = 15;
float ORDER_CHANGE_2_Y = 2.6;
float ORDER_CHANGE_3_X = 24;
float ORDER_CHANGE_3_Y = 2.6;
float ORDER_CHANGE_4_X = 33;
float ORDER_CHANGE_4_Y = 2.6;
float ORDER_CHANGE_5_X = 41;
float ORDER_CHANGE_5_Y = 2.6;
float ORDER_CHANGE_6_X = 50;
float ORDER_CHANGE_6_Y = 2.6;
float ORDER_EXEC_X = 28;
float ORDER_EXEC_Y = 0.0;
タップする座標が分かったらそれぞれを関数にまとめておきます
長くなるので折りたたみ
void skill1(int num) {
Serial.printf("exec Skill1-%d\n", num);
switch (num) {
case 1:
moveTo(SKILL_1_1_X, SKILL_1_1_Y);
break;
case 2:
moveTo(SKILL_1_2_X, SKILL_1_2_Y);
break;
case 3:
moveTo(SKILL_1_3_X, SKILL_1_3_Y);
break;
default:
break;
}
tap();
}
void skill2(int num) {
Serial.printf("exec Skill2-%d\n", num);
switch (num) {
case 1:
moveTo(SKILL_2_1_X, SKILL_2_1_Y);
break;
case 2:
moveTo(SKILL_2_2_X, SKILL_2_2_Y);
break;
case 3:
moveTo(SKILL_2_3_X, SKILL_2_3_Y);
break;
default:
break;
}
tap();
}
void skill3(int num) {
Serial.printf("exec Skill3-%d\n", num);
switch (num) {
case 1:
moveTo(SKILL_3_1_X, SKILL_3_1_Y);
break;
case 2:
moveTo(SKILL_3_2_X, SKILL_3_2_Y);
break;
case 3:
moveTo(SKILL_3_3_X, SKILL_3_3_Y);
break;
default:
break;
}
tap();
}
void skillTo(int num) {
Serial.printf("exec Skill to %d\n", num);
switch (num) {
case 1:
moveTo(OPPONENT_1_X, OPPONENT_1_Y);
break;
case 2:
moveTo(OPPONENT_2_X, OPPONENT_2_Y);
break;
case 3:
moveTo(OPPONENT_3_X, OPPONENT_3_Y);
break;
default:
break;
}
tap();
}
void masterSkill(int num) {
moveTo(MASTER_SKILL_X, MASTER_SKILL_Y);
tap();
switch (num) {
case 1:
moveTo(MASTER_SKILL_1_X, MASTER_SKILL_1_Y);
break;
case 2:
moveTo(MASTER_SKILL_2_X, MASTER_SKILL_2_Y);
break;
case 3:
moveTo(MASTER_SKILL_3_X, MASTER_SKILL_3_Y);
break;
default:
break;
}
tap();
}
void orderChange(int num) {
switch (num) {
case 1:
moveTo(ORDER_CHANGE_1_X, ORDER_CHANGE_1_Y);
break;
case 2:
moveTo(ORDER_CHANGE_2_X, ORDER_CHANGE_2_Y);
break;
case 3:
moveTo(ORDER_CHANGE_3_X, ORDER_CHANGE_3_Y);
break;
case 4:
moveTo(ORDER_CHANGE_4_X, ORDER_CHANGE_4_Y);
break;
case 5:
moveTo(ORDER_CHANGE_5_X, ORDER_CHANGE_5_Y);
break;
case 6:
moveTo(ORDER_CHANGE_6_X, ORDER_CHANGE_6_Y);
break;
case 7:
moveTo(ORDER_EXEC_X, ORDER_EXEC_Y);
break;
default:
break;
}
tap();
}
void hougu(int num) {
switch (num) {
case 1:
moveTo(HOUGU_1_X, HOUGU_1_Y);
break;
case 2:
moveTo(HOUGU_2_X, HOUGU_2_Y);
break;
case 3:
moveTo(HOUGU_3_X, HOUGU_3_Y);
break;
default:
break;
}
tap();
}
void card(int num) {
switch (num) {
case 1:
moveTo(CARD_1_X, CARD_1_Y);
break;
case 2:
moveTo(CARD_2_X, CARD_2_Y);
break;
case 3:
moveTo(CARD_3_X, CARD_3_Y);
break;
case 4:
moveTo(CARD_4_X, CARD_4_Y);
break;
case 5:
moveTo(CARD_5_X, CARD_5_Y);
break;
default:
break;
}
tap();
}
void atack() {
moveTo(ATTACK_X, ATTACK_Y);
tap();
}
コマンドを指定できるようにする
図形を書くにしろ画面をタップするにしろタップする場所を連続して指定しないといけないので
タップするコマンドをSDカード上のファイルに保存して選択できるようにします
ここはESP-NOWなりUARTなりで通信してもいいと思いますので都合のいい方法を選んでください
今回はこのようなJSONを用意しました
{
"data": [
{
"name": "rans",
"command": {
"wave1": "SK11,ST3,SK21,ST3,MS3,ST3,AT,HG3,CD4,CD5",
"wave2": "SK23,ST3,SK33,AT,HG3,CD4,CD5",
"wave3": "SK12,SK22,SK13,ST3,AT,HG3,CD4,CD5"
}
}
]
}
コマンドの意味は
SKxy→x人目のスキルyを実行
STx→スキルのターゲットをx人目とする
MSx→マスタースキルを使う
AT→Attackボタンを押す
HGx→x人目の宝具カードを選択
CDx→x番目のカードを選択
となっています
JSONをSDカード上に置いたら読み込んでjsonArrayに格納します
ArduinoJsonを使用しました
ArduinoJson: Efficient JSON serialization for embedded C++
https://arduinojson.org/
File f = SD.open("/fgo.json");
DeserializationError error = deserializeJson(jsonObject, f);
JsonArray jsonArry = jsonObject["data"].as<JsonArray>();
JSONの内容が取得出来たらM5Stack TreeViewに渡して選択できるようにします
GitHub - lovyan03/M5Stack_TreeView: M5Stack TreeView menu UI library.
https://github.com/lovyan03/M5Stack_TreeView
tv.itemHeight = 25;
tv.itemWidth = 100;
tv.setTextFont(2);
for (int i = 0; i < jsonArry.size(); i++) {
Serial.printf("sd:jsonArry[0].name=%s\n",
(const char *)(jsonArry[i]["name"]));
}
for (int i = 0; i < jsonArry.size(); i++) {
tv.addItems(std::vector<MenuItem *>{
new MenuItem((const char *)(jsonArry[i]["name"]), i,
std::vector<MenuItem *>{new MenuItem("Wave1", 1, func),
new MenuItem("Wave2", 2, func),
new MenuItem("Wave3", 3, func
)})});
}
tv.begin();
TreeViewでコマンドを選択するとtag番号が取得できるのでJsonArrayから該当のコマンドを取得します
void func(MenuItem *mi) {
Serial.print(mi->parentItem()->tag);
Serial.print(":");
Serial.println(mi->tag);
JsonArray jsonArry = jsonObject["data"].as<JsonArray>();
Serial.printf("sd:jsonArry.size=%d\n", jsonArry.size());
String str = String(mi->tag);
str = "wave" + str;
Serial.printf("name:%s\n",
(const char *)jsonArry[mi->parentItem()->tag]["name"]);
Serial.printf(
"%s:%s\n", str,
(const char *)(jsonArry[mi->parentItem()->tag]["command"][str]));
decodeCommand(jsonArry[mi->parentItem()->tag]["command"][str]);
}
該当のコマンド行が取得出来たら行をデコードして先ほど作った該当の位置をタップする関数に渡します
void decodeCommand(String str) {
Serial.printf("command:%s\n", str.c_str());
int index = 0;
while (true) {
String com = str.substring(index, str.indexOf(",", index));
Serial.printf("%s\n", com);
index = str.indexOf(",", index) + 1;
if (com.startsWith("SK")) {
// Serial.println("Skill");
// Serial.println(atoi(com.substring(2, 3).c_str()));
switch (atoi(com.substring(2, 3).c_str())) {
case 1:
skill1(atoi(com.substring(3).c_str()));
break;
case 2:
skill2(atoi(com.substring(3).c_str()));
break;
case 3:
skill3(atoi(com.substring(3).c_str()));
break;
default:
break;
}
} else if (com.startsWith("AT")) {
atack();
} else if (com.startsWith("MS")) {
masterSkill(atoi(com.substring(2).c_str()));
} else if (com.startsWith("ST")) {
skillTo(atoi(com.substring(2).c_str()));
} else if (com.startsWith("OC")) {
orderChange(atoi(com.substring(2).c_str()));
} else if (com.startsWith("HG")) {
hougu(atoi(com.substring(2).c_str()));
} else if (com.startsWith("CD")) {
card(atoi(com.substring(2).c_str()));
} else if (com.startsWith("RS")) {
resetOrigin("X");
resetOrigin("Y");
}
if (index == 0) {
break;
}
}
}
これでJSONに列挙されたコマンドが順次実行されて自動操縦が可能になります
うまくいきました🤗
— もけ@ムギ㌠ (@coppercele) September 10, 2021
ただタイムラプスしないといけないくらい時間がかかるんで実用性は微妙かも🤣🤣🤣#M5Stack pic.twitter.com/MCSxDfefOw
最後にメッチャ長くなりましたがソースを置いておきます
長くなるので折りたたみ
#include <ArduinoJson.h>
#include <M5Stack.h>
#include <M5TreeView.h>
#include <string.h>
#include "GrblControl.h"
#define STEPMOTOR_I2C_ADDR 0x70
#define SERVO_ADDR 0x53
GRBL _GRBL = GRBL(STEPMOTOR_I2C_ADDR);
int degree = 0;
int PEN_UP = 130;
int PEN_DOWN = 100;
float SKILL_1_1_X = 0;
float SKILL_1_1_Y = 0.7;
float SKILL_1_2_X = 4;
float SKILL_1_2_Y = 0.7;
float SKILL_1_3_X = 7.5;
float SKILL_1_3_Y = 0.7;
float SKILL_2_1_X = 13.5;
float SKILL_2_1_Y = 0.7;
float SKILL_2_2_X = 17.5;
float SKILL_2_2_Y = 0.7;
float SKILL_2_3_X = 21;
float SKILL_2_3_Y = 0.7;
float SKILL_3_1_X = 27.5;
float SKILL_3_1_Y = 0.7;
float SKILL_3_2_X = 31;
float SKILL_3_2_Y = 0.7;
float SKILL_3_3_X = 34.5;
float SKILL_3_3_Y = 0.7;
float OPPONENT_1_X = 17;
float OPPONENT_1_Y = 1.5;
float OPPONENT_2_X = 29;
float OPPONENT_2_Y = 1.5;
float OPPONENT_3_X = 39;
float OPPONENT_3_Y = 1.5;
float ATTACK_X = 52;
float ATTACK_Y = 0.5;
float CARD_1_X = 8;
float CARD_1_Y = 0.7;
float CARD_2_X = 17;
float CARD_2_Y = 0.7;
float CARD_3_X = 29;
float CARD_3_Y = 0.7;
float CARD_4_X = 39;
float CARD_4_Y = 0.7;
float CARD_5_X = 50;
float CARD_5_Y = 0.7;
float HOUGU_1_X = 17;
float HOUGU_1_Y = 3.9;
float HOUGU_2_X = 29;
float HOUGU_2_Y = 3.9;
float HOUGU_3_X = 38;
float HOUGU_3_Y = 3.9;
float MASTER_SKILL_X = 54;
float MASTER_SKILL_Y = 2.6;
float MASTER_SKILL_1_X = 42;
float MASTER_SKILL_1_Y = 2.6;
float MASTER_SKILL_2_X = 46;
float MASTER_SKILL_2_Y = 2.6;
float MASTER_SKILL_3_X = 50;
float MASTER_SKILL_3_Y = 2.6;
float ORDER_CHANGE_1_X = 7;
float ORDER_CHANGE_1_Y = 2.6;
float ORDER_CHANGE_2_X = 15;
float ORDER_CHANGE_2_Y = 2.6;
float ORDER_CHANGE_3_X = 24;
float ORDER_CHANGE_3_Y = 2.6;
float ORDER_CHANGE_4_X = 33;
float ORDER_CHANGE_4_Y = 2.6;
float ORDER_CHANGE_5_X = 41;
float ORDER_CHANGE_5_Y = 2.6;
float ORDER_CHANGE_6_X = 50;
float ORDER_CHANGE_6_Y = 2.6;
float ORDER_EXEC_X = 28;
float ORDER_EXEC_Y = 0.0;
float x = 0.0;
float y = 0.0;
M5TreeView tv;
StaticJsonDocument<2048> jsonObject;
// addr 0x11 means "control the number 1 servo by angle"
void Servo_write_angle(uint8_t number, uint8_t angle) {
Wire.beginTransmission(SERVO_ADDR);
Wire.write(0x10 | number);
Wire.write(angle);
Wire.endTransmission();
}
void penUp() {
// servo(PEN_UP);
Servo_write_angle(0, PEN_UP);
delay(100);
// myservo.detach();
}
void penDown() {
Servo_write_angle(0, PEN_DOWN);
delay(100);
// myservo.detach();
}
void resetOrigin(char *axis) {
_GRBL.SetMode("distance"); // 相対移動モード
std::string str = "G1 1 F200";
str.insert(3, axis);
// USE Gcode
_GRBL.Gcode(const_cast<char *>(str.c_str())); // +1移動
str = "G1 -0.3 F200";
str.insert(3, axis);
while (!_GRBL.InLock()) {
_GRBL.Gcode(
const_cast<char *>(str.c_str())); // -0.2ずつロックされるまで移動
delay(100);
}
Serial.println("Locked");
Serial.println("Back to Origin");
str = "G1 0.2 F200";
str.insert(3, axis);
while (_GRBL.InLock()) {
_GRBL.UnLock(); // リミットスイッチのロックを解除する
_GRBL.SetMode("distance"); // absoluteに戻るので再設定
_GRBL.Gcode(const_cast<char *>(str.c_str()));
delay(300);
// スイッチが押しっぱなしだと即座にロックに戻るので移動してから少し待つ
}
_GRBL.SetMode("absolute");
Serial.println("Moved to Origin");
}
void moveTo(float toX, float toY) {
String command = "G1 X";
command += String(toX);
command += " F200";
Serial.println(command.c_str());
_GRBL.Gcode((char *)(command.c_str()));
command = "G1 Y";
command += String(toY);
command += " F200";
Serial.println(command.c_str());
_GRBL.Gcode((char *)(command.c_str()));
_GRBL.WaitIdle();
}
void tap() {
penDown();
penUp();
}
void skill1(int num) {
Serial.printf("exec Skill1-%d\n", num);
switch (num) {
case 1:
moveTo(SKILL_1_1_X, SKILL_1_1_Y);
break;
case 2:
moveTo(SKILL_1_2_X, SKILL_1_2_Y);
break;
case 3:
moveTo(SKILL_1_3_X, SKILL_1_3_Y);
break;
default:
break;
}
tap();
}
void skill2(int num) {
Serial.printf("exec Skill2-%d\n", num);
switch (num) {
case 1:
moveTo(SKILL_2_1_X, SKILL_2_1_Y);
break;
case 2:
moveTo(SKILL_2_2_X, SKILL_2_2_Y);
break;
case 3:
moveTo(SKILL_2_3_X, SKILL_2_3_Y);
break;
default:
break;
}
tap();
}
void skill3(int num) {
Serial.printf("exec Skill3-%d\n", num);
switch (num) {
case 1:
moveTo(SKILL_3_1_X, SKILL_3_1_Y);
break;
case 2:
moveTo(SKILL_3_2_X, SKILL_3_2_Y);
break;
case 3:
moveTo(SKILL_3_3_X, SKILL_3_3_Y);
break;
default:
break;
}
tap();
}
void skillTo(int num) {
Serial.printf("exec Skill to %d\n", num);
switch (num) {
case 1:
moveTo(OPPONENT_1_X, OPPONENT_1_Y);
break;
case 2:
moveTo(OPPONENT_2_X, OPPONENT_2_Y);
break;
case 3:
moveTo(OPPONENT_3_X, OPPONENT_3_Y);
break;
default:
break;
}
tap();
}
void masterSkill(int num) {
moveTo(MASTER_SKILL_X, MASTER_SKILL_Y);
tap();
switch (num) {
case 1:
moveTo(MASTER_SKILL_1_X, MASTER_SKILL_1_Y);
break;
case 2:
moveTo(MASTER_SKILL_2_X, MASTER_SKILL_2_Y);
break;
case 3:
moveTo(MASTER_SKILL_3_X, MASTER_SKILL_3_Y);
break;
default:
break;
}
tap();
}
void orderChange(int num) {
switch (num) {
case 1:
moveTo(ORDER_CHANGE_1_X, ORDER_CHANGE_1_Y);
break;
case 2:
moveTo(ORDER_CHANGE_2_X, ORDER_CHANGE_2_Y);
break;
case 3:
moveTo(ORDER_CHANGE_3_X, ORDER_CHANGE_3_Y);
break;
case 4:
moveTo(ORDER_CHANGE_4_X, ORDER_CHANGE_4_Y);
break;
case 5:
moveTo(ORDER_CHANGE_5_X, ORDER_CHANGE_5_Y);
break;
case 6:
moveTo(ORDER_CHANGE_6_X, ORDER_CHANGE_6_Y);
break;
case 7:
moveTo(ORDER_EXEC_X, ORDER_EXEC_Y);
break;
default:
break;
}
tap();
}
void hougu(int num) {
switch (num) {
case 1:
moveTo(HOUGU_1_X, HOUGU_1_Y);
break;
case 2:
moveTo(HOUGU_2_X, HOUGU_2_Y);
break;
case 3:
moveTo(HOUGU_3_X, HOUGU_3_Y);
break;
default:
break;
}
tap();
}
void card(int num) {
switch (num) {
case 1:
moveTo(CARD_1_X, CARD_1_Y);
break;
case 2:
moveTo(CARD_2_X, CARD_2_Y);
break;
case 3:
moveTo(CARD_3_X, CARD_3_Y);
break;
case 4:
moveTo(CARD_4_X, CARD_4_Y);
break;
case 5:
moveTo(CARD_5_X, CARD_5_Y);
break;
default:
break;
}
tap();
}
void atack() {
moveTo(ATTACK_X, ATTACK_Y);
tap();
}
void decodeCommand(String str) {
Serial.printf("command:%s\n", str.c_str());
int index = 0;
while (true) {
String com = str.substring(index, str.indexOf(",", index));
Serial.printf("%s\n", com);
index = str.indexOf(",", index) + 1;
if (com.startsWith("SK")) {
// Serial.println("Skill");
// Serial.println(atoi(com.substring(2, 3).c_str()));
switch (atoi(com.substring(2, 3).c_str())) {
case 1:
skill1(atoi(com.substring(3).c_str()));
break;
case 2:
skill2(atoi(com.substring(3).c_str()));
break;
case 3:
skill3(atoi(com.substring(3).c_str()));
break;
default:
break;
}
} else if (com.startsWith("AT")) {
atack();
} else if (com.startsWith("MS")) {
masterSkill(atoi(com.substring(2).c_str()));
} else if (com.startsWith("ST")) {
skillTo(atoi(com.substring(2).c_str()));
} else if (com.startsWith("OC")) {
orderChange(atoi(com.substring(2).c_str()));
} else if (com.startsWith("HG")) {
hougu(atoi(com.substring(2).c_str()));
} else if (com.startsWith("CD")) {
card(atoi(com.substring(2).c_str()));
} else if (com.startsWith("RS")) {
resetOrigin("X");
resetOrigin("Y");
} else if (com.startsWith("AC")) {
if (clockStarted) {
clockStarted = false;
} else {
clockStarted = true;
xTaskCreatePinnedToCore(clockTask, "clockTask", 4096, NULL, 1, NULL, 0);
}
}
if (index == 0) {
break;
}
}
}
void func(MenuItem *mi) {
Serial.print(mi->parentItem()->tag);
Serial.print(":");
Serial.println(mi->tag);
JsonArray jsonArry = jsonObject["data"].as<JsonArray>();
Serial.printf("sd:jsonArry.size=%d\n", jsonArry.size());
String str = String(mi->tag);
str = "wave" + str;
Serial.printf("name:%s\n",
(const char *)jsonArry[mi->parentItem()->tag]["name"]);
Serial.printf(
"%s:%s\n", str,
(const char *)(jsonArry[mi->parentItem()->tag]["command"][str]));
decodeCommand(jsonArry[mi->parentItem()->tag]["command"][str]);
}
void setup() {
M5.begin(true, true, true, true);
_GRBL.Init(400, 400, 400, 30);
_GRBL.SetMode("absolute");
dacWrite(25, 0); // ノイズ対策
tv.itemHeight = 25;
tv.itemWidth = 100;
tv.setTextFont(2);
File f = SD.open("/fgo.json");
DeserializationError error = deserializeJson(jsonObject, f);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
JsonArray jsonArry = jsonObject["data"].as<JsonArray>();
Serial.printf("sd:jsonArry.size=%d\n", jsonArry.size());
for (int i = 0; i < jsonArry.size(); i++) {
Serial.printf("sd:jsonArry[0].name=%s\n",
(const char *)(jsonArry[i]["name"]));
}
for (int i = 0; i < jsonArry.size(); i++) {
tv.addItems(std::vector<MenuItem *>{
new MenuItem((const char *)(jsonArry[i]["name"]), i,
std::vector<MenuItem *>{new MenuItem("Wave1", 1, func),
new MenuItem("Wave2", 2, func),
new MenuItem("Wave3", 3, func
)})});
}
tv.begin();
// penUp();
delay(1000);
Servo_write_angle(0, PEN_UP);
}
void loop() {
M5.update();
tv.update();