Raspberry Pi アプリのリモート開発（.NET Lチカ編）

[Lチカ編](https://qiita.com/b-wind/items/a805c34dcd7b3cafb3b8)では、Visual Studio によるリモート開発環境から GPIO へ接続したLEDを点滅させるプログラムを作成しました。
今回は「.NET Lチカ編」として、同じ機能を持つプログラムを .NET Core 環境で動く C# プログラムとして作成します。

#### ■ 免責事項
本稿のすべての内容について、どのような被害・損害が生じても利用者が一切の責任を追うものとし、本稿の投稿者や記載内容の著作者は一切の責任を負わないものとします。



# Raspbian OS と .NET Core
アプリケーションプロジェクトの作成やビルドを行うには .NET Core SDK が必要です。
この SDK は64bitモードでなければ導入できませんが、現行の Raspberry Pi (Raspbian OS) は 32bit モードで動作しているため、同 SDK を利用することができません。

よって、64bit モードで動作する Windows や Mac OSX などの別環境が必要となります。
<font color ="red">
**【残念なお知らせ】**
2018年6月現在、ARMプロセッサ向けの .NET Core 2.0 環境では、Visual Studio からのリモートデバッグ機能は正常に動作しません。
リモートデバッグ不可となれば開発効率の大幅な低下を招きます。
.NET Core 2.1 では状況が改善される可能性もありますが、現時点では Raspberry Pi 向けの .NET Core アプリケーションの作成には大きなコストが必要と言わざるを得ない状況です。
</font>

# 回路図・ブレッドボード配線図
同じ回路を利用するので、詳細は [Lチカ編](https://qiita.com/b-wind/items/a805c34dcd7b3cafb3b8) を参照してください。

# 新規プロジェクトの作成
.NET Core プラットフォームで動く「コンソール アプリ」プロジェクトを作成します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/90456/c7f5ec85-9e7a-75e6-a559-91f5992512e5.png)

# プログラムの修正
自動生成された「Program.cs」は「Hello World!」と表示するだけの単純なプログラムです。
流用できる内容ではないので、Lチカ用に大幅に作り直します。

## 修正の要点

**1. wiringPi を利用する DllImport 宣言を用意する**
/usr/include 下の「wiringPi.h」を参考に、GPIO 操作用の wiringPi ライブラリを呼び出すための DllImport 宣言を作成します。

```C#
using System.Runtime.InteropServices;

[DllImport("libwiringPi")]
static extern int wiringPiSetupGpio();

[DllImport("libwiringPi")]
static extern int pinMode(int pin, PINMODE mode);

[DllImport("libwiringPi")]
static extern int digitalWrite(int pin, int value);
```

[参考サイト]
　[Microsoft: ネイティブ相互運用性](https://docs.microsoft.com/ja-jp/dotnet/standard/native-interop)

**2. wiringPi API 用の定数値を用意する**
こちらも /usr/include 下の「wiringPi.h」を参考に、必要な定数（enum/const）宣言を作成します。

```C#
/// <summary>
/// Pin modes
/// </summary>
enum PINMODE
{
    INPUT,
    OUTPUT,
    PWM_OUTPUT,
    GPIO_CLOCK,
    SOFT_PWM_OUTPUT,
    SOFT_TONE_OUTPUT,
    PWM_TONE_OUTPUT
}

private static readonly int LOW = 0;
private static readonly int HIGH = 1;
```

**3. Main関数を Lチカ編 と同様の構造にする**
C/C++ と C＃ は言語仕様が近い上に、呼び出す wiringPi API の名前や引数にも違いはありません。
[Lチカ編](https://qiita.com/b-wind/items/a805c34dcd7b3cafb3b8) のプログラムを、ほぼそのまま流用することが可能です。

```C#
        static void Main(string[] args)
        {
            wiringPiSetupGpio();

            pinMode(LED, PINMODE.OUTPUT);

            for (int lp = 0; lp < 10; lp++)
            {
                digitalWrite(LED, HIGH);  //オン
                Thread.Sleep(500); //ミリ秒
                digitalWrite(LED, LOW);   //オフ
                Thread.Sleep(500);
            }
            return;
        }
```

## 修正後の Program.cs

下記が修正後の「Program.cs」となります。

```C#
using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace DotNetBlink1
{
    class Program
    {
        [DllImport("libwiringPi")]
        static extern int wiringPiSetupGpio();

        [DllImport("libwiringPi")]
        static extern int pinMode(int pin, PINMODE mode);

        [DllImport("libwiringPi")]
        static extern int digitalWrite(int pin, int value);

        /// <summary>
        /// Pin modes
        /// </summary>
        enum PINMODE
        {
            INPUT,
            OUTPUT,
            PWM_OUTPUT,
            GPIO_CLOCK,
            SOFT_PWM_OUTPUT,
            SOFT_TONE_OUTPUT,
            PWM_TONE_OUTPUT
        }

        private static readonly int LOW = 0;
        private static readonly int HIGH = 1;

        /// <summary>
        /// LED ピン - wiringPi ピン 0 は BCM_GPIO 23 です。
        /// wiringPiSetupSys で初期化する場合は、BCM 番号付けを使用する必要があります
        /// 別のピン番号を選択する場合は、BCM 番号付けを使用してください。
        /// </summary>
        private static readonly int LED = 23;

        static void Main(string[] args)
        {
            wiringPiSetupGpio();

            pinMode(LED, PINMODE.OUTPUT);

            for (int lp = 0; lp < 10; lp++)
            {
                digitalWrite(LED, HIGH);  //オン
                Thread.Sleep(500); //ミリ秒
                digitalWrite(LED, LOW);   //オフ
                Thread.Sleep(500);
            }
            return;
        }
    }
}
```

# Windows環境でのビルドと配置
Windows 上で Visual Studio を使用している場合、「発行」機能を利用すれば Raspberry Pi 向けバイナリファイルを簡単に作成することができます。
また、必要なファイルが「publish」フォルダへすべて出力されるため、Raspberry Pi への配布作業が非常に簡単という利点もあります。

出力先フォルダを変更できるので、ビルド後に手動でファイルコピーする手間も省けて良いことづくしのようにも見えますが、認証が必要な共有フォルダを指定するとアクセスエラーが発生するという問題も抱えています。

開発作業に直接関係のない無用なトラブルを避けるため、ここでは「ビルド後の実行ファイルを認証が必要な共有フォルダへ格納する」ためのバッチファイルを作成し、それを Visual Studio の外部ツールとして登録・実行することにします。

## バッチファイルの作成
バッチファイルには以下の機能を組み込みます。

* Linux(ARM)向けに、.NET Core アプリケーションをビルドする。
* 認証が必要な Windows (Samba) 共有フォルダへ接続する。
* 共有フォルダ下の特定フォルダへ、実行用のすべてのファイルをコピーする。
* コピー後、共有フォルダへの接続を解除する。

以下が、これらの機能を組み込んだバッチファイルです。
（set PASSWORD=******** には共有フォルダのパスワードを指定してください）

```
@echo off

rem 第１引数： ビルド構成　（"Debug" or "Release"）
rem 第２引数： ターゲット名（例. "DotNetBlink1"）
rem 第３引数： Visual Studio が認識するバイナリファイルの出力パス

set PROJECTS_PATH=\\raspberrypi\pi_projects
set TARGET_NAME=%2
set USERNAME=pi
set PASSWORD=********

rem ビルド構成（Debug or Release)に合致するビルドを実施。
dotnet publish -c %1 -r linux-arm

rem ビルド構成（Debug or Release)に合致するバイナリファイルの格納パス文字列を生成する。
rem 　※ 第３引数のフォルダパス文字列の「Debug」あるいは「Release」という単語を、
rem 　　 第１引数 に応じて適切な方へ置換する。
set binPathStr=%3
if "%~1"=="Release" (
  set cnvBinPathStr=%binPathStr:Debug=Release%
) ELSE (
  set cnvBinPathStr=%binPathStr:Release=Debug%
)

set SRC_PATH=%cnvBinPathStr%\linux-arm\publish
set DST_PATH=%PROJECTS_PATH%\%PROJECT_NAME%

rem ネットワーク共有フォルダへの接続
net use %PROJECTS_PATH% %PASSWORD% /user:%USERNAME% /Y

rem バイナリファイル群のコピー
del /F /Q %DST_PATH%\*.*
xcopy %SRC_PATH%\*.* %DST_PATH%\. /E /Y

rem 共有フォルダからの切断
net use %PROJECTS_PATH% /delete
```

上記のバッチファイルをプロジェクトフォルダの直下に格納した上で、Visual Studio の外部ツールとして登録します。
ビルド構成はバッチファイルの引数で切り替えるので、必要に応じて Debug用 や Release用 の外部ツール登録を実施してください。

下表は、Release 用として外部ツールを登録する場合の設定内容です。
Debug 用として設定する際は、最初の引数を "Debug" に変更してください。

|項目|設定内容|
|:--|:-----|
|タイトル|任意の名称 <br/>例.　.NET Core ビルド - Raspberry Pi (Release)|
|コマンド|バッチファイルの名称 <br/>例.　DotNetCorePublish.bat|
|引数|<code>"Release" $(TargetName) $(BinDir)</code>|
|初期ディレクトリ|<code>$(ProjectDir)</code>|
|出力ウィンドウを使用|チェックなし|
|起動時に引数を入力|チェックなし|
|終了時にウィンドウを閉じる|チェックなし|

## 発行先フォルダの作成
「[準備編：共有フォルダの作成](https://qiita.com/b-wind/items/06a9cce8fc9d1ab47047#%E5%85%B1%E6%9C%89%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%AE%E4%BD%9C%E6%88%90)」を参照し、共有フォルダ「pi_projects」を作成してください。

Visual Studio 上のプロジェクト名が発行先のフォルダ名となるので、「pi_projects」の直下に同じ名前のフォルダを作成してください。
　　発行先フォルダ名の例：　DotNetBlink1

## ビルドの実行
作成した外部ツールを選択し、ビルド および Raspberry Pi への配置を実行します。
[ツール] メニューに登録された外部ツールをクリックしてください。

[Release/Debug 両方の外部ツールを登録したメニューの例]
![image.png](https://qiita-image-store.s3.amazonaws.com/0/90456/66438284-5909-c813-5882-0ca02bd92f6f.png)


# Mac OSX 環境でのビルドと配置
Mac版の Visual Studio には  Windows版 Visual Studio の「発行」に相当する機能が無く、ビルド＆配置を行うには Windows 環境でのバッチファイルに相当するシェルスクリプトが不可欠です。
ここでは、「ビルド後の実行ファイルを共有フォルダへ格納する」ためのシェルスクリプトを作成し、それを Visual Studio の外部ツールとして登録・実行することにします。

## ユーザープロジェクトフォルダの自動マウントの設定
Windows 環境でのバッチファイルには共有フォルダへの接続処理が組み込みました。
しかし、Mac OSX 環境でのシェルスクリプトに同様の処理は必要ありません。
OS が標準で提供する「automount」機能を利用し、共有フォルダへのアクセスを契機に自動的に接続するように設定します。

　[参考サイト]
　　[OSXでSambaの共有ディスクを起動時にマウントしておく](https://qiita.com/qoAop/items/887a10025673da33096f)

共有フォルダへの接続は、対象のフォルダへのアクセス（正確には、それをマウントしたマウントポイントへのアクセス）時に実施され、一定時間アクセスがなければ自動的に切断されます。

**1. マウントポイントの指定**
マウント先のフォルダと設定ファイル名を「/etc/auto_master」に追記します。
　[書式]
　　{マウント先} {設定ファイル名}

```bash
$ sudo vi /etc/auto_master
```

```
#
# Automounter master map
#
+auto_master            # Use directory service
/net                    -hosts          -nobrowse,hidefromfinder,nosuid
/home                   auto_home       -nobrowse,hidefromfinder
/Network/Servers        -fstab
/-                      -static
/mnt                    auto_smb
```
ファイルの末尾にマウント先の情報を追記します。
上記の例では、ルート直下に作成した「mnt」フォルダを、マウント先フォルダとしています。

**2. マウント情報の設定**
定義ファイル（ここでは「auto_smb」）を新たに作成し、マウント情報を設定します。
　[書式]
　　{マウント名}　-fstype=snbfs,soft ://{ユーザ名}:{パスワード}@{Raspberry Piのホスト名 または IPアドレス}/{共有名}

```bash
$ sudo vi /etc/auto_smb
```

```
pi_projects -fstype=smbfs,soft ://pi:*******@raspberrypi.local/pi_projects
```
このファイルには複数のマウント情報を記述可能です。
上記の例では、認証付き共有フォルダへ接続するためのマウント情報を設定しています。
（******* には、共有フォルダへアクセスする際のパスワードを指定します）

**3. マウント情報の反映**
ルート直下に「mnt」フォルダを作成してから、「automount」コマンドを実行してマウント情報を即時反映します。

```bash
$ sudo mkdir /mnt
$ sudo automount -vc
automount: /net updated
automount: /home updated
automount: /mnt mounted
automount: no unmounts
```

**4. 共有フォルダへの接続確認**
マウント先のフォルダへアクセスし、共有フォルダへアクセス可能であることを確認します。
環境にも依存しますが、初回アクセス時はフォルダの内容が表示されるまで数秒以上かかる場合があります。

```bash
$ ls /mnt/pi_projects
Blink1         DotNetBlink1
```

なお、共有フォルダを公開している Raspberry Pi がシャットダウンしている場合、下記のように一見異なる事象が発生したかのようなメッセージが表示されるので注意が必要です。

```bash
$ ls /mnt/pi_projects
ls: : Too many users
```

## シェルスクリプトの作成
シェルスクリプトには以下の機能を組み込みます。

* Linux(ARM)向けに、.NET Core アプリケーションをビルドする。
* 共有フォルダ下の特定フォルダへ、実行用のすべてのファイルをコピーする。

以下が、これらの機能を組み込んだシェルスクリプトです。

```bash
#!/bin/bash

# 第１引数： ビルド構成　（"Debug" or "Release"）
# 第２引数： Visual Studio が認識するバイナリファイルの出力パス

# ビルド構成（Debug or Release)に合致するビルドを実施。
dotnet publish -c $1 -r linux-arm
echo "ビルド終了"
echo

SRC_PATH="$2/linux-arm/publish/"
DST_PATH="/mnt/pi_projects/DotNetBlink2/"

echo "古いファイルをフォルダごと削除します。"
rm -rf ${DST_PATH}
echo "削除終了。"
echo

# バイナリファイル群のコピー
echo "新しいバイナリファイルのコピーを開始します。"
cp -rf ${SRC_PATH} ${DST_PATH}
echo "コピー終了。"
```

上記のシェルスクリプトをプロジェクトフォルダの直下に格納した上で、Visual Studio の外部ツールとして登録します。
ビルド構成は Visual Studio で選択中の内容（Release/Debug）によって切り替わるので、Debug用 や Release用 という形で外部ツールを登録する必要はありません。

下表は、外部ツールを登録する際の設定内容です。

|項目|設定内容|
|:--|:-----|
|タイトル|任意の名称 <br/>例.　.NET Core ビルド - Raspberry Pi|
|コマンド|シェルスクリプトの名称 <br/>例.　<code>${ProjectDir}/DotNetCorePublish.sh</code>|
|引数|<code>${ProjectConfigName} ${TargetDir}</code>|
|初期ディレクトリ|<code>$(ProjectDir)</code>|
|出力ウィンドウを使用|チェックあり|
|起動時に引数を入力|チェックなし|
|終了時にウィンドウを閉じる|チェックなし|

## 発行先フォルダの作成

「[準備編：共有フォルダの作成](https://qiita.com/b-wind/items/06a9cce8fc9d1ab47047#%E5%85%B1%E6%9C%89%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%AE%E4%BD%9C%E6%88%90)」を参照し、共有フォルダ「pi_projects」を作成してください。

Visual Studio でのプロジェクト名が発行先のフォルダ名となるので、「pi_projects」の直下に同じ名前のフォルダを作成してください。
　　発行先フォルダ名の例：　DotNetBlink2

## ビルドの実行
作成した外部ツールを選択し、ビルド および Raspberry Pi への配置を実行します。
[ツール] メニューに登録された外部ツールをクリックしてください。

[外部ツールを登録したメニューの例]
![image.png](https://qiita-image-store.s3.amazonaws.com/0/90456/b8870e1b-8b72-9b83-97fb-d624ece5612b.png)

# プログラムの実行
Windows/Mac OSX のどちらでビルドしても実行方法に違いはありません。
実行権(X)を付けてから、通常のプログラムやシェルスクリプトのように起動してください。

```bash
$ chmod +x DotNetBlink1
$ ls -l DotNetBlink1*
-rwxr-xr-x 1 pi pi 67684  6月 19 23:15 DotNetBlink1
-rwxr--r-- 1 pi pi 20339  6月 23 05:36 DotNetBlink1.deps.json
-rwxr--r-- 1 pi pi  4608  6月 23 05:03 DotNetBlink1.dll
-rwxr--r-- 1 pi pi   656  6月 23 05:03 DotNetBlink1.pdb
-rwxr--r-- 1 pi pi    28  6月 23 05:36 DotNetBlink1.runtimeconfig.json
$ ./DotNetBlink1
```

# 考察
Windows/Mac OSX 共に、Lチカのプログラムソースに違いはありません。
言語も C#/.NET で統一できるため、「環境・機器構成の統一」や「一定水準のスキルを持つ作業要員の確保」が容易くなるなど、業務面での大きなメリットが期待できます。

その反面、現時点ではリモートデバッグをサポートしていないという重大なデメリットも存在します。
実業務で広く受け入れられるまで、あと一歩というところです。

次版.NET Core 2.1 に期待しましょう。