【オーメダル&フルボトル連動】プログライズライターをつくる

あけましておめでとうございます。今年も宜しくお願い申し上げます。

さて、今回は2020年最初の作品、『プログライズライター』をご紹介します。

作品概要

『ライター(書き込み機)』の名の通りで、プログライズキーに対してデータの書き込みを行うマシン(の、玩具)です。書き込みを行うために、作成するプログライズキーと同じ生物をモチーフとするオーメダル又はフルボトルを使用するというのが、今回の作品のポイントです。例えば『バッタ』のオーメダルを使用すると、書き込みによって『ライジングホッパー』として機能するプログライズキーとなり、『ウルフ』のフルボトルを使用すると、書き込みによって『シューティングウルフ』として機能するプログライズキーが出来上がります。

『ライター』とは言っているものの、あくまで玩具ということで、実際に全ての音声データや発光色のデータを毎回ライターからプログライズキーに書き込んでいるわけではありません。詳細は後述しますが、ライターからプログライズキーに書き込んでいるデータは、『このプログライズキーをどのプログライズキーとして機能させるか』を示すID情報(整数値)のみです。

つまり、(現時点で対応している)全てのプログライズキーの音声データや発光色のデータは、初めからプログライズキー側に全て格納されていて、ライターはどのプログライズキーにするかを選択しているだけ、ということになります。

そのため、このプログライズキーは単体で何にでもなり得る『マルチプログライズキー』と言えるものになっているのですが、ただボタンをポチポチ押して切り替えても面白くないな、と思いましたので、共通する生物モチーフの多いオーメダルと連動させることをまず思いつきました。

ただ、それだけだと対応できないプログライズキーがそこそこ存在しましたので(シューティングウルフ、スパーキングジラフ、ガトリングヘッジホッグなど)、それらをフォローするためにフルボトルにも対応させることにしました。

 

使用するアイテムに合わせて、プログライズキーも『TYPE: O MEDAL』と『TYPE: FULLBOTTLE』の二種類を用意しました。と言っても、この二つはラベルを貼り替えているだけで、実態としては一つしか作っていません。好きな方を選んで使用する形になります。

 

ハードウェア解説

まずはプログライズキー側の解説です。

ベースはDX版ならなんでも良かったのですが、基本黒で塗装するつもりだったので、ベース色がそれに近い灰色である『パンチングコング』をベースにしています。

 

内側のシールは剥がしてしまい、外側の塗装はMr.カラーのうすめ液を染み込ませた布で拭き取りました。クリアパーツ上の塗装なので、うっかり除光液とかで塗装を溶かしてしまうと、クリアパーツまで白く変色してしまうので注意です。

余談ですが、上記の塗装の剥がし方をを豆知識としてTwitterに投稿してみたところ、179件のいいねがついたので、「やってみたいけどやり方がわからない」という人が結構多かったのだなあという感じです。自分はガシャット改造のときにこのやり方を知りたかったのですが、なかなか見つけられなくて、結局いくつかのガシャットを犠牲にして自力でこのやり方に到達しました。お役に立ったようで何よりです。

そして『TYPE: O MEDAL』と『TYPE: FULLBOTTLE』の二つのラベルですが、作成にあたり、ZXライドウォッチの際に昭和ライダーのデザインを引き受けてくださった方に再びご協力頂きました。こちらからイメージをお伝えしたものを実際にデザインとして起こしてもらい、それを見ながら、双方のアイデアを取り入れながら最終的に仕上げて頂きました。いつもご協力頂きありがとうございます。

プログライズキーということで、”~ing”という名前のルールだけは最低限守った方が良いかとも思ったのですが、無理矢理そのフォーマットに押し込むとどの名前も何だかイマイチな感じで、また既に『アサルトウルフ』という例外も存在していることから、そこは拘らずに、最終的には素直に作品名を入れることで落ち着きました。上記の写真は試行錯誤の跡です。

プログライズキー自体は一つしか作っていないため、この二つのラベルは必要に応じて貼り替えて使用する形になりますが、簡単に貼り替えられるように、スマホ画面の保護フィルムでよく使われている自己吸着シートを利用しています。

100均で売っているもので充分です。プリンタで印刷したラベルをこのシートに貼ってからプログライズキーに貼り付けるようにすれば、しっかり張り付いてくれますし、剥がすときも綺麗に剥がすことができます。また、今回はやっていませんが、既存のプログライズキーのデザインのラベルを作れば、ベース色は黒になってしまいますが、完全にそのプログライズキーとして遊ぶことも可能になります。

 

シンボルの部分については、TYPE : O MEDALの場合は、オーメダルをそのままはめ込むことで、オーメダルが元になっている感(?)を出しています。ただ、メダルをそのままはめ込むのはちょっと厳しかったので、分解して絵柄のパーツのみをはめ込むようにしています。

一方、TYPE: FULLBOTTLEの場合は、フルボトルをそのままはめ込むようなことはもちろんできません。

 

どうしたものかと思ってWeb上を色々探していたところで、フルボトルのデザインを正確にイラストに起こされている画像を発見しました。これを是非使わせてもらいたいと思って製作者の方を探したところ、ブラジルにお住まいのデザイナー、markolios様が作成されたものとわかりました。そこで、利用についてご相談したところご快諾頂き、今回使用させて頂きました。ご協力大変感謝致します。歯車がデザインされていることで、すごくビルド感が出て、とても気に入っています。オーメダルとフルボトルで重複する生物(ゴリラ、トラなど)については、どちらも対応しているのですが、シンボル部分で違いが出てくるので面白いです。

 

続いて内部構造のお話です。まずはいつもの、基本の具材のご紹介からです。

最後のスピーカーですが、玩具に元々組み込まれているスピーカーと直径がほぼ一緒で、かつ、薄くて音が元々のものより良い(気がする)ので、最近多用しています。

それから、今回特徴的な具材は以下の2つです。

ホールセンサは『オーソライズ』ギミックのために今回使用してみました。試していませんが、リードスイッチでも代用できるかもしれません。

こういう部品の組み込みはガシャット、ライドウォッチとやってきましたが、ガシャットと同等、ライドウォッチと比べるとちょっと大変、という感じでした。スペース的にはかなり余裕がないです。

 

ガシャット、ライドウォッチからもはや伝統の『二つ並んだベルト連動スイッチ』ですが、いつもここの認識スイッチをディテクタスイッチなどで自作して調整に苦労していたのですが、今回は「玩具の基板を破壊して、そこだけそのまま使う」という作戦に出ました。おかげでここは調整不要でラクに作業できました。

 

今回はネオディケイド・ネオディエンドライドウォッチのときと同様、後からプログラムの修正や音声データの追加が発生するタイプだったので、SDカードのスロットやArduinoの書き込みコネクタにはアクセスしやすい形でまとめています。ついでに充電もここから行えるようにしています。このあたりだったり、あとホールセンサやLEDの位置がレイアウト上の制約になって、なかなか大変でした。

そして今回の書き込み機能実現のポイントになっているのが、ここから顔を出している赤外線受信モジュールです。クウガライドウォッチZXライドウォッチに続いて三回連続での採用です。それだけ、使いこなせるようになると工作の幅が広がる部品と言えます。プログライズキーライター側の赤外線LEDの点滅に乗って送られてくる整数値を受信すると、その値に応じて再生する音声やLEDの発光色を切り替えるようになっています。

回路図としては、こんな感じになります。

 

続いてライター側です。

 

筐体は適当な大きさのケースを探してきて加工する、という手もありましたが、内部構造を考えると結局中は自作しないといけなかったので、オールプラ板&プラ棒で自作してみました。最後に黒く塗装しましたが、スプレー缶丸々一本使ってしまいました。黒いプラ板とかないのかしら。。。

今回のプログライズキー側もそうでしたが、今までは既にある玩具の中にいかに部品を詰め込むかというところで頑張っていたので、当初「今回は好きなサイズで作れるからスペース余裕でラクだな~」とか思っていたのですが、そんな過去の自分をグーで殴ってやりたいぐらいに大変でした。プラ板工作が初めてということもありましたが、寸法計算するのはめんどくさいしキレイにカットするのも大変、いざ組み合わせてみると部品は干渉するし、ネジとナットは飛んでいくし想定より全然足りなくて何度も買い足すし、プラ板の切り屑がリビングまで付いてきてしまって奥様に何してんだと問われたりもするし、とにかく大変でした。とりあえず、P板カッターとピンバイスは大活躍でした。

実際に筐体作成に使った部品も一応紹介しておきます。

あと、ネジは2mm x 10mmが基本で、要所要所で2mm x 6mm2mm x 15mmを使っています。これらはホームセンターで大量に買い足しました。ヘビーに使わないのであれば全部接着剤で済ます、という手もあったのですが、今回は後々子供にも遊んでほしかったので、できるだけ頑丈に作るようにしました。

全体的なデザインは、オーメダルとフルボトルを使うというところが、何となくコインでジュースを買うのに対応するような感じがしたので、一般的な自販機っぽい雰囲気になるようにしたいと思いました。その上で、自分が小さい頃にギリギリオモチャ屋さんで見た記憶のある、ファミコンディスクシステムのディスクライター的な雰囲気も出したいなと思って、こんな形になりました。あんまり複雑なデザインはそもそも作れない、というのもありますけれど。

個人的に拘ったのは、このプログライズキーの挿入部の蓋です。内部構造を見えにくくするという効果はありますが、別になくても機能的には何も問題なく遊べます。ただ、先程のディスクシステム然り、自分が子供の頃にアホほど遊んでいたファミコン、スーファミ、64然り、何かしらカートリッジを差し込むものには蓋があった方が遊ぶときに絶対気持ち良い(?)と思ったので、プログライズキーの抜き差しに応じて自動で開閉する蓋を付けてみました。

機構的な設計は苦手なので、重力と磁石を使うとかもっと上手い方法はあったのかもしれませんが、とりあえずバネを使って試行錯誤して何とかしました。

 

続いて内部構造のお話です。使用した電子部品はこちらになります。

ライター側はスペースの制約があまりなかったので、Groveシステムをベースにプロトを作ってしまって、それをそのまま筐体に突っ込んでしまうことにしました。LEDバーは機能の本質には全く関係なくて、ただ進捗っぽいものを表示して書き込んでいる感を出すためだけのものですが、やっぱりあって良かったと思います。

Groveシステムにも赤外線LEDはあるのですが、使い方がよくわからなかったので、使い慣れたものを使っています。

ライター側の音声再生には、おなじみDFPlayerではなく、同じ会社が作っているMP3ボイスモジュールを今回初めて採用してみました。DFPlayerはモジュール単体は安いのですが、動かすのにマイクロSDカードが必要なので、意外とコストがかかります。特に、再生する音声データがそこまで大きくない場合は、確実にオーバースペックです。一方、こちらは音声データの容量は8MBとかなり控えめですが、効果音を少し鳴らしたいとかなら必要十分な容量ですので、SDカードを買い足さなくてよい分、コスト的にはお買い得です。ただ、サイズが意外と大きいので玩具への組み込みは意外と難しいかもしれないのと、あと開発環境によってはArduinoからプログラムで制御するときにちょっとハマるかもしれないので、意外と使いこなしが難しいかもしれません。プログラムについては、詳細は最後のソースコードの417~427行目のコメント文に記載していますが、曲の番号を指定してもその番号の曲が素直に再生されない、という現象が起こります。ただ、普通に使えている人もいるようなので、Mac特有の現象なのかもしれません。

挿入したプログライズキーの固定のために、プログライズホルダーを丸々埋め込んでいます。これのあるなしで、プログライズキーの挿し心地と安定感が段違いです。

 

さて、ライター側の肝になるのが、オーメダルとフルボトルの認識です。まずオーメダルの認識ですが、これを実現するために、

 

中古で買ったDXオースキャナーの基板を丸々そのまま突っ込んでいます。市販のRFIDリーダーの部品を買ってきて頑張る、という手もあったのかもしれませんが、それで確実にオーメダルをスキャンできる保証はありませんし、また電波を使用する関係上、それが日本の技適を通過したものかとかを気にするのも面倒だったので、玩具の基板をそのまま使うのが最も確実でラクかな、と思ってこうしました。

オースキャナーの基板は、メイン制御部と思われる基板と、メダル(RFID)スキャン用の基板の二つに分かれていて、ちゃんと解析すればスキャン用基板だけを使うこともできるのだと思いますが、今回はそこまで頑張る必要もないかなと思いましたので、スキャン用基板がメイン基板に送っている信号をArduinoにも流して、その信号をArduinoで解読することにしました。詳しくはソフトウェア解説の方で説明します。

一方フルボトルの方は、過去にトランスチームガン・カスタムという作品を作ったときに一度認識はやっていて、今回も認識用のスイッチとフルボトルの装填部分はトランスチームガンの部品をそのまま突っ込んでいます。トランスチームガン・カスタムの調子が悪くなっていたので、潔くトランスチームガン・カスタムを破壊して、部品をこちらに移植しました。

回路図としては、こんな感じになります。

 

ソフトウェア解説

だいぶ長くなってしまいましたが、最後、ソフトウェアの解説です。ソースコードの全文は、プログライズキー側とライター側、まとめて本記事の最後に掲載しています。

まずはプログライズキー側です。ソフト的に特筆すべきことは特にないのですが、とりあえずとにもかくにもプログライズキーの状態遷移がわかっていないと何もプログラムが書けないので、まずは挙動解析からです。ボタンは天面部をA、ドライバー連動部の上からB, Cとしています。

プログライズキーの状態は大きく「オーソライズ前」と「オーソライズ後」に分かれていて、それぞれの状態からスイッチの押し方によって、オーソライズ前だと武器用とフォースライザー用、オーソライズ後だとゼロワンドライバー用とショットライザー用に分岐していく形になります。

細かいところですが、プログラムの組み方としては、ガシャットとライドウォッチのときは結構厳密にやっていて、「今のボタンの押され方の組み合わせから次のボタンの押され方に変わるとき、今どういう状態か」を網羅的に調べて処理を分岐させていたのですが、今回は厳密に調べるのが面倒だったので、「今の状態で、今のボタンの押され方から次にどういうボタンの押され方をするか」という形で、玩具的に必要そうな部分だけを調べて記述するようにしています。そのため、厳密には玩具と異なる挙動になるところもあるかもしれませんが、普通に遊ぶ分には特に問題なさそうだったので、これでOKとしています。

あと、今回ちょっとハマったところとして、赤外線受信のところがあります。赤外線信号を「いつでも」受信できるようにしようと思うと、Arduinoloop関数の中の処理を極力軽くしておかないと、Arduinoが赤外線信号の受信をチェックするインターバルが長くなってしまい、ライター側の赤外線LEDが発光してもそれをキャッチできなくなってしまいます。そのため、全体を「書き込み前」と「書き込み後」に分けて、「書き込み前」のときは赤外線信号を逃さずキャッチできるように極力looo処理を軽くして、メインの発光やら音声再生の処理は「書き込み後」にだけ行われるようにしています。

「書き込み後」から「書き込み前」の状態に戻すために、特別に”Initialize(初期化)”という操作を加えています。上部のボタンを5秒程長押しすると「書き込み前」の状態に戻って、再び赤外線信号を受信してプログライズキーの書き込みができるようになります。このイニシャライズの発動条件は結構悩んで、最初はライターにキーを差し込んだときにドライバー連動ボタンが同時に押されるなどして、書き込む前に自動的にイニシャライズが走るようにしようかと思ったのですが、それだとフォースライザーで遊ぶときに勝手にイニシャライズが走ってしまったりしたので、誤作動しないように長押し5秒という条件で発動するようにしました。

 

続いて、プログライズキーライター側です。こちらのメインは、「どうやってオーメダルをArduinoに認識させるか」というところです。

先に述べたとおり、オースキャナーの基板は、メイン制御用とメダルスキャン用の2つに分かれています。この間は5つの線で繋がれていて、GND(), VDD(+), CLOCK, DATA, ABLENと記載されています。前2つは電源関連で良いとして、あとの3つからArduinoでどう情報を引き出すかです。

スキャン用の基板の方を見てみると水晶振動子の部品が付いているので、「スキャン用基板はメイン基板からはABLEN経由で指示を受けて、メダルを読み取ったらCLOCK信号にDATAを同期させる形でメイン基板に信号を送っているのでは」と推測しました。そして、何枚かのメダルをスキャンさせたときのCLOCKDATA01の変化を観察してみると、完全に理解したわけではありませんが、少なくともメダルを1枚読み込んだときには01の変化に以下のような法則性があることがわかりました。

今回は複数メダルを認識させてコンボ発動とかまではする必要はなかったので、ここまでの理解でプログラムを組んでみました。

 

続いてフルボトルの方の認識ですが、こちらは先に述べたとおり、以前にトランスチームガン・カスタムという作品を作ったときに、一度プログラムは作成しています。ただ、改めてトランスチームガン・カスタムで遊んでみると、フルボトルを全然認識してくれなくなっており、どうやらスイッチ部分の経年劣化に対して非常に弱い認識プログラムになっていたことがわかりました。

というわけで、認識ピンの認識アルゴリズムを全面的に見直して、一から組み直しました。結果、誤認識することはまだそこそこありますが、前よりはちゃんと認識してくれるようになりました。詳細は省略しますが、簡単に言うと「認識ピンのあるときは01の値がバタつくが、認識ピンのないときは0しか現れない」という、ある種当たり前の前提に則ってプログラムを書くようにしています。

 

最後に赤外線信号の送信ですが、プログライズキー側の受信失敗の可能性を減らすために5回連続の再送処理は追加しているものの、 基本はZXライドウォッチのときとほぼ変わっていませんので、詳しくはそちらをご参照ください。オーメダル or フルボトルを認識して得られたID(整数値)を送っているだけです。

念のため補足しておくと、書き込みの効果音が流れている間中ずっと赤外線信号を送り続けているわけではなく、実際に信号を送っているのは音楽が終わった直後の最後の一瞬だけです。LEDバーが示している進捗状況も、「それっぽさ」を出すためだけの、完全なフェイクです。まあ、あくまでオモチャということで。

 

まとめ

ということで、『プログライズキーライター』のご紹介でした。今回は「プログライズキーを作り出す工程」を作品化したので、「何かすごいプログライズキーそのもの」を期待されていた方は、申し訳ありません。全く新しいプログライズキーを創り出そうと思うと、必然的に音声データを作成しないといけなくなるのですが、自分はデータの編集は出来ても、ゼロから音声を作り出すことはできません。その制約の中で最大限楽しく遊べるものを目指した結果、今回はこういう形になりました。実際、長男も楽しそうに遊んでくれているので、まー良かったかなと思っています。

 

ちなみに、今回は作り込んでいませんが、レジェンドライダーのメダルやボトルを使ってプログライズキーを生成できるように機能拡張するのも面白いかもしれません。丁度オフィシャルにレジェンドライダープログライズキーも発表されたことですし。他にも、セルメダルを使ってゼツメライズキーを作ったり、というのもアリだと思います。今回、なかなか汎用性のある仕組みを作れたかな、と思っています。

というわけで、今年もこんな感じで適当に間を空けつつ続けられれば良いなあと思いますので、引き続き宜しくお願い致します。

 

ソースコード

最後に参考としてソースコードの全文を掲載しておきます。まずはプログライズキー側です。

////////// 基本定義 ////////////////////////////////////////////////////////////

#define SW_A_PIN 4
#define SW_B_PIN 5
#define SW_C_PIN 6

#define AUTHORIZE_WAITING_TIME 30000
#define AUTHORIZE_KEEPING_TIME 30000
#define ZEROONEDRIVER_CHANGING_TIME 10000
#define SHOTRISER_CHANGING_TIME     10000

// 3つのボタン入力の表現
#define N_BUTTON 3
#define ON  LOW
#define OFF HIGH

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};

// 赤外線信号受信前と受信後で制御内容が大きく分かれる
boolean is_initialized = true;
#define INITIALIZE_TIME 5000
unsigned long initialize_start_time = 0;

// 状態の定義
#define STATE_SINGLE_A                    1
#define STATE_SINGLE_B                    2
#define STATE_WEAPON_READY                3
#define STATE_WEAPON                      4
#define STATE_WEAPON_FINISH               5
#define STATE_FORCERISER_READY            6
#define STATE_FORCERISER                  7
#define STATE_FORCERISER_FINISH_READY     8
#define STATE_FORCERISER_FINISH           9
#define STATE_AUTHORIZED_A               10
#define STATE_AUTHORIZED_B               11
#define STATE_ZEROONEDRIVER_READY        12
#define STATE_ZEROONEDRIVER              13
#define STATE_ZEROONEDRIVER_FINISH_READY 14
#define STATE_ZEROONEDRIVER_FINISH       15
#define STATE_SHOTRISER_READY            16
#define STATE_SHOTRISER                  17
#define STATE_SHOTRISER_FINISH_READY     18
#define STATE_SHOTRISER_FINISH           19

uint8_t prev_state = STATE_SINGLE_A;
uint8_t state      = STATE_SINGLE_A;

boolean is_authorize_ready = false;
unsigned long authorize_waiting_start_time = 0;
unsigned long authorize_keeping_start_time = 0;
unsigned long changed_time = 0;

uint8_t progrise_key_id = 0;

////////// IR信号の受信 ////////////////////////////////////////////////////////////

#define IR_RX_PIN    7

#define IR_ON  LOW
#define IR_OFF HIGH

#define LEN_IR_DATA  8

// 使用する赤外線受信モジュール(OSRB38C9AA)の中心周波数
// (キャリア周波数)が37.9kHzなので、それに応じて定数を定める
#define IR_LONG_DURATION_MICRO_SEC 600 

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

#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

#define SOUND_VOLUME_DEFAULT 20 // 0〜30 通常は23

// 共通音声
#define SOUND_AUTHORIZE               1
#define SOUND_ERRORRISE               2
#define SOUND_ZETSUMERISE             3 // まだ音声はないが、確保しておく
#define SOUND_INITIALIZE              4 // オリジナル音声
#define SOUND_SHOTRISER_FINISH_READY  5

// 固有音声
#define SOUND_ABILITY                1
#define SOUND_NAME_ABILITY           2
#define SOUND_PUN_NAME_DESCRIPTION   3
#define SOUND_NAME_DESCRIPTION       4
#define SOUND_NAME                   5
#define SOUND_FINISH_START           6
#define SOUND_FINISH_LONG            7
#define SOUND_FINISH_SHORT           8
#define SOUND_FINISH_SHOTRISER       9

#define SOUND_OFFSET_HOPPER     5
#define SOUND_OFFSET_WOLF      14
#define SOUND_OFFSET_CHEETAH   23
#define SOUND_OFFSET_SCORPION  32
#define SOUND_OFFSET_FALCON    41
#define SOUND_OFFSET_SHARK     50
#define SOUND_OFFSET_KONG      59
#define SOUND_OFFSET_TIGER     68
#define SOUND_OFFSET_HORNET    77
#define SOUND_OFFSET_BEAR      86
#define SOUND_OFFSET_MAMMOTH   95
#define SOUND_OFFSET_HERCULES 104
#define SOUND_OFFSET_GIRAFFE  113
#define SOUND_OFFSET_HEDGEHOG 122
#define SOUND_OFFSET_BUFFALO  131
#define SOUND_OFFSET_SPIDER   140
#define SOUND_OFFSET_STAG     149
#define SOUND_OFFSET_PENGUIN  158
#define SOUND_OFFSET_WHALE    167
#define SOUND_OFFSET_LION     176
#define SOUND_OFFSET_PANDA    185
#define SOUND_OFFSET_KANGAROO 194
#define SOUND_OFFSET_HOPPER_2 203

// 無音時間の調整
#define SOUND_WAIT_MS_AUTHORIZE               700 // 1000 -> 800 -> 700
#define SOUND_WAIT_MS_WEAPON                 3200 // 3000 -> 3200
#define SOUND_WAIT_MS_ZEROONEDRIVER          3000
#define SOUND_WAIT_MS_SHOTRISER              4700 // 5000 -> 4900 -> 4700
#define SOUND_WAIT_MS_FORCERISER             2800 // 3000 -> 2900 -> 2800
#define SOUND_WAIT_MS_WEAPON_FINISH          3140 // 3240 -> 3140
#define SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_1  800 // 1000 -> 900 -> 850 -> 800
#define SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_2 5580 // 5680 -> 5580
#define SOUND_WAIT_MS_SHOTRISER_FINISH_READY 1500 // 1000 -> 1300 -> 1500
#define SOUND_WAIT_MS_SHOTRISER_FINISH        800 // 1000 -> 900 -> 800
#define SOUND_WAIT_MS_FORCERISER_FINISH_1     600 // 1000 -> 800 -> 700 -> 600
#define SOUND_WAIT_MS_FORCERISER_FINISH_2    2440 // 2540 -> 2440

#define SOUND_WAITING_STATE_NONE                    0
#define SOUND_WAITING_STATE_AUTHORISE               1
#define SOUND_WAITING_STATE_WEAPON                  2
#define SOUND_WAITING_STATE_ZEROONEDRIVER           3
#define SOUND_WAITING_STATE_SHOTRISER               4
#define SOUND_WAITING_STATE_FORCERISER              5
#define SOUND_WAITING_STATE_WEAPON_FINISH           6
#define SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_1  7
#define SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_2  8
#define SOUND_WAITING_STATE_SHOTRISER_FINISH_READY  9
#define SOUND_WAITING_STATE_SHOTRISER_FINISH       10
#define SOUND_WAITING_STATE_FORCERISER_FINISH_1    11
#define SOUND_WAITING_STATE_FORCERISER_FINISH_2    12

unsigned long sound_wait_start_time = 0;
uint8_t sound_waiting_state = SOUND_WAITING_STATE_NONE;
uint8_t sound_offset = 0;

SoftwareSerial ss_mp3_player(2, 3); // RX, TX
DFRobotDFPlayerMini mp3_player;

void control_sound(uint8_t offset, unsigned long now_ms){
  if(prev_state != state){ // 状態遷移直後の処理
    switch(state){
    case STATE_SINGLE_A:
      if(prev_state == STATE_SINGLE_B){
        mp3_player.playMp3Folder(offset + SOUND_NAME_ABILITY);
      }else{
        mp3_player.pause();
      }
      break;
    case STATE_SINGLE_B:
      if(progrise_key_id != 0){
        mp3_player.playMp3Folder(offset + SOUND_ABILITY);
      }else{
        mp3_player.playMp3Folder(SOUND_ERRORRISE);
      }
      break;
    case STATE_WEAPON_READY:
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
        mp3_player.pause();
        break;
      default:
        ;
      }
      break;
    case STATE_WEAPON:
      switch(prev_state){
      case STATE_SINGLE_A:
      case STATE_SINGLE_B:
      case STATE_WEAPON_READY:
        sound_waiting_state = SOUND_WAITING_STATE_WEAPON;
        sound_wait_start_time = now_ms;
        break;
      default:
        ;
      }
      break;
    case STATE_WEAPON_FINISH:
      mp3_player.playMp3Folder(offset + SOUND_FINISH_START);
      sound_waiting_state = SOUND_WAITING_STATE_WEAPON_FINISH;
      sound_wait_start_time = now_ms;
      break;
    case STATE_FORCERISER_READY:
      mp3_player.pause();
      break;
    case STATE_FORCERISER:
      sound_waiting_state = SOUND_WAITING_STATE_FORCERISER;
      sound_wait_start_time = now_ms;
      break;
    case STATE_FORCERISER_FINISH_READY:
      break;
    case STATE_FORCERISER_FINISH:
      sound_waiting_state = SOUND_WAITING_STATE_FORCERISER_FINISH_1;
      sound_wait_start_time = now_ms;
      break;
    case STATE_AUTHORIZED_A:
      if(prev_state != STATE_AUTHORIZED_B){
        mp3_player.pause();
        sound_waiting_state = SOUND_WAITING_STATE_AUTHORISE;
        sound_wait_start_time = now_ms;
      }
      break;
    case STATE_AUTHORIZED_B:
      mp3_player.playMp3Folder(offset + SOUND_ABILITY);
      break;
    case STATE_ZEROONEDRIVER_READY:
      mp3_player.pause();
      break;
    case STATE_ZEROONEDRIVER:
      sound_waiting_state = SOUND_WAITING_STATE_ZEROONEDRIVER;
      sound_wait_start_time = now_ms;
      break;
    case STATE_ZEROONEDRIVER_FINISH_READY:
      break;
    case STATE_ZEROONEDRIVER_FINISH:
      sound_waiting_state = SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_1;
      sound_wait_start_time = now_ms;
      break;
    case STATE_SHOTRISER_READY:
      break;
    case STATE_SHOTRISER:
      if(prev_state == STATE_SHOTRISER_READY){
        sound_waiting_state = SOUND_WAITING_STATE_SHOTRISER;
        sound_wait_start_time = now_ms;
      }
      break;
    case STATE_SHOTRISER_FINISH_READY:
      mp3_player.playMp3Folder(offset + SOUND_ABILITY);
      sound_waiting_state = SOUND_WAITING_STATE_SHOTRISER_FINISH_READY;
      sound_wait_start_time = now_ms;
      break;
    case STATE_SHOTRISER_FINISH:
      mp3_player.pause();
      sound_waiting_state = SOUND_WAITING_STATE_SHOTRISER_FINISH;
      sound_wait_start_time = now_ms;
      break;
    default:
      ;
    }
  }else{ // 状態遷移後の時間経過処理
    switch(sound_waiting_state){
    case SOUND_WAITING_STATE_NONE:
      break;
    case SOUND_WAITING_STATE_AUTHORISE:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_AUTHORIZE){
        mp3_player.playMp3Folder(SOUND_AUTHORIZE);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_WEAPON:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_WEAPON){
        mp3_player.playMp3Folder(offset + SOUND_NAME_ABILITY);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_ZEROONEDRIVER:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_ZEROONEDRIVER){
        mp3_player.playMp3Folder(offset + SOUND_PUN_NAME_DESCRIPTION);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_SHOTRISER:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_SHOTRISER){
        mp3_player.playMp3Folder(offset + SOUND_NAME_DESCRIPTION);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_FORCERISER:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_FORCERISER){
        mp3_player.playMp3Folder(offset + SOUND_NAME);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_WEAPON_FINISH:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_WEAPON_FINISH){
        mp3_player.playMp3Folder(offset + SOUND_FINISH_SHORT);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_1:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_1){
        mp3_player.playMp3Folder(offset + SOUND_FINISH_START);
        sound_waiting_state = SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_2;
        sound_wait_start_time = now_ms;
      }
      break;
    case SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_2:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_2){
        mp3_player.playMp3Folder(offset + SOUND_FINISH_LONG);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_SHOTRISER_FINISH_READY:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_SHOTRISER_FINISH_READY){
        mp3_player.playMp3Folder(SOUND_SHOTRISER_FINISH_READY);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_SHOTRISER_FINISH:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_SHOTRISER_FINISH){
        mp3_player.playMp3Folder(offset + SOUND_FINISH_SHOTRISER);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    case SOUND_WAITING_STATE_FORCERISER_FINISH_1:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_FORCERISER_FINISH_1){
        mp3_player.playMp3Folder(offset + SOUND_FINISH_START);
        sound_waiting_state = SOUND_WAITING_STATE_FORCERISER_FINISH_2;
        sound_wait_start_time = now_ms;
      }
      break;
    case SOUND_WAITING_STATE_FORCERISER_FINISH_2:
      if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_FORCERISER_FINISH_2){
        mp3_player.playMp3Folder(offset + SOUND_FINISH_LONG);
        sound_waiting_state = SOUND_WAITING_STATE_NONE;
      }
      break;
    default:
      ;
    }
  }
}

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

#define LED_COLOR_PIN 8

#include <Adafruit_NeoPixel.h>
#define N_COLOR_LED 1

struct color_rgb {
  uint8_t r;
  uint8_t g;
  uint8_t b;
};

struct color_rgb COLOR_OFF        = {  0,  0,  0};
struct color_rgb COLOR_WHITE      = {255,255,255};
struct color_rgb COLOR_RED        = {255,  0,  0};
struct color_rgb COLOR_BLUE       = {  0,  0,255};
struct color_rgb COLOR_GREEN      = {  0,255,  0};
struct color_rgb COLOR_YELLOW     = {255,255,  0};
struct color_rgb COLOR_PURPLE     = {255,  0,255};
struct color_rgb COLOR_BROWN      = {255,102,  0}; // {153, 51, 0}
struct color_rgb COLOR_TEAL       = {  0,255,255};
struct color_rgb COLOR_ORANGE     = {255,165,  0};
struct color_rgb COLOR_SKYBLUE    = {  0,191,255};
struct color_rgb COLOR_CHARTREUSE = {127,255,  0};
struct color_rgb COLOR_GOLD       = {255,215,  0};

struct color_rgb *color = &COLOR_OFF;

boolean is_led_active = false;
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 = false;

uint8_t prev_r = 0;
uint8_t prev_g = 0;
uint8_t prev_b = 0;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_COLOR_LED, LED_COLOR_PIN, NEO_GRB);

void update_color(uint8_t r, uint8_t g, uint8_t b){
  if(prev_r != r || prev_g != g || prev_b != b){
    pixels.setPixelColor(0, pixels.Color(r,g,b));
  }
  prev_r = r;
  prev_g = g;
  prev_b = b;
}

void led_base_pattern_on(struct color_rgb *color){
  inc_dim_start_time = 0;
  update_color(color->r,color->g,color->b);
}

void led_base_pattern_off(){
  inc_dim_start_time = 0;
  update_color(0,0,0);
}

void led_base_pattern_blink(struct color_rgb *color, unsigned long now_ms, int interval_ms){
  inc_dim_start_time = 0;
  if(now_ms - prev_blink_time >= interval_ms){
    if(is_lighting){
      update_color(0,0,0);
    }else{
      update_color(color->r,color->g,color->b);
    }
    is_lighting = !is_lighting;
    prev_blink_time = now_ms;
  }
}

void led_base_pattern_inc(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_time == 0){
    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 r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  update_color(r_step*current_step, g_step*current_step, b_step*current_step);
}

void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_time == 0){
    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 r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  update_color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step));
}

void led_base_pattern_blink_slowly(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_time == 0){
    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 r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  if(is_inc){
    update_color(r_step*current_step, g_step*current_step, b_step*current_step);
  }else{
    update_color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step));
  }
  if(now_ms - inc_dim_start_time > interval_ms){
    is_inc = !is_inc;
    inc_dim_start_time = 0;
  }
}

void led_pattern_simple_on_off(struct color_rgb *color, unsigned long delay_ms){
  led_base_pattern_on(color);
  pixels.show();
  delay(delay_ms);
  led_base_pattern_off();
  pixels.show();
}

// プログライズキーにおいては、発光が必要なのは
// STATE_SINGLE_A, STATE_SINGLE_ B, STATE_AUTHORIZED_A, STATE_AUTHORIZED_Bのみ

void led_pattern_ability(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
  if(passed_ms <= 500){                          led_base_pattern_on(color);}
  else if(500 < passed_ms && passed_ms <= 1200){ led_base_pattern_dim(color, now_ms, 700, 10);}
  else{                                          led_base_pattern_off();}
}

void led_pattern_name_ability(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
  if(passed_ms <= 500){                           led_base_pattern_on(color);}
  else if( 500 < passed_ms && passed_ms <=  900){ led_base_pattern_dim(color, now_ms, 400, 10);}
  else if( 900 < passed_ms && passed_ms <= 1600){ led_base_pattern_on(color);}
  else if(1600 < passed_ms && passed_ms <= 1800){ led_base_pattern_dim(color, now_ms, 200, 10);}
  else if(1800 < passed_ms && passed_ms <= 2800){ led_base_pattern_on(color);}
  else if(2800 < passed_ms && passed_ms <= 3600){ led_base_pattern_dim(color, now_ms, 800, 10);}
  else{                                           led_base_pattern_off();}
}

void led_pattern_authorize(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
  if(passed_ms <= 1000){                          led_base_pattern_inc(color, now_ms, 1000, 10);}
  else if(1000 < passed_ms && passed_ms <= 1800){ led_base_pattern_on(color);}
  else if(1800 < passed_ms && passed_ms <= 2800){ led_base_pattern_dim(color, now_ms, 1000, 10);}
  else{                                           led_base_pattern_blink_slowly(color, now_ms, 800, 10);}
}

void led_pattern_authorize_ability(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
  if(passed_ms <= 500){                          led_base_pattern_on(color);}
  else if(500 < passed_ms && passed_ms <= 1000){ led_base_pattern_dim(color, now_ms, 500, 10);}
  else{                                          led_base_pattern_blink_slowly(color, now_ms, 800, 10);}
}

void control_led(struct color_rgb *color, unsigned long now_ms){

  if(prev_state != state){
    if(prev_state != STATE_SINGLE_B && state == STATE_SINGLE_A){
      // STATE B以外からのSTATE Aへの状態遷移では、LEDを発光させない
      is_led_active = false;
      led_base_pattern_off();
    }else if(state == STATE_SHOTRISER_READY){
      ; // ショットライザーの変身待機は例外でリセットしない
    }else{
      // 基本的には状態が遷移するとリセットしてLEDを有効にする
      is_led_active = true;
      led_pattern_start_time = now_ms;
    }
  }

  unsigned long passed_ms = now_ms - led_pattern_start_time;

  switch(state){
  case STATE_SINGLE_A:
    if(is_led_active){
      led_pattern_name_ability(color, now_ms, passed_ms);
    }
    break;
  case STATE_SINGLE_B:
    led_pattern_ability(color, now_ms, passed_ms);
    break;
  case STATE_WEAPON_READY:
    led_base_pattern_off();
    break;
  case STATE_WEAPON_FINISH:
    led_base_pattern_off();
    break;
  case STATE_FORCERISER_READY:
    led_base_pattern_off();
    break;
  case STATE_FORCERISER:
    led_base_pattern_off();
    break;
  case STATE_FORCERISER_FINISH_READY:
    led_base_pattern_off();
    break;
  case STATE_FORCERISER_FINISH:
    led_base_pattern_off();
    break;
  case STATE_AUTHORIZED_A:
    led_pattern_authorize(color, now_ms, passed_ms);
    break;
  case STATE_AUTHORIZED_B:
    led_pattern_authorize_ability(color, now_ms, passed_ms);
    break;
  case STATE_ZEROONEDRIVER_READY:
    led_base_pattern_off();
    break;
  case STATE_ZEROONEDRIVER:
    led_base_pattern_off();
    break;
  case STATE_ZEROONEDRIVER_FINISH_READY:
    led_base_pattern_off();
    break;
  case STATE_ZEROONEDRIVER_FINISH:
    led_base_pattern_off();
    break;
  case STATE_SHOTRISER_READY:
    led_pattern_authorize(color, now_ms, passed_ms);
    break;
  case STATE_SHOTRISER:
    led_base_pattern_off();
    break;
  case STATE_SHOTRISER_FINISH_READY:
    led_base_pattern_off();
    break;
  case STATE_SHOTRISER_FINISH:
    led_base_pattern_off();
    break;
  default:
    ;
  }

  pixels.show();
}

////////// ホールセンサ処理 ////////////////////////////////////////////////////////////////

// ホールIC SK8552G SIP-3を使用

#define HALL_SENSOR_PIN A0
#define HALL_SENSOR_THRESHOLD 500
#define HALL_SENSOR_OFF 0
#define HALL_SENSOR_ON  1

uint8_t hall_sensor = HALL_SENSOR_OFF;
uint8_t prev_hall_sensor = HALL_SENSOR_OFF;

////////// ユーティリティ関数 /////////////////////////////////////////////////////

void authorize_ready(unsigned long now_ms){
  is_authorize_ready = true;
  authorize_waiting_start_time = now_ms;
}

void reset_state(){
  if(state == STATE_SINGLE_B){
    // BからAへの変化は通常は音声が鳴るため、初期化としてのAへの変化のときのみ音声を鳴らさないための配慮
    prev_state = STATE_SINGLE_A;
  }
  state = STATE_SINGLE_A;
  is_authorize_ready = false;
  sound_waiting_state = SOUND_WAITING_STATE_NONE;
  is_led_active = false;
}

void print_state(){
  if(prev_state != state){
    switch(state){
    case STATE_SINGLE_A:                   Serial.println(F("State: SINGLE_A")); break;
    case STATE_SINGLE_B:                   Serial.println(F("State: SINGLE_B")); break;
    case STATE_WEAPON_READY:               Serial.println(F("State: WEAPON_READY")); break;
    case STATE_WEAPON:                     Serial.println(F("State: WEAPON")); break;
    case STATE_WEAPON_FINISH:              Serial.println(F("State: WEAPON_FINISH")); break;
    case STATE_FORCERISER_READY:           Serial.println(F("State: FORCERISER_READY")); break;
    case STATE_FORCERISER:                 Serial.println(F("State: FORCERISER")); break;
    case STATE_FORCERISER_FINISH_READY:    Serial.println(F("State: FORCERISER_FINISH_READY")); break;
    case STATE_FORCERISER_FINISH:          Serial.println(F("State: FORCERISER_FINISH")); break;
    case STATE_AUTHORIZED_A:               Serial.println(F("State: AUTHORIZED_A")); break;
    case STATE_AUTHORIZED_B:               Serial.println(F("State: AUTHORIZED_B")); break;
    case STATE_ZEROONEDRIVER_READY:        Serial.println(F("State: ZEROONEDRIVER_READY")); break;
    case STATE_ZEROONEDRIVER:              Serial.println(F("State: ZEROONEDRIVER")); break;
    case STATE_ZEROONEDRIVER_FINISH_READY: Serial.println(F("State: ZEROONEDRIVER_FINISH_READY")); break;
    case STATE_ZEROONEDRIVER_FINISH:       Serial.println(F("State: ZEROONEDRIVER_FINISH")); break;
    case STATE_SHOTRISER_READY:            Serial.println(F("State: SHOTRISER_READY")); break;
    case STATE_SHOTRISER:                  Serial.println(F("State: SHOTRISER")); break;
    case STATE_SHOTRISER_FINISH_READY:     Serial.println(F("State: SHOTRISER_FINISH_READY")); break;
    case STATE_SHOTRISER_FINISH:           Serial.println(F("State: SHOTRISER_FINISH")); break;
    default:                               Serial.println(F("State: Unknow_msn"));
    }
  }
}

////////// メインのループ処理 ////////////////////////////////////////////////////////////

void loop_main(){
  ////////////////////////////// メインの状態遷移管理 //////////////////////////////
  unsigned long now_ms = millis();

  uint8_t input_port = PIND; // ピン0〜7のデジタル入力の同時読み取り
  sw[0] = bitRead(input_port, SW_A_PIN);
  sw[1] = bitRead(input_port, SW_B_PIN);
  sw[2] = bitRead(input_port, SW_C_PIN);

  // ボタン操作による状態遷移管理。ホールセンサと時間経過による遷移は別で扱う
  switch(state){
  case STATE_SINGLE_A:
    if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_SINGLE_B;
        authorize_ready(now_ms);
      }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        if(progrise_key_id != 0){
          state = STATE_WEAPON_READY;
        }
      }else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        if(progrise_key_id != 0){
          state = STATE_WEAPON;
        }
      }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        if(progrise_key_id != 0){
          state = STATE_FORCERISER_READY;
        }
      }
    }
    break;
  case STATE_SINGLE_B:
    if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_SINGLE_A;
        authorize_ready(now_ms);
      }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        if(progrise_key_id != 0){
          state = STATE_WEAPON_READY;
        }
      }else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        if(progrise_key_id != 0){
          state = STATE_WEAPON;
        }
      }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        if(progrise_key_id != 0){
          state = STATE_FORCERISER_READY;
        }
      }
    }
    break;
  case STATE_WEAPON_READY:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_WEAPON;
      }
    }
    break;
  case STATE_WEAPON:
    if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        state = STATE_WEAPON_READY;
      }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_WEAPON_FINISH;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_WEAPON_FINISH:
    if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_WEAPON;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_FORCERISER_READY:
    if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_FORCERISER;
      }
    }
    break;
  case STATE_FORCERISER:
    if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_FORCERISER_FINISH_READY;
      }
    }
    break;
  case STATE_FORCERISER_FINISH_READY:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_FORCERISER_FINISH;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_FORCERISER_FINISH:
    if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_FORCERISER_FINISH_READY;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_AUTHORIZED_A:
    if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
        state = STATE_AUTHORIZED_B;
      }else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
        state = STATE_ZEROONEDRIVER_READY;
      }else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_ZEROONEDRIVER;
        changed_time = now_ms;
      }else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_SHOTRISER_READY;
      }
    }
    break;
  case STATE_AUTHORIZED_B:
    if(memcmp(prev_sw, ON_OFF_OFF, N_BUTTON) == 0){
      if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_AUTHORIZED_A;
      }
    }
    break;
  case STATE_ZEROONEDRIVER_READY:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_ZEROONEDRIVER;
        changed_time = now_ms;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        state = STATE_SINGLE_A;
      }
    }
    break;
  case STATE_ZEROONEDRIVER:
    if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_ZEROONEDRIVER_FINISH_READY;
      }
    }
    break;
  case STATE_ZEROONEDRIVER_FINISH_READY:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0 ){
      if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        if(now_ms - changed_time >= ZEROONEDRIVER_CHANGING_TIME){
          state = STATE_ZEROONEDRIVER_FINISH;
        }
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_ZEROONEDRIVER_FINISH:
    if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_ZEROONEDRIVER_FINISH_READY;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_SHOTRISER_READY:
    if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_SHOTRISER;
        changed_time = now_ms;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_SHOTRISER:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0 || memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0 ){
      if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0 || memcmp(sw, ON_OFF_ON, N_BUTTON) == 0 || memcmp(sw, ON_ON_ON, N_BUTTON) == 0){
        if(now_ms - changed_time >= SHOTRISER_CHANGING_TIME){
          state = STATE_SHOTRISER_FINISH_READY;
        }
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_SHOTRISER_FINISH_READY:
    if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0 ){
      if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
        state = STATE_SHOTRISER_FINISH;
      }else if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0 || memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
        prev_state = STATE_SHOTRISER; // 擬似的に状態変化を起こす
        state = STATE_SHOTRISER_FINISH_READY;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  case STATE_SHOTRISER_FINISH:
    if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
      if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
        state = STATE_SHOTRISER;
      }else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
        reset_state();
      }
    }
    break;
  default:
    ;
  } 

  ////////////////////////////// ホールセンサによる状態遷移管理 //////////////////////////////

  uint16_t hall_sensor_value = analogRead(HALL_SENSOR_PIN);
  if(hall_sensor_value < HALL_SENSOR_THRESHOLD){
    hall_sensor = HALL_SENSOR_OFF;
  }else{
    hall_sensor = HALL_SENSOR_ON;
  }

  switch(state){
  case STATE_SINGLE_A:
  case STATE_SINGLE_B:
    if(progrise_key_id != 0 && is_authorize_ready && prev_hall_sensor == HALL_SENSOR_OFF && hall_sensor == HALL_SENSOR_ON){
      state = STATE_AUTHORIZED_A;
      authorize_keeping_start_time = now_ms;
    }
    break;
  case STATE_AUTHORIZED_A:
  case STATE_AUTHORIZED_B:
    if(prev_hall_sensor == HALL_SENSOR_OFF && hall_sensor == HALL_SENSOR_ON){
      prev_state = STATE_SINGLE_A; // 擬似的に状態変化を起こす
      state = STATE_AUTHORIZED_A;
      authorize_keeping_start_time = now_ms;
    }
    break;
  default:
    ;
  }

  ////////////////////////////// 時間経過による状態遷移管理 //////////////////////////////

  // 1. オーソライズ待機の解除
  switch(state){
  case STATE_SINGLE_A:
  case STATE_SINGLE_B:
    if(is_authorize_ready && now_ms - authorize_waiting_start_time >= AUTHORIZE_WAITING_TIME){
      reset_state();
    }
    break;
  default:
    ;
  }

  // 2. オーソライズの解除
  switch(state){
  case STATE_AUTHORIZED_A:
  case STATE_AUTHORIZED_B:
    if(now_ms - authorize_keeping_start_time >= AUTHORIZE_KEEPING_TIME){
      reset_state();
    }
    break;
  default:
    ;
  }

  ////////////////////////////// 初期化処理 //////////////////////////////

  if(sw[0] == OFF){
    initialize_start_time = now_ms;
  }

  if(sw[0] == ON && now_ms - initialize_start_time >= INITIALIZE_TIME){
    is_initialized = true;
    Serial.println(F("Initialize."));
    progrise_key_id = 0;
    reset_state();
    mp3_player.playMp3Folder(SOUND_INITIALIZE);
    led_pattern_simple_on_off(&COLOR_WHITE, 1500);
  }

  ////////// デバッグ用表示 ////////////////////
  //print_state();
  //Serial.println(hall_sensor_value);

  ////////// 音声再生処理 ////////////////////
  control_sound(sound_offset, now_ms);

  ////////// LED発光処理 ////////////////////
  control_led(color, now_ms);

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

////////// サブのループ処理(主に赤外線受信処理) ////////////////////////////////////////////////////////////

void loop_sub(){

  ////////////////////////////// ボタン処理 //////////////////////////////
  sw[0] = digitalRead(SW_A_PIN);
  if(prev_sw[0] == OFF && sw[0] == ON){
    mp3_player.playMp3Folder(SOUND_ERRORRISE);
    led_pattern_simple_on_off(&COLOR_WHITE, 1500);
  }
  prev_sw[0] = sw[0];

  ////////////////////////////// IR信号受信処理 //////////////////////////////

  while(1){ // 常にIR信号の受信待ちにする
    // スタートビット(1)チェック
    if(digitalRead(IR_RX_PIN) == IR_ON){ // とりあえず何かしらのIR信号を受信した
      delayMicroseconds(400);
    }else{
      break; // 何もIR信号を受信していないときはここで終了
    }

    if(digitalRead(IR_RX_PIN) == IR_ON){ // 400μs待ってまたスタートビットの信号が続いているかチェックする
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // 受信した信号はスタートビットではなかったので、ここで終了
    }

    // スタートビットが確認できたので、送信された整数値を読み取る
    uint8_t number = 0;
    for(uint8_t i=0;i<LEN_IR_DATA;i++){
      number = number | (!digitalRead(IR_RX_PIN) << i); // 8bitの右端から受信する
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }

    // ストップビット(1010)チェック
    if(digitalRead(IR_RX_PIN) == IR_ON){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }
    if(digitalRead(IR_RX_PIN) == IR_OFF){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }
    if(digitalRead(IR_RX_PIN) == IR_ON){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }
    if(digitalRead(IR_RX_PIN) == IR_OFF){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }

    Serial.print(F("ID: "));
    Serial.println(number);

    // ストップビットまで確認できたので、送信されたオーメダルorフルボトルのIDを確認
    switch(number){

    case 3: // バッタ(オーメダル)
      progrise_key_id = 1; // ライジングホッパー
      sound_offset = SOUND_OFFSET_HOPPER;
      color = &COLOR_YELLOW;
      break;

    case 49: // ウルフ(フルボトル)
      progrise_key_id = 2; // シューティングウルフ
      sound_offset = SOUND_OFFSET_WOLF;
      color = &COLOR_BLUE;
      break;

    case 6: // チーター(オーメダル)
      progrise_key_id = 3; // ラッシングチーター
      sound_offset = SOUND_OFFSET_CHEETAH;
      color = &COLOR_ORANGE;
      break;

    case 28: // サソリ(オーメダル)
      progrise_key_id = 4; // スティングスコーピオン
      sound_offset = SOUND_OFFSET_SCORPION;
      color = &COLOR_PURPLE;
      break;

    //case X: // サメ(オーメダル)
    case 15: // サメ(フルボトル)
      progrise_key_id = 6; // バイティングジャーク
      sound_offset = SOUND_OFFSET_SHARK;
      color = &COLOR_TEAL;
      break;

    case 8: // ゴリラ(オーメダル&フルボトル) ... フルボトルは本来の番号から振替
      progrise_key_id = 7; // パンチングコング
      sound_offset = SOUND_OFFSET_KONG;
      color = &COLOR_WHITE;
      break;

    case 5:  // トラ(オーメダル)
    case 25: // トラ(フルボトル)
      progrise_key_id = 8; // フレイミングタイガー
      sound_offset = SOUND_OFFSET_TIGER;
      color = &COLOR_RED;
      break;

    case 29: // ハチ(オーメダル)
    case 43: // ハチ(フルボトル)
      progrise_key_id = 9; // ライトニングホーネット
      sound_offset = SOUND_OFFSET_HORNET;
      color = &COLOR_YELLOW;
      break;

    case 26: // シロクマ(オーメダル)
    case 51: // クマ(フルボトル)
      progrise_key_id = 10; // フリージングベアー
      sound_offset = SOUND_OFFSET_BEAR;
      color = &COLOR_SKYBLUE;
      break;

    case 9: // ゾウ(オーメダル)
      progrise_key_id = 11; // ブレイキングマンモス
      sound_offset = SOUND_OFFSET_MAMMOTH;
      color = &COLOR_WHITE;
      break;

    case 23: // カブトムシ(フルボトル)
      progrise_key_id = 12; // アメイジングヘラクレス
      sound_offset = SOUND_OFFSET_HERCULES;
      color = &COLOR_CHARTREUSE;
      break;

    case 55: // キリン(フルボトル)
      progrise_key_id = 13; // スパーキングジラフ
      sound_offset = SOUND_OFFSET_GIRAFFE;
      color = &COLOR_YELLOW;
      break;

    case 64: // ハリネズミ(フルボトル)
      progrise_key_id = 14; // ガトリングヘッジホック
      sound_offset = SOUND_OFFSET_HEDGEHOG;
      color = &COLOR_GREEN;
      break;   

    case 32: // ウシ(オーメダル)
      progrise_key_id = 15; // クラッシングバッファロー
      sound_offset = SOUND_OFFSET_BUFFALO;
      color = &COLOR_RED;
      break;

    case 19: // スパイダー(フルボトル)
      progrise_key_id = 16; // トラッピングスパイダー
      sound_offset = SOUND_OFFSET_SPIDER;
      color = &COLOR_PURPLE;
      break;

    case 1:  // クワガタ(オーメダル)
    case 94: // クワガタ(フルボトル)
      progrise_key_id = 17; // エキサイティングスタッグ
      sound_offset = SOUND_OFFSET_STAG;
      color = &COLOR_ORANGE;
      break;

    case 27: // ペンギン(オーメダル)
    case 59: // ペンギン(フルボトル)
      progrise_key_id = 18; // ストーミングペンギン
      sound_offset = SOUND_OFFSET_PENGUIN;
      color = &COLOR_SKYBLUE;
      break;

    //case X:  // クジラ(オーメダル)
    case 31: // クジラ(フルボトル)
      progrise_key_id = 19; // スプラッシングホエール
      sound_offset = SOUND_OFFSET_WHALE;
      color = &COLOR_BLUE;
      break;

    case 4:  // ライオン(オーメダル)
    case 62: // ライオン(フルボトル)
      progrise_key_id = 20; // ダイナマイティングライオン
      sound_offset = SOUND_OFFSET_LION;
      color = &COLOR_RED;
      break;

    case 34: // パンダ(オーメダル & フルボトル) ... フルボトルは本来の番号から振替
      progrise_key_id = 21; // スカウティングパンダ
      sound_offset = SOUND_OFFSET_PANDA;
      color = &COLOR_WHITE;
      break;

    case 35: // カンガルー(オーメダル)
      progrise_key_id = 22; // ホッピングカンガルー
      sound_offset = SOUND_OFFSET_KANGAROO;
      color = &COLOR_GREEN;
      break;

    case 122: // スーパーバッタ(オーメダル)
      progrise_key_id = 23; // シャイニングホッパー
      sound_offset = SOUND_OFFSET_HOPPER_2;
      color = &COLOR_GOLD;
      break;

    default:
      ;
    }

    // 認識完了効果
    mp3_player.playMp3Folder(sound_offset + SOUND_PUN_NAME_DESCRIPTION);
    led_base_pattern_on(color);
    pixels.show();
    delay(11000);
    led_base_pattern_off();
    pixels.show();

    reset_state();

    is_initialized = false;
  }
}

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

void setup(){
  Serial.begin(115200);
  pinMode(SW_A_PIN,  INPUT_PULLUP);
  pinMode(SW_B_PIN,  INPUT_PULLUP);
  pinMode(SW_C_PIN,  INPUT_PULLUP);
  pinMode(IR_RX_PIN, INPUT_PULLUP);
  pinMode(LED_COLOR_PIN, OUTPUT);

  // ---------- MP3プレイヤーセットアップ ----------
  ss_mp3_player.begin(9600);
  if (!mp3_player.begin(ss_mp3_player)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin music_player:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
  Serial.println(F("mp3_player online."));
  mp3_player.setTimeOut(500); //Set serial communictaion time out 500ms
  mp3_player.volume(SOUND_VOLUME_DEFAULT);  //Set volume value (0~30).  

  // フルカラーLED初期化
  pixels.begin();
  pixels.clear();

  // 起動エフェクト
  led_pattern_simple_on_off(&COLOR_WHITE, 1000);
}

void loop(){
  if(is_initialized){
    loop_sub();
  }else{
    loop_main();
  }
}

続いてプログライズライター側です。

////////// 基本定義 ////////////////////////////////////////////////////////////

#define MP3_RX_PIN        2
#define MP3_TX_PIN        3
#define BUTTON_INPUT_PIN  4
#define BUTTON_COLOR_PIN  5
#define LED_BAR_DATA_PIN  6
#define LED_BAR_CLOCK_PIN 7
#define MEDAL_CLOCK_PIN   8
#define MEDAL_DATA_PIN    9
#define MEDAL_CTRL_PIN   10
#define BOTTLE_A_PIN     11
#define BOTTLE_B_PIN     12
#define BOTTLE_C_PIN     13
#define IR_TX_PIN        A0
#define BOTTLE_LED_PIN   A1

#define WRITING_TIME_MEDAL_MS   8600
#define WRITING_TIME_BOTTLE_MS 16800

#define STATE_READ_READY  0
#define STATE_WRITE_READY 1
#define STATE_WRITING     2
uint8_t state = STATE_READ_READY;
uint8_t prev_state = STATE_READ_READY;

#define BTN_ON  HIGH
#define BTN_OFF LOW

uint8_t btn_state = BTN_OFF;
uint8_t prev_btn_state = BTN_OFF;

unsigned long writing_start_time = 0;

////////// オーメダル読み取り処理関係 ////////////////////////////////////////////////////////////////

#define MEDAL_READ_STAGE_BEFORE 0
#define MEDAL_READ_STAGE_1ST    1
#define MEDAL_READ_STAGE_2ND    2
#define MEDAL_READ_STAGE_3RD    3
#define MEDAL_READ_STAGE_AFTER  4

uint8_t prev_medal_clock_value = 1; // 初期値は1
uint8_t prev_medal_data_value  = 1; // 初期値は1
uint8_t prev_medal_ctrl_value  = 1; // 初期値は1
uint8_t medal_clock_value = 1;
uint8_t medal_data_value  = 1;
uint8_t medal_ctrl_value  = 1;

uint8_t medal_read_stage = MEDAL_READ_STAGE_BEFORE;
uint8_t zero_counter = 0;
uint8_t one_counter  = 0;
uint8_t bit_index = 0;

uint8_t saved_zero_counter = 0;
uint8_t saved_one_counter  = 0;

uint8_t temp_medal_id = 0;
uint8_t medal_id = 0;
uint8_t prev_medal_id = 0;

void reset_count(){
  zero_counter = 0;
  one_counter  = 0;
}

////////// フルボトル読み取り処理関係 ////////////////////////////////////////////////////////////////

#define PIN_ON  LOW
#define PIN_OFF HIGH

#define N_READER_PINS 3
#define BOTTLE_READER_DELAY_MS 1 // フルボトル読み取り中にだけ適用するディレイ

#define BOTTLE_GROUP_NONE 0
#define BOTTLE_GROUP_A    1
#define BOTTLE_GROUP_C    2

#define BOTTLE_READ_STAGE_BEFORE 0
#define BOTTLE_READ_STAGE_1ST    1
#define BOTTLE_READ_STAGE_2ND    2
#define BOTTLE_READ_STAGE_3RD    3
#define BOTTLE_READ_STAGE_4TH    4
#define BOTTLE_READ_STAGE_AFTER  5

#define RESET_COUNT 200

uint8_t pin_A = PIN_OFF;
uint8_t pin_B = PIN_OFF;
uint8_t pin_C = PIN_OFF;
uint8_t before_pin_A = PIN_OFF;
uint8_t before_pin_B = PIN_OFF;
uint8_t before_pin_C = PIN_OFF;

uint8_t bottle_group = BOTTLE_GROUP_NONE;
uint8_t bottle_read_stage = BOTTLE_READ_STAGE_BEFORE;
uint8_t read_values[4] = {0,0,0,0};
uint16_t big_digit_counter    = 0;
uint16_t little_digit_counter = 0;
uint16_t reset_counter = 0;

uint8_t bottle_id = 0;

void off_on_count(){
  switch(bottle_group){
  case BOTTLE_GROUP_A:
    if(before_pin_B == PIN_OFF && pin_B == PIN_ON){
      little_digit_counter++;
    }
    if(before_pin_C == PIN_OFF && pin_C == PIN_ON){
      big_digit_counter++;
    }
    break;
  case BOTTLE_GROUP_C:
    if(before_pin_A == PIN_OFF && pin_A == PIN_ON){
      little_digit_counter++;
    }
    if(before_pin_B == PIN_OFF && pin_B == PIN_ON){
      big_digit_counter++;
    }
    break;
  default:
    ;
  }
} 

void fix_value(){
  uint8_t value = 0;
  if(little_digit_counter > 0){
    if(big_digit_counter == 0){
      value = 0;
    }else{
      value = 2;
    }
  }else if(big_digit_counter > 0){
    value = 1;
  }
  // 値の格納
  read_values[bottle_read_stage-1] = value;
}

void reset_digit_counter(){
  big_digit_counter = 0;
  little_digit_counter = 0;
}

void identify_bottle(){
  bottle_id = read_values[0]*27 + read_values[1]*9 + read_values[2]*3 + read_values[3] + 1;
  if(bottle_group == BOTTLE_GROUP_C){
    bottle_id += 81;
  }
  // Serial.println(read_values[0]); // Debug
  // Serial.println(read_values[1]); // Debug
  // Serial.println(read_values[2]); // Debug
  // Serial.println(read_values[3]); // Debug
  Serial.print("Fullbottle ID: ");
  Serial.println(bottle_id);
}

void print_bottle_read_stage(){
  Serial.print(F("Fullbottle Read Stage: "));
  Serial.println(bottle_read_stage);
}

void print_digit_counter(){
  Serial.print(F("BigDigit: "));
  Serial.println(big_digit_counter);
  Serial.print(F("LittleDigit: "));
  Serial.println(little_digit_counter);
}

////////// LEDバー処理関係 ////////////////////////////////////////////////////////////////

#include <LED_Bar.h>

LED_Bar bar(LED_BAR_CLOCK_PIN, LED_BAR_DATA_PIN);

////////// フルカラーLED(キー)処理関係 ////////////////////////////////////////////////////////////////

#include <Adafruit_NeoPixel.h>
#define N_COLOR_LED 1

struct color_rgb {
  uint8_t r;
  uint8_t g;
  uint8_t b;
};

struct color_rgb COLOR_OFF    = {  0,  0,  0};
struct color_rgb COLOR_WHITE  = {127,127,127};
struct color_rgb COLOR_RED    = {127,  0,  0};
struct color_rgb COLOR_BLUE   = {  0,  0,127};
struct color_rgb COLOR_GREEN  = {  0,127,  0};
struct color_rgb COLOR_YELLOW = {127,127,  0};
struct color_rgb COLOR_PURPLE = {127,  0,127};
struct color_rgb COLOR_BROWN  = {153, 51,  0};

struct color_rgb *color = &COLOR_OFF;

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

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_COLOR_LED, BUTTON_COLOR_PIN, NEO_GRB);

void led_base_pattern_on(struct color_rgb *color){
  pixels.setPixelColor(0, pixels.Color(color->r,color->g,color->b));
}

void led_base_pattern_off(){
  pixels.setPixelColor(0, pixels.Color(0,0,0));
}

void led_base_pattern_blink(struct color_rgb *color, unsigned long now_ms, int interval_ms){
  if(now_ms - prev_blink_time >= interval_ms){
    if(is_lighting){
      pixels.setPixelColor(0, pixels.Color(0,0,0));
    }else{
      pixels.setPixelColor(0, pixels.Color(color->r,color->g,color->b));
    }
    is_lighting = !is_lighting;
    prev_blink_time = now_ms;
  }
}

void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_time == 0){
    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;
    inc_dim_start_time = 0;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  pixels.setPixelColor(0, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
}

void led_pattern_writing_medal(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
  if(passed_ms <= 850){                           led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(1);}
  else if( 850 < passed_ms && passed_ms <= 1700){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(2);}
  else if(1700 < passed_ms && passed_ms <= 2550){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(3);}
  else if(2550 < passed_ms && passed_ms <= 3400){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(4);}
  else if(3400 < passed_ms && passed_ms <= 4250){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(5);}
  else if(4250 < passed_ms && passed_ms <= 5100){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(6);}
  else if(5100 < passed_ms && passed_ms <= 5950){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(7);}
  else if(5950 < passed_ms && passed_ms <= 6800){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(8);}
  else if(6800 < passed_ms && passed_ms <= 7650){ led_base_pattern_dim(color, now_ms, 850, 10);
                                                  bar.setLevel(9);}
  else if(7650 < passed_ms && passed_ms <= 8500){ led_base_pattern_on(color);
                                                  bar.setLevel(10);}
  else{                                           led_base_pattern_on(color);}
}

void led_pattern_writing_bottle(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
  if(passed_ms <= 1750){                            led_base_pattern_blink(color, now_ms, 440);
                                                    bar.setLevel(1);}
  else if( 1750 < passed_ms && passed_ms <=  3500){ led_base_pattern_blink(color, now_ms, 440);
                                                    bar.setLevel(2);}
  else if( 3500 < passed_ms && passed_ms <=  5250){ led_base_pattern_blink(color, now_ms, 440);
                                                    bar.setLevel(3);}
  else if( 5250 < passed_ms && passed_ms <=  7000){ led_base_pattern_blink(color, now_ms, 440);
                                                    bar.setLevel(4);}
  else if( 7000 < passed_ms && passed_ms <=  7900){ led_base_pattern_blink(color, now_ms, 340);
                                                    bar.setLevel(5);}
  else if( 7900 < passed_ms && passed_ms <=  8800){ led_base_pattern_blink(color, now_ms, 340);
                                                    bar.setLevel(6);}
  else if( 8800 < passed_ms && passed_ms <=  9700){ led_base_pattern_blink(color, now_ms, 340);
                                                    bar.setLevel(7);}
  else if( 9700 < passed_ms && passed_ms <= 10600){ led_base_pattern_blink(color, now_ms, 340);
                                                    bar.setLevel(8);}
  else if(10600 < passed_ms && passed_ms <= 11500){ led_base_pattern_blink(color, now_ms, 340);
                                                    bar.setLevel(9);}
  else if(11500 < passed_ms && passed_ms <= 12400){ led_base_pattern_on(color);
                                                    bar.setLevel(10);}
  else{                                             led_base_pattern_on(color);}
}

void control_led(unsigned long now_ms){
  if(prev_state != state){
    switch(state){
    case STATE_READ_READY:
      led_base_pattern_off();
      bar.setLevel(0);
      break;
    case STATE_WRITE_READY:
      if(medal_id != 0){
        switch(medal_id){
        case 1:   color = &COLOR_GREEN;  break; // クワガタ
        case 3:   color = &COLOR_GREEN;  break; // バッタ
        case 4:   color = &COLOR_YELLOW; break; // ライオン
        case 5:   color = &COLOR_YELLOW; break; // トラ
        case 6:   color = &COLOR_YELLOW; break; // チーター
        case 8:   color = &COLOR_WHITE;  break; // ゴリラ
        case 9:   color = &COLOR_WHITE;  break; // ゾウ
        case 26:  color = &COLOR_WHITE;  break; // シロクマ
        case 27:  color = &COLOR_BLUE;   break; // ペンギン
        case 28:  color = &COLOR_PURPLE; break; // サソリ
        case 29:  color = &COLOR_YELLOW; break; // ハチ
        case 32:  color = &COLOR_WHITE;  break; // ウシ
        case 34:  color = &COLOR_WHITE;  break; // パンダ
        case 35:  color = &COLOR_YELLOW; break; // カンガルー
        //case XX:  color = &COLOR_BLUE;   break; // サメ
        //case XX:  color = &COLOR_BLUE;   break; // クジラ
        case 122: color = &COLOR_GREEN;  break; // スーパーバッタ
        default:  color = &COLOR_OFF;           // その他
        }
      }else if(bottle_id != 0){
        switch(bottle_id){
        case  3: color = &COLOR_BROWN;  break; // ゴリラ
        case  9: color = &COLOR_WHITE;  break; // パンダ
        case 15: color = &COLOR_BLUE;   break; // サメ
        case 19: color = &COLOR_PURPLE; break; // スパイダー
        case 23: color = &COLOR_BROWN;  break; // カブトムシ
        case 25: color = &COLOR_YELLOW; break; // トラ
        case 31: color = &COLOR_BLUE;   break; // クジラ
        case 43: color = &COLOR_YELLOW; break; // ハチ
        case 49: color = &COLOR_WHITE;  break; // ウルフ
        case 51: color = &COLOR_YELLOW; break; // クマ
        case 55: color = &COLOR_YELLOW; break; // キリン
        case 59: color = &COLOR_BLUE;   break; // ペンギン
        case 62: color = &COLOR_YELLOW; break; // ライオン
        case 64: color = &COLOR_WHITE;  break; // ハリネズミ
        case 94: color = &COLOR_GREEN;  break; // クワガタ
        default: color = &COLOR_OFF;          // その他
        }
      }
      led_base_pattern_on(color);
      break;
    case STATE_WRITING:
      led_pattern_start_time = now_ms;
      break;
    default:
      ;
    }
  }

  if(state == STATE_WRITING){
    unsigned long passed_ms = now_ms - led_pattern_start_time;
    if(medal_id != 0){
      led_pattern_writing_medal(color, now_ms, passed_ms);
    }else if(bottle_id != 0){
      led_pattern_writing_bottle(color, now_ms, passed_ms);
    }
  }

  pixels.show();
}

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

#include <SoftwareSerial.h>

#define SOUND_VOLUME_DEFAULT 20 // 0〜30

#define SOUND_POWER_ON            1
#define SOUND_MEDAL_OTHERS        2
#define SOUND_MEDAL_WRITING       3
#define SOUND_BOTTLE_OTHERS       4
#define SOUND_BOTTLE_WRITING      5
#define SOUND_BOTTLE_EJECT        6

#define SOUND_MEDAL_STAG          7
#define SOUND_MEDAL_HOPPER        8
#define SOUND_MEDAL_LION          9
#define SOUND_MEDAL_TIGER        10
#define SOUND_MEDAL_CHEETAH      11
#define SOUND_MEDAL_GORILLA      12
#define SOUND_MEDAL_ELEPHANT     13
#define SOUND_MEDAL_WHITE_BEAR   14
#define SOUND_MEDAL_PENGUIN      15
#define SOUND_MEDAL_SCORPION     16
#define SOUND_MEDAL_BEE          17
#define SOUND_MEDAL_BUFFALO      18
#define SOUND_MEDAL_PANDA        19
#define SOUND_MEDAL_KANGAROO     20
#define SOUND_MEDAL_SHARK        21
#define SOUND_MEDAL_WHALE        22
#define SOUND_MEDAL_SUPER_HOPPER 23

#define SOUND_BOTTLE_GORILLA     24
#define SOUND_BOTTLE_PANDA       25
#define SOUND_BOTTLE_SHARK       26
#define SOUND_BOTTLE_SPIDER      27
#define SOUND_BOTTLE_BEETLE      28
#define SOUND_BOTTLE_TIGER       29
#define SOUND_BOTTLE_WHALE       30
#define SOUND_BOTTLE_BEE         31
#define SOUND_BOTTLE_WOLF        32
#define SOUND_BOTTLE_BEAR        33
#define SOUND_BOTTLE_GIRAFFE     34
#define SOUND_BOTTLE_PENGUIN     35
#define SOUND_BOTTLE_LION        36
#define SOUND_BOTTLE_HEDGEHOG    37
#define SOUND_BOTTLE_STAG        38

SoftwareSerial ss_mp3_player(MP3_RX_PIN, MP3_TX_PIN);

void mp3_play(uint8_t track){
  // この定義だと再生曲数は255に限定されるので注意。
  // Upper-Byte, Lower-Byteをちゃんと処理してやれば65535曲まで対応可能だが、
  // 容量的にそこまで使うことはほぼないと思われる。

  // なぜか、指定したトラックに対して以下のようにファイルが再生される
  // 指定:1 -> 再生:01.mp3
  // 指定:2 -> 再生:不可
  // 指定:3 -> 再生:02.mp3
  // 指定:4 -> 再生:不可
  // 指定:5 -> 再生:03.mp3
  // 指定:6 -> 再生:不可
  // 指定:7 -> 再生:04.mp3
  // 指定:8 -> 再生:不可
  // ...
  // 原因は不明だが、とりあえず上記仕様に合わせるようにしておく

  uint8_t temp_track = track*2 - 1;
  uint8_t play[6] = {0xAA,0x07,0x02,0x00,temp_track,temp_track+0xB3};
  ss_mp3_player.write(play,6);
  Serial.print(F("MP3 Play: No."));
  Serial.println(track);
}

void mp3_stop(){
  unsigned char stop[4] = {0xAA,0x04,0x00,0xAE};
  ss_mp3_player.write(stop,4);
  Serial.println(F("MP3 Stop"));
}

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

void control_sound(){
  if(prev_state != state){
    switch(state){
    case STATE_READ_READY:
      break;
    case STATE_WRITE_READY:
      if(medal_id != 0){
        switch(medal_id){
        case  1:  mp3_play(SOUND_MEDAL_STAG);       break; // クワガタ
        case  3:  mp3_play(SOUND_MEDAL_HOPPER);     break; // バッタ
        case  4:  mp3_play(SOUND_MEDAL_LION);       break; // ライオン
        case  5:  mp3_play(SOUND_MEDAL_TIGER);      break; // トラ
        case  6:  mp3_play(SOUND_MEDAL_CHEETAH);    break; // チーター
        case  8:  mp3_play(SOUND_MEDAL_GORILLA);    break; // ゴリラ
        case  9:  mp3_play(SOUND_MEDAL_ELEPHANT);   break; // ゾウ
        case 26:  mp3_play(SOUND_MEDAL_WHITE_BEAR); break; // シロクマ
        case 27:  mp3_play(SOUND_MEDAL_PENGUIN);    break; // ペンギン
        case 28:  mp3_play(SOUND_MEDAL_SCORPION);   break; // サソリ
        case 29:  mp3_play(SOUND_MEDAL_BEE);        break; // ハチ
        case 32:  mp3_play(SOUND_MEDAL_BUFFALO);    break; // ウシ
        case 34:  mp3_play(SOUND_MEDAL_PANDA);      break; // パンダ
        case 35:  mp3_play(SOUND_MEDAL_KANGAROO);   break; // カンガルー
        //case XX: mp3_play(SOUND_MEDAL_SHARK);        break; // サメ
        //case XX: mp3_play(SOUND_MEDAL_WHALE);        break; // クジラ
        case 122:  mp3_play(SOUND_MEDAL_SUPER_HOPPER); break; // スーパーバッタ
        default:  mp3_play(SOUND_MEDAL_OTHERS);            // その他
        }
      }else if(bottle_id != 0){
        switch(bottle_id){
        case  3: mp3_play(SOUND_BOTTLE_GORILLA);  break; // ゴリラ
        case  9: mp3_play(SOUND_BOTTLE_PANDA);    break; // パンダ
        case 15: mp3_play(SOUND_BOTTLE_SHARK);    break; // シャーク
        case 19: mp3_play(SOUND_BOTTLE_SPIDER);   break; // スパイダー
        case 23: mp3_play(SOUND_BOTTLE_BEETLE);   break; // カブトムシ
        case 25: mp3_play(SOUND_BOTTLE_TIGER);    break; // トラ
        case 31: mp3_play(SOUND_BOTTLE_WHALE);    break; // クジラ
        case 43: mp3_play(SOUND_BOTTLE_BEE);      break; // ハチ
        case 49: mp3_play(SOUND_BOTTLE_WOLF);     break; // ウルフ
        case 51: mp3_play(SOUND_BOTTLE_BEAR);     break; // クマ
        case 55: mp3_play(SOUND_BOTTLE_GIRAFFE);  break; // キリン
        case 59: mp3_play(SOUND_BOTTLE_PENGUIN);  break; // ペンギン
        case 62: mp3_play(SOUND_BOTTLE_LION);     break; // ライオン
        case 64: mp3_play(SOUND_BOTTLE_HEDGEHOG); break; // ハリネズミ
        case 94: mp3_play(SOUND_BOTTLE_STAG);     break; // クワガタ
        default: mp3_play(SOUND_BOTTLE_OTHERS);        // その他
        }
      }
      break;
    case STATE_WRITING:
      if(medal_id != 0){
        mp3_play(SOUND_MEDAL_WRITING);
      }else if(bottle_id != 0){
        mp3_play(SOUND_BOTTLE_WRITING);
      }
      break;
    default:
      ;
    }
  }
}

////////////////////////// IR送信関係 ////////////////////////// 

// 対向の赤外線受信モジュール(OSRB38C9AA)の中心周波数
// (キャリア周波数)が37.9kHzなので、それに応じて定数を定める

// IR_SHORT_DURATION_MICRO_SECは、
// 16MHzのArduinoなら10、8MHzのArduinoなら5ぐらい。
#define IR_SHORT_DURATION_MICRO_SEC  10

#define IR_LONG_DURATION_MICRO_SEC  600
#define NUM_IR_ON_OFF                23 // 26μs/回 * 23回 = 598μs

#define LEN_IR_DATA  8 // 今回は8bitで表現される整数値をおくる

#define IR_SEND_RETRY 5 // 一回で受信してもらえる保証がないので、繰り返し送るようにする

void send_bit_0(){
  digitalWrite(IR_TX_PIN, LOW);
  delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}

void send_bit_1(){
  // 1回のループでおよそ26μsになるようにIR_SHORT_DURATION_MICRO_SECを設定。
  // (←うまく動かないときはまずこの値を調整する)
  // ループの全体でおよそIR_LONG_DURATION_MICRO_SEC(=600)になるようにする
  for(uint8_t i=0;i<NUM_IR_ON_OFF;i++){
    digitalWrite(IR_TX_PIN, HIGH);
    delayMicroseconds(IR_SHORT_DURATION_MICRO_SEC);
    digitalWrite(IR_TX_PIN, LOW);
    delayMicroseconds(IR_SHORT_DURATION_MICRO_SEC);
  }
}

void send_ir(uint8_t number){
  // スタートビットの送信
  send_bit_1();

  // 整数値(8bit)の送信
  for(uint8_t i=0;i<LEN_IR_DATA;i++){
    uint8_t send_bit = bitRead(number, i); // 8bitの右端から送信する
    if(send_bit == 1){
      send_bit_1();
    }else{
      send_bit_0();
    }
  }

  // ストップビットの送信
  send_bit_1();
  send_bit_0();
  send_bit_1();
  send_bit_0();

  // 送信完了表示
  Serial.print(F("Sent: "));
  Serial.println(number);
}

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

void setup(){
  Serial.begin(115200);
  pinMode(BUTTON_INPUT_PIN, INPUT_PULLUP);
  pinMode(MEDAL_CLOCK_PIN, INPUT_PULLUP);
  pinMode(MEDAL_DATA_PIN,  INPUT_PULLUP);
  pinMode(MEDAL_CTRL_PIN,  INPUT_PULLUP);
  pinMode(BOTTLE_A_PIN, INPUT_PULLUP);
  pinMode(BOTTLE_B_PIN, INPUT_PULLUP);
  pinMode(BOTTLE_C_PIN, INPUT_PULLUP);
  pinMode(IR_TX_PIN, OUTPUT);
  pinMode(BOTTLE_LED_PIN, OUTPUT);

  // MP3プレイヤーセットアップ
  ss_mp3_player.begin(9600);
  mp3_set_volume(SOUND_VOLUME_DEFAULT);

  // フルカラーLED(ボタン)初期化
  pixels.begin();
  pixels.clear();
  pixels.show();

  // LEDバー初期化
  bar.setLevel(0);

  // LEDセットアップ
  digitalWrite(IR_TX_PIN, LOW);
  digitalWrite(BOTTLE_LED_PIN, LOW);

  Serial.println(F("Read Stage: Before"));
  print_bottle_read_stage(); // Debug

  mp3_play(SOUND_POWER_ON);
}

void loop(){

  unsigned long now_ms = millis();
  uint8_t input_port = PINB; // ピン8〜13のデジタル入力の同時読み取り  

  //////////////////// オーメダル読み取り処理 ////////////////////

  medal_clock_value = bitRead(input_port, 0);
  medal_data_value  = bitRead(input_port, 1);
  medal_ctrl_value  = bitRead(input_port, 2);

  // CTRL信号での状態遷移管理
  if(prev_medal_ctrl_value == 1 && medal_ctrl_value == 0){
    medal_read_stage = MEDAL_READ_STAGE_1ST;
    Serial.println(F("Read Stage: 1st"));
  }else if(prev_medal_ctrl_value == 0 && medal_ctrl_value == 1){
    medal_read_stage = MEDAL_READ_STAGE_BEFORE;
    Serial.println(F("Read Stage: Before"));
    temp_medal_id = 0;
    bit_index = 0;
    reset_count();
  }

  // CLOCKとDATAからの値読み込み
  if(prev_medal_clock_value != medal_clock_value || prev_medal_data_value != medal_data_value){
    //Serial.print(medal_clock_value);
    //Serial.print(medal_data_value);
    //Serial.println(medal_ctrl_value);

    // CLOCKが1のときのDATAの値を使用する
    if(medal_clock_value == 1){
      if(medal_data_value == 0){
        saved_one_counter = one_counter;
        zero_counter += 1;
        one_counter  =  0;
      }else{
        saved_zero_counter = zero_counter;
        zero_counter =  0;
        one_counter  += 1;
      }
    }

    switch(medal_read_stage){
    case MEDAL_READ_STAGE_BEFORE:
      // この段階では、何もしない。CTRL信号での状態遷移を待つ
      ;
      break;
    case MEDAL_READ_STAGE_1ST:
      if(saved_zero_counter == 8 && one_counter == 1){
        medal_read_stage = MEDAL_READ_STAGE_2ND;
        Serial.println(F("Read Stage: 2nd"));
      }
      break;
    case MEDAL_READ_STAGE_2ND:
      if(saved_one_counter == 2 && zero_counter == 1){
        medal_read_stage = MEDAL_READ_STAGE_3RD;
        Serial.println(F("Read Stage: 3rd"));
        reset_count();
      }
      break;
    case MEDAL_READ_STAGE_3RD:
      if(saved_one_counter == 0 && zero_counter == 1){
        // 0確定
        bit_index++;
        reset_count();
      }else if(saved_one_counter > 1 && zero_counter == 1){
        // 1確定
        uint8_t num_of_1 = saved_one_counter - 1;
        for(uint8_t i=0;i<num_of_1;i++){
          temp_medal_id = temp_medal_id | (B10000000 >> bit_index);
          bit_index++;
        }
        reset_count();
      }

      // 読み取り完了
      if(bit_index == 8){
        if(prev_medal_id != temp_medal_id){
          medal_id = temp_medal_id;
          Serial.print(F("Medal ID: "));
          Serial.println(medal_id);
          bottle_id = 0; // このタイミングでボトルのIDはリセットしておく

          bit_index = 0;
          medal_read_stage = MEDAL_READ_STAGE_AFTER;
          Serial.println(F("Read Stage: AFTER"));

          state = STATE_WRITE_READY;
          prev_medal_id = medal_id;
        }
      }
      break;
    case MEDAL_READ_STAGE_AFTER:
      // 本来なら後処理のデータを読み取る必要があるが、今回は省略する
      break;
    default:
      ;
    }
  }

  prev_medal_clock_value = medal_clock_value;
  prev_medal_data_value  = medal_data_value;
  prev_medal_ctrl_value  = medal_ctrl_value;

  //////////////////// フルボトル読み取り処理 ////////////////////

  pin_A = bitRead(input_port, 3);
  pin_B = bitRead(input_port, 4);
  pin_C = bitRead(input_port, 5);

  //if((before_pin_A != pin_A) || (before_pin_B != pin_B) || (before_pin_C != pin_C)){
  //  Serial.print(pin_A);
  //  Serial.print(pin_B);
  //  Serial.println(pin_C);
  //}

  switch(bottle_read_stage){
  case BOTTLE_READ_STAGE_BEFORE:
    if(before_pin_A == PIN_OFF & pin_A == PIN_ON){
      bottle_group = BOTTLE_GROUP_A;
      Serial.println(F("Fullbottle Read Group A Ready."));
      bottle_read_stage++;
      print_bottle_read_stage(); // Debug
    }else if(before_pin_C == PIN_OFF && pin_C == PIN_ON){
      bottle_group = BOTTLE_GROUP_C;
      Serial.println(F("Fullbottle Read Group C Ready."));
      bottle_read_stage++;
      print_bottle_read_stage(); // Debug
    }
    break;
  case BOTTLE_READ_STAGE_1ST: // 1
  case BOTTLE_READ_STAGE_2ND: // 2
  case BOTTLE_READ_STAGE_3RD: // 3
    // OFF-ON変化のカウント(チャタリング前提)
    off_on_count(); 

    if((bottle_group == BOTTLE_GROUP_A && before_pin_A == PIN_OFF && pin_A == PIN_ON) ||
       (bottle_group == BOTTLE_GROUP_C && before_pin_C == PIN_OFF && pin_C == PIN_ON)){
        if(big_digit_counter > 0 || little_digit_counter > 0){
          //print_digit_counter(); // Debug
          fix_value();
          reset_digit_counter();
          bottle_read_stage++;
          print_bottle_read_stage(); // Debug
        }
    }

    delay(BOTTLE_READER_DELAY_MS);
    break;
  case BOTTLE_READ_STAGE_4TH: // 4
    off_on_count(); 

    if((bottle_group == BOTTLE_GROUP_A && before_pin_A == PIN_ON && pin_A == PIN_OFF) ||
       (bottle_group == BOTTLE_GROUP_C && before_pin_C == PIN_ON && pin_C == PIN_OFF)){
      fix_value();
      //print_digit_counter(); // Debug
      reset_digit_counter();
      // ボトルの特定
      identify_bottle();
      medal_id = 0; // このタイミングでメダルのIDはリセットしておく
      digitalWrite(BOTTLE_LED_PIN, HIGH);
      state = STATE_WRITE_READY;

      bottle_read_stage++;
      print_bottle_read_stage(); // Debug
    }

    delay(BOTTLE_READER_DELAY_MS);
    break;
  case BOTTLE_READ_STAGE_AFTER: // 5
    if(pin_A == PIN_OFF && pin_B == PIN_OFF && pin_C == PIN_OFF){
      reset_counter++;
    }else{
      reset_counter = 0;
    }

    if(reset_counter > RESET_COUNT){
      mp3_play(SOUND_BOTTLE_EJECT);
      digitalWrite(BOTTLE_LED_PIN, LOW);
      reset_counter = 0;
      bottle_read_stage = BOTTLE_READ_STAGE_BEFORE;
      bottle_group = BOTTLE_GROUP_NONE;
      reset_digit_counter();
      for(int i=0;i<4;i++){
        read_values[i] = 0;
      }
      state = STATE_READ_READY;
      bottle_id = 0;
      print_bottle_read_stage(); // Debug
    }

    delay(BOTTLE_READER_DELAY_MS);
    break;
  default:
    ;
  }

  before_pin_A = pin_A;
  before_pin_B = pin_B;
  before_pin_C = pin_C;  

  //////////////////// ボタン処理 ////////////////////

  btn_state = digitalRead(BUTTON_INPUT_PIN);
  if(state == STATE_WRITE_READY && prev_btn_state == BTN_OFF && btn_state == BTN_ON){
    Serial.println(F("Pushed."));
    if(medal_id != 0 || bottle_id != 0){
      state = STATE_WRITING;
      writing_start_time = now_ms;
    }
  }
  prev_btn_state = btn_state;

  //////////////////// 時間経過処理 ////////////////////

  if(state == STATE_WRITING){
    if(medal_id != 0 && now_ms - writing_start_time >= WRITING_TIME_MEDAL_MS){
      for(uint8_t i=0;i<IR_SEND_RETRY;i++){
        send_ir(medal_id);
        delay(100);
      }
      state = STATE_READ_READY;
      medal_id = 0;
      prev_medal_id = 0;
    }else if(bottle_id != 0 && now_ms - writing_start_time >= WRITING_TIME_BOTTLE_MS){
      // ゴリラとパンダだけは、他のメダルのIDとのバッティングを防ぐため、番号を振り替えて送信する
      if(bottle_id == 3){
        bottle_id = 8; // ゴリラのメダルIDと同じ
      }else if(bottle_id == 9){
        bottle_id = 34; // パンダのメダルIDと同じ
      }

      for(uint8_t i=0;i<IR_SEND_RETRY;i++){
        send_ir(bottle_id);
        delay(100);
      }
      state = STATE_READ_READY;
      bottle_id = 0;
    }
  }

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

  ////////// LED発光処理(ボタン&バー) ////////////////////
  control_led(now_ms);

  prev_state = state;
}