冬の朝をもっと快適に！Azure×Nature Remoで実現するエアコン自動化プロジェクト

## 1.はじめに
こんにちは！寒い日が続いていますね。冬の朝、布団から出るのが本当に大変だと感じることはありませんか？私自身、寒さが苦手なので、朝起きる少し前にエアコン（暖房）を自動でONにする仕組みを作りました。このおかげで、目覚める瞬間から快適に過ごせるようになりました。

この仕組みは、スマートリモコン「 **Nature Remo** 」とクラウドサービスを使って実現しています。これまでに、 **Amazon Web Services（AWS）** を使ったバージョンと **Google Cloud Platform（GCP）** を使ったバージョンを構築しました。

https://qiita.com/nishifeoda/items/d8ff4a5f10b1c3cce364

https://qiita.com/nishifeoda/items/9b5cc9ea3c748de65b8c

そして今回は、**Azure（Microsoft Azure）** を活用して同様の仕組みを作ることに挑戦します。具体的には、 **Azure Functions** 、**Azure Container Apps** 、 そして **Azure Logic Apps** というサービスを組み合わせて、設定した時間にエアコンが自動でONになるようにします。

「でも、Nature Remoの公式アプリを使えば簡単にスケジュールを設定してエアコンを操作できるのでは？」と思う方もいらっしゃるかもしれません。その通りです。実際、公式アプリは十分便利で、多くの場合それで事足ります。しかし、今回あえてこのような回り道を選んだのには理由があります。それは、クラウドを学び、実践するためです。

このプロジェクトでは、エアコンを操作する仕組みを作るだけでなく、クラウドサービスを活用するスキルを身につけることを目指しています。AWSやGCP、Azureといった主要なクラウドプラットフォームを実際に触れることで、それぞれの特性や使い方を深く理解することができます。また、この仕組みはエアコンに限らず、他のシステムにも応用可能です。効率だけを求めるなら公式アプリを使うのが最適ですが、学びのためにあえて「回り道」を選ぶことにしました。

なお、過去の記事でも触れましたが、エアコンの制御は仮に仕組みが止まっても大きな問題にはなりません。ただし、これが業務に関わる重要なシステムの場合、仕組みの停止が致命的な影響を及ぼすこともあります。そのため、こうしたケースではリスク対策が必要です。他のサービスを併用したり、AWSやAzure、GCPといった複数のクラウドを組み合わせた冗長な構成にすることが有効です。今回のAzureでの取り組みも、そうした多様な選択肢を模索する一環として行っています。

## 2.Nature Remoとは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/116e7027-4fee-1149-aea9-63ad433cdee3.png)
（引用元: [Nature公式サイト](https://shop.nature.global/products/nature-remo-3)）

Nature Remoは、Nature株式会社が提供する家電をスマホや音声で操作できるスマートリモコンです。赤外線リモコンの信号を再現し、家電をIoT化することで、スマートホームを手軽に実現できます。例えば、外出先からエアコンを操作したり、スマートスピーカーと連携して音声で家電を動かしたりできます。また、搭載されたセンサーを活用して、温度や時間に応じた自動操作も可能です。Nature Remoには複数のモデルがあり、用途に応じて選べます。スマートホームを始めたい方に最適な製品です。

なお、今回はNature Remoの初期設定方法については触れません。初期設定について詳しくは、[公式ページ](https://support.nature.global/hc/ja/articles/900001792563-Nature-Remo-%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%9E%E3%83%8B%E3%83%A5%E3%82%A2%E3%83%AB-%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A%E6%96%B9%E6%B3%95)をご覧ください。

# ソースコード
今回使用したソースコードは下記のレポジトリにあります。ぜひ参考にしてみてください。

https://github.com/Nakkinakki55/AzureNatureRemoAutomation/tree/main


## 3.Nature Remoを操作する方法
この記事では、Nature RemoのAPIをNode.jsを使って操作する方法について説明します。コードの説明、アクセストークンの取得方法や事前の設定については、以前の記事を参考にしてください。

https://qiita.com/nishifeoda/items/9b5cc9ea3c748de65b8c

### 3.1.エアコンの電源をOFFにするコード
このコードは、Nature RemoのAPIを使ってエアコンの電源をOFFにするものです。
「なぜ上記のQiitaの記事に書かれているNode.jsのコードを使わないのか？」と疑問に思う方もいるかもしれませんが、今回このコードは後述するAzure Functionsで使用するためのものです。Azure FunctionsではNode.jsを利用できないため、C#を採用しています。この理由については後ほど詳しく説明します。

```01.ac_turn_off.cs
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace aircon_off
{
    public class Function1
    {
        private readonly ILogger<Function1> _logger;
        private static readonly HttpClient _httpClient = new HttpClient();

        // コンストラクタでILoggerを注入
        public Function1(ILogger<Function1> logger)
        {
            _logger = logger;
        }

        [Function("Function1")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            // Nature Remo APIの設定
            const string token = "<冬の朝をもっと快適に！Cloud Run×Cloud Scheduler×Nature Remoで実現するエアコン自動化プロジェクト で取得したトークン>"; // トークンを設定
            const string deviceId = "<device_list.jsonで取得した登録している家電のID>"; // 家電のIDを設定
            string url = $"https://api.nature.global/1/appliances/{deviceId}/aircon_settings";

            try
            {
                // リクエストの内容を設定
                var content = new StringContent("button=power-off", Encoding.UTF8, "application/x-www-form-urlencoded");

                // 必要なヘッダーを設定
                _httpClient.DefaultRequestHeaders.Clear();
                _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
                _httpClient.DefaultRequestHeaders.Add("accept", "application/json");

                // Nature Remo APIにPOSTリクエストを送信
                HttpResponseMessage response = await _httpClient.PostAsync(url, content);

                // ステータスコードが成功の場合
                if (response.IsSuccessStatusCode)
                {
                    string responseData = await response.Content.ReadAsStringAsync();
                    _logger.LogInformation("Nature Remoへの送信に成功しました。");
                    _logger.LogInformation($"レスポンスデータ: {responseData}");

                    // 成功メッセージを返却
                    return new OkObjectResult($"Nature Remo送信成功! レスポンスデータ: {responseData}");
                }
                else
                {
                    // ステータスコードが予期しない場合の処理
                    _logger.LogError($"予期しないステータスコード: {response.StatusCode}");
                    return new StatusCodeResult((int)response.StatusCode);
                }
            }
            catch (Exception ex)
            {
                // エラー発生時の処理
                _logger.LogError($"エラーが発生しました: {ex.Message}");
                return new ObjectResult($"エラーが発生しました: {ex.Message}") { StatusCode = 500 };
            }
        }
    }
}

```

### 3.2.エアコンの暖房モードを設定し、温度を24℃にするコード
次のコードでは、エアコンを暖房モードにし、温度を24℃に設定します。また、風量と風向きも自動調整に設定します。こちらも先ほど説明した通りC#で書いています。

```02.ac_set_heating_24_auto.cs
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace aircon_on
{
    public class Function1
    {
        private readonly ILogger<Function1> _logger;
        private static readonly HttpClient _httpClient = new HttpClient();

        // コンストラクタでILoggerを注入
        public Function1(ILogger<Function1> logger)
        {
            _logger = logger;
        }

        [Function("Function1")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            // Nature Remo APIの設定
            const string token = "<冬の朝をもっと快適に！Cloud Run×Cloud Scheduler×Nature Remoで実現するエアコン自動化プロジェクト で取得したトークン>"; // トークンを設定
            const string deviceId = "<device_list.jsonで取得した登録している家電のID>"; // 家電のIDを設定
            string url = $"https://api.nature.global/1/appliances/{deviceId}/aircon_settings";

            try
            {
                // 送信するデータをURLエンコードされた形式で作成
                var data = new StringContent(
                    "operation_mode=warm&temperature=24&temperature_unit=c&button=&air_volume=auto&air_direction=auto&air_direction_h=auto",
                    Encoding.UTF8,
                    "application/x-www-form-urlencoded"
                );

                // 必要なヘッダーを設定
                _httpClient.DefaultRequestHeaders.Clear();
                _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
                _httpClient.DefaultRequestHeaders.Add("accept", "application/json");

                // Nature Remo APIにPOSTリクエストを送信
                HttpResponseMessage response = await _httpClient.PostAsync(url, data);

                // ステータスコードが成功の場合
                if (response.IsSuccessStatusCode)
                {
                    string responseData = await response.Content.ReadAsStringAsync();
                    _logger.LogInformation("Nature Remoへの送信に成功しました。");
                    _logger.LogInformation($"レスポンスデータ: {responseData}");

                    // 成功メッセージを返却
                    return new OkObjectResult($"Nature Remo送信成功! レスポンスデータ: {responseData}");
                }
                else
                {
                    // ステータスコードが予期しない場合の処理
                    _logger.LogError($"予期しないステータスコード: {response.StatusCode}");
                    return new StatusCodeResult((int)response.StatusCode);
                }
            }
            catch (Exception ex)
            {
                // エラー発生時の処理
                _logger.LogError($"エラーが発生しました: {ex.Message}");
                return new ObjectResult($"エラーが発生しました: {ex.Message}") { StatusCode = 500 };
            }
        }
    }
}
```

### 3.3.エアコン操作APIの統合と活用
最後に、これまで紹介したエアコン操作の機能を統合し、APIとして提供するコード例をお見せします。こちらはC# ではなく前回使用したNode.jsのコードを使用しています。こちらは理由を後述します。このコードを使えば、エアコンの電源をオフにしたり、温度を設定したりする操作をWebリクエストを通じて簡単に実行できます。複数の機能を一つのコードベースにまとめているため、管理や拡張がしやすくなっています。なおこちらのコードのファイルの拡張子は .js に設定してください。
```app.js
import axios from 'axios';
import express from 'express';
const app = express();

// トークンとデバイスIDを設定
const token = '<冬の朝をもっと快適に！Cloud Run×Cloud Scheduler×Nature Remoで実現するエアコン自動化プロジェクト で取得したトークン>'; // トークンを設定
const deviceId = '<device_list.jsonで取得した登録している家電のID>'; // 家電のIDを設定
const url = `https://api.nature.global/1/appliances/${deviceId}/aircon_settings`;


// 現在の日時を取得してフォーマットするヘルパー関数
function getCurrentDateTime() {
    const now = new Date();
    const options = {
        timeZone: 'Asia/Tokyo',
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        fractionalSecondDigits: 3,
        hour12: false
    };
    return now.toLocaleString('ja-JP', options).replace(/\//g, '-').replace(',', '');
}

// 暖房をオフにする関数
async function turnHeatingOff(res) {
    const data = new URLSearchParams({
        button: 'power-off', // 電源オフボタン
    });

    try {
        console.log(`[${getCurrentDateTime()}] 暖房をオフにする操作開始...`);
        
        const response = await axios.post(url, data.toString(), {
            headers: {
                'Authorization': `Bearer ${token}`,
                'accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded',
            },
        });

        console.log(`[${getCurrentDateTime()}] 暖房をオフにする操作完了`);

        if (response.status === 200) {
            res.send('暖房をオフにしました!');
            console.log(`[${getCurrentDateTime()}] 操作成功: response.status = ${response.status}`);
            console.log(`[${getCurrentDateTime()}] Response data:`, response.data); // レスポンスデータを出力
        } else {
            res.status(500).send('暖房のオフ操作に失敗しました。');
            console.error(`[${getCurrentDateTime()}] 操作失敗: Unexpected status code ${response.status}`);
            console.error(`[${getCurrentDateTime()}] Response data:`, response.data); // レスポンスデータを出力
        }
    } catch (error) {
        res.status(500).send('暖房のオフ操作中にエラーが発生しました。');
        console.error(`[${getCurrentDateTime()}] エラー: ${error.message}`);
    }
}

// 暖房の温度を設定する関数
async function setHeatingTemperature(res, temperature) {
    const data = new URLSearchParams({
        operation_mode: 'warm',  // 暖房モード
        temperature: temperature.toString(),
        temperature_unit: 'c',
        air_volume: 'auto',
        air_direction: 'auto',
        air_direction_h: 'auto',
    });

    try {
        console.log(`[${getCurrentDateTime()}] 暖房の温度を${temperature}度に設定する操作開始...`);
        
        const response = await axios.post(url, data.toString(), {
            headers: {
                'Authorization': `Bearer ${token}`,
                'accept': 'application/json',
                'Content-Type': 'application/x-www-form-urlencoded',
            },
        });

        console.log(`[${getCurrentDateTime()}] 暖房の温度を${temperature}度に設定する操作完了`);

        if (response.status === 200) {
            res.send(`暖房の温度を${temperature}度に設定しました!`);
            console.log(`[${getCurrentDateTime()}] 操作成功: response.status = ${response.status}`);
            console.log(`[${getCurrentDateTime()}] Response data:`, response.data); // レスポンスデータを出力
        } else {
            res.status(500).send('温度設定に失敗しました。');
            console.error(`[${getCurrentDateTime()}] 操作失敗: Unexpected status code ${response.status}`);
            console.error(`[${getCurrentDateTime()}] Response data:`, response.data); // レスポンスデータを出力
        }
    } catch (error) {
        res.status(500).send('温度設定中にエラーが発生しました。');
        console.error(`[${getCurrentDateTime()}] エラー: ${error.message}`);
    }
}

// ルートエンドポイント
app.get('/', (req, res) => {
    res.send('暖房操作用APIにようこそ!');
});


// 暖房をオフにするルート
app.get('/heating/off', async (req, res) => {
    await turnHeatingOff(res);
});

// 暖房の温度を設定するルート（例：温度25度に設定）
app.get('/heating/temperature/:temp', async (req, res) => {
    const temperature = req.params.temp;
    await setHeatingTemperature(res, temperature);
});

// サーバーをポート8080で起動
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
    console.log(`ac_automation_controller: listening on port ${PORT}`);
});
```

## 4.Azure Functionsの設定方法
3.Nature Remoを操作する方法で紹介したコードは、同じネットワーク内でしか動作しません。つまり、外出先からではエアコンを操作できません。これでは困りますよね。しかし、今回紹介する Azure Functions を使えば、その悩みを解決できます。

Azure Functionsを使えば、外出先でも自宅のエアコンを操作できるようになります。もちろん、いくつかの方から「同じネットワーク内で動けば十分だ」という意見があるかもしれません。しかし、もしこのステップを踏まないと、Azure Container Appsからエアコンのスケジュール実行ができなくなってしまいます。そのため、この章は必ず実施してください。基本的には公式ドキュメントの通りに行えば問題なくできますが、今回はその手順をわかりやすく解説します。

今回は、Azure（Microsoft Azure）のアカウント作成から、先ほど紹介したコードのデプロイまでを詳しく解説します。説明にはスクリーンショットを多用し、ステップごとに分かりやすく進めていきますので、安心して進めてください。

### 4.1.設定方法
1.[公式サイト](https://azure.microsoft.com/ja-jp)にアクセスし、新しいアカウントを作成してください。このとき、必ず「プリペイド」を選択してください。「Azure 無料アカウント」を選ぶと一部の機能が利用できないためです。

2.アカウントの作成が完了したら「Go to Azure portal」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ee6c45d8-9e29-3ac2-132f-4030b5e6a766.png)

3.ではここからAzure Functions上で関数を作成します。[公式ドキュメント](https://learn.microsoft.com/ja-jp/azure/azure-functions/supported-languages?tabs=isolated-process%2Cv4&pivots=programming-language-csharp)によると、Azure Functionsで使用できる開発言語は、 **C#、JavaScript、Python、Java、PowerShell、TypeScript、そしてGoやRust** などの言語です。そのため、前回の開発で使用したNode.jsのコードは利用できません。今回は、Azureを開発しているMicrosoftが手掛けた **C#** を使用します。理由は2つあります。1つ目は、公式ドキュメントで最初に例示されていた言語だからです。2つ目は、Azureと同じ開発元の言語であるため、互換性が高いと考えたからです。

4.まずはVisual Studioをインストールしていない人はの最新版を[Visual Studio](https://visualstudio.microsoft.com/ja/)をインストールしてください。ちなみに2025年1月現在はVisual Studio 2022が最新版のようです。

5.Visual Studioでまずは「新しいプロジェクトを作成」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/c0bc4ff5-1ce7-f3ab-c18d-e295a547a5c8.png)


6.検索窓の「function」と入力して、「Azure Functions」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/562e2962-0ba2-685a-7364-b340a337af31.png)

7.プロジェクト名を入力して、「次へ」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/78567ad9-db56-2a30-6428-3bd059314c2f.png)

8.Fucitons workerは「.NET 8.0 Isolated(長期的なサポート)」、Fuctionは「Http trigger」、Authorization levelは「Anonymous」を選択してください。完了したら「作成」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/4a7fabf6-ea58-ad74-96ce-e2365a5fd072.png)

9. **今回は3.1.エアコンの電源をOFFにするコード** を使用した関数を作成します

10.上部に今回のプロジェクト名が書かれた名称の▶ボタンがあるのでクリックしてください。今回だと「aircon_off」という名称になっていると思います。

11.コマンドプロンプト画面で
```bash
Functions:

        Function1: [GET,POST] http://localhost:7100/api/Function1
```
というメッセージが出力されるので、このリンクをGoogle Chromeなどのブラウザのアドレスバーに貼り付けて、Enterキーを押してください。これでエアコンの電源がOFFになれば成功です。

12.ソリューション エクスプローラーでプロジェクトを右クリックし、「発行」 を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ae38cd8f-37b3-1b56-f427-046fc3d2e79f.png)
次に、「ターゲット]」で 「Azure」 を選び、「次へ」 をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/76ab8680-0bc7-5ff2-0f46-95824f6bd77b.png)

13.「Azure Function App (Windows)」を選択して、「次へ」クリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/891d76e1-2c33-c015-7c3a-f2816f13008b.png)

14.「Functions インスタンス」を選択して、 「新しいインスタンスの新規作成」をクリックs手してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ea82ffaf-ff03-0b6d-8fcd-4ef19eb08df4.png)

15.「リソース グループ」などを設定し、「作成」ボタンを押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/4d33d0cc-5c46-c3e9-92c3-7eee67a27ba0.png)

16.少し待機すれば以下のメッセージボックスが表示されます。「完了」ボタンを押下してください。そしてまた完了したら今度は「閉じる」ボタンを押下してください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/21798246-9563-5621-3a04-75170ec5cf4a.png)

17.すべての工程が完了したら以下のメッセージボックスが表示されます。「発行」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/90ca9b74-133c-5bf8-76d9-d5059b4a82b6.png)

18.右クリックして「Azure portal で開く」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/0cd09754-234e-6276-8327-7b342629e57c.png)

19.関数の中にある名前の中にある関数名をコピーしてください。今回の場合だと「Function1」です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/3e984048-7298-3265-04f9-7d0fd7fce787.png)


20.実行したいときは以下のAzure portalに書かれているURLを参考にして
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/29560767-bbe0-7ceb-fc88-83de478b33e6.png)

```text
[Azure portalに書かれているURL]/api/19でコピーした値?name=Functions
```
このリンクをGoogle Chromeなどのブラウザのアドレスバーに貼り付けて、Enterキーを押してください。これでエアコンの電源がOFFになれば成功です。

### まとめ
これで、URLを使用してエアコンを操作できる仕組みが完成しました。エアコンの電源をONにしたい場合はコードを変更するだけです。ただし、Azure Functionsはルーティング処理には向いていないため、エアコン操作のような複数のパスに応じた処理を実現するには適していません。

例えば、以下のようなURLを使ってエアコンを操作したい場合：
- /heating/off で暖房をオフにする
- /heating/temperature/25 で暖房の温度を25℃に設定する

このような動作を実現するには、ルーティング処理が必要です。しかし、Azure Functionsではこれを直接行うことが難しいため、別の選択肢が必要となります。

その解決策として Azure Container Apps を使用することが推奨されます。Azure Container Appsはルーティング処理をサポートしており、複数のエンドポイントを簡単に構築できます。このため、今回の「3.3. エアコン操作APIの統合と活用」のコードをより効率的に実行する環境として適しています。

## 5.Azure Container Appsの設定方法
この章では、Azure上でExpressを使ったルーティング処理を動作させる方法をご紹介します。具体的には、「3.3. エアコン操作APIの統合と活用」のコードを実際に動かす手順を解説します。今回は、Azure Container Appsを使用するする方法を採用します。ほかにもいくつかの手法がありますが、調査した結果、この方法が最も簡単で実践しやすいと判断しました。それでは、具体的な手順を見ていきましょう。

### 5.1.設定方法
1.まずはAzure CLI を持っていない人は[公式サイト](https://learn.microsoft.com/ja-jp/cli/azure/install-azure-cli)からインストールしてください。

2.まずはコマンドプロンプト上で、Azureにログインしてください
```bash
az login
```

3.upgrade コマンドを実行します。
```bash
az upgrade
```

4.CLI 用の Azure Container Apps 拡張機能をインストールまたは更新します。
```bash
az extension add --name containerapp --upgrade --allow-preview true
```

5.Microsoft.App と Microsoft.OperationalInsights の名前空間を登録します。
```bash
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights
```

6.Azure CLI を使ってリソースグループを作成する方法を説明します。まず、以下のコマンドを実行してリソースグループを作成します。このとき、your_resource-group の部分には任意のリソースグループ名を指定してください。また、--location の後にはリージョン名を入力します。
```bash
az group create --name "your_resource-group" --location "northeurope"
```
リージョン名を確認するには、次のコマンドを実行してください。このコマンドを実行すると、利用可能なリージョン名の一覧が表示されます。
```bash
az account list-locations --output table
```
表示されたリージョン名の中から希望のものを選び、--location の後に貼り付けて使用してください。これで、指定したリージョンにリソースグループが作成されます。

7.Git をまだインストールしていない方は、まず [Git](https://git-scm.com/downloads) をダウンロードしてインストールしてください。その後、以下のコマンドを実行してサンプルコードを取得してください。「サンプルコードは必要ないのでは？」と思うかもしれませんが、この章での開発で使用するため、必ず取得してください。
```bash
git clone https://github.com/azure-samples/containerapps-albumapi-javascript.git
cd containerapps-albumapi-javascript/src
```

8.今回はこの取得したコードを改修して、Azure Container Appsにデプロイします。まずpackage.jsonの中身を以下のように書き換えてください
```package.json
{
  "name": "node",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  },
  "type": "module",
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.26.1",
    "express": "^4.21.2",
    "morgan": "^1.10.0"
  }
}
```

9.app.jsの中身を 3.3.エアコン操作APIの統合と活用 に書いているコードに書き換えてください

10.axiosとexpressをインストールしてください
```bash
npm install axios express
```

11.以下のコマンドを実行して、コードをデプロイします
```bash
az containerapp up ^
  --name "app-name" ^
  --resource-group "your_resource-group" ^
  --location "northeurope" ^
  --environment "app-name-env" ^
  --source .
```

12.デプロイが成功すると、次のようなメッセージが表示されます。
```bash
Browse to your container app at: http://×××××××××××.io
```
と表示されるので、このリンクをGoogle Chromeなどのブラウザのアドレスバーに貼り付けて、Enterキーを押してください。ブラウザで以下のように表示されれば成功です。なお、メッセージではリンクが http として表示されますが、ブラウザで実行すると自動的に 
```bash
https://×××××××××××.io
```
に切り替わりますので、安心して利用できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/a9d4341a-d94f-520c-3d35-c61d5d710e61.png)

13.エアコンを操作する際は、以下のルーティングを使用してください。

暖房をオフにする場合
```text
https://×××××××××××.io/heating/off
```
暖房を24度に設定する場合
```text
https://×××××××××××.io/heating/temperature/24
```
を実行してください。これで準備完了です。

## 6.Azure Logic Appsの設定方法
ここまで来たら、あともう一息です！残る作業は、Azure Logic Appsの設定だけです。Azure Logic Appsは、スケジュール実行等を行うことができるサービスです。実行のタイミングを細かく制御できるため、とても便利です。基本的には公式ドキュメントに従えば問題ありませんが、今回はより分かりやすく解説します。

### 6.1.設定方法
1.Azure Logic Appsの設定方法を紹介します。ホーム画面から、画面左上にある検索窓に「Logic Apps」と入力し、検索結果から 「ロジックアプリ」 を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/4a0f27a2-bece-5dc6-ebf0-e1adfd55fd12.png)

2.「追加」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/78d3706f-298b-68e5-060c-4087d4058466.png)

3.「従量課金」の「マルチテナント」を選択してください。完了したら「選択」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/b103f06a-34e0-b84a-d08c-427d9b55c027.png)

4.ロジックアプリの名前を入力してください。次に、リソースグループを選択します。リージョンは、リソースグループで設定したリージョンと同じものを選んでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/48ce7c83-f359-b1ad-418f-3ff3c53ea245.png)

5.「リソースに移動」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ac48d2d2-88b1-6a23-1459-723980a71ab5.png)

6.「ロジック アプリ デザイナー」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ff2e342f-1750-bba3-f8cd-3eae85a1762f.png)

7.「トリガーの追加」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/05689674-fc82-8bc6-1bcc-524da52eefd6.png)

8.検索窓に「schedule」と入力すると、時計マークのアイコンが表示されます。その中から「Recurrence」を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/d2230772-81e3-a005-fb19-58f245ff40f4.png)

9.値を入力して、定期実行したい曜日や時間を設定してください。ちなみに下記の画像だと毎週月から金曜日の日本時間（UTC+09:00）の午前10時30分に定期実行されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/0aebe2c0-795d-1fc6-0c61-5b76b31f1ba3.png)

10.完了したら「保存」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/512e6f6c-1b7d-95f1-934a-09a6f5586d52.png)

11.「+」をクリックして、「アクションの追加」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/83607edd-f7ef-3bea-572d-0a2b405d9762.png)

12.検索窓に「HTTP」と入力して、その中から「HTTP」を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/ecaa2950-5c98-4751-2b9a-76f25c82e875.png)

13.URIには今回、4.Azure Functionsの設定方法や5.Azure Container Appsの設定方法で取得したURLを入力してください。そしてMethodは「GET」にしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/e5e7246a-1b23-6fb4-5643-5756808998d7.png)

14.完了したら「保存」をクリックしてください。これで完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561875/512e6f6c-1b7d-95f1-934a-09a6f5586d52.png)




## まとめ
いかがでしたでしょうか？これで、毎日決まった時間にエアコン（暖房）を自動でONにできるようになり、快適な朝を迎えることができるようになりました。今回は、Nature Remo、Azure Functions、Azure Container Apps、Azure Logic Appsの使い方を丁寧に解説したため、少し長文になってしまいましたが、ここまでお付き合いいただき、ありがとうございます。

今回の内容は、あくまでAzure（Microsoft Azure）のAzure Functions、Azure Container Apps、Azure Logic Appsを使ったエアコンの電源制御に関するものでした。エアコンの電源制御は、仮に仕組みが止まっても大きな問題は起こりませんが、もし業務に関わる重要なシステムで同様の仕組みを導入する場合は、リスクへの備えが必要です。

たとえば、今回使用したAzure Functions、Azure Container Apps、Azure Logic Appsだけに依存するのではなく、他のサービスを併用したり、切り替えが可能な構成にするのも一つの方法です。その際は、三大クラウドであるAWS（Amazon Web Services）、Azure（Microsoft Azure）、GCP（Google Cloud Platform）のいずれかを併用するのが効果的です。これらのクラウドサービスは利用者が多いため、トラブル時の情報が入手しやすいというメリットがあります。

GCP（Google Cloud Platform）やAWS（Amazon Web Services）を使った方法は前回私が書いた記事をご覧ください。

https://qiita.com/nishifeoda/items/9b5cc9ea3c748de65b8c

https://qiita.com/nishifeoda/items/d8ff4a5f10b1c3cce364

今回、Azure（Microsoft Azure）を利用してエアコン制御システムを開発しましたが、全体的に開発しやすいと感じました。特に、Azure Functionsで関数をデプロイする際は、開発に使ったVisual Studioの機能をそのまま利用できるため、手間が少なくスムーズに進められました。また、Azure Container Appsでアプリをデプロイする場合も、GitHub上にあるサンプルコードを少し修正するだけで対応でき、そこまで難しくはありませんでした。ただし、Azure Logic Appsで定期実行の設定を行う際、独自のワークフローを使う必要があり、少し慣れが必要だと感じました。

とはいえ、Azure（Microsoft Azure）には他のサービスにはない利点があります。Azureはその名前の通りMicrosoft製のクラウドサービスです。今回使用したVisual StudioもMicrosoftが開発しており、さらにGitHubも現在はMicrosoftの傘下です。また、ビジネスシーンで広く使われているWordやExcelといったOffice製品もMicrosoftが提供しているため、Azureはこれらの製品との連携が非常に強力です。

さらに、もしMicrosoft製品やサービスに変更があった場合でも、Azureは最速で対応する可能性が高いです。そのため、特にビジネス用途でクラウドサービスを活用する場合、Azureは非常に有力な選択肢だと思います。また、AzureはMicrosoftアカウントがあればすぐに利用を開始できるため、新たに別のアカウントを作成する手間を省け、運用管理コストを抑えられる点も魅力的です。結論として、業務でクラウドサービスを導入するなら、現在のビジネス環境においてはAzure（Microsoft Azure）が最適だと感じました。

最後までお読みいただき、ありがとうございました。

## 参考文献
[スマートリモコンの仕組みとは？Natureスタッフが説明してみた。Nature公式サイト](https://shop.nature.global/blogs/nature-store/what_is_smart_remote?srsltid=AfmBOoptTEOKN7T28FizSR3lA9aR1aIWk5YC4LT3HNmXyHmLj_h0C-BV)
[アレクサとNature Remoでできること｜照明やテレビを操作しよう - Nature](https://nature.global/blog/18843/)
[Nature RemoのAPIでターミナルから家電を操作してみた | DevelopersIO](https://dev.classmethod.jp/articles/nature-remo-api/)
[Nature Remo API でエアコンを操作する（Slackでエアコン操作も） | yuu26-memo](https://blog.yuu26.com/nature-remo-api-control/)
[Swagger UI - Nature Remo](https://swagger.nature.global/)
[Azure Functions の概要 | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-get-started?pivots=programming-language-csharp)
[Azure Functions でサポートされている言語 | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/azure-functions/supported-languages?tabs=isolated-process%2Cv4&pivots=programming-language-csharp)
[クイック スタート: Visual Studio を使用して Azure で初めての C# 関数を作成する | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-create-your-first-function-visual-studio)
[Azure Container Apps のドキュメント | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/container-apps/)
[クイックスタート: Azure portal を使用して最初のコンテナー アプリをデプロイする | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/container-apps/quickstart-portal)
[クイックスタート: ローカル ファイルシステムから Azure Container Apps へのアプリのビルドとデプロイを行う | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/container-apps/quickstart-code-to-cloud?tabs=bash%2Cjavascript)
[曜日指定でLogic Appsを実行する #logicapps - Qiita](https://qiita.com/yossihard/items/70cd6f4cbdb08e860938)
[ワークフローで繰り返し実行されるトリガーのスケジュールについて - Azure Logic Apps | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/logic-apps/concepts-schedule-automated-recurring-tasks-workflows)
[マイクロソフトはGitHubの買収で、オープンソースの世界にも「君臨」する | WIRED.jp](https://wired.jp/2018/06/07/microsofts-github-deal/)