RaspberryのDocker上でGPIOをWiringPiで使う

# はじめに
RaspberryもGPIO（というかハードウェア全般）も初心者がDockerのコンテナでタクトスイッチのオンオフをConsoleに出力するだけのお話

# 環境
・Raspberry
・.NetCore 3.1(開発側）
・WiringPi
・実行はDocker（インストールしておいてください）

ソースは[こちら](https://github.com/kyamamoto03/TactInvoke.git)

# ハマりどころ
## その１
### 現象
.NetCoreのruntimeコンテナでGPIOのアプリを起動したいので、マイクロソフトのイメージmcr.microsoft.com/dotnet/core/runtime:3.1-bionic-arm32v7を使ったところapt-getでWiringPiがインストールできませんでした（知っている方教えてください）
### 対処法
DockerfileにてラインタイムイメージにコンパイラをインストールしWiringPiをビルドすることで解決

## その２
### 現象
RaspberryでGPIOを扱う場合に登場する/dev/gpioがコンテナ上ではアクセスができなくて困った。
### 対処法
Docerのコマンドに--deviceといパラメータがこれを使えばいいことが判明
だけど、今度はパーミッションエラーが発生
--privilegedコマンドを使えば回避できることが判明(特権モードで実行なので慎重に)

# 手順
## 開発側
1. TackInvokerというフォルダを作成
2. TackInvokerフォルダに移動
3. dotnet new console
4. Program.csを下記のファイルで置き換える
5. TackInvokerフォルダの上に下記のDockerfileを配置
6. docker build -t {自身のDockerHubID}/Tackinvoker:latest .
7. docker push {自身のDockerHubID}/Tackinvoker:latest

## Raspberry側
1. docker run -it --rm --device /dev/gpiomem --privileged {自身のDockerHubID}/Tackinvoker:latest

# 素材達
## Program.cs
```C#
using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace TackInvoker
{
    class Program
    {

        public const int INPUT = 0;
        public const int OUTPUT = 1;

        public const int INT_EDGE_FALLING = 1;
        public const int INT_EDGE_RISING = 2;

        public const int Tack_PIN = 17;

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

        [DllImport("wiringPi")]
        extern static void pinMode(int pin, int mode);

        [DllImport("wiringPi")]
        extern static void digitalWrite(int pin, int mode);


        [DllImport("wiringPi")]
        extern static int wiringPiISR(int pin, int edgeType, CallbackFunc func);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void CallbackFunc();

        static void Main(string[] args)
        {
            int ret = 0;

            // wiringPiのセットアップ
            wiringPiSetupGpio();

            // GPIO をINPUTに設定する.
            pinMode(Tack_PIN, INPUT);

            CallbackFunc callBackFunc = delegate () {
                Console.WriteLine("CallbackFunc is called !");

            };

            // GPIO がONになったらコールバック関数を呼ぶ.
            ret = wiringPiISR(Tack_PIN, INT_EDGE_RISING, callBackFunc);


            // 無限に待機する.
            Thread.Sleep(Int32.MaxValue);

        }
    }
}
```
## Dockerfile
```dockerfile

# ビルドイメージ
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build

WORKDIR /app

# copy csproj and restore as distinct layers
COPY *.sln .

COPY TackInvoker/*.csproj ./  TackInvoker/

# copy everything else and build app

COPY TackInvoker ./TackInvoker/
RUN dotnet restore TackInvoker

WORKDIR /app/TackInvoker
RUN dotnet publish -c Release -o out

# ランタイムイメージ
FROM mcr.microsoft.com/dotnet/core/runtime:3.1-bionic-arm32v7

# WiringPiをインストールする
RUN apt-get update
RUN apt-get install -y libi2c-dev
RUN apt-get install -y git-core
RUN apt-get install -y sudo
RUN apt-get install -y make
RUN apt-get install -y gcc
RUN git clone https://github.com/WiringPi/WiringPi.git
WORKDIR /WiringPi
RUN ./build

WORKDIR /app

COPY --from=build /app/TackInvoker/out ./

ENTRYPOINT ["dotnet", "TackInvoker.dll"]
```
##