【C#】.NET Core 2.0でRaspberryPi 3のGPIOを操作してLチカ！

# 【C#】.NET Core 2.0でRaspberryPi 3のGPIOを操作してLチカ！
ついに来ました、.NET Core 2.0！
https://github.com/dotnet/core/blob/master/roadmap.md

今回は、RaspberryPi × .NET CoreでGpioピンを操作したLチカをしてみて、手順を備忘録として残したいと思います。また、作成したサンプルは、Githubに公開しています。ご参考になれば幸いです。
https://github.com/logikuma1024/PiTest2

## この備忘録のゴール
__.NET Core 2.0を使って、RaspberryPi上からGPIOを操作し、LEDランプをチカチカ点滅させる__！

## 実験環境
* Microsoft VisualStudio2017 Version 15.3
* .NET Core 2.0
* Raspberry Pi3 Model B (OS:Raspbian)

※Raspberry Piの方の環境構築については、私の以前の記事を参照して下さると幸いです。
【C#】RasperryPi 3に.NET Coreを入れてHello World！
http://qiita.com/logikuma/items/de8c987dc2308a96256d

## 手順

### 1. RaspberryPi側の配線
* ブレットボードとLED、抵抗を使って回路作成。
    * 今回、ブレットボード・抵抗・LEDはOSOYOO /スターターキットに付属したものを用いました。手軽に色々試せて楽しいので、スターターキットの購入はおすすめです。<br>[OSOYOO RaspberryPi 初心者演習用パーツセット](https://www.amazon.co.jp/%E5%88%9D%E5%BF%83%E8%80%85%E6%BC%94%E7%BF%92%E7%94%A8%E3%83%91%E3%83%BC%E3%83%84%E3%82%BB%E3%83%83%E3%83%88-%E5%9B%9E%E8%B7%AF%E9%85%8D%E7%B7%9A%E5%9B%B3%E3%81%A8%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B9%E3%82%B1%E3%83%83%E3%83%81%E6%9C%89%E3%82%8A-%E3%82%B9%E3%82%BF%E3%83%BC%E3%82%BF%E3%83%BC%E5%AD%A6%E7%BF%92%E3%82%AD%E3%83%83%E3%83%88-%E3%83%AA%E3%83%AC%E3%83%BC%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E5%88%B6%E5%BE%A1-%E8%B5%A4%E5%A4%96%E7%B7%9A%E3%83%AA%E3%83%A2%E3%82%B3%E3%83%B3%E7%AD%89IoT%E3%82%92%E5%AE%9F%E8%B7%B5%E3%81%99%E3%82%8B%E9%9B%BB%E5%AD%90%E9%83%A8%E5%93%81%E3%82%BB%E3%83%83%E3%83%88/dp/B01M6ZFNSS/ref=sr_1_2?ie=UTF8&qid=1503895093&sr=8-2&keywords=raspberry+pi+starter+osoyoo)
* __Gpio(2)→220Ω抵抗→LED→GND(6)__っていう構成で回路を繋ぎます。<br>回路構築について、詳しくは[こちらのページ](http://deviceplus.jp/hobby/raspberrypi_entry_009/)を参考にさせて頂きました。分かりやすく解説も載ってましたので、特に詰まることなく回路作成できました。
* 抵抗についてはそれぞれ色が似ていますので、わかりづらいかもしれませんが、私は220Ωのものを使用しています。また、LEDには方向があります（アノード・カソード）。間違えないように確認して、配線します。
* 回路を組み終わったら、GPIOを操作し動作可否を確かめてみると良いかもしれません。GPIOについての詳しい説明は本題と外れますので省略しますが、Raspbianであれば__/sys/class/gpio__下に管理されているので、そこのファイル読み書きを行うことで簡単に制御できます。例えば、下記のようなコマンドを発行すると、とりあえずLEDを光らせることが出来るはずです。

```bash
    # GPIO2番をアクティブにする（exportに書き込みを行うとgpio2という仮想ディレクトリが作成され、
    # ピンに対する操作が可能になる。）
    $ echo 2 > /sys/class/gpio/export
    # GPIO2番の入力 / 出力の方向を設定する。今回はLEDを光らせるので、「direction」に「out」を設定する。
    $ echo out > /sys/class/gpio/gpio2/direction
    # GPIO2番の「value」にONである「1」を設定し、光るかテストする。
    $ echo 1 > /sys/class/gpio/gpio2/value
    # GPIO2番の「value」にOFFである「0」を設定し、消えるかテストする。
    $ echo 0 > /sys/class/gpio/gpio2/value
    # 利用し終わったらGPIO2番は非アクティブにする。
    $ echo 2 > /sys/class/gpio/unexport
```

* 次は、アプリケーションの作成をしますが、基本的には今行ったコマンドを.NET Coreを使ってC#でプログラム化するだけです。

### 2. アプリケーションの生成
* VisualStudio2017をインストールし、.NET Core2.0を利用可能にします。  
* バージョンが15.3まで上がっていない場合は、VisualStudioInstallerから更新を選択し、アップデートを行いましょう。
* ファイル > 新規作成 > プロジェクト > コンソールアプリケーション（.NET Core）を選択します。プロジェクト名は適当になんか決めておきます。私は今回「PiTest2」として生成しました。
* プロジェクトが生成されたら、アプリケーションを作成します。
* 実装について、とりあえず実験用に楽にGPIOを触れる簡単なヘルパクラスを作成し、Main側から利用しています。単純にファイルI/Oさえ出来れば問題ないので、Program.cs上にザザっと書いてしまってもいいと思います。


~~~csharp:GpioOperationHelper.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Linq;

namespace PiTest2.Model.Helper
{
    /// <summary>
    /// GPIO操作をヘルプするクラス
    /// ------------------------------
    /// FixMe:規模が大きくなってきたら、ピンの状態管理させてコントローラクラスにしたい。
    /// 現在のままだと、存在しないGPIOであったりデータ方向が適正でない場合に良い制御ができない。
    /// </summary>
    class GpioOperationHelper
    {
        /// <summary>
        /// 指定したGPIOピンをオープンします。
        /// </summary>
        /// <param name="gpio"></param>
        public static void Open(int gpio)
        {
            //gpioがオープン済みでない場合のみ
            if(!IsOpen(gpio))
            {
                File.WriteAllText("/sys/class/gpio/export", gpio.ToString());
            }
        }

        /// <summary>
        /// 指定したGPIOピンをクローズします。
        /// </summary>
        /// <param name="gpio"></param>
        public static void Close(int gpio)
        {
            File.WriteAllText("/sys/class/gpio/unexport", gpio.ToString());
        }

        /// <summary>
        /// 指定したGPIOピンがオープン済みかを確認します。
        /// </summary>
        /// <param name="gpio"></param>
        /// <returns></returns>
        public static bool IsOpen(int gpio)
        {
            return Directory.Exists($"/sys/class/gpio/gpio{gpio}");
        }

        /// <summary>
        /// 指定したGPIOピンの信号方向を取得します。
        /// </summary>
        /// <param name="direction"></param>
        public static string GetDirection(int gpio)
        {
            return File.ReadAllText($"/sys/class/gpio/gpio{gpio}/direction",Encoding.Default);
        }

        /// <summary>
        /// 指定したGPIOピンに信号方向を設定します。
        /// </summary>
        /// <param name="gpio"></param>
        /// <param name="direction"></param>
        public static void SetDirection(int gpio, GpioDirection direction)
        {
            if(direction == GpioDirection.In)
                File.WriteAllText($"/sys/class/gpio/gpio{gpio}/direction", "in");
            else
                File.WriteAllText($"/sys/class/gpio/gpio{gpio}/direction", "out");
        }

        /// <summary>
        /// 指定したGPIOピンのvalueを取得します。
        /// </summary>
        /// <param name="gpio"></param>
        /// <returns></returns>
        public static int GetValue(int gpio)
        {
            return Convert.ToInt32(File.ReadAllText($"/sys/class/gpio/gpio{gpio}/value", Encoding.Default));
        }

        /// <summary>
        /// 指定したGPIOピンのvalueを設定します。
        /// </summary>
        /// <param name="gpio"></param>
        /// <param name="value"></param>
        public static void SetValue(int gpio, int value)
        {
            File.WriteAllText($"/sys/class/gpio/gpio{gpio}/value", value.ToString());
        }
    }

    /// <summary>
    /// GPIOの信号方向
    /// </summary>
    public enum GpioDirection
    {
        In,Out
    }
}
~~~

~~~csharp:Program.cs
using PiTest2.Model.Helper;
using System;
using System.Threading.Tasks;
using System.Linq;

namespace PiTest2
{
    class Program
    {
        /// <summary>
        /// Application Entry
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //2番ピンが開いていない場合はOpenします。
            GpioOperationHelper.Open(2);

            //信号を出力方向に設定します。。
            GpioOperationHelper.SetDirection(2, GpioDirection.Out);

            //Lチカタスクを作成します。
            var task = Task.Run(async () =>
            {
                //とりあえず10回ON/OFF
                foreach(var i in Enumerable.Range(1,10))
                {
                    //トグルさせてみる。
                    if (GpioOperationHelper.GetValue(2) == 1)
                    {
                        Console.WriteLine("Off");
                        GpioOperationHelper.SetValue(2, 0);
                    }
                    else
                    {
                        Console.WriteLine("On");
                        GpioOperationHelper.SetValue(2, 1);
                    }
                    //0.5秒ごとにチカチカ！
                    await Task.Delay(500);
                }
            });

            //タスクを実行し、完了を待機します。
            task.Wait();
        }
    }
}
~~~

* Linuxでのechoに相当する構文として__WriteAllText__、catに相当する構文として__ReadAllText__を使用しました。アプリケーションを作成したら、次はlinux-arm向けにpublishを行います。

### 3. linux-arm用ビルドの実行
* アプリケーションを作成したら、linux-arm用にビルドする。プロジェクトルートを開いて下記コマンドを入力。

~~~
   > dotnet publish -r linux-arm
~~~

* プロジェクトルート下の__/bin/Debug/netcoreapp2.0/linux-arm/publish__にarm上で動作するファイル一式が入っているので、「__publishフォルダの内容を全て__」Raspberry Piに転送。publishフォルダ毎転送した方が色々楽かもしれませんね。
* 特にcsprojとかを弄る必要もなく、コマンド一発で生成されるので、非常に楽です。

### 4. 実行
* 転送されたフォルダに下記コマンドを発行して実行権限を追加し、ディレクトリに移動して実行する。

~~~bash
    # 権限を実行可能に変更
    $ chmod 755 -R publish/
    # ディレクトリを移動
    $ cd publish
    # アプリケーションを実行。起動するにはアプリケーション名をCallして下さい。
    $ ./PiTest2
~~~

* 実行結果はこんな感じになりました。見づらいかもしれませんが、黄色のLEDがチカチカ動作しているのを確認出来ました！

![test.gif](https://qiita-image-store.s3.amazonaws.com/0/131683/e7434bc8-b635-8683-f03e-cb4d142febf7.gif)

## 感想
* GPIOピン操作について、もっと良い方法を探したい。
    * 今回の方法はあまり良いとは言えない。状態管理も出来ていないし、起こり得る例外もキャッチし切れていない。.NET CoreにおけるGPIOピンの操作について、より良い操作方法・操作ライブラリをご存知の方、教えて頂けると幸いです。
* 上手くできなかった部分があるとはいえ、とにかく現実世界のものが動くってのは面白いなと感じました。ピン操作をPythonで試したことはありましたが、今回C#を使って操作できたのはとっても嬉しかったです。ほんと、.NET Core楽しいです。
* monoやIoT Coreでの実装例は幾つかありましたが、.NET CoreでのLチカ実装例はあまりなく、特にGPIO操作の部分は結構苦戦しました。echoやcatはそのままのコマンド叩かなくても、ファイル操作で代替出来る事にもっと早く気付けていれば、あまりハマらず出来たなぁと反省してます。
* アナログ入力 / 出力にも挑戦してみたいなぁ。まだ実装イメージが付きませんが、センサ情報とかを入力して解析するようなものを作ってみたいです。

## 参考
.NET Core Roadmap
https://github.com/dotnet/core/blob/master/roadmap.md

JEREMY LINDSAY
https://jeremylindsayni.wordpress.com/2017/05/01/controlling-gpio-pins-using-a-net-core-2-webapi-on-a-raspberry-pi-using-windows-10-or-ubuntu/

IT女子のラズベリーパイ入門奮闘記
http://deviceplus.jp/hobby/raspberrypi_entry_009/