高校文化祭の食販で自動注文機を作った話

#経緯
私の学校では、毎年文化祭で高校3年生が食販をする伝統があり、私のクラスでは「生姜焼きパン」を売ることになりました。（本記事の主旨とは逸れるがこれがめちゃくちゃ美味い）
基本的に食販は以下のようなフローで行われます。
<img width="767" alt="Screen Shot 2019-11-15 at 23.52.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459379/17f0ba04-ed2e-b985-cf36-daee2d924f58.png">
このフローは厳守です。即ち注文が入らない場合は調理できません。作り置きは衛生上問題があるため不可です。
つまり、注文数と調理数を適切に管理しない場合、両者が食い違い大量の廃棄が出る可能性すらあるのです。従来はボードなどを用いてごちゃごちゃやっていたらしいですが、あまりにも非効率で間違いが生じる可能性も否めません。そこで私たちはサーバーを用意して、文明の利器「スマートフォン」を用いた一元化管理を考えました。

#基本的な仕組み

基本的なシステムは下図の通りです。セキュリティー的にどうなの？とかは訊かないでください。あくまでも文化祭用です。商用ではありません。(ここ重要)

<img width="1607" alt="Screen Shot 2019-11-16 at 0.27.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459379/37d290cd-68cd-0223-b9b7-bad66d80644b.png">


簡単に言うと、注文個数をHTTPRequestを用いてサーバー側に送信して、一意性のある注文IDを返し、そしてそのIDを印刷したレシートを発行して、購入者の識別を行う、という仕組みになっています。


1.サーバーに関して

今回はConoHaVPS(RAM2GB/SSD50GBプラン)を契約しました。OSはCentOSです。
僕が通っている高校は国立大附属校なので、予算がありません。そのため.tkドメインを取得し、利用しました。
サーバーにて運用したWebAPIはRuby on Railsを使っています。

2.1.注文機(iPad)について

iPad Pro(Gen1,12.9inch)を利用しました。
採用理由は以下の2点です。

・出来るだけ大きなディスプレイを利用することで、操作性の向上を図るため。

・最新のモデルは上にスワイプすることでホームに戻ることができるが、このモデルはホームボタンがあるため、ボタンを覆うことでホーム画面へのアクセスを防ぎやすいため。

Xcodeの実機Simulation機能を用いて、Macと同一のApple IDでiPadにログインすることで実機テスト扱いとして利用しました。

2.2.サーマルプリンタとの通信について

利用したサーマルプリンタはStar mC-Print3です。このモデルはiOS Appの為に通信用SDKが公開されており、非常にiOSデバイスとの親和性が高い物になります。今回は利用しませんでしたがサーマルプリンタの状態(紙詰まり、温度異常等)を検知し、アプリ側にその情報を渡すことができます。

サーマルプリンターとの通信はBT/BTLow Energy/USB/LANのいずれかを選択できます。

今回はiPad本体の給電に対応するUSB経由での通信を採用しました。
流行り(？)のBluetooth Low Energy経由で通信を行なうと、すべての情報を送信できないことがあるらしいです。そのため印刷時には、全情報を送信したか否かを確認し、送信予定通信量に満たない場合は再度差分を送信する仕組みがSDKにあるそうです。

2.3.筐体について

筐体は学校の倉庫に眠る昨年の廃材を用いました。一般的なPC冷却台の上にiPadを固定し、そのまとまりを斜めに設置しています。
![B41D1DB6-E23E-445C-ABF9-2A8C1049F510.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459379/2e125d6a-1566-0a55-9cb7-5e914a939921.jpeg)
![9F3A3C0C-1152-44AB-9387-2A38C15F146A.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459379/905d492c-d4e9-59f4-d5a5-d58de9f0a1e7.jpeg)



#開発中にみた悪夢
それはある日の会話が発端だった・・・
当初は金券をスタッフが受け取った際に、画面にコマンドを入力してレシート印刷の画面に遷移するという仕様でした。でもこれってダサいじゃないですか。「自動注文機」なのに途中で画面側からスタッフが入力するのは非効率、そしてロマンに欠けます。
というわけでサーバーサイド側と相談し、以下のようなフローで金券をスタッフが受けとった、という情報をアプリ側に渡すことを考えました。

<img width="1654" alt="Screen Shot 2019-11-16 at 10.36.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459379/6a0370f0-183b-ad4b-49d3-d0ee8eb0be4e.png">


簡単にいうと数秒に一回、サーバー側に金券を受け取ったか否かの情報をリクエストし、真（受け取った）という情報を得た場合にレシート印刷に進むというわけです。

というわけで仕様も決まり、この機能を実装するコードを書くことに。
最初に僕が書いたのは以下のようなコードです。

```swift:PaymentViewController.swift
  DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
 //HTTPRequest
 //情報の真偽確認
  }
```
これをtrueを得るまでループする、といったコードです。（古いコードが残っておらずうろ覚えですみません。）
勘の良い方ならお気づきかもしれませんが、僕は相当なやらかしをしています。

そうです。プログラムをRunした瞬間、SimulatorのCPU使用率が上昇しまくります。そして本番環境でサーバーサイドのプログラムを書いていたサーバーサイド担当から「急に激重になったんだけど💢」とLINEが来ます。そりゃそうですよね、高速でスレッドを立ち上げまくり、HTTPRequestを大量に送りつけているんですから…
ごめんなさい。反省しています。
そして改善したコードが以下の通りです。

```swift:PaymentViewController.swift
 paymentIfCompletedBool = false
   
    queue.async {
        
        apicheck: while paymentIfCompletedBool == false {
            Thread.sleep(forTimeInterval: 3.0)
            let boolcheckurl = URL(string: "https://viedofrance.tk/hashkey/" + orderId + "/")!
            var request = URLRequest(url: boolcheckurl)
            request.httpMethod = "GET"
            URLSession.shared.dataTask(with: request){ (data, response, error) in
                if error != nil{}
                guard let data = data else {return}

                let json = try? JSONDecoder().decode(responseJsonBool.self, from:data)
                paymentIfCompleted = json!.bool!
                if paymentIfCompleted == "false" {
                    paymentIfCompletedBool = false
                }
                if paymentIfCompleted == "true" {
                    paymentIfCompletedBool = true
                }
                
                
                }.resume()

            if paymentIfCompletedBool == true {
                self.performSegue(withIdentifier: "toPrintView", sender: nil)
               orderIdWithBlank = " " + orderId + " "
                break apicheck
                
            }          
        }
```
見事動きました！やった！
コードが汚い、命名規則がゴミなどは承知です。正直このコード出すのも恥ずかしいのです()
まあチーム開発ではないので自分さえわかれば良いやという発想です。許してください。

こうして悪夢のサーバーにアプリが攻撃してしまう問題は改善しました。めでたしめでたし。

#本番（文化祭当日）のシステム構成

文化祭当日のシステム構成は下図の通りになります。マ◯ドナルドをリスペクトしています。
<img width="869" alt="Screen Shot 2019-11-15 at 23.22.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459379/83aa9b15-b2bf-1918-2a52-e760690507bc.png">

当日は某ファーストフード店のように、露店の前にテレビを設置して、受け渡しが可能な状態の注文番号を掲示していました。
様子はこんな感じです。（お世話になっている高校の先輩のツイートより引用）

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">後輩の注文システム、マジすごくておったまげた<br>聞いたらサーバーサイド(Rails)とクライアントサイド(iOS)分担して二人で作ったらしく、筐体まで作り込まれてて、ただただ脱帽… <a href="https://t.co/5VfFwBxhxs">pic.twitter.com/5VfFwBxhxs</a></p>&mdash; ど(∩❛ڡ❛∩)ら (@d0ra1998) <a href="https://twitter.com/d0ra1998/status/1170204099427495937?ref_src=twsrc%5Etfw">September 7, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#宣伝

本件について、高校生新聞に掲載されています。

http://www.koukouseishinbun.jp/articles/-/5804

昔にこんなのも作ってます。是非お読みください。

http://www.koukouseishinbun.jp/articles/-/2793


#感想
実はSwiftのプログラムは初めて書いたのですが、やはりプログラミングは習うより慣れよ、ですね。コードをどれだけ模写しても、本だけを読み続けても、Progateに張り付いていても能力は伸びないと思います。実践的に開発することが言語習得の近道だと気付きました。

大学は情報系に進む予定です。大学に進学した際にはQiitaの記事も増えると思うのでその時はよろしくお願いします・・・
というわけで受験生に戻ります。最後までお読みいただきありがとうございました。

高校生以下の方で似たようなことをやりたいと考えている方がいらっしゃったらコメントください。相談に乗ります。
お気づきの点があれば、コメント欄に書いていただけると嬉しいです。