ASP.NET CoreでArduinoとシリアル通信してみる

ASP.NET Core serial communication with Arduino
Arduinoなどのマイコンを汎用性の高いブラウザからリモート操作したいと思ったことはないでしょうか。Google ChromeにはChrome App APIとしてシリアル通信を直接操作できる命令が含まれていましたが、Chromeアプリは今後廃止されるため、将来性は保証されません。

ArduinoならWebUSBを使っての通信もできないことはありませんが、USBシリアル変換アダプターのチップごとにプログラムを組まなければならず、互換品では動かない可能性が高いため、こちらも応用が利きません。

つまるところ、幅広い環境で使おうとなると、ローカルサーバーを経由してのリモート操作が今のところ最善のようです。

今回紹介するのは、ASP.NET Coreで開発したサーバーアプリで通信する方法です。.NET Coreがベースなので、C#でプログラミングできる、Node.js(JavaScript)やPythonよりも高速、Linuxでも動かせると多様なメリットがあります。コンパイルされた実行ファイルには簡易サーバーも含まれているため、追加のフレームワークやライブラリーも基本的に不要です。

まずはプロジェクトファイルを作ります。Visual Studio 2019を立ち上げたら、新規プロジェクトより「ASP.NET Core Webアプリケーション」を作成します。
アプリケーションの種類には「API」を選びます。
プロジェクトが作成されたら、既存のAPI関連ファイルは削除しましょう。
.NET Core向けのシリアルポートライブラリーを導入します。NuGetパッケージマネージャーより「System.IO.Ports」を探して、それをインストールします。
「Startup.cs」ファイルを開き、以下のテキストを追加します。これにより、「wwwroot」フォルダー以下に配置されたHTMLファイルなどを、直に読み込むようになります。
startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    // Add those
    app.UseDefaultFiles();
    app.UseStaticFiles();

    app.UseEndpoints(endpoints => {
        endpoints.MapControllers();
    });
}
ブラウザとのやりとりはXMLHttpRequestなどからのPOSTで行うことにします。プロジェクトフォルダーの「Controller」内に「SerialController.cs」を作成します。

POSTの受信は、対象のメソッドに「HttpPost」属性を追加することで実装できます。URLも指定する場合は「Route」属性も追加します。

以下の例では「http://localhost/serial」へ送られてきたKey-Value形式によるJSONテキストをDictionaryクラスで受け取ります。SerialPort.Read()は同期処理なので、ブロッキングを防ぐため、非同期でJsonオブジェクトを返しています。

ControllerBaseは通信のたびに新しく作られるため、SerialPortクラスはstaticにするなどで共有しておく必要があります。

task.cs
public class JsonObject : Dictionary<string, object>
{
    public string AsString(string key)
    {
        object o;
        if (TryGetValue(key, out o) == false) return string.Empty;
        return o.ToString();
    }
}

[ApiController]
public class TestController : ControllerBase
{
    private static SerialPort sp = null;

    private Task<string> Read()
    {
        return Task.Run(() => {
            string s = sp.ReadTo("\n");
            s = s.Trim('\r', '\n');
            return s;
        });
    }

    [HttpPost]
    [Route("serial/")]
    public async Task<JsonResult> Post(JsonObject v)
    {
        var r = new JsonObject();

        string action = v.AsString("action");
        r["action"] = action;
        r["error"] = false;
        r["value"] = string.Empty;

        try {
            switch (action) {
            case "open":
                Open(v);
                break;
            case "close":
                Close();
                break;
            case "read":
                r["value"] = await Read();
                break;
            case "write":
                Write(v);
                break;
            }
        } catch(Exception ex) {
            r["error"] = true;
            r["value"] = ex.Message;
        }

        return new JsonResult(r);
    }
}
これらを踏まえたサンプルプロジェクトをGitHubで配布しています。このサンプルでは、Arudinoがシリアル通信で改行コードを受け取ると、それまでの文字列をそのまま返してきます(非同期テストのため、返信は一定回数ごと)。受け取った文字列はjQuery/Ajaxによって、ほぼリアルタイムで内容が更新されます。
2019/12/04