Electric ImpでiPhoneからLチカしてみる。

基本的に本家のGet Startedの [Agents and Internet of Things Apps](https://developer.electricimp.com/gettingstarted/explorer/agents) を少しカスタマイズして、iPhoneアプリからLチカする一連のコードを記載します。

### 実行イメージ

| アプリ側          | electric imp |
|:-----------------|:------------------|
| <img width="375" alt="IMG_0126.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/57579/dbc8c376-9af8-fa3e-82f0-33b1047e523d.png"> |<img width="375" alt="スクリーンショット 2017-12-09 23.46.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/57579/581f6912-1d0e-7af8-b451-c448311ed405.png"> |
|<img width="375" alt="iphone-app.png" src="https://qiita-image-store.s3.amazonaws.com/0/57579/a8855e06-481f-cf14-1250-98c667b3de4c.png">|<img width="375" alt="スクリーンショット 2017-12-09 23.47.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/57579/6d4f03b8-01c4-c4be-fb21-1d3e6848dc5c.png">|


## Electric Impの面白いところ

<img width="1298" alt="editor.png" src="https://qiita-image-store.s3.amazonaws.com/0/57579/c86ce5ad-8008-721f-4553-81ee821f0b3c.png">

ブラウザ上でコードを組んだらそれが実機に反映されて、Wifi経由の動作コマンドをすごく少ないコードで実装できること。
標準で気温 / GPS / ジャイロセンサーを搭載しているので、ベーシックなセンサー動作の実装が非常に簡単。
コードを追えていないのですが、SalesForce / IBM Watson / PLCなんかとの接続もできるようなので、かなり簡単に外部Webサービスとの連携もできそうです。

## Electric Impにコード実装

Agents and Internet of Things AppsのAgentとDeviceのコードをカスタマイズし、RGBのカラーコードを受け取れるようにします。

```Agent.

// 受信パラメータ r g b を
// led = 1 [On] or 0 [Off] 
// r = Red   [0..255]
// g = Green [0..255]
// b = Blue  [0..255]
function requestHandler(request, response) {
    try {
        if ("led" in request.query) {
            if (request.query.led == "1" || request.query.led == "0") {
                local ledState = (request.query.led == "0") ? false : true;

                // データはArrayで送る
                device.send("set.led", [ledState, request.query.r, request.query.g, request.query.b]); 
            }
        }
    
        response.send(200, "OK");
    } catch (ex) {
        response.send(500, "Internal Server Error: " + ex);
    }
}

http.onrequest(requestHandler);
```

```Device.
#require "WS2812.class.nut:3.0.0"

spi <- null;
led <- null;

function setLedState(data) {
    // data[0] = On / Off
    // data[1] = Red   [0..255]
    // data[2] = Green [0..255]
    // data[3] = Blue  [0..255]
    local color = data[0] ? [data[1].tointeger(), data[2].tointeger(), data[3].tointeger()] : [0,0,0];
    led.set(0, color).draw();
}

spi = hardware.spiAHSR;
spi.configure(MSB_FIRST, 6000);

led = WS2812(spi, 1);
hardware.pinH.configure(DIGITAL_OUT, 1);

// Agentのdevice.sendで呼び出されるメソッド名に応じたDevice実装関数を呼び出す
agent.on("set.led", setLedState);
```

## iPhoneにコード実装

### コード

```Podfile.
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'ledSwitch' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ledSwitch
  pod 'Gryphon', '~> 3.1'
  pod 'Font-Awesome-Swift'
end
```

```ViewController.swift
import UIKit
import Gryphon
import Font_Awesome_Swift

class ViewController: UIViewController {
    @IBOutlet weak var lblAppICON: UILabel!
    @IBOutlet weak var lblColor: UILabel!
    @IBOutlet weak var sldRed: UISlider!
    @IBOutlet weak var sldGreen: UISlider!
    @IBOutlet weak var sldBlue: UISlider!
    @IBOutlet weak var btnChange: UIButton!
    @IBOutlet weak var btnStop: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // アプリケーションロゴ設定
        lblAppICON.FAIcon = FAType.FARssSquare

        // 実行ボタンのアイコンやカラー設定
        btnChange.layer.borderWidth = 1.0
        btnChange.layer.cornerRadius = 4.0
        btnChange.setFAText(prefixText: "Change ", icon: FAType.FAPowerOff, postfixText: "", size: 24.0, forState: .normal, iconSize: 24.0)
        btnChange.setFATitleColor(color: UIColor.white)
        btnChange.backgroundColor = UIColor(displayP3Red: CGFloat(72.0 / 255.0), green: CGFloat(165.0 / 255.0), blue: CGFloat(109.0 / 255.0), alpha: 1.0)
        btnChange.layer.borderColor = btnChange.backgroundColor?.cgColor
        
        // 停止ボタンのアイコンやカラー設定
        btnStop.layer.borderWidth = 1.0
        btnStop.layer.cornerRadius = 4.0
        btnStop.layer.borderColor = UIColor.darkGray.cgColor
        btnStop.setFAText(prefixText: "Lights out ", icon: FAType.FAStopCircleO, postfixText: "", size: 24.0, forState: .normal, iconSize: 24.0)
        btnStop.setFATitleColor(color: UIColor.white)
        btnStop.backgroundColor = UIColor.darkGray

        // スライダで設定したカラーのプレビュー
        lblColor.layer.borderWidth = 1.0
        lblColor.layer.cornerRadius = 8.0
        lblColor.clipsToBounds = true
        lblColor.text = ""
        sldRed.value = 1
        sldGreen.value = 0
        sldBlue.value = 0
        lblColor.backgroundColor = UIColor(displayP3Red: CGFloat(sldRed.value), green: CGFloat(sldGreen.value), blue: CGFloat(sldBlue.value), alpha: 1.0)
        lblColor.layer.borderColor = lblColor.backgroundColor?.cgColor
    }

    
    @IBAction func changeRGBColor(_ sender: Any) {
        // 色変更をプレビュー表示する
        lblColor.backgroundColor = UIColor(displayP3Red: CGFloat(sldRed.value), green: CGFloat(sldGreen.value), blue: CGFloat(sldBlue.value), alpha: 1.0)
        lblColor.layer.borderColor = lblColor.backgroundColor?.cgColor
    }
    
    @IBAction func doLedColorSet(_ sender: Any) {
        // GryphonでAPIをコール
        API
            .Messages
            // Device側は0..255の値になるので、0..1の値を変換
            .getMessage(onOff: 1, red: Int(sldRed.value * 255), green: Int(sldGreen.value * 255), blue: Int(sldBlue.value * 255))
            .response { (result) in
                switch result {
                case let .response(statusCode):
                    print("StatusCode=\(statusCode)")
                case let .error(error):
                    print("Error=\(error)")
                }
        }
    }
    
    @IBAction func doStopLight(_ sender: Any) {
        API
            .Messages
            .getMessage(onOff: 0, red: 0, green: 0, blue: 0)
            .response { (result) in
            switch result {
            case let .response(statusCode):
                print("StatusCode=\(statusCode)")
            case let .error(error):
                print("Error=\(error)")
            }
        }
    }
    
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}
```

```API.swift
import Foundation
import Gryphon

final class API {
    /*
     * Your common property or function.
     */
}

extension API {
    
    final class Messages: Requestable {
        
        // required `Requestable`
        static var baseURL: String {
            return "https://agent.electricimp.com/"
        }
        
        // required `Requestable`
        static var path: String {
            return "デバイスのコード"
        }
        
        // Returns message(String) from server or error reason(Error).
        class func getMessage(onOff: Int, red: Int, green: Int, blue: Int) -> Task<String, Error> {
            let task = Task<String, Error> { result in
                // 普通にURL形式で送る
                let url = URL(string: baseURL + path + "?led=" + onOff.description + "&r=" + red.description + "&g=" + green.description + "&b=" + blue.description)!
                var request = URLRequest(url: url)
                request.httpMethod = "GET"
                let session = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                    guard let data = data,
                        let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [AnyObject],
                        let message = json?[0]["result"] as? String else {
                            result(.error(ResponseError.unexceptedResponse(error as AnyObject)))
                            return
                    }
                    result(.response(message))
                })
                session.resume()
            }
            return task
        }
        
        // Returns status code if the submittion is succeeded.
        class func postMessage() -> Task<Int, Error> {
            let task = Task<Int, Error> { result in
                let url = URL(string: baseURL + path)!
                var request = URLRequest(url: url)
                request.httpMethod = "POST"
                let session = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
                    guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { return }
                    if 200...299 ~= statusCode {
                        result(.response(statusCode))
                    }else{
                        result(.error(ResponseError.unacceptableStatusCode(statusCode)))
                    }
                })
                session.resume()
            }
            return task
        }
    }
}
```

量産になるといろいろ超えるべきハードルがありますが、
プロトタイピングでとにかく早く、手軽にという目的であれば
これはすごくいいツールなんじゃないかなぁと思っています。