Swiftで書くシリアル通信 〜macOS - Arduino を例に〜

## はじめに

[VALU Advent Calendar 2018](https://qiita.com/advent-calendar/2018/valu) の8日目です!

VALU では iOS 人材ですが，趣味では電子工作も好きだったりします。今回は，Swift の魅力をもっとお伝えしようと，Swift を用いてシリアル通信が簡単に実現可能なライブラリ，[ORSSerialPort](https://github.com/armadsen/ORSSerialPort) をご紹介致します。

今回は，シリアル通信で Arduino (Arduino UNO R3) と通信をするプログラムを作成していこうと思います。

## [Arduino](https://www.arduino.cc) とは

<img src="https://qiita-image-store.s3.amazonaws.com/0/195913/2d6ec271-9c65-d886-ff1f-c44c67e13e0c.jpeg" height="300px">

> [https://www.arduino.cc](https://www.arduino.cc):
> Open-source electronic prototyping platform enabling users to create interactive electronic objects.

[Arduino](https://www.arduino.cc) とは，基盤がオープンソースで公開されている，安価な開発用マイコンボードを指します。Amazon でも 3,000 円程度で入手できるので試作用にはとても便利です。内部には AVR が載っていて，5-12 VといくつかのI/Oボードとクロックが搭載されています。

それでは以下より，プロジェクトを作成していきます。

## プロジェクトファイルの作成 (CLIの場合)

最初に，プロジェクトファイルを作成します。今回は [Swift Package Manager](https://github.com/apple/swift-package-manager) を利用しました。

```bash
$ mkdir (プロジェクト名) && cd $_ && swift package init --type executable && swift build && swift package generate-xcodeproj
```

残念ながらライブラリが Swift PM に対応していないので，[Wiki](https://github.com/armadsen/ORSSerialPort/wiki/Installing-ORSSerialPort) に習い，submodule として組み込むようにします。

```bash
$ git submodule add https://github.com/armadsen/ORSSerialPort.git Frameworks/ORSSerialPort
```

submodule を追加したら，次に `(プロジェクト名).xcworkspace` を作成します。
そして，自身のプロジェクトおよび `Frameworks/ORSSerialPort/Framework Project/ORSSerialPort.xcodeproj` を workspace に追加します。以下のようであれば良いでしょう。また，この段階で一度 `ORSSerial` scheme をビルドしておきましょう。

<img width="264" alt="ss 2018-12-06 at 12.25.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/195913/484e5882-fb3e-1f3d-9905-19395c41f952.png">

## プロジェクトファイルの作成 (macOS Appの場合)

macOS アプリの場合は，CocoaPods を利用します。プロジェクトを作成した後，`Podfile` に以下を入力します。

```:Podfile
pod 'ORSSerialPort'
```

Terminal にて以下のコマンドでインストールして完了です。

```
$ pod install
```

## .swift ファイルの編集 (macOS)

接続状態を監視するための `ORSSerialPortDelegate` を継承した `Delegate` クラスを用意しました。「ポート名」と「ボーレート」によって接続し，テキストの送受信ができるようにしました。

```main.swift
import ORSSerial

// ポート名
let portName: String = "(ポート名)"
// ボーレート
let baudRate: NSNumber = 9600
// 通知用デリゲート
let delegate = Delegate()

// ORSSerialPortManager を利用することでポート名が取得できます。
// print(ORSSerialPortManager.shared().availablePorts.map { $0.name })
// ORSSerialPortManager.shared().availablePorts[0].open()

// ポート名を元に初期化する
guard let port = ORSSerialPort(path: portName) else { exit(1) }
// デリゲートの指定
port.delegate = delegate
// ボーレートの指定
port.baudRate = baudRate
// 接続を開始
port.open()

RunLoop.current.run()
```

```Delegate.swift
import ORSSerial

class Delegate: NSObject, ORSSerialPortDelegate {
    /// 改行が入力されるまでの文字列を格納するテキストバッファ
    private var textBuffer = ""

    /// 入力
    private func input(_ serialPort: ORSSerialPort) {
        print("<< ", terminator: "")
        let input = readLine() ?? ""
        guard
            input != "q",
            let data = input.data(using: String.Encoding.utf8)
        else {
            serialPort.close()
            exit(0)
        }

        serialPort.send(data)
    }

    /// 出力
    private func output() {
        print(">> \(textBuffer)")
        textBuffer = ""
    }

    /// ポートからの入力を検知した際に呼ばれる
    func serialPort(_ serialPort: ORSSerialPort, didReceive data: Data) {
        guard let string: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue) as String? else { return }
        guard string.contains("\r") || string.contains("\n") else {
            textBuffer += string
            return
        }

        textBuffer += string
            .replacingOccurrences(of: "\r", with: "")
            .replacingOccurrences(of: "\n", with: "")

        output()
        input(serialPort)
    }

    /// ポート接続がシステムから開放された際に呼ばれる
    func serialPortWasRemovedFromSystem(_ serialPort: ORSSerialPort) {
        print("Serial port \(serialPort) was removed from system")
    }

    /// ポート接続にエラーが生じた際に呼ばれる
    func serialPort(_ serialPort: ORSSerialPort, didEncounterError error: Error) {
        print("Serial port (\(serialPort)) encountered error: \(error)")
    }

    /// ポート接続が完了した際に呼ばれる
    func serialPortWasOpened(_ serialPort: ORSSerialPort) {
        print("Serial port \(serialPort) was opened")
    }
}
```

## .ino ファイルの編集 (Arduino)

Arduino を mac に接続し，以下のコードを upload します。
今回は例として入力ピン番号に応じて 1秒間点灯させるプログラムを書いてみました。

```java:serial.ino
// 全てOutputモードに設定
void pinModeAllOutput() {
  int UnoPinNum = 13;
  for(int i=0; i <= UnoPinNum; i++) {
    pinMode(i, OUTPUT);
  }
}

// String to Int
int convertNum(String str) {
  return str.toInt();
}

// 1秒間点灯
void flash(int num) {
  digitalWrite(num, HIGH);
  delay(1000);
  digitalWrite(num, LOW);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinModeAllOutput();
  Serial.println("Start!");
}

void loop() {
  // put your main code here, to run repeatedly:
  if (Serial.available()) {
    String str = Serial.readStringUntil('\n');
    String strx = str + '\n';
    int len = strx.length() + 1;
    char buf[len];
    strx.toCharArray(buf, len);
    
    int num = convertNum(buf);
    Serial.println(num);
    flash(num);
  }
  
  Serial.flush();
  delay(2);
}
```

## 接続先の選択

Terminal から以下を入力し，有効なポートを検索します。

```
$ ls -l /dev/cu.*
crw-rw-rw-  1 root  wheel   21,   3 12  8 00:00 /dev/cu.usbmodem140001
```

上記の場合 `/dev/cu.usbmodem141001` がポート名となります。
`main.swift` の `var portName: String = "(ポート名)"` に代入します。

また，`ORSSerialPort` にある `ORSSerialPortManager` クラスを利用しすることでも，現在有効なポート一覧を取得することが可能です。
`ORSSerialPortManager.shared().availablePorts` にて取得可能です。

## いざ接続 !

USB 接続を確認したら，Xcode から project をビルドして実行します。
Arduino UNO は 13 番ピンに LED が接続されており，LED を接続せずとも L チカが可能です。

Xcode の デバッグエリアに「13」と送信して Enter を押すと，デバッグエリアに「>> 13」と返り，13 番ピンに隣接した LED が 1 秒間点灯します。

```:log
Serial port usbmodem141001 was opened
>> Start!
<< 13
>> 13
<< q
Serial port usbmodem141001 was removed from system
Program ended with exit code: 0
```

## おわりに

いかがでしたでしょうか。

今回は，Swift で書くシリアル通信についての記事でした。

実行可能なコードは全て掲載しておりますので，GitHub 等のリンクは特にありません。もし利用される場合はライブラリに合わせ MIT としておきます。

ライブラリの [Example projects](https://github.com/armadsen/ORSSerialPort#example-projects) に複数のサンプル例がありますので，こちらも参照してみてください。

IoT 機器を自作するのもとても楽しいですよ。

明日の [VALU Advent Calendar 2018](https://qiita.com/advent-calendar/2018/valu) は，セルオートマトンについてだそうです。どんな内容なのでしょうか。。

## References

* [armadsen/ORSSerialPort](https://github.com/armadsen/ORSSerialPort)
* [https://www.arduino.cc](https://www.arduino.cc)
* [Serial Port Programming in Swift for MacOS](https://www.mac-usb-serial.com/docs/tutorials/serial-port-programming-swift-mac-os-x.html)
* [ORSSerialPort/LICENSE](https://github.com/armadsen/ORSSerialPort/blob/master/LICENSE)