オリジナルガイアメモリをつくる 〜ハザードメモリ〜

およそ3ヶ月ぶりの更新です。本業の忙しさに加えて、新しい題材、基板、技術の勉強で随分時間がかかってしまいました。

今回ご紹介するのはオリジナルガイアメモリ『ハザードメモリ』です。

製作経緯

今現在(2020/8/7時点)のリアルタイムライダー作品は『ゼロワン』なのですが、『ゼロワン』関連の改造については個人的には『プログライズライター』でやりきってしまった感があったので、最近は過去作品を題材にすることが多くなっていました。

その結果が『友情バーストゴースト眼魂』や『DX変身音叉 音角 ver.2.0』なのですが、その後特に作りたいものがなく、折角なのでこの間に何か新しい技術を勉強してみようかと思い、「無線通信を使って何かできないかな」ということをボンヤリ考え始めました。

最初は「ファイズフォンでエクシードチャージできるファイズエッジとかできないかな」とか考えていたのですが、そうこうしているウチに「ガイアメモリのプログラムを作るのを手伝ってほしい」という相談がきました。普段はそういう「作ってほしい」系の相談は受けないのですが、たまたまやることもなく、またガイアメモリはこれまで扱ったことがなかったので、引き受けることにしました。

結果、プログラムの添削をするより自分がイチから書く方が早いということになりましたので、モチベーションを上げるために自分ごととして考えられるよう、自分の方でも「ガイアメモリを題材に何かを作る」というテーマを設定することにしました。これと、先ほどの「無線通信を使った何か」をミックスしてしばらく考えてみた結果、「無線通信でベストマッチやツインマキシマムを実現する」というアイデアを思いつきました。

特徴

デザインモチーフはもちろん、『仮面ライダービルド』に登場した暴走強化アイテム『ハザードトリガー』です。

余談ですが、過去にハザードトリガーの発光改造をやっていて、自分としてもかなりお気に入りの作品なので宜しければご視聴ください。

最初はダブルドライバーのLスロット(ボディサイド)用のメモリとして考えていました。というのも、『サイクロンハザード』『ヒートハザード』『ルナハザード』という響きが、何だか自然災害を操る強敵みたいでカッコ良いなあと思ったからです。また、ファングメモリと組み合わせたら『ファングハザード』で大暴走フォームになってそれもまたおもしろそう、という考えもありました。ただ、ボディサイド用のメモリにするとなると、一応ルール上は端子部を金色にしないといけなくて、それだとハザードトリガーっぽさがちょっと減ってしまうのが気になるところでした。

そんなわけで、「それなら逆に、端子部を元のハザードトリガーと同じ銀色にすることを優先したらどうなるか?」を考えてみると、『ハザードジョーカー』『ハザードメタル』となって、これはこれでまあアリかなと思っていたところ、「よく考えたら『ハザード』と『トリガー』で『ハザードトリガー』になるじゃん!ベストマッチじゃん!」ということにここでようやく気づいて、結局メモリを両スロット対応にして、「ベストマッチ!」という音声を鳴らす仕様が決まりました。

 

そんなわけで、ボタン長押しで両スロットに、ついでにロストドライバーにも対応できるようにしてみました。この操作で、変身時の音声や発光のタイミングを切り替えられるようにしています。切り替えたときに流れる「逆サイド!」「ワンサイド!」の音声は、ジーニアスフルボトルの音声を使用しています。元々類似点の多い『ダブル』と『ビルド』ですが、ものすごく丁度良い音声があって良かったです。

 

あと、両スロット対応にするため、ラベルも貼り替えで文字の向きを変更できるようにしてみました。

 

続いて「ベストマッチ!」ですが、トリガーと組み合わせたときには素直に「ベストマッチ!」にして、ファングと組み合わせたときは暴走中間フォーム同士の組み合わせということで「スーパーベストマッチ!」にしてみました。

この「ベストマッチ/スーパーベストマッチ」は、トリガーメモリとファングメモリが、それぞれメインのボタンを押されたときに一瞬だけ無線通信を行うことで実現しています。つまり、実は今回、ハザードメモリだけではなく、トリガーメモリとファングメモリも改造しています。これも、今回時間のかかった要因の一つです。ファングメモリについては、無線通信対応させるついでに眼を赤く発光させたり、顎が自動で展開されるような改造も追加しています。これらの改造内容については、また後述致します。

 

このハザードメモリのメイン機能が、ツインマキシマムの発動です。トリガーメモリについてはトリガーマグナムへの装填、ファングメモリについては鼻の角(?)の大量連打をきっかけに無線通信を行って発動するようにしてみました。

ツインマキシマムの発動については、例えば特別なボタン操作によって内部モードを切り替えるとかいうやり方でも実現できたかもしれませんが、今回は無線通信を使うことで、より自然な形でツインマキシマムを発動できるところに拘ってみました。

ハードウェア解説

今回は新しい基板と技術を使ったのでハード解説もソフト解説も長くなりそうですが、とりあえずハードウェアの方から先に解説してみます。

まずラベルですが、いつもの如く、ZXライドウォッチプログライズライターのラベルを作ってくださった方に作成頂きました。いつもありがとうございます。

アルファベットの「H」は、ビルドドライバーやハザードトリガーに共通して現れている斜めのデザインをベースに、ハザードシンボルの「!」を2つ組み合わせる形でデザインして頂きました。

ホログラムについては、過去に発売されたガイアメモリのコンプリートセレクションに付属されていたシールをバラ売りされていた方がおられたので、そちらから購入させて頂きました。

プログライズライター音声拡張パックでもやっていますが、スマホ用の吸着式の液晶画面保護シートにラベルを貼ることで、L/Rスロット用にラベルを容易に貼り替えることができるようにしています。

全体はガンダムマーカーのロイヤルメタレッド、コスモメタブルー、シャインシルバーで塗装しています。ガンダムマーカーエアブラシシステムを使用しました。

…が、ガイアメモリはドライバーへの着脱を繰り返すと、こんな感じでいとも簡単に塗装が禿げてきますので、全体塗装は全くオススメいたしません。ボタン部や端子部など、ポイントに絞った塗装をオススメ致します(それも、繰り返し遊んでいると剥げてくるのですが)。

続いて、電子部品の具材のご紹介です。

これまでの私の作品からの一番の変化点であり、また今回の作品の肝になる部品です。これまではマイコンとしてほぼArduino Pro Mini (3.3V)を採用してきたのですが、今回は無線通信機能を扱うため、M5StackシリーズのATOM Liteを採用しました。

このサイズでありながら、WiFiBLE(Bluetooth Low Energy)を扱えるという優れものです。また、Arduino Pro Miniほどではないにしても、GPIO端子が豊富に用意されているのも魅力です。開発もArduino IDEで行えるので、純粋なArduinoを使った開発からの移行も容易です。いくつか注意事項はありますが、それはソフトウェア解説の方で改めて説明致します。

そしてATOM Liteを使用する上で重要なのは、3.7Vのリチウムイオンポリマー充電池で動作可能ということです。初めは5Vぐらい供給しないとダメかなと思っていたのですが、試してみたら問題なく動く。これで一気に作品作りの幅が広がりました。ただ、電池の消費は激しいので、あくまでプロトタイプを作る上では良い、という認識です。

あと、持っていない方は、

こちらの充電池用コネクタと、

こんな感じの充電器はあった方が良いと思います。

最近メインで使っているMP3モジュールは、今回も続投です。後述しますが、ガイアメモリは内部スペースの余裕が全然ないので、本来ベストな選択は、より小型なDFPlayerだと思います。ただ、DFPlayerSDカードを必要とすることから、コストを重視するなら、やはりこちらのモジュールということになります。何せ今回はガイアメモリを3本も改造しているもんで、費用も単純に3倍かかるのです。

スピーカーも、元々のDX付属のスピーカーではなく、最近メインで使っているこちらを続投採用です。このスピーカーの薄さのおかげでギリギリ部品が全部収まった感があります。

あとはもちろん、ベースとなるDXのガイアメモリが必要です。中古屋さんか、メルカリなどのフリマアプリで入手するのが良いと思います。余裕があれば複数入手して、一つは実験用として割り切って色々試してみてからメインの作品を作った方が、最終的なクオリティは上がると思います。

 

さて、今回は3つのメモリ(ハザード、トリガー、ファング)を改造していますが、ハードウェアとしては基本的な考え方は全部同じなので、ハザードメモリに絞って解説します。最後に補足で、ファングメモリだけのプチ改造についても触れます。

今回は赤色に発光させるつもりだったので、ベースはDX版のヒートメモリにしました。

まず、中身の突起は可能な限りカットします。結果、電池蓋をネジで固定することもできない状態になりましたので、蓋はやむなく両面テープで固定する形にしました。

続いて配線はこんな感じです。

実際に配線したものは、こんな感じです。

ガイアメモリのスイッチは3つありますが、個別に自前で用意するのは大変なので、元の基板をそのまま使用しています。

右上の黒い部分は(おそらく)音声チップが入っていて、これが残っていると配線の邪魔になるので、

大胆にカットします。

そして、一見奇妙に見えるかもしれませんが、LEDの足の部分を切断して、

LEDの残った足の部分とスイッチの部分を繋げます。LEDの方は切断した脚に直接配線するようにします。

そして、裏側をこんな感じで配線します。スペースの都合で、配線はこちら側の面に集中させるようにしています。

 

続いてMP3ボイスモジュールとスピーカーは、間を絶縁させつつ密着させます。これでギリギリメモリに収まるサイズになります。

 

最後にマイコンのATOM Liteですが、ケースに入れたままだとサイズ的にやや辛いので、中身だけを取り出して使っています。ケースは簡単に開くことができます。

ちなみに、私の普段の改造では遊びやすくするために電源スイッチをつけるようにしているのですが、今回はスペースとデザインの都合で省略しました。遊ぶときは、ちょっと面倒ですが、都度蓋を開けて通電させるようにしています。

 

以上が基本的な構造です。最後にファングについてちょっとだけ補足です。

 

ファングはBLE通信機能を追加させるためだけに、元々の(尻尾がある分より複雑な)商品仕様とほぼ同じプログラムを書くという、何というか労力に大変見合わないことをすることになってしまったので、せっかくなので2つの機能を追加することにしました。どちらも既に他の方がやられているものにはなりますが。

 

一つはメモリ状態での眼の発光です。別に劇中で発光していたことはないと思いますが、せっかくなので光らせてみました。

こんな感じで赤色LEDを仕込んで、回転軸の部分を通して配線しています。

もう一つは顎の自動展開です。最後に手動で顎をグイッと出すのはイマイチなので、ここは是非やっておきたいポイントでした。

これはおそらく先駆者の方々もほとんどそうしていると思いますが、磁石を使って実現しています。

 

まず、顎(脚)パーツの穴をテーパーで少しずつ広げて、たまたま3Dプリンタを改修した時に余っていたベアリングをはめ込みました。穴の広げ過ぎに注意。

そして軸回りで脚と干渉する部分は削って、元々のねじ穴の棒も短く切った後、

脚(顎)と本体部分の干渉を防ぐため、ワッシャを左右に2枚ずつ挟んで本体をねじ止めします。ネジは、これまたたまたまですが、過去の玩具改造で余ったものを使いました。

これで、少なくとも「脚がプラプラする状態」になるので、続いて磁石をつけて固定されるようにしていきます。

一つはここ、回転軸の近くです。顎の展開後にここで固定することになるので、ネオジム磁石二つで強く引き合うようにしています。

 

もう一つはここ、足先の部分です。こちらは通常時のロック用なので、あんまり磁力が強いと顎が展開しなくなってしまいます。ということで、こちらは一方はネジにして、また極力弱く引き合うように磁石の向きを調整して固定しています。

こんな感じで、自分はたまたま手元にあった部品を使ったら上手くいったという感じなので、あんまり万人向けのやり方ではないかもしれません。が、何か参考になる部分があれば幸いです。

ちなみにファングメモリは中古市場では基本お値段高めで常時推移しているので、改造のためにファングメモリを部分的にとはいえ破壊するのは、なかなか勇気がいることかもしれません。

ソフトウェア解説

ここまでで既にだいぶ長くなってしまっていますが、最後にソフトウェア解説です。ここはサラッと流すことも多いのですが、今回は基板を変えてBLEまで使ってしまっているので、解説しないわけにもいかないかなと思います。

まず、とにもかくにも状態遷移の確認です。ガシャット、ライドウォッチ 、プログライズキーに比べればだいぶシンプルではあるのですが、発光と音声、BluetoothのスキャンのONタイミングとかも併記しているため、ちょっとごちゃついています。

これをベースにプログラムを組んでいくわけですが、ATOM Liteのプログラミングも基本はArduinoとほぼ同じです。ただ、2点、注意点があります。

まず、LEDの制御などでanalogWrite()は使用できません。代わりにledcWrite()を使用する形になります。また、ledcWrite()を使用するためには、事前準備でledcSetup()ledAttachPin()を呼び出す必要があります。

もう一つは、MP3ボイスモジュールとの通信にSoftwareSerialは使用できません。が、代わりにハードウェアシリアルが使えるので特に問題ありません。私のプログラム中ではSerial1を使用しています。ソースコードは本記事の最後にまとめて掲載しておりますので、実態はそちらを見て頂くのが早いと思います。

上記に気をつければ、ATOM Liteを使った基本的なガイアメモリのプログラムは書けると思います。問題は、BLE通信をどうやって組み込むかです。それを考えるには、BLE通信の基礎を知っておく必要があります。

BLEの通信形態はコネクション型と非コネクション型がありますが、今回は単発の信号のやり取りでコトは済むので、簡単な非コネクション型でプログラムを組みます。具体的には、ハザードメモリ側は基本常時スキャン状態にしておいて、トリガーメモリとファングメモリは、メモリ名が鳴るときとマキシマムドライブが発動するときに、一瞬だけアドバタイズパケットを送出させるようにします。

具体的にどういうコードを書けば良いのかはソースコードを見て頂けば大体わかると思いますが、難しいのはハザードメモリ側、スキャン側の方です。

ひょっとしたら私が無知なだけで回避方法はあるのかもしれませんが、BLEのスキャンを開始させると、その時点でloop()の処理はストップしてしまいます。つまり、発光処理の途中でスキャンをスタートさせたらそこで発光は止まってしまうし、一般的なHIGH-LOW変化検出によるボタン操作も認識できなくなってしまいます。よって、スキャンを開始させるstart()を呼び出すのは、基本的に発光エフェクトが終わった後になります。

再びloop()を再開させるには、スキャンを停止させる必要があります。方法は2つで、スキャン開始時にstart()で指定した時間が経過するまで待つか、stop()を呼び出して停止させるかです。loop()が止まっている状態でstop()を呼び出す方法もまた2つで、アドバタイズパケットの受信処理を記述したコールバック関数内で呼び出すか、GPIOHIGH-LOW検知の割り込みハンドラ内で呼び出すことです。今回はどちらも採用していて、アドバタイズパケットを受信する、または何かしらのボタン操作あった時点でスキャンを停止させてloop()処理を再開させるようにしています。割り込みハンドラの中にあんまり複雑な処理を記述してしまうと動作が不安定になるので、基本はBLEスキャンの停止のみで、必要に応じて簡単な条件分岐を入れるぐらいに留める必要があります。

ちなみに、アドバタイズの方はスタートさせても特にloop()が止まったりすることはありません。このあたりの理屈はよくわかりませんが、今回はとりあえず動けばいいんだ動けば、ということで、あんまり深追いはしません。

BLEのプログラミングの大きな注意事項はそんなところかと思いますが、他にも時間設定とかの細々したところが色々あるので、とりあえず以下のようにしてみました。

  • スキャンは省エネのため間欠制御にするのが基本であるものの、間欠の時間間隔を長くするほど通信のタイムラグが発生しやすくなるので、今回はリアルタイム性重視で常時スキャンに設定(= intervalとwindowを同じ値に設定)
  • スキャン側(ハザードメモリ)が常時スキャンしてくれているので、アドバタイズ側(トリガーメモリ、ファングメモリ)のパケット送出時間は200ms間のみ
  • スキャン側は対向のガイアメモリの送出するアドバタイズパケットだけでなく、周辺のBLE機器の発する全てのアドバタイズパケットを受信するので、ガイアメモリが送出したパケットのみをフィルタリングするために、アドバタイズパケットのManufactureData領域に任意のサービスUUIDを設定

ソフトウェアの解説はこんなところです。ちなみに、このハザードメモリにはBLE機能の他に「ダブルドライバーの両スロットと、ロストドライバーに対応する(=スロットごとに音声・発光の再生タイミングを最適化する)」という機能があります。直感ですが、BLEよりそっちのやり方を教えて欲しい」という方の方が多そうな気がしますので、それはまた後日、それだけに特化したガイアメモリを作る形で、別記事で紹介できればと思います。

まとめ

以上、オリジナルガイアメモリ『ハザードメモリ』のご紹介でした。今回は自然に作りたいと思ったというより、『無線通信』『ガイアメモリ』という二つのお題から無理矢理捻り出すような感じで作り出すことになりましたが、最終的にはおもしろい形にまとまったかなと思います。何より自分のスキルとして、簡易的なものですが無線通信(BLE)を扱えるようになったので、今後の作品づくりの幅を広げられたのが大きいと思います。

ただ、すぐ上でも書きましたが、このガイアメモリは正直機能盛り盛り過ぎて、あんまり作りたいと思う人はいないのではないかと思います。それよりは、「ダブルドライバーの両スロットと、ロストドライバーに対応する(=スロットごとに音声・発光の再生タイミングを最適化する)」という機能だけを持ったガイアメモリを作りたいと思う人の方が多い気がしています。今回作ったガイアメモリのソースコードは、後から見直してみるとちょっとゴチャついている感じもしているので、後日上記の機能に絞って、ソースコード全体をもう少し整理したメモリを作って記事にする予定です。普通のガイアメモリを自作されたい方はそちらを参考にした方が良いと思いますので、今しばらくお待ちください。

 

 

ソースコード

今回はハザード、トリガー、ファングの3つのメモリを作成しましたが、正直どれもコードの作り込みは甘いです。特にファングは「動けばいいんだ動けば」という感じでロクに設計もせずに作ってしまったので、とてもお見せできる状態ではありません。

ということで、BLE通信のアドバタイズ側(送信側)のサンプルとしてトリガーメモリのコード、スキャン側(受信側)のサンプルとしてハザードメモリのコードを公開します。が、繰り返しになりますがどちらもあまり綺麗にかけていないので、「無線通信不要だけどガイアメモリは作りたい」という方向けに、BLE関係の機能を除いて全体をもう少し整理したコードを後日公開する予定です。

トリガーメモリ(※BLEアドバタイザー、送信側)

////////// Arduino開発環境構築リファレンス ////////////////////////////////////////
// https://docs.m5stack.com/#/en/arduino/arduino_development

////////// 基本定義 ////////////////////////////////////////////////////////////
// 本来ならM5Atomとして開発すべきだが、コンパイルエラーが解決できないので、同じマイコンを使用しているM5StickCとして開発する
// #include "M5Atom.h"
#include "M5StickC.h" 

#include "BLEDevice.h"
#include "BLEServer.h"
#include "BLEUtils.h"

#define LOOP_DELAY_MS 20

#define MP3_RX_PIN  32 // Groveポート
#define MP3_TX_PIN  26 // Groveポート
#define SW_1_PIN    33
#define SW_2_PIN    23
#define SW_3_PIN    19
#define LED_PIN     22

#define ON  LOW
#define OFF HIGH

// 3つのボタン入力の表現
#define N_BUTTON 3
const uint8_t OFF_OFF_OFF[] = {OFF, OFF, OFF};
const uint8_t OFF_OFF_ON[]  = {OFF, OFF,  ON};
const uint8_t OFF_ON_OFF[]  = {OFF,  ON, OFF};
const uint8_t OFF_ON_ON[]   = {OFF,  ON,  ON};
const uint8_t ON_OFF_OFF[]  = {ON,  OFF, OFF};
const uint8_t ON_OFF_ON[]   = {ON,  OFF,  ON};
const uint8_t ON_ON_OFF[]   = {ON,   ON, OFF};
const uint8_t ON_ON_ON[]    = {ON,   ON,  ON};
uint8_t prev_sw[] = {OFF, OFF, OFF};
uint8_t sw[]      = {OFF, OFF, OFF};

// 各モードで変身時の音声再生タイミングが異なる
#define MODE_W_R  0 // ダブルドライバーRスロットモード
#define MODE_W_L  1 // ダブルドライバーLスロットモード
#define MODE_LOST 2 // ロストドライバーモード

#define LONG_PRESS_MS 2000 // モード変更のための長押し時間

#define STATE_INIT           0
#define STATE_TEMP           1
#define STATE_CHANGE_READY   2
#define STATE_CHANGE         3
#define STATE_CRITICAL_READY 4
#define STATE_CRITICAL       5

uint8_t state      = STATE_INIT;
uint8_t prev_state = STATE_INIT;

uint8_t mode = MODE_W_L; // デフォルトはダブルドライバーLスロットモードとする
unsigned long long_press_start_time = 0;
boolean is_sw_1_pressing = false;

void print_state(){
  if(prev_state != state){
    switch(state){
    case STATE_INIT:           Serial.println(F("STATE_INIT")); break;
    case STATE_TEMP:           Serial.println(F("STATE_TEMP")); break;
    case STATE_CHANGE_READY:   Serial.println(F("STATE_CHANGE_READY")); break;
    case STATE_CHANGE:         Serial.println(F("STATE_CHANGE")); break;
    case STATE_CRITICAL_READY: Serial.println(F("STATE_CRITICAL_READY")); break;
    case STATE_CRITICAL:       Serial.println(F("STATE_CRITICAL")); break;
    default: ;
    }
  }
}

////////// 音声処理 /////////////////////////////////////////////////////////////

// 従来はSoftwareSerialを使ってDFPlayerやMP3ボイスモジュールと接続していたが、
// ESP32はデフォルトではソフトウェアシリアルは使用できない。
// ただし、3系統のハードウェアシリアルを使用できるので、そちらを使って通信を行う。
// SerialはUSB経由でのPCとの通信に使用するので、Serial1 または Serial2を使用する。

#define SOUND_VOLUME_DEFAULT 24 // 0〜30

#define SOUND_MEMORY_NAME          1
#define SOUND_CHANGE_READY         2
#define SOUND_MEMORY_EJECT         3
#define SOUND_DRIVER_OPEN          4
#define SOUND_CHANGE               5
#define SOUND_DRIVER_CLOSE         6
#define SOUND_CRITICAL_READY       7
#define SOUND_CRITICAL             8
#define SOUND_REVERSE_SIDE         9
#define SOUND_ONE_SIDE            10

#define WAIT_SOUND_STATE_NONE   0
#define WAIT_SOUND_STATE_NAME   1
#define WAIT_SOUND_STATE_CHANGE 2

uint8_t wait_sound_state = WAIT_SOUND_STATE_NONE;

#define WAIT_SOUND_FOR_W_R_NAME_MS  1200 // ドライバー開音再生開始後のメモリ名音までの再生待ち時間(WドライバーRスロット側)
#define WAIT_SOUND_FOR_W_L_NAME_MS  2000 // ドライバー開音再生開始後のメモリ名音までの再生待ち時間(WドライバーLスロット側)
#define WAIT_SOUND_FOR_LOST_NAME_MS 1200 // ドライバー開音再生開始後のメモリ名音までの再生待ち時間(ロストドライバー)

#define WAIT_SOUND_FOR_W_R_CHANGE_MS  2200 // メモリ名音再生開始後の変身音までの再生待ち時間(WドライバーRスロット側)
#define WAIT_SOUND_FOR_W_L_CHANGE_MS  3000 // メモリ名音再音開始後の変身音までの再生待ち時間(WドライバーLスロット側)
#define WAIT_SOUND_FOR_LOST_CHANGE_MS 1500 // メモリ名音再音開始後の変身音までの再生待ち時間(ロストドライバー)

unsigned long sound_wait_start_time = 0;

void mp3_play(uint8_t track){
  // この定義だと再生曲数は255に限定されるので注意。
  // Upper-Byte, Lower-Byteをちゃんと処理してやれば65535曲まで対応可能だが、
  // 容量的にそこまで使うことはほぼないと思われる。
  uint8_t play[6] = {0xAA,0x07,0x02,0x00,track,track+0xB3};
  Serial1.write(play,6);
}

void mp3_stop(){
  unsigned char stop[4] = {0xAA,0x04,0x00,0xAE};
  Serial1.write(stop,4);
}

void mp3_set_volume(uint8_t vol){
  uint8_t volume[5] = {0xAA,0x13,0x01,vol,vol+0xBE};
  Serial1.write(volume,5);
}

void control_sound(){

  unsigned long now_ms = millis();

  if(prev_state != state){ // 状態遷移直後の処理
    switch(state){
    case STATE_INIT:
      if(prev_state == STATE_CHANGE_READY
      || prev_state == STATE_CHANGE
      || prev_state == STATE_CRITICAL_READY
      || prev_state == STATE_CRITICAL){
        mp3_play(SOUND_MEMORY_EJECT);
        wait_sound_state = WAIT_SOUND_STATE_NONE;
      }
      break;
    case STATE_TEMP:
      if(prev_state == STATE_INIT){
        mp3_play(SOUND_MEMORY_NAME);
      }else{
        mp3_play(SOUND_MEMORY_EJECT);
        wait_sound_state = WAIT_SOUND_STATE_NONE;
      }
      break;
    case STATE_CHANGE_READY:
      if(prev_state == STATE_INIT){
        mp3_play(SOUND_CHANGE_READY);
      }else if(prev_state == STATE_CHANGE){
        mp3_play(SOUND_DRIVER_CLOSE);
        wait_sound_state = WAIT_SOUND_STATE_NONE;
      }
      break;
    case STATE_CHANGE:
      // まずはドライバー開音を鳴らして、変身音鳴らしの待ちに入る
      mp3_play(SOUND_DRIVER_OPEN);
      sound_wait_start_time = now_ms;
      wait_sound_state = WAIT_SOUND_STATE_NAME;
      break;
    case STATE_CRITICAL_READY:
      if(prev_state == STATE_INIT){
        mp3_play(SOUND_CRITICAL_READY);
      }
      break;
    case STATE_CRITICAL:
      mp3_play(SOUND_CRITICAL);
      break;
    default:
      ;
    }
  }else{ // 状態遷移後の時間経過処理
    switch(wait_sound_state){
    case WAIT_SOUND_STATE_NONE:
      break;
    case WAIT_SOUND_STATE_NAME:
      if(mode == MODE_W_R){
        // ダブルドライバーRスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_R_NAME_MS){
          mp3_play(SOUND_MEMORY_NAME);
          sound_wait_start_time = now_ms;
          wait_sound_state = WAIT_SOUND_STATE_CHANGE;
        }
      }else if(mode == MODE_W_L){
        // ダブルドライバーLスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_L_NAME_MS){
          mp3_play(SOUND_MEMORY_NAME);
          sound_wait_start_time = now_ms;
          wait_sound_state = WAIT_SOUND_STATE_CHANGE;
        }
      }else{
        // ロストドライバーモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_LOST_NAME_MS){
          mp3_play(SOUND_MEMORY_NAME);
          sound_wait_start_time = now_ms;
          wait_sound_state = WAIT_SOUND_STATE_CHANGE;
        }
      }
      break;
    case WAIT_SOUND_STATE_CHANGE:
      if(mode == MODE_W_R){
        // ダブルドライバーRスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_R_CHANGE_MS){
          mp3_play(SOUND_CHANGE);
          wait_sound_state = WAIT_SOUND_STATE_NONE;
        }
      }else if(mode == MODE_W_L){
        // ダブルドライバーLスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_L_CHANGE_MS){
          mp3_play(SOUND_CHANGE);
          wait_sound_state = WAIT_SOUND_STATE_NONE;
        }
      }else{
        // ロストドライバーモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_LOST_CHANGE_MS){
          mp3_play(SOUND_CHANGE);
          wait_sound_state = WAIT_SOUND_STATE_NONE;
        }
      }
      break;
    default:
      ;
    }
  }
}

////////// 発光処理 /////////////////////////////////////////////////////////////

// ESP32では、analogWriteを使用することはできない。

#define PWM_HZ        5000 // PWM周波数
#define PWM_LEVEL        8 // PWMの段階(8ならMAX255,10ならMAX1023)
#define PWM_CHANNEL_1    1 // PWMのチャンネル(0~15で設定可)

#define LED_BRIGHTNESS 192 // 255が上限値

boolean is_led_active     = false; // 電源投入直後のSTATE_INITのときのLED発光を無効にするためのフラグ
boolean is_memory_ejected = false; // STATE_INITに遷移するときの発光パターンを切り替えるためのフラグ
boolean is_change_off     = false; // STATE_CHANGE_READYに遷移するときの発光パターンを切り替えるためのフラグ
boolean is_critical       = false; // STATE_CRITICAL_READYに遷移するときの発光パターンを切り替えるためのフラグ

unsigned long led_pattern_start_time = 0;
unsigned long prev_blink_time        = 0;
unsigned long inc_dim_start_time = 0;
boolean is_lighting = false;
boolean is_inc = true;

int  prev_interval_ms = 0;
uint8_t prev_steps = 0;

// 設定したLEDパターンを実行する。この間、スイッチの操作などは受け付けられないので注意
void execute_led_pattern(void (*func)(unsigned long, unsigned long), unsigned long duration_ms){
  unsigned long now_ms = millis();
  unsigned long start_time = now_ms;
  for(unsigned long i=0;i<duration_ms;i+=LOOP_DELAY_MS){
    now_ms = millis();
    func(now_ms - start_time, now_ms);
    delay(LOOP_DELAY_MS);
  }
}

void led_base_pattern_on(){
  ledcWrite(PWM_CHANNEL_1, LED_BRIGHTNESS);
  prev_interval_ms = 0;
  prev_steps = 0;
}

void led_base_pattern_off(){
  ledcWrite(PWM_CHANNEL_1, 0);
  prev_interval_ms = 0;
  prev_steps = 0;
}

void led_base_pattern_blink(unsigned long now_ms, int interval_ms){
  if(now_ms - prev_blink_time >= interval_ms){
    if(is_lighting){
      ledcWrite(PWM_CHANNEL_1, 0);
    }else{
      ledcWrite(PWM_CHANNEL_1, LED_BRIGHTNESS);
    }
    is_lighting = !is_lighting;
    prev_blink_time = now_ms;
  }

  prev_interval_ms = interval_ms;
  prev_steps = 0;
}

void led_base_pattern_inc(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_BRIGHTNESS/steps;
  ledcWrite(PWM_CHANNEL_1, l_step*current_step);

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_base_pattern_dim(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_BRIGHTNESS/steps;
  ledcWrite(PWM_CHANNEL_1, l_step*(steps-current_step));

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_base_pattern_blink_slowly(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
    is_inc = true;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_BRIGHTNESS/steps;
  if(is_inc){
    ledcWrite(PWM_CHANNEL_1, l_step*current_step);
  }else{
    ledcWrite(PWM_CHANNEL_1, l_step*(steps-current_step));
  }
  if(now_ms - inc_dim_start_time >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_time = now_ms;
  }

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_pattern_memory_name(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 400){                          led_base_pattern_dim(now_ms, 400, 10);}
  else if(400 < passed_ms && passed_ms <= 1200){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                          led_base_pattern_off();}
}

void led_pattern_memory_ejected(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 300){                          led_base_pattern_on();}
  else if(300 < passed_ms && passed_ms <= 1000){ led_base_pattern_dim(now_ms, 700, 20);}
  else{                                          led_base_pattern_off();}
}

void led_pattern_change_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 525){                         led_base_pattern_on();}
  else if(525 < passed_ms && passed_ms <= 800){ led_base_pattern_dim(now_ms, 275, 10);}
  else{                                         led_base_pattern_blink_slowly(now_ms, 550, 10);}
}

void led_pattern_change(unsigned long passed_ms, unsigned long now_ms){
  switch(mode){
  case MODE_W_R:
    if(passed_ms <= 600){                             led_base_pattern_inc(now_ms, 600, 10);} // ドライバー開
    else if(  600 < passed_ms && passed_ms <=  1100){ led_base_pattern_dim(now_ms, 500, 10);}
    else if( 1100 < passed_ms && passed_ms <=  1200){ led_base_pattern_off();}
    else if( 1200 < passed_ms && passed_ms <=  1600){ led_base_pattern_dim(now_ms, 400, 10);} // メモリ名
    else if( 1600 < passed_ms && passed_ms <=  2400){ led_base_pattern_dim(now_ms, 800, 20);}
    else if( 2400 < passed_ms && passed_ms <=  3400){ led_base_pattern_off();}
    else if( 3400 < passed_ms && passed_ms <=  3900){ led_base_pattern_dim(now_ms, 500, 10);} // 変身音
    else if( 3900 < passed_ms && passed_ms <=  4400){ led_base_pattern_dim(now_ms, 500,  9);}
    else if( 4400 < passed_ms && passed_ms <=  4600){ led_base_pattern_dim(now_ms, 200, 10);}
    else if( 4600 < passed_ms && passed_ms <=  5600){ led_base_pattern_on();}
    else if( 5600 < passed_ms && passed_ms <=  6800){ led_base_pattern_dim(now_ms, 1200, 20);}
    else if( 6800 < passed_ms && passed_ms <=  8300){ led_base_pattern_inc(now_ms, 1500, 20);} // 変身完了後の点灯
    else if( 8300 < passed_ms && passed_ms <= 12900){ led_base_pattern_on();}
    else if(12900 < passed_ms && passed_ms <= 14400){ led_base_pattern_dim(now_ms, 1500, 20);}
    else{                                             led_base_pattern_off();}
    break;
  case MODE_W_L:
    if(passed_ms <= 600){                             led_base_pattern_inc(now_ms, 600, 10);} // ドライバー開
    else if(  600 < passed_ms && passed_ms <=  1100){ led_base_pattern_dim(now_ms, 500, 10);}
    else if( 1100 < passed_ms && passed_ms <=  2000){ led_base_pattern_off();}
    else if( 2000 < passed_ms && passed_ms <=  2400){ led_base_pattern_dim(now_ms, 400, 10);} // メモリ名
    else if( 2400 < passed_ms && passed_ms <=  3200){ led_base_pattern_dim(now_ms, 800, 20);}
    else if( 3200 < passed_ms && passed_ms <=  5000){ led_base_pattern_off();}
    else if( 5000 < passed_ms && passed_ms <=  5500){ led_base_pattern_dim(now_ms, 500, 10);} // 変身音
    else if( 5500 < passed_ms && passed_ms <=  6000){ led_base_pattern_dim(now_ms, 500,  9);}
    else if( 6000 < passed_ms && passed_ms <=  6200){ led_base_pattern_dim(now_ms, 200, 10);}
    else if( 6200 < passed_ms && passed_ms <=  7200){ led_base_pattern_on();}
    else if( 7200 < passed_ms && passed_ms <=  8400){ led_base_pattern_dim(now_ms, 1200, 20);}
    else if( 8400 < passed_ms && passed_ms <=  9900){ led_base_pattern_inc(now_ms, 1500, 20);} // 変身完了後の点灯
    else if( 9900 < passed_ms && passed_ms <= 12900){ led_base_pattern_on();}
    else if(12900 < passed_ms && passed_ms <= 14400){ led_base_pattern_dim(now_ms, 1500, 20);}
    else{                                             led_base_pattern_off();}
    break;
  case MODE_LOST:
    if(passed_ms <= 600){                             led_base_pattern_inc(now_ms, 600, 10);} // ドライバー開
    else if(  600 < passed_ms && passed_ms <=  1100){ led_base_pattern_dim(now_ms, 500, 10);}
    else if( 1100 < passed_ms && passed_ms <=  1200){ led_base_pattern_off();}
    else if( 1200 < passed_ms && passed_ms <=  1600){ led_base_pattern_dim(now_ms, 400, 10);} // メモリ名
    else if( 1600 < passed_ms && passed_ms <=  2400){ led_base_pattern_dim(now_ms, 800, 20);}
    else if( 2400 < passed_ms && passed_ms <=  2700){ led_base_pattern_off();}
    else if( 2700 < passed_ms && passed_ms <=  3200){ led_base_pattern_dim(now_ms, 500, 10);} // 変身音
    else if( 3200 < passed_ms && passed_ms <=  3700){ led_base_pattern_dim(now_ms, 500,  9);}
    else if( 3700 < passed_ms && passed_ms <=  3900){ led_base_pattern_dim(now_ms, 200, 10);}
    else if( 3900 < passed_ms && passed_ms <=  4900){ led_base_pattern_on();}
    else if( 4900 < passed_ms && passed_ms <=  6100){ led_base_pattern_dim(now_ms, 1200, 20);}
    else if( 6100 < passed_ms && passed_ms <=  7600){ led_base_pattern_inc(now_ms, 1500, 20);} // 変身完了後の点灯
    else if( 7600 < passed_ms && passed_ms <= 10600){ led_base_pattern_on();}
    else if(10600 < passed_ms && passed_ms <= 12100){ led_base_pattern_dim(now_ms, 1500, 20);}
    else{                                             led_base_pattern_off();}
    break;
  default:
   ;
  }
}

void led_pattern_change_off(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 300){                          led_base_pattern_on();}
  else if(300 < passed_ms && passed_ms <= 1000){ led_base_pattern_dim(now_ms, 700, 20);}
  else{                                          led_base_pattern_off();}
}

void led_pattern_critical_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 525){                           led_base_pattern_on();}
  else if( 525 < passed_ms && passed_ms <=  800){ led_base_pattern_dim(now_ms, 275, 10);}
  else if( 800 < passed_ms && passed_ms <= 1200){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(1200 < passed_ms && passed_ms <= 2000){ led_base_pattern_dim(now_ms, 800, 20);}
  else if(2000 < passed_ms && passed_ms <= 2300){ led_base_pattern_off();}
  else if(2300 < passed_ms && passed_ms <= 2500){ led_base_pattern_on();}
  else if(2500 < passed_ms && passed_ms <= 2900){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(2900 < passed_ms && passed_ms <= 3000){ led_base_pattern_on();}
  else if(3000 < passed_ms && passed_ms <= 3700){ led_base_pattern_dim(now_ms, 700, 20);}
  else if(3700 < passed_ms && passed_ms <= 4000){ led_base_pattern_off();}
  else if(4000 < passed_ms && passed_ms <= 7500){ led_base_pattern_inc(now_ms, 3500, 10);}
  else{                                           led_base_pattern_blink_slowly(now_ms, 140, 10);}
}

void led_pattern_critical(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 300){                            led_base_pattern_blink(now_ms, 80);}
  else if(  300 < passed_ms && passed_ms <=10300){ led_base_pattern_on();}
  else if(10300 < passed_ms && passed_ms <=11300){ led_base_pattern_dim(now_ms, 1000, 20);}
  else{                                            led_base_pattern_off();}
}

void led_pattern_reverse_side(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 200){                           led_base_pattern_off();}
  else if( 200 < passed_ms && passed_ms <=  600){ led_base_pattern_on();}
  else if( 600 < passed_ms && passed_ms <= 1200){ led_base_pattern_dim(now_ms, 600, 20);}
  else if(1200 < passed_ms && passed_ms <= 2000){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_one_side(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 200){                           led_base_pattern_off();}
  else if( 200 < passed_ms && passed_ms <=  700){ led_base_pattern_dim(now_ms, 500, 20);}
  else if( 700 < passed_ms && passed_ms <= 1500){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                           led_base_pattern_off();}
}

void control_led(){

  unsigned long now_ms = millis();

  if(prev_state != state){
    // 電源ON直後はLEDをOFFにしているので、最初の状態遷移以降にLEDを有効化させるための処理
    is_led_active = true;
    if(state == STATE_INIT){
      // STATE_INITに状態遷移するときは、
      // STATE_TEMPから遷移するときとそれ以外から遷移するときとで
      // 発光パターンを切り替える必要がある
      if(prev_state == STATE_TEMP){
        is_memory_ejected = false;
        // このときはSTATE_TEMPで既に始まっている発光パターンを継続させるので、
        // led_pattern_start_timeはリセットしない
      }else{
        is_memory_ejected = true;
        led_pattern_start_time = now_ms;
      }
    }else if(state == STATE_TEMP){
      // STATE_TEMPに状態遷移するときは、
      // STATE_INITから遷移するときとそれ以外から遷移するときとで
      // 発光パターンを切り替える必要がある
      if(prev_state == STATE_INIT){
        is_memory_ejected = false;
      }else{
        is_memory_ejected = true;
      }
      led_pattern_start_time = now_ms;
    }else if(state == STATE_CHANGE_READY){
      // STATE_CHANGE_READYに状態遷移するときも、STATE_INITから遷移するときと、
      // STATE_CHANGEから遷移するときとで、発光パターンを切り替える必要がある
      if(prev_state == STATE_INIT){
        is_change_off = false;
        led_pattern_start_time = now_ms;
      }else if(prev_state == STATE_CHANGE){
        is_change_off = true;
        led_pattern_start_time = now_ms;
      }
    }else if(state == STATE_CRITICAL_READY){
      // STATE_CRITICAL_READYに状態遷移するときも、STATE_INITから遷移するときと、
      // STATE_CRITICALから遷移するときとで、発光パターンを切り替える必要がある
      if(prev_state == STATE_INIT){
        is_critical = false;
        led_pattern_start_time = now_ms;
      }else if(prev_state == STATE_CRITICAL){
        is_critical = true;
        // このときはSTATE_CRITICALで既に始まっている発光パターンを継続させるので、
        // led_pattern_start_timeはリセットしない
      }
    }else{
      // 上記以外の状態遷移では、発光パターンのリセットのみ実施
      led_pattern_start_time = now_ms;
    }
  }

  unsigned long passed_ms = now_ms - led_pattern_start_time;

  switch(state){
  case STATE_INIT:
    if(is_led_active){
      if(is_memory_ejected){
        led_pattern_memory_ejected(passed_ms, now_ms);
      }else{
        // STATE_TEMPで既に始まっている発光パターンを継続させる
        led_pattern_memory_name(passed_ms, now_ms);
      }
    }
    break;
  case STATE_TEMP:
    if(is_memory_ejected){
      led_pattern_memory_ejected(passed_ms, now_ms);
    }else{
      led_pattern_memory_name(passed_ms, now_ms);
    }
    break;
  case STATE_CHANGE_READY:
    if(is_change_off){
      led_pattern_change_off(passed_ms, now_ms);
    }else{
      led_pattern_change_ready(passed_ms, now_ms);
    }
    break;
  case STATE_CHANGE:
    led_pattern_change(passed_ms, now_ms);
    break;
  case STATE_CRITICAL_READY:
    if(is_critical){
      led_pattern_critical(passed_ms, now_ms);
    }else{
      led_pattern_critical_ready(passed_ms, now_ms);
    }
    break;
  case STATE_CRITICAL:
    led_pattern_critical(passed_ms, now_ms);
    break;
  default:
    ;
  }
}

////////// BLE処理 ////////////////////////////////////////////////////////////

// Service UUID
// Macのターミナルの"uuidgen"コマンドで生成
#define SERVICE_UUID "9D264561-039A-4EE9-B1B9-52116066B59C"
#define MEMORY_COMMAND_TRIGGER_NAME     1
#define MEMORY_COMMAND_TRIGGER_CRITICAL 2

#define WAIT_BLE_ADVERTISE_STATE_NONE         0
#define WAIT_BLE_ADVERTISE_STATE_READY        1
#define WAIT_BLE_ADVERTISE_STATE_ADVERTISING  2
uint8_t wait_ble_advertise_state = WAIT_BLE_ADVERTISE_STATE_NONE;

#define WAIT_BLE_ADVERTISE_FOR_NAME_MS            1500
#define WAIT_BLE_ADVERTISE_FOR_CRITICAL_READY_MS  4000
#define WAIT_BLE_ADVERTISE_FOR_END_MS              200
unsigned long ble_advertise_wait_start_time = 0;

BLEServer *pServer;
BLEAdvertising *pAdvertising;

void setAdvData(BLEAdvertising *pAdvertising, uint8_t value){
  BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();

  // アドバタイズはペイロードとして全体で31バイトまでデータを詰め込める。
  // 「長さ(1 byte)+AD Type(1 byte)+AD Data(X byte)」(=AD structure)を繰り返す形で格納する。
  // 「長さ」は、AD TypeとAD Dataを足した長さ。
  // 以下は1行で一つのAD structureを設定している

  // Flags ... AD Type:0x01 デバイスの発見や機能についての設定
  //  ESP_BLE_ADV_FLAG_LIMIT_DISC         ... ビット0 ... LE Limited Discoverable Mode(デバイスの発見に時間制限がある場合)
  //  ESP_BLE_ADV_FLAG_GEN_DISC           ... ビット1 ... LE General Discoverable Mode(通常はこのモードを使用)
  //  ESP_BLE_ADV_FLAG_BREDR_NOT_SPT      ... ビット2 ... BR/EDR Not Supported(BLEのみをサポートする場合)
  //  ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT ... ビット3 ... Simultaneous LE and BR/EDR to Same Device Capable (Controller)
  //  ESP_BLE_ADV_FLAG_DMT_HOST_SPT       ... ビット4 ... Simultaneous LE and BR/EDR to Same Device Capble (Host)
  oAdvertisementData.setFlags(ESP_BLE_ADV_FLAG_BREDR_NOT_SPT || ESP_BLE_ADV_FLAG_GEN_DISC); // 1+1+1=3 byte

  // Complete local name ... AD Type:0x09 デバイスの完全な名前
  // 完全な名前が長くなりそうなときは、AD Type:0x08のShortened local nameを使う(setShortName)
  oAdvertisementData.setName("gaT"); // 1+1+3=5 byte

  // ServiceUUIDの設定(※長さとAD Typeの値は関数内で自動設定される)
  oAdvertisementData.setCompleteServices(BLEUUID(SERVICE_UUID)); // 1+1+16=18 byte

  // Manufacture specific dataの設定 ... AD Type:0xFF
  //  先頭2バイトは企業の識別子の設定が必要。テスト用には0xFFFFを設定して良い
  std::string strManufacturerData = "";
  strManufacturerData += (char)0xff; // Manufaucture ID
  strManufacturerData += (char)0xff; // Manufaucture ID
  strManufacturerData += (char)value;
  oAdvertisementData.setManufacturerData(strManufacturerData); // 1+1+3=5 byte

  // 計 3+5+18+5=31
  pAdvertising->setAdvertisementData(oAdvertisementData);
}

void ble_advertise_start(uint8_t value){
  Serial.println(F("BLE Advertise Start."));
  setAdvData(pAdvertising, value);
  pAdvertising->start();
}

void ble_advertise_stop(){
  pAdvertising->stop();
  Serial.println(F("BLE Advertise Stop."));
}

// アドバタイズをするのは、STATE_INITからSTATE_TEMPに遷移して一定時間後と、
// STATE_INITからSTATE_CRITICAL_READYに遷移して一定時間経過したときのみ

void control_ble(){

  unsigned long now_ms = millis();

  if(prev_state != state){
    // 状態遷移直後の処理
    switch(state){
    case STATE_INIT:
      break;
    case STATE_TEMP:
      if(prev_state == STATE_INIT){
        ble_advertise_wait_start_time = now_ms;
        wait_ble_advertise_state = WAIT_BLE_ADVERTISE_STATE_READY;
      }
      break;
    case STATE_CHANGE_READY:
      break;
    case STATE_CHANGE:
      break;
    case STATE_CRITICAL_READY:
      if(prev_state == STATE_INIT){
        ble_advertise_wait_start_time = now_ms;
        wait_ble_advertise_state = WAIT_BLE_ADVERTISE_STATE_READY;
      }
      break;
    case STATE_CRITICAL:
      break;
    default:
      ;
    }
  }else{
    switch(wait_ble_advertise_state){
    case WAIT_BLE_ADVERTISE_STATE_NONE:
      break;
    case WAIT_BLE_ADVERTISE_STATE_READY:
      if(state == STATE_INIT || state == STATE_TEMP){
        if(now_ms - ble_advertise_wait_start_time >= WAIT_BLE_ADVERTISE_FOR_NAME_MS){
          ble_advertise_start(MEMORY_COMMAND_TRIGGER_NAME);
          wait_ble_advertise_state = WAIT_BLE_ADVERTISE_STATE_ADVERTISING;
          ble_advertise_wait_start_time = now_ms;
        }
      }else if(state == STATE_CRITICAL_READY){
        if(now_ms - ble_advertise_wait_start_time >= WAIT_BLE_ADVERTISE_FOR_CRITICAL_READY_MS){
          ble_advertise_start(MEMORY_COMMAND_TRIGGER_CRITICAL);
          wait_ble_advertise_state = WAIT_BLE_ADVERTISE_STATE_ADVERTISING;
          ble_advertise_wait_start_time = now_ms;
        }
      }
      break;
    case WAIT_BLE_ADVERTISE_STATE_ADVERTISING:
      if(now_ms - ble_advertise_wait_start_time >= WAIT_BLE_ADVERTISE_FOR_END_MS){
        ble_advertise_stop();
        wait_ble_advertise_state = WAIT_BLE_ADVERTISE_STATE_NONE;
      }
      break;
    default:
      ;
    }
  }
}

////////// メイン処理 ////////////////////////////////////////////////////////////

void setup(){
  M5.begin();

  Serial.begin(115200);
  pinMode(SW_1_PIN, INPUT_PULLUP);
  pinMode(SW_2_PIN, INPUT_PULLUP);
  pinMode(SW_3_PIN, INPUT_PULLUP);
  pinMode(LED_PIN,  OUTPUT);

  // MP3プレイヤーセットアップ
  Serial1.begin(9600, SERIAL_8N1, MP3_RX_PIN, MP3_TX_PIN);
  mp3_set_volume(SOUND_VOLUME_DEFAULT);

  // LEDセットアップ
  ledcSetup(PWM_CHANNEL_1, PWM_HZ, PWM_LEVEL);
  ledcAttachPin(LED_PIN, PWM_CHANNEL_1);

  led_base_pattern_off();

  // BLEアドバタイズ準備
  BLEDevice::init("GaiaMemory");
  pServer = BLEDevice::createServer();
  pAdvertising = pServer->getAdvertising();
}

void loop(){

  unsigned long now_ms = millis();

  //////////////////// 状態遷移管理 ////////////////////
  sw[0] = digitalRead(SW_1_PIN);
  sw[1] = digitalRead(SW_2_PIN);
  sw[2] = digitalRead(SW_3_PIN);

  switch(state){
  case STATE_INIT:
    if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_TEMP;
        is_sw_1_pressing = true; // モード変更長押し認識開始
        long_press_start_time = now_ms;
      }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE_READY;
      }else if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE;
      }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL_READY;
      }else if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL;
      }
    }
    break;
  case STATE_TEMP:
    if(memcmp(prev_sw, ON_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
        is_sw_1_pressing = false; // モード変更長押し認識終了
      }else if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE;
        is_sw_1_pressing = false; // モード変更長押し認識終了
      }else if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL;
        is_sw_1_pressing = false; // モード変更長押し認識終了
      }
    }
    break;
  case STATE_CHANGE_READY:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
      if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  case STATE_CHANGE:
    if(memcmp(prev_sw, ON_ON_OFF, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE_READY;
      }else if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_TEMP;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  case STATE_CRITICAL_READY:
    if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  case STATE_CRITICAL:
    if(memcmp(prev_sw, ON_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL_READY;
      }else if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_TEMP;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  default:
    ;
  }

  ////////// SW1長押し認識処理 ///////////////
  if(is_sw_1_pressing && now_ms - long_press_start_time >= LONG_PRESS_MS){
    switch(mode){
    case MODE_W_L:
      mode = MODE_W_R;
      mp3_play(SOUND_REVERSE_SIDE); // 「逆サイド」
      execute_led_pattern(led_pattern_reverse_side, 2000);
      break;
    case MODE_W_R:
      mode = MODE_LOST;
      mp3_play(SOUND_ONE_SIDE); // 「ワンサイド」
      execute_led_pattern(led_pattern_one_side, 2000);
      break;
    case MODE_LOST:
      mode = MODE_W_L;
      mp3_play(SOUND_MEMORY_EJECT); // デフォルトに戻る
      execute_led_pattern(led_pattern_memory_ejected, 1200);
      break;
    default:
      ;
    }
    is_sw_1_pressing = false;
  }

  ////////// 音声再生処理 ////////////////////
  control_sound();

  ////////// LED発光処理 ////////////////////
  control_led();

  ////////// BLE処理 ///////////////////////
  control_ble();

  ////////// デバッグ処理 ////////////////////
  print_state();

  ////////// 処理状態の保持 //////////////////
  prev_sw[0] = sw[0];
  prev_sw[1] = sw[1];
  prev_sw[2] = sw[2];
  prev_state = state;

  delay(LOOP_DELAY_MS);
}

ハザードメモリ(※BLEオブザーバー、受信側)

////////// Arduino開発環境構築リファレンス ////////////////////////////////////////
// https://docs.m5stack.com/#/en/arduino/arduino_development

////////// 基本定義 ////////////////////////////////////////////////////////////
// 本来ならM5Atomとして開発すべきだが、コンパイルエラーが解決できないので、同じマイコンを使用しているM5StickCとして開発する
// #include "M5Atom.h"
#include "M5StickC.h" 

#define LOOP_DELAY_MS 20

#define MP3_RX_PIN  32 // Groveポート
#define MP3_TX_PIN  26 // Groveポート
#define SW_1_PIN    33
#define SW_2_PIN    23
#define SW_3_PIN    19
#define LED_PIN     22

#define ON  LOW
#define OFF HIGH

// 3つのボタン入力の表現
#define N_BUTTON 3
const uint8_t OFF_OFF_OFF[] = {OFF, OFF, OFF};
const uint8_t OFF_OFF_ON[]  = {OFF, OFF,  ON};
const uint8_t OFF_ON_OFF[]  = {OFF,  ON, OFF};
const uint8_t OFF_ON_ON[]   = {OFF,  ON,  ON};
const uint8_t ON_OFF_OFF[]  = {ON,  OFF, OFF};
const uint8_t ON_OFF_ON[]   = {ON,  OFF,  ON};
const uint8_t ON_ON_OFF[]   = {ON,   ON, OFF};
const uint8_t ON_ON_ON[]    = {ON,   ON,  ON};
uint8_t prev_sw[] = {OFF, OFF, OFF};
uint8_t sw[]      = {OFF, OFF, OFF};

// 各モードで変身時の音声再生タイミングが異なる
#define MODE_W_R  0 // ダブルドライバーRスロットモード
#define MODE_W_L  1 // ダブルドライバーLスロットモード
#define MODE_LOST 2 // ロストドライバーモード

#define LONG_PRESS_MS 1600 // モード変更のための長押し時間

#define STATE_INIT           0
#define STATE_TEMP           1
#define STATE_CHANGE_READY   2
#define STATE_CHANGE         3
#define STATE_CRITICAL_READY 4
#define STATE_CRITICAL       5
uint8_t state      = STATE_INIT;
uint8_t prev_state = STATE_INIT;

#define MEMORY_CONNECT_STATE_NONE            0
#define MEMORY_CONNECT_STATE_TRIGGER         1
#define MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM 2
#define MEMORY_CONNECT_STATE_FANG            3
#define MEMORY_CONNECT_STATE_FANG_MAXIMUM    4
uint8_t memory_connect_state = MEMORY_CONNECT_STATE_NONE;
unsigned long wait_super_best_match_start_time = 0;

uint8_t mode = MODE_W_R; // デフォルトはダブルドライバーRスロットモードとする
unsigned long long_press_start_time = 0;
boolean is_sw_1_pressing = false;

void print_state(){
  if(prev_state != state){
    switch(state){
    case STATE_INIT:           Serial.println(F("STATE_INIT")); break;
    case STATE_TEMP:           Serial.println(F("STATE_TEMP")); break;
    case STATE_CHANGE_READY:   Serial.println(F("STATE_CHANGE_READY")); break;
    case STATE_CHANGE:         Serial.println(F("STATE_CHANGE")); break;
    case STATE_CRITICAL_READY: Serial.println(F("STATE_CRITICAL_READY")); break;
    case STATE_CRITICAL:       Serial.println(F("STATE_CRITICAL")); break;
    default: ;
    }
  }
}

////////// 音声処理 /////////////////////////////////////////////////////////////

// 従来はSoftwareSerialを使ってDFPlayerやMP3ボイスモジュールと接続していたが、
// ESP32はデフォルトではソフトウェアシリアルは使用できない。
// ただし、3系統のハードウェアシリアルを使用できるので、そちらを使って通信を行う。
// SerialはUSB経由でのPCとの通信に使用するので、Serial1 または Serial2を使用する。

#define SOUND_VOLUME_DEFAULT 25 // 0〜30

#define SOUND_MEMORY_NAME                  1
#define SOUND_CHANGE_READY                 2
#define SOUND_MEMORY_EJECT                 3
#define SOUND_DRIVER_OPEN                  4
#define SOUND_CHANGE                       5
#define SOUND_DRIVER_CLOSE                 6
#define SOUND_CRITICAL_READY               7
#define SOUND_CRITICAL                     8
#define SOUND_REVERSE_SIDE                 9
#define SOUND_ONE_SIDE                    10
#define SOUND_BEST_MATCH                  11
#define SOUND_SUPER_BEST_MATCH            12
#define SOUND_TWIN_CRITICAL_TRIGGER_READY 13
#define SOUND_TWIN_CRITICAL_TRIGGER       14
#define SOUND_TWIN_CRITICAL_FANG          15

#define WAIT_SOUND_STATE_NONE             0
#define WAIT_SOUND_STATE_NAME             1
#define WAIT_SOUND_STATE_CHANGE           2
#define WAIT_SOUND_STATE_SUPER_BEST_MATCH 3

uint8_t wait_sound_state = WAIT_SOUND_STATE_NONE;

#define WAIT_SOUND_FOR_W_R_NAME_MS  1200 // ドライバー開音再生開始後のメモリ名音までの再生待ち時間(WドライバーRスロット側)1000
#define WAIT_SOUND_FOR_W_L_NAME_MS  2000 // ドライバー開音再生開始後のメモリ名音までの再生待ち時間(WドライバーLスロット側)2000
#define WAIT_SOUND_FOR_LOST_NAME_MS 1200 // ドライバー開音再生開始後のメモリ名音までの再生待ち時間(ロストドライバー)      1000

#define WAIT_SOUND_FOR_W_R_CHANGE_MS  2200 // メモリ名音再生開始後の変身音までの再生待ち時間(WドライバーRスロット側)2000
#define WAIT_SOUND_FOR_W_L_CHANGE_MS  3000 // メモリ名音再音開始後の変身音までの再生待ち時間(WドライバーLスロット側)2000
#define WAIT_SOUND_FOR_LOST_CHANGE_MS 1500 // メモリ名音再音開始後の変身音までの再生待ち時間(ロストドライバー)     1200

#define WAIT_SOUND_FOR_SUPER_BEST_MATCH_MS 1200

unsigned long sound_wait_start_time = 0;

void mp3_play(uint8_t track){
  // この定義だと再生曲数は255に限定されるので注意。
  // Upper-Byte, Lower-Byteをちゃんと処理してやれば65535曲まで対応可能だが、
  // 容量的にそこまで使うことはほぼないと思われる。
  uint8_t play[6] = {0xAA,0x07,0x02,0x00,track,track+0xB3};
  Serial1.write(play,6);
}

void mp3_stop(){
  unsigned char stop[4] = {0xAA,0x04,0x00,0xAE};
  Serial1.write(stop,4);
}

void mp3_set_volume(uint8_t vol){
  uint8_t volume[5] = {0xAA,0x13,0x01,vol,vol+0xBE};
  Serial1.write(volume,5);
}

void control_sound(){

  unsigned long now_ms = millis();

  if(prev_state != state){ // 状態遷移直後の処理
    switch(state){
    case STATE_INIT:
      if(prev_state == STATE_CHANGE_READY
      || prev_state == STATE_CHANGE
      || prev_state == STATE_CRITICAL_READY
      || prev_state == STATE_CRITICAL){
        mp3_play(SOUND_MEMORY_EJECT);
        wait_sound_state = WAIT_SOUND_STATE_NONE;
      }
      break;
    case STATE_TEMP:
      if(prev_state == STATE_INIT){
        mp3_play(SOUND_MEMORY_NAME);
        if(memory_connect_state == MEMORY_CONNECT_STATE_FANG){
          // 「ファング」を先に認識しているときだけ、後に「スーパーベストマッチ!」の音声を繋げる
          sound_wait_start_time = now_ms;
          wait_sound_state = WAIT_SOUND_STATE_SUPER_BEST_MATCH;
        }
      }else{
        mp3_play(SOUND_MEMORY_EJECT);
        wait_sound_state = WAIT_SOUND_STATE_NONE;
      }
      break;
    case STATE_CHANGE_READY:
      if(prev_state == STATE_INIT){
        mp3_play(SOUND_CHANGE_READY);
      }else if(prev_state == STATE_CHANGE){
        mp3_play(SOUND_DRIVER_CLOSE);
        wait_sound_state = WAIT_SOUND_STATE_NONE;
      }
      break;
    case STATE_CHANGE:
      // まずはドライバー開音を鳴らして、変身音鳴らしの待ちに入る
      mp3_play(SOUND_DRIVER_OPEN);
      sound_wait_start_time = now_ms;
      wait_sound_state = WAIT_SOUND_STATE_NAME;
      break;
    case STATE_CRITICAL_READY:
      if(prev_state == STATE_INIT){
        if(memory_connect_state == MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM){
          mp3_play(SOUND_TWIN_CRITICAL_TRIGGER_READY);
        }else{
          mp3_play(SOUND_CRITICAL_READY);
        }
      }
      break;
    case STATE_CRITICAL:
      if(memory_connect_state == MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM){
        mp3_play(SOUND_TWIN_CRITICAL_TRIGGER);
      }else{
        mp3_play(SOUND_CRITICAL);
      }
      break;
    default:
      ;
    }
  }else{ // 状態遷移後の時間経過処理
    switch(wait_sound_state){
    case WAIT_SOUND_STATE_NONE:
      break;
    case WAIT_SOUND_STATE_NAME:
      if(mode == MODE_W_R){
        // ダブルドライバーRスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_R_NAME_MS){
          mp3_play(SOUND_MEMORY_NAME);
          sound_wait_start_time = now_ms;
          wait_sound_state = WAIT_SOUND_STATE_CHANGE;
        }
      }else if(mode == MODE_W_L){
        // ダブルドライバーLスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_L_NAME_MS){
          mp3_play(SOUND_MEMORY_NAME);
          sound_wait_start_time = now_ms;
          wait_sound_state = WAIT_SOUND_STATE_CHANGE;
        }
      }else{
        // ロストドライバーモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_LOST_NAME_MS){
          mp3_play(SOUND_MEMORY_NAME);
          sound_wait_start_time = now_ms;
          wait_sound_state = WAIT_SOUND_STATE_CHANGE;
        }
      }
      break;
    case WAIT_SOUND_STATE_CHANGE:
      if(mode == MODE_W_R){
        // ダブルドライバーRスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_R_CHANGE_MS){
          mp3_play(SOUND_CHANGE);
          wait_sound_state = WAIT_SOUND_STATE_NONE;
        }
      }else if(mode == MODE_W_L){
        // ダブルドライバーLスロットモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_W_L_CHANGE_MS){
          mp3_play(SOUND_CHANGE);
          wait_sound_state = WAIT_SOUND_STATE_NONE;
        }
      }else{
        // ロストドライバーモードになっているとき
        if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_LOST_CHANGE_MS){
          mp3_play(SOUND_CHANGE);
          wait_sound_state = WAIT_SOUND_STATE_NONE;
        }
      }
      break;
    case WAIT_SOUND_STATE_SUPER_BEST_MATCH:
      if(now_ms - sound_wait_start_time >= WAIT_SOUND_FOR_SUPER_BEST_MATCH_MS){
        mp3_play(SOUND_SUPER_BEST_MATCH);
        wait_sound_state = WAIT_SOUND_STATE_NONE;
      }
      break;
    default:
      ;
    }
  }
}

////////// 発光処理 /////////////////////////////////////////////////////////////

// ESP32では、analogWriteを使用することはできない。

#define PWM_HZ        5000 // PWM周波数
#define PWM_LEVEL        8 // PWMの段階(8ならMAX255,10ならMAX1023)
#define PWM_CHANNEL_1    1 // PWMのチャンネル(0~15で設定可)

#define LED_BRIGHTNESS 192 // 255が上限値

boolean is_led_active     = false; // 電源投入直後のSTATE_INITのときのLED発光を無効にするためのフラグ
boolean is_memory_ejected = false; // STATE_INITに遷移するときの発光パターンを切り替えるためのフラグ
boolean is_change_off     = false; // STATE_CHANGE_READYに遷移するときの発光パターンを切り替えるためのフラグ
boolean is_critical       = false; // STATE_CRITICAL_READYに遷移するときの発光パターンを切り替えるためのフラグ

unsigned long led_pattern_start_time = 0;
unsigned long prev_blink_time        = 0;
unsigned long inc_dim_start_time = 0;
boolean is_lighting = false;
boolean is_inc = true;

int  prev_interval_ms = 0;
uint8_t prev_steps = 0;

// 設定したLEDパターンを実行する。この間、スイッチの操作などは受け付けられないので注意
void execute_led_pattern(void (*func)(unsigned long, unsigned long), unsigned long duration_ms){
  unsigned long now_ms = millis();
  unsigned long start_time = now_ms;
  for(unsigned long i=0;i<duration_ms;i+=LOOP_DELAY_MS){
    now_ms = millis();
    func(now_ms - start_time, now_ms);
    delay(LOOP_DELAY_MS);
  }
}

void led_base_pattern_on(){
  ledcWrite(PWM_CHANNEL_1, LED_BRIGHTNESS);
  prev_interval_ms = 0;
  prev_steps = 0;
}

void led_base_pattern_off(){
  ledcWrite(PWM_CHANNEL_1, 0);
  prev_interval_ms = 0;
  prev_steps = 0;
}

void led_base_pattern_blink(unsigned long now_ms, int interval_ms){
  if(now_ms - prev_blink_time >= interval_ms){
    if(is_lighting){
      ledcWrite(PWM_CHANNEL_1, 0);
    }else{
      ledcWrite(PWM_CHANNEL_1, LED_BRIGHTNESS);
    }
    is_lighting = !is_lighting;
    prev_blink_time = now_ms;
  }

  prev_interval_ms = interval_ms;
  prev_steps = 0;
}

void led_base_pattern_inc(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_BRIGHTNESS/steps;
  ledcWrite(PWM_CHANNEL_1, l_step*current_step);

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_base_pattern_dim(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_BRIGHTNESS/steps;
  ledcWrite(PWM_CHANNEL_1, l_step*(steps-current_step));

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_base_pattern_blink_slowly(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_time = now_ms;
    is_inc = true;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_BRIGHTNESS/steps;
  if(is_inc){
    ledcWrite(PWM_CHANNEL_1, l_step*current_step);
  }else{
    ledcWrite(PWM_CHANNEL_1, l_step*(steps-current_step));
  }
  if(now_ms - inc_dim_start_time >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_time = now_ms;
  }

  prev_interval_ms = interval_ms;
  prev_steps = steps;
}

void led_pattern_memory_name(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 200){                          led_base_pattern_dim(now_ms, 200, 10);}
  else if(200 < passed_ms && passed_ms <= 1000){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                          led_base_pattern_off();}
}

void led_pattern_memory_ejected(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 300){                          led_base_pattern_on();}
  else if(300 < passed_ms && passed_ms <= 1000){ led_base_pattern_dim(now_ms, 700, 20);}
  else{                                          led_base_pattern_off();}
}

void led_pattern_change_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 525){                         led_base_pattern_on();}
  else if(525 < passed_ms && passed_ms <= 800){ led_base_pattern_dim(now_ms, 275, 10);}
  else{                                         led_base_pattern_blink_slowly(now_ms, 550, 10);}
}

void led_pattern_change(unsigned long passed_ms, unsigned long now_ms){
  switch(mode){
  case MODE_W_R:
    if(passed_ms <= 600){                             led_base_pattern_inc(now_ms, 600, 10);} // ドライバー開
    else if(  600 < passed_ms && passed_ms <=  1100){ led_base_pattern_dim(now_ms, 500, 10);}
    else if( 1100 < passed_ms && passed_ms <=  1200){ led_base_pattern_off();}
    else if( 1200 < passed_ms && passed_ms <=  1400){ led_base_pattern_dim(now_ms, 200, 10);}  // メモリ名
    else if( 1400 < passed_ms && passed_ms <=  2200){ led_base_pattern_dim(now_ms, 800, 20);}
    else if( 2200 < passed_ms && passed_ms <=  3400){ led_base_pattern_off();}
    else if( 3400 < passed_ms && passed_ms <=  3800){ led_base_pattern_on();}                 // 変身音
    else if( 3800 < passed_ms && passed_ms <=  4500){ led_base_pattern_blink(now_ms, 50);}
    else if( 4500 < passed_ms && passed_ms <=  7300){ led_base_pattern_on();}
    else if( 7300 < passed_ms && passed_ms <=  9300){ led_base_pattern_dim(now_ms, 2000, 20);}
    else if( 9300 < passed_ms && passed_ms <= 10800){ led_base_pattern_inc(now_ms, 1500, 20);} // 変身完了後の点灯
    else if(10800 < passed_ms && passed_ms <= 13800){ led_base_pattern_on();}
    else if(13800 < passed_ms && passed_ms <= 16900){ led_base_pattern_dim(now_ms, 1500, 20);}
    else{                                             led_base_pattern_off();}
    break;
  case MODE_W_L:
    if(passed_ms <= 600){                             led_base_pattern_inc(now_ms, 600, 10);} // ドライバー開
    else if(  600 < passed_ms && passed_ms <=  1100){ led_base_pattern_dim(now_ms, 500, 10);}
    else if( 1100 < passed_ms && passed_ms <=  2000){ led_base_pattern_off();}
    else if( 2000 < passed_ms && passed_ms <=  2200){ led_base_pattern_dim(now_ms, 200, 10);}  // メモリ名
    else if( 2200 < passed_ms && passed_ms <=  3000){ led_base_pattern_dim(now_ms, 800, 20);}
    else if( 3000 < passed_ms && passed_ms <=  5000){ led_base_pattern_off();}
    else if( 5000 < passed_ms && passed_ms <=  5400){ led_base_pattern_on();}                 // 変身音
    else if( 5400 < passed_ms && passed_ms <=  6100){ led_base_pattern_blink(now_ms, 50);}
    else if( 6100 < passed_ms && passed_ms <=  8900){ led_base_pattern_on();}
    else if( 8900 < passed_ms && passed_ms <= 10900){ led_base_pattern_dim(now_ms, 2000, 20);}
    else if(10900 < passed_ms && passed_ms <= 12400){ led_base_pattern_inc(now_ms, 1500, 20);} // 変身完了後の点灯
    else if(12400 < passed_ms && passed_ms <= 15400){ led_base_pattern_on();}
    else if(15400 < passed_ms && passed_ms <= 16900){ led_base_pattern_dim(now_ms, 1500, 20);}
    else{                                             led_base_pattern_off();}
    break;
  case MODE_LOST:
    if(passed_ms <= 600){                             led_base_pattern_inc(now_ms, 600, 10);} // ドライバー開
    else if(  600 < passed_ms && passed_ms <=  1100){ led_base_pattern_dim(now_ms, 500, 10);}
    else if( 1100 < passed_ms && passed_ms <=  1200){ led_base_pattern_off();}
    else if( 1200 < passed_ms && passed_ms <=  1400){ led_base_pattern_dim(now_ms, 200, 10);}  // メモリ名
    else if( 1400 < passed_ms && passed_ms <=  2200){ led_base_pattern_dim(now_ms, 800, 20);}
    else if( 2200 < passed_ms && passed_ms <=  2700){ led_base_pattern_off();}
    else if( 2700 < passed_ms && passed_ms <=  3100){ led_base_pattern_on();}                 // 変身音
    else if( 3100 < passed_ms && passed_ms <=  3700){ led_base_pattern_blink(now_ms, 50);}
    else if( 3800 < passed_ms && passed_ms <=  6600){ led_base_pattern_on();}
    else if( 6600 < passed_ms && passed_ms <=  8600){ led_base_pattern_dim(now_ms, 2000, 20);}
    else if( 8600 < passed_ms && passed_ms <= 10100){ led_base_pattern_inc(now_ms, 1500, 20);} // 変身完了後の点灯
    else if(10100 < passed_ms && passed_ms <= 13100){ led_base_pattern_on();}
    else if(13100 < passed_ms && passed_ms <= 14600){ led_base_pattern_dim(now_ms, 1500, 20);}
    else{                                             led_base_pattern_off();}
    break;
  default:
   ;
  }
}

void led_pattern_change_off(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 300){                          led_base_pattern_on();}
  else if(300 < passed_ms && passed_ms <= 1000){ led_base_pattern_dim(now_ms, 700, 20);}
  else{                                          led_base_pattern_off();}
}

void led_pattern_critical_ready(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 525){                           led_base_pattern_on();}
  else if( 525 < passed_ms && passed_ms <=  800){ led_base_pattern_dim(now_ms, 275, 10);}
  else if( 800 < passed_ms && passed_ms <= 1400){ led_base_pattern_on();}
  else if(1400 < passed_ms && passed_ms <= 2000){ led_base_pattern_dim(now_ms, 600, 10);}
  else if(2000 < passed_ms && passed_ms <= 2200){ led_base_pattern_on();}
  else if(2200 < passed_ms && passed_ms <= 2600){ led_base_pattern_dim(now_ms, 400, 10);}
  else if(2600 < passed_ms && passed_ms <= 2700){ led_base_pattern_on();}
  else if(2700 < passed_ms && passed_ms <= 3400){ led_base_pattern_dim(now_ms, 700, 20);}
  else if(3400 < passed_ms && passed_ms <= 3700){ led_base_pattern_off();}
  else{                                           led_base_pattern_blink_slowly(now_ms, 200, 10);}
}

void led_pattern_critical(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 300){                           led_base_pattern_blink(now_ms, 80);}
  else if( 300 < passed_ms && passed_ms <= 1100){ led_base_pattern_dim(now_ms, 800, 10);}
  else if(1100 < passed_ms && passed_ms <= 1300){ led_base_pattern_off();}
  else if(1300 < passed_ms && passed_ms <= 5800){ led_base_pattern_inc(now_ms, 4500, 50);}
  else if(5800 < passed_ms && passed_ms <= 6300){ led_base_pattern_dim(now_ms, 500, 10);}
  else if(6300 < passed_ms && passed_ms <= 7500){ led_base_pattern_blink(now_ms, 50);}
  else if(7500 < passed_ms && passed_ms <= 9000){ led_base_pattern_dim(now_ms, 1500, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_reverse_side(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 100){                           led_base_pattern_off();}
  else if( 100 < passed_ms && passed_ms <=  500){ led_base_pattern_on();}
  else if( 500 < passed_ms && passed_ms <= 1100){ led_base_pattern_dim(now_ms, 600, 20);}
  else if(1100 < passed_ms && passed_ms <= 1900){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_one_side(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 200){                           led_base_pattern_off();}
  else if( 200 < passed_ms && passed_ms <=  700){ led_base_pattern_dim(now_ms, 500, 20);}
  else if( 700 < passed_ms && passed_ms <= 1500){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_best_match(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 100){                           led_base_pattern_off();}
  else if( 100 < passed_ms && passed_ms <=  600){ led_base_pattern_dim(now_ms, 500, 20);}
  else if( 600 < passed_ms && passed_ms <= 1400){ led_base_pattern_dim(now_ms, 800, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_super_best_match(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 100){                           led_base_pattern_off();}
  else if( 100 < passed_ms && passed_ms <=  400){ led_base_pattern_dim(now_ms, 300, 10);}
  else if( 400 < passed_ms && passed_ms <= 1200){ led_base_pattern_dim(now_ms, 800, 20);}
  else if(1200 < passed_ms && passed_ms <= 1800){ led_base_pattern_blink(now_ms, 40);}
  else if(1800 < passed_ms && passed_ms <= 2500){ led_base_pattern_on();}
  else if(2500 < passed_ms && passed_ms <= 3000){ led_base_pattern_dim(now_ms, 500, 20);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_critical_ready_twin_trigger(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 525){                           led_base_pattern_on();}                 // ボタン音
  else if( 525 < passed_ms && passed_ms <=  800){ led_base_pattern_dim(now_ms, 275, 10);}
  else if( 800 < passed_ms && passed_ms <= 1400){ led_base_pattern_on();}                 // ハザード
  else if(1400 < passed_ms && passed_ms <= 2000){ led_base_pattern_dim(now_ms, 600, 10);}
  else if(2000 < passed_ms && passed_ms <= 2500){ led_base_pattern_dim(now_ms, 500, 10);} // オーバーフロー
  else if(2500 < passed_ms && passed_ms <= 3300){ led_base_pattern_dim(now_ms, 800, 10);}
  else if(3300 < passed_ms && passed_ms <= 3500){ led_base_pattern_off();}
  else{                                           led_base_pattern_blink_slowly(now_ms, 200, 10);}
}

void led_pattern_critical_twin_trigger(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 300){                             led_base_pattern_blink(now_ms, 80);}    // ON
  else if(  300 < passed_ms && passed_ms <=  900){ led_base_pattern_dim(now_ms, 600, 10);}
  else if(  900 < passed_ms && passed_ms <=  1500){ led_base_pattern_blink(now_ms, 40);} // ヤベーイ
  else if( 1500 < passed_ms && passed_ms <=  2500){ led_base_pattern_dim(now_ms, 1000, 20);}
  else if( 2500 < passed_ms && passed_ms <=  6700){ led_base_pattern_dim(now_ms, 4200, 50);} // 必殺音
  else if( 6700 < passed_ms && passed_ms <=  7900){ led_base_pattern_inc(now_ms, 1200, 20);}
  else if( 7900 < passed_ms && passed_ms <=  8100){ led_base_pattern_off();}
  else if( 8100 < passed_ms && passed_ms <=  9900){ led_base_pattern_blink(now_ms, 50);}
  else if( 9900 < passed_ms && passed_ms <= 10900){ led_base_pattern_dim(now_ms, 1000, 20);}
  else{                                             led_base_pattern_off();}
}

void led_pattern_critical_twin_fang(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms <= 500){                             led_base_pattern_dim(now_ms, 500, 10);} // オーバーフロー
  else if(  500 < passed_ms && passed_ms <=  1100){ led_base_pattern_dim(now_ms, 600, 10);}
  else if( 1100 < passed_ms && passed_ms <=  2000){ led_base_pattern_off();}
  else if( 2000 < passed_ms && passed_ms <=  2200){ led_base_pattern_on();} // マキシマムドライブ
  else if( 2200 < passed_ms && passed_ms <=  2600){ led_base_pattern_dim(now_ms, 400, 10);}
  else if( 2600 < passed_ms && passed_ms <=  2700){ led_base_pattern_on();}
  else if( 2700 < passed_ms && passed_ms <=  3400){ led_base_pattern_dim(now_ms, 700, 20);}
  else if( 3400 < passed_ms && passed_ms <=  3500){ led_base_pattern_off();}
  else if( 3500 < passed_ms && passed_ms <=  7700){ led_base_pattern_dim(now_ms, 4200, 50);} // 必殺音
  else if( 7700 < passed_ms && passed_ms <=  8900){ led_base_pattern_inc(now_ms, 1200, 20);}
  else if( 8900 < passed_ms && passed_ms <=  9100){ led_base_pattern_off();}
  else if( 9100 < passed_ms && passed_ms <= 10900){ led_base_pattern_blink(now_ms, 50);}
  else if(10900 < passed_ms && passed_ms <= 11900){ led_base_pattern_dim(now_ms, 1000, 20);}
  else{                                             led_base_pattern_off();}
}

void control_led(){

  unsigned long now_ms = millis();

  if(prev_state != state){
    // 電源ON直後はLEDをOFFにしているので、最初の状態遷移以降にLEDを有効化させるための処理
    is_led_active = true;
    if(state == STATE_INIT){
      // STATE_INITに状態遷移するときは、
      // STATE_TEMPから遷移するときとそれ以外から遷移するときとで
      // 発光パターンを切り替える必要がある
      if(prev_state == STATE_TEMP){
        is_memory_ejected = false;
        // このときはSTATE_TEMPで既に始まっている発光パターンを継続させるので、
        // led_pattern_start_timeはリセットしない
      }else{
        is_memory_ejected = true;
        led_pattern_start_time = now_ms;
      }
    }else if(state == STATE_TEMP){
      // STATE_TEMPに状態遷移するときは、
      // STATE_INITから遷移するときとそれ以外から遷移するときとで
      // 発光パターンを切り替える必要がある
      if(prev_state == STATE_INIT){
        is_memory_ejected = false;
      }else{
        is_memory_ejected = true;
      }
      led_pattern_start_time = now_ms;
    }else if(state == STATE_CHANGE_READY){
      // STATE_CHANGE_READYに状態遷移するときも、STATE_INITから遷移するときと、
      // STATE_CHANGEから遷移するときとで、発光パターンを切り替える必要がある
      if(prev_state == STATE_INIT){
        is_change_off = false;
      }else if(prev_state == STATE_CHANGE){
        is_change_off = true;
      }
      led_pattern_start_time = now_ms;
    }else if(state == STATE_CRITICAL_READY){
      // STATE_CRITICAL_READYに状態遷移するときも、STATE_INITから遷移するときと、
      // STATE_CRITICALから遷移するときとで、発光パターンを切り替える必要がある
      if(prev_state == STATE_INIT){
        is_critical = false;
        led_pattern_start_time = now_ms;
      }else if(prev_state == STATE_CRITICAL){
        is_critical = true;
        // このときはSTATE_CRITICALで既に始まっている発光パターンを継続させるので、
        // led_pattern_start_timeはリセットしない
      }
    }else{
      // 上記以外の状態遷移では、発光パターンのリセットのみ実施
      led_pattern_start_time = now_ms;
    }
  }

  unsigned long passed_ms = now_ms - led_pattern_start_time;

  switch(state){
  case STATE_INIT:
    if(is_led_active){
      if(is_memory_ejected){
        led_pattern_memory_ejected(passed_ms, now_ms);
      }else{
        // STATE_TEMPで既に始まっている発光パターンを継続させる
        led_pattern_memory_name(passed_ms, now_ms);
      }
    }
    break;
  case STATE_TEMP:
    if(is_memory_ejected){
      led_pattern_memory_ejected(passed_ms, now_ms);
    }else{
      led_pattern_memory_name(passed_ms, now_ms);
    }
    break;
  case STATE_CHANGE_READY:
    if(is_change_off){
      led_pattern_change_off(passed_ms, now_ms);
    }else{
      led_pattern_change_ready(passed_ms, now_ms);
    }
    break;
  case STATE_CHANGE:
    led_pattern_change(passed_ms, now_ms);
    break;
  case STATE_CRITICAL_READY:
    if(is_critical){
      if(memory_connect_state == MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM){
        led_pattern_critical_twin_trigger(passed_ms, now_ms);
      }else{
        led_pattern_critical(passed_ms, now_ms);
      }
    }else{
      if(memory_connect_state == MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM){
        led_pattern_critical_ready_twin_trigger(passed_ms, now_ms);
      }else{
        led_pattern_critical_ready(passed_ms, now_ms);
      }
    }
    break;
  case STATE_CRITICAL:
    if(memory_connect_state == MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM){
      led_pattern_critical_twin_trigger(passed_ms, now_ms);
    }else{
      led_pattern_critical(passed_ms, now_ms);
    }
    break;
  default:
    ;
  }
}

////////// BLE処理 ////////////////////////////////////////////////////////////

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

// Service UUID
// Macのターミナルの"uuidgen"コマンドで生成
#define SERVICE_UUID "9D264561-039A-4EE9-B1B9-52116066B59C"
#define MEMORY_COMMAND_NONE             0
#define MEMORY_COMMAND_TRIGGER_NAME     1
#define MEMORY_COMMAND_TRIGGER_CRITICAL 2
#define MEMORY_COMMAND_FANG_NAME        3
#define MEMORY_COMMAND_FANG_CRITICAL    4

#define SCAN_DURATION_S   180 // 音声・発光のエフェクト処理後、3分間スキャンを実行する。なお、1秒未満の設定は不可能
#define SCAN_INTERVAL_MS  100
#define SCAN_WINDOWS_MS   100 // Interval以下の値にする必要あり。Intervalの間の内、何msスキャンするか

#define WAIT_BLE_SCAN_STATE_NONE          0
#define WAIT_BLE_SCAN_STATE_NAME_OR_EJECT 1
#define WAIT_BLE_SCAN_STATE_CHANGE_READY  2
#define WAIT_BLE_SCAN_STATE_CHANGE        3
uint8_t wait_ble_scan_state = WAIT_SOUND_STATE_NONE;

#define WAIT_BLE_SCAN_FOR_NAME_OR_EJECT_MS  1700
#define WAIT_BLE_SCAN_FOR_CHANGE_READY_MS   1500
#define WAIT_BLE_SCAN_FOR_W_R_CHANGE_MS    17000
#define WAIT_BLE_SCAN_FOR_W_L_CHANGE_MS    17000
unsigned long ble_scan_wait_start_time = 0;

boolean is_super_best_match_ready = false;

BLEScan* pBLEScan;

void ble_scan_start(){
  Serial.println(F("BLE Scan Start."));
  pBLEScan->start(SCAN_DURATION_S, false);
}

void ble_scan_stop(){
  pBLEScan->clearResults();
  pBLEScan->stop();
  Serial.println(F("BLE Scan Stop."));
}

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      //Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
      if(advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(BLEUUID(SERVICE_UUID))){
        Serial.println(F("GA Found!"));
        // サービスUUIDを見て、自作のガイアメモリの発するアドバタイズパケットであることを確認
        if(advertisedDevice.haveManufacturerData()){
          // ManufacturerDataが入っていれば処理
          std::string data = advertisedDevice.getManufacturerData();
          int manufacture_id = data[1] << 8 | data[0];
          if(manufacture_id == 0xffff){
            uint8_t value = (uint8_t)data[2];
            switch(value){
            case MEMORY_COMMAND_NONE:
              memory_connect_state = MEMORY_CONNECT_STATE_NONE;
              break;
            case MEMORY_COMMAND_TRIGGER_NAME:
              memory_connect_state = MEMORY_CONNECT_STATE_TRIGGER;
              break;
            case MEMORY_COMMAND_TRIGGER_CRITICAL:
              memory_connect_state = MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM;
              break;
            case MEMORY_COMMAND_FANG_NAME:
              memory_connect_state = MEMORY_CONNECT_STATE_FANG;
              break;
            case MEMORY_COMMAND_FANG_CRITICAL:
              memory_connect_state = MEMORY_CONNECT_STATE_FANG_MAXIMUM;
              break;
            default:
              memory_connect_state = MEMORY_CONNECT_STATE_NONE;
            }
            wait_ble_scan_state = WAIT_BLE_SCAN_STATE_NONE;
            ble_scan_stop();
          }
        }
      }
    }
};

// トリガーとファングがアドバタイズするタイミングは以下:
/// 1. 初期状態でメモリ名を鳴らしたとき
//      トリガー値・ファング値
//  2. トリガーメモリをマキシマムスロットに挿したとき
//      ドリガーマキシマムドライブ値:
//  3. ファングメモリでマキシマムドライブを発動したとき(スイッチ10回連打時)
//      ファングマキシマムドライブ値:

// スキャンは基本的に状態変化が起こったあと、発光処理が終わったあとに開始する。
// スキャンを開始させないのは、以下の状態へ変化したとき
//   1. STATE_CHANGE_READY(※STATE_INITから変化したとき、STATE_CHANGEから変化した場合はスキャンを開始)
//   2. STATE_CRITICAL_READY
//   3. STATE_CRITICAL
// スキャンして受信した情報のリセットタイミングは以下:
//   1. トリガー値とファング値のとき ... ドライバーを閉めたとき、ドライバーからメモリを抜いたとき
//   2. トリガーマキシマムドライブ値 ... マキシマムスロットからメモリを抜いたとき
//   3. ファングマキシマムドライブ値 ... 値を保持しない(即時で1回処理するのみ)

void control_ble(){

  unsigned long now_ms = millis();

  if(prev_state != state){
    // 状態遷移直後の処理
    switch(state){
    case STATE_INIT:
      ble_scan_stop();
      ble_scan_wait_start_time = now_ms;
      wait_ble_scan_state = WAIT_BLE_SCAN_STATE_NAME_OR_EJECT;
      // トリガーのツインマキシマム認識状態はこのタイミングで解除する
      if(prev_state == STATE_CRITICAL_READY || prev_state == STATE_CRITICAL){
        memory_connect_state = MEMORY_CONNECT_STATE_NONE;
      }
      break;
    case STATE_TEMP:
      // ファングを既に認識済みの場合は、「スーパーベストマッチ!」を発生させる
      if(memory_connect_state == MEMORY_CONNECT_STATE_FANG){
        is_super_best_match_ready = true;
        wait_super_best_match_start_time = now_ms;
      }
      break;
    case STATE_CHANGE_READY:
      if(prev_state == STATE_CHANGE){
        ble_scan_stop();
        ble_scan_wait_start_time = now_ms;
        wait_ble_scan_state = WAIT_BLE_SCAN_STATE_CHANGE_READY;
      }
      break;
    case STATE_CHANGE:
      ble_scan_stop();
      ble_scan_wait_start_time = now_ms;
      wait_ble_scan_state = WAIT_BLE_SCAN_STATE_CHANGE;
      break;
    case STATE_CRITICAL_READY:
      break;
    case STATE_CRITICAL:
      break;
    default:
      ;
    }
  }else{
    switch(wait_ble_scan_state){
    case WAIT_BLE_SCAN_STATE_NONE:
      break;
    case WAIT_BLE_SCAN_STATE_NAME_OR_EJECT:
      if(now_ms - ble_scan_wait_start_time >= WAIT_BLE_SCAN_FOR_NAME_OR_EJECT_MS){
        wait_ble_scan_state = WAIT_BLE_SCAN_STATE_NONE;
        ble_scan_start();
      }
      break;
    case WAIT_BLE_SCAN_STATE_CHANGE_READY:
      if(now_ms - ble_scan_wait_start_time >= WAIT_BLE_SCAN_FOR_CHANGE_READY_MS){
        wait_ble_scan_state = WAIT_BLE_SCAN_STATE_NONE;
        ble_scan_start();
      }
      break;
    case WAIT_BLE_SCAN_STATE_CHANGE:
      if(mode == MODE_W_R){
        if(now_ms - ble_scan_wait_start_time >= WAIT_BLE_SCAN_FOR_W_R_CHANGE_MS){
          wait_ble_scan_state = WAIT_BLE_SCAN_STATE_NONE;
          ble_scan_start();
        }
      }else if(mode == MODE_W_L){
        if(now_ms - ble_scan_wait_start_time >= WAIT_BLE_SCAN_FOR_W_L_CHANGE_MS){
          wait_ble_scan_state = WAIT_BLE_SCAN_STATE_NONE;
          ble_scan_start();
        }
      }
      break;
    default:
      ;
    }
  }

  // BLEコマンドを受信した結果の処理はここに記述(ハンドラ内は軽量化)
  switch(memory_connect_state){
  case MEMORY_CONNECT_STATE_NONE:
    break;
  case MEMORY_CONNECT_STATE_TRIGGER:
    mp3_play(SOUND_BEST_MATCH);
    execute_led_pattern(led_pattern_best_match, 2000);
    memory_connect_state = MEMORY_CONNECT_STATE_NONE;
    break;
  case MEMORY_CONNECT_STATE_TRIGGER_MAXIMUM:
    break;
  case MEMORY_CONNECT_STATE_FANG:
    // メモリがドライバーに挿さっていないときは、こちらの「ハザード!」の後に続けて鳴らす。
    // メモリがドライバーに挿さっているときは、即座に鳴らす。
    if(state == STATE_CHANGE_READY || state == STATE_CHANGE){
      mp3_play(SOUND_SUPER_BEST_MATCH);
      execute_led_pattern(led_pattern_super_best_match, 3000);
      memory_connect_state = MEMORY_CONNECT_STATE_NONE;
    }
    break;
  case MEMORY_CONNECT_STATE_FANG_MAXIMUM:
    mp3_play(SOUND_TWIN_CRITICAL_FANG);
    execute_led_pattern(led_pattern_critical_twin_fang, 12000);
    memory_connect_state = MEMORY_CONNECT_STATE_NONE;
    break;
  default:
    ;
  }

  ////////// 「スーパーベストマッチ!」を発生させるための時間経過処理 ///////////////
  if(is_super_best_match_ready && now_ms - wait_super_best_match_start_time > WAIT_SOUND_FOR_SUPER_BEST_MATCH_MS){
    mp3_play(SOUND_SUPER_BEST_MATCH);
    execute_led_pattern(led_pattern_super_best_match, 3000);
    memory_connect_state = MEMORY_CONNECT_STATE_NONE;
    is_super_best_match_ready = false;
  }
}

////////// ボタン割り込み処理 /////////////////////////////////////////////////

// ボタン操作があればその時点でBLEのスキャンを止める

void IRAM_ATTR onSW_1(){
  ble_scan_stop();
}

void IRAM_ATTR onSW_2(){
  ble_scan_stop();
}

void IRAM_ATTR onSW_3(){
  ble_scan_stop();
}

////////// メイン処理 ////////////////////////////////////////////////////////////

void setup(){
  M5.begin();

  Serial.begin(115200);
  pinMode(SW_1_PIN,   INPUT_PULLUP);
  pinMode(SW_2_PIN,   INPUT_PULLUP);
  pinMode(SW_3_PIN,   INPUT_PULLUP);
  pinMode(LED_PIN,    OUTPUT);

  // ピンの割り込み受付設定
  attachInterrupt(SW_1_PIN, onSW_1, CHANGE);
  attachInterrupt(SW_2_PIN, onSW_2, CHANGE);
  attachInterrupt(SW_3_PIN, onSW_3, CHANGE);

  // MP3プレイヤーセットアップ
  Serial1.begin(9600, SERIAL_8N1, MP3_RX_PIN, MP3_TX_PIN);
  mp3_set_volume(SOUND_VOLUME_DEFAULT);

  // LEDセットアップ
  ledcSetup(PWM_CHANNEL_1, PWM_HZ, PWM_LEVEL);
  ledcAttachPin(LED_PIN, PWM_CHANNEL_1);

  led_base_pattern_off();

  // BLEスキャン準備
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(false); // パッシブスキャン設定
  pBLEScan->setInterval(SCAN_INTERVAL_MS);
  pBLEScan->setWindow(SCAN_WINDOWS_MS);

}

void loop(){

  unsigned long now_ms = millis();

  //////////////////// 状態遷移管理 ////////////////////
  sw[0] = digitalRead(SW_1_PIN);
  sw[1] = digitalRead(SW_2_PIN);
  sw[2] = digitalRead(SW_3_PIN);

  switch(state){
  case STATE_INIT:
    if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_TEMP;
        is_sw_1_pressing = true; // モード変更長押し認識開始
        long_press_start_time = now_ms;
      }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE_READY;
      }else if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE;
      }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL_READY;
      }else if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL;
      }
    }
    break;
  case STATE_TEMP:
    if(memcmp(prev_sw, ON_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
        is_sw_1_pressing = false; // モード変更長押し認識終了
      }else if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE;
        is_sw_1_pressing = false; // モード変更長押し認識終了
      }else if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL;
        is_sw_1_pressing = false; // モード変更長押し認識終了
      }
    }
    break;
  case STATE_CHANGE_READY:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
      if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  case STATE_CHANGE:
    if(memcmp(prev_sw, ON_ON_OFF, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        state = STATE_CHANGE_READY;
      }else if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_TEMP;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  case STATE_CRITICAL_READY:
    if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  case STATE_CRITICAL:
    if(memcmp(prev_sw, ON_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_CRITICAL_READY;
      }else if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_TEMP;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_INIT;
      }
    }
    break;
  default:
    ;
  }

  ////////// モード切替のためのSW1長押し認識処理 ///////////////
  if(is_sw_1_pressing && now_ms - long_press_start_time >= LONG_PRESS_MS){
    switch(mode){
    case MODE_W_R:
      mode = MODE_W_L;
      mp3_play(SOUND_REVERSE_SIDE); // 「逆サイド」
      execute_led_pattern(led_pattern_reverse_side, 2000);
      break;
    case MODE_W_L:
      mode = MODE_LOST;
      mp3_play(SOUND_ONE_SIDE); // 「ワンサイド」
      execute_led_pattern(led_pattern_one_side, 2000);
      break;
    case MODE_LOST:
      mode = MODE_W_R;
      mp3_play(SOUND_MEMORY_EJECT); // デフォルトに戻る
      execute_led_pattern(led_pattern_memory_ejected, 1200);
      break;
    default:
      ;
    }
    is_sw_1_pressing = false;
  }

  ////////// BLE処理 ///////////////////////
  // BLEのスキャンが始まると、ボタン割り込みが入るまでループはここで止まってしまうことに注意
  control_ble();

  ////////// 音声再生処理 ////////////////////
  control_sound();

  ////////// LED発光処理 ////////////////////
  control_led();

  ////////// デバッグ処理 ////////////////////
  print_state();

  ////////// 処理状態の保持 //////////////////
  prev_sw[0] = sw[0];
  prev_sw[1] = sw[1];
  prev_sw[2] = sw[2];
  prev_state = state;

  delay(LOOP_DELAY_MS);
}