Help us understand the problem. What is going on with this article?

IoT Edge の Azure Functions を試して見た (2)

More than 1 year has passed since last update.

前回 IoT Edge の Azure Functions を試して見た (1) でモジュールのインストールまでを実施しました。次はエッジの上で動作する Azure Functions を開発して、その上で、アラートを Azure Functions と、Service Bus を使って検出するというところまでやって見ました。

Edge で動作する Azure Functions を作成する

  • Deploy Azure Function as an IoT Edge module - preview を参考に、Edge で動作する Azure Functions を作ります。前提条件として下記のことが揃っていること。

  • Visual Studio Code

  • C# for Visual Studio Code extension

  • Azure IoT Edge extension for Visual Studio Code

  • Docker

  • .NET Core 2.0 SDK

ちなみに、Azure IoT Edge extension for Visual Studio Code はいい感じでログをみれたり、 Docker をデプロイできたりします。

さて始めましょう。

Azure Container Registry のセットアップ

DokcerHub でもいいのですが、折角なのでプライベートリポジトリを試して見ます。Azure Container Registry のセットアップ自体はどこにでもあるので書きませんが、作成して、ホスト名、ユーザ名、パスワードを取得します。Edge の方で、プライベートリポジトリにあるバイナリを使いたい時は必要になります。

Azure Function 作成

テンプレートを作成する。

次のコマンドで、IoTEdge 用の Function をインストールします。

$ dotnet new -i Microsoft.Azure.IoT.Edge.Function

名前はなんでもいいのですが、FilterFunction という名前で作って見ましょう。

$ dotnet new aziotedgefunction -n FilterFunction

FilterFunction のディレクトリができて、その中に Edge 用の Azure Functions のフルセットが出来上がります。

Screen Shot 2017-11-29 at 10.46.13 PM.png

Azure Function のコードを書く

残念ながらインテリセンスはありませんが、コードを書いて見ましょう。

run.csx

#r "Microsoft.Azure.Devices.Client"
#r "Newtonsoft.Json"

using System.IO;
using Microsoft.Azure.Devices.Client;
using Newtonsoft.Json;

class MessageBody
{
    public Machine machine {get;set;}
    public Ambient ambient {get; set;}
    public string timeCreated {get; set;}
}
class Machine
{
   public double temperature {get; set;}
   public double pressure {get; set;}         
}
class Ambient
{
   public double temperature {get; set;}
   public int humidity {get; set;}         
}

public static async Task Run(Message messageReceived, IAsyncCollector<Message> output, TraceWriter log)
{
const int temperatureThreshold = 25;

byte[] messageBytes = messageReceived.GetBytes();
var messageString = System.Text.Encoding.UTF8.GetString(messageBytes);

if (!string.IsNullOrEmpty(messageString))
{
    // Get the body of the message and deserialize it
    var messageBody = JsonConvert.DeserializeObject<MessageBody>(messageString);

    if (messageBody != null && messageBody.machine.temperature > temperatureThreshold)
    {
        // We will send the message to the output as the temperature value is greater than the threashold
        var filteredMessage = new Message(messageBytes);
        // We need to copy the properties of the original message into the new Message object
        foreach (KeyValuePair<string, string> prop in messageReceived.Properties)
        {
            filteredMessage.Properties.Add(prop.Key, prop.Value);
        }
        // We are adding a new property to the message to indicate it is an alert
        filteredMessage.Properties.Add("MessageType", "Alert");
        // Send the message        
        await output.AddAsync(filteredMessage);
        log.Info("Received and transferred a message with temperature above the threshold");
    }
}

}

function.json

{
    "bindings": [
        {
            "type": "edgeHubTrigger",
            "name": "messageReceived",
            "InputName": "input1",
            "direction": "in"
        },
        {
            "type": "edgeHub",
            "name": "output",
            "outputName": "alertOutput",
            "batchSize": 10,
            "direction": "inout"
        }
    ]
}

ポイントは多くありませんがここです。Edge の Function は、Edgeからメッセージを受け取りって、最終的に、エッッジに吐きます。下記のロジックで、Properties に対して MessageType を足していますが、これは、メッセージのボディではなく、ヘッダとかメタ情報に近いものです。残りのファンクションの設定は、IoT Hub 側で行います。ここでは、閾値以上の気温の場合、アラートを出しています。この情報は、IoTHub に送信されるメッセージボディには現れません。このアラートは、IoTEdgeが管理します。

        filteredMessage.Properties.Add("MessageType", "Alert");
        // Send the message        
        await output.AddAsync(filteredMessage);

IoT Edge のビルドとプッシュ

IoT Edge Azure Functions は、Docker にパックして配布されますが、このビルドと、プッシュが Visual Stdio Code のエクステンションでサポートされいますので、注意点含めて流して見ます。

docker image の作成

Dockerfile が複数ありますが自分のEdge端末の種類に合わせてDockerfileを選択して右クリックします。そして、Build IoT Edge module Docker image を選びます。

Screen Shot 2017-11-29 at 11.05.15 PM.png

次がポイントですが、次の画面で、Select folder as EXE_DIR ボタンを押します。ポイントはこの指定しているディレクトリが、host.json のあるディレクトリになっていることです。このディレクトリにあるファイルの内容が、docker image に取り込まれます。

Screen Shot 2017-11-29 at 11.05.32 PM.png

次に、この画面になりますので、ここに、Azure Container Registry に格納するイメージ名を書きます。基本的に、{Azure Container Registry host name}/{function name}:{version} になっています。サンプルだと、latest を指定するように書いていますが、バージョン番号にすることをお勧めします。(あとでアップデートやロールバックが楽なので)

Screen Shot 2017-11-29 at 11.06.17 PM.png

エンターを押すと、あとは、下記のコマンドが、VSCode の 内臓ターミナルで実行されます。ディレクトリが同じであれば、このコマンドを直接叩いても構いません。

$ docker build -f "/Users/ushio/Codes/IoTEdge/functions/FilterFunction/Docker/linux-x64/Dockerfile" --build-arg EXE_DIR="." -t "iotregistry.azurecr.io/filterfunction:1.0.6" "/Users/ushio/Codes/IoTEdge/functions/FilterFunction"
Sending build context to Docker daemon  17.41kB
Step 1/4 : FROM microsoft/azureiotedge-functions-binding:1.0-preview
 ---> d75b891c5691
Step 2/4 : ENV AzureWebJobsScriptRoot /home/site/wwwroot
 ---> Using cache
 ---> 04456026fc09
Step 3/4 : ARG EXE_DIR=.
 ---> Using cache
 ---> 3492a0c63b1f
Step 4/4 : COPY $EXE_DIR/ /home/site/wwwroot
 ---> aad55f1f1e93
Removing intermediate container c6f361bad218

Docker images のプッシュ

Command + Shift + P を選択して次の画面にします。Edge: Push IoT Edge module Docker image を選びます。

Screen Shot 2017-11-29 at 11.16.01 PM.png

イメージの作成と同じイメージ名を設定してエンターすると、リポジトリにイメージがプッシュされます。

Screen Shot 2017-11-29 at 11.17.37 PM.png

Azure Functions を実行したらエラーになる場合は?

下記のエラーが出たら、先ほどの、EXE_DIR が間違っている、つまりhost.json のあるディレクトリじゃないところでDocker のビルドをしているため、Azure Functions のファイルが十分コンテナの中に格納されていない感じですので、そこを疑ってください。

No job functions found. Try making your job classes and methods public. If you're using binding extensions (e.g. ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. config.UseServiceBus(), config.UseTimers(), etc.).

IoT Hub から Functions をデプロイする

さて、作成したFunctions は IoT Hub からデプロイします。
デバイスを選択します。

Screen Shot 2017-11-29 at 11.41.42 PM.png

Set Modules でモジュールをデプロイしたり、変更したりします。

Screen Shot 2017-11-29 at 11.42.00 PM.png

私の環境では、すでに、filterfunction をデプロイしていますが、皆様の場合ないはず。Add IoT Edge Module を選択します。

Screen Shot 2017-11-29 at 11.42.17 PM.png

名前をつけます。fileterfunction のような感じでつけます。それと先ほど作成した docker image を指定してセーブ

Screen Shot 2017-11-29 at 11.43.04 PM.png

次の画面で、モジュールのルーティングを設定します。tempSensor という前回デプロイしたダミーデータを生成するモジュールのアウトプットを、filterfunction に入れて、filterfunctionのアウトプットを、Upstream つまり、IoTHub に送るという意味合いになっています。

Screen Shot 2017-11-29 at 11.43.23 PM.png

これでデプロイしてデバイスの画面に、戻ってインストールが完了してたらOKです。

Screen Shot 2017-11-29 at 11.50.42 PM.png

これで、データがやってきているはずですがdocker logs で直接コンテナのログを見たり、Azure IoT Hub 側に来たデータを見て見てもどこにも、MessageType: Alert を指定したものが見当たりません。なぜかというと、それは、メッセージの Body に含まれる情報ではなく、IoTEdge に送られたメタ情報だからです。じゃあ、それをどうやって見ましょうか? CSharp の DLL のモジュールを作ったり、VSCode のIoTEdge extensionで見ることはできますが、どうしたらいいでしょうか?

Alert を取得するために、IoT Edge のルートと Service Bus + Functions を設定する。

最初に単独で、Service Bus を作っておきましょう。Queue も作っておきます。Storage Queue との違いは、持てるデータが256K に対して、512k まで持てるとかの違い(だったと思います。)

Screen Shot 2017-11-29 at 11.54.44 PM.png

次に、IoT Hub に戻って、Endpoint を指定します。

Screen Shot 2017-11-29 at 11.57.26 PM.png

Route を指定します。

Screen Shot 2017-11-29 at 11.57.47 PM.png

こうなるためには、 Add で次の項目を入力します。

ポイントは、Query string のところで、Azure Functions ないで指定した MessageTypeを指定してフィルタをかけます。こうすると、メッセージの中で、MessageTypeAlert のもののみ指定したEndpoint(つまり先ほどの Service Bus)にメッセージが Queue に格納されて投げられます。

Screen Shot 2017-11-29 at 11.57.56 PM.png

Azure Functions で アラートの上がったメッセージを取得する

最終確認で、先ほどの、Service Bus に投げられたQueue を Service Bus Queue トリガーの Azure Functions を作成して、検出します。

run.csx

using System;
using System.Threading.Tasks;

public static void Run(string myQueueItem, TraceWriter log)
{
    log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
}

function.json

{
  "bindings": [
    {
      "name": "myQueueItem",
      "type": "serviceBusTrigger",
      "direction": "in",
      "queueName": "temperature",
      "connection": "iotedge_RootManageSharedAccessKey_SERVICEBUS",
      "accessRights": "Manage"
    }
  ],
  "disabled": false
}

バッチリアラートが上がった情報のみをゲットできました。

Screen Shot 2017-11-29 at 2.19.25 PM.png

次回

本当はこの後、トリガーのデータが多すぎるので、Stream Analytics でメッセージの数を減らしたのちに、メール通知することにしました。Stream Analytics -> Azure Functions -> SendGrid という構成ですが、今日はもう力尽きたのでこの辺で。

Resource

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away