IoTプロトコル

REST API

HTTPベースのシンプルなAPI設計様式。

概要

REST API(Representational State Transfer API)は、2000年にRoy Fieldingがその博士論文で提唱したアーキテクチャスタイル「REST」の原則に基づいて設計されたWebインターフェイスです。REST自体はプロトコルではなく設計思想であり、HTTP上でリソース指向のAPIを構築するための一連の制約(制約条件群)として定義されています。

IoT分野において、REST APIはクラウドプラットフォームとデバイスを繋ぐ主要インターフェイスとして広く使われています。AWS IoT、Azure IoT Hub、Google Cloud IoT、Ambientなど、ほぼすべてのIoTクラウドサービスがREST APIを提供しており、センサーデータのアップロード、デバイス設定の変更、ファームウェア更新のトリガーなど多様な操作が行えます。

REST APIの最大の強みは標準的なHTTPを使うため、あらゆるプログラミング言語・プラットフォームから呼び出せる汎用性にあります。ESP32からPython、JavaScriptまで、同じAPIエンドポイントを共通インターフェイスとして使えます。


歴史・背景

1990年代後半、SOAP(Simple Object Access Protocol)やCORBA、XML-RPCなど複雑なWebサービス規格が乱立していました。これらはWSDLによる複雑なインターフェイス定義を必要とし、実装コストが高いという課題がありました。

2000年、カリフォルニア大学アーバイン校のRoy Fieldingは博士論文「Architectural Styles and the Design of Network-based Software Architectures」の中でRESTを提唱。HTTPそのものの設計に内在するアーキテクチャパターンを体系化したものです。

2000年代後半からREST APIが急速に普及し、TwitterやFacebook、Google MapsのAPIが「RESTful」を標榜するようになると、Webサービスのデファクトスタンダードになりました。IoTの普及期(2010年代)にはほぼすべてのクラウドIoTプラットフォームがREST APIをメインインターフェイスとして採用しました。


技術仕様

RESTの6原則

RESTアーキテクチャは以下の6つの制約から成ります。

制約説明
クライアント-サーバー分離UIとデータ管理を分離し、各々独立して進化できる
ステートレス各リクエストは完結している(サーバーはセッション状態を保持しない)
キャッシュ可能レスポンスにキャッシュ可否を明示できる
統一インターフェイスリソースはURIで識別し、HTTPメソッドで操作
階層化システムプロキシ・ゲートウェイを介した多層構成を許容
コードオンデマンド(任意)実行可能コードをダウンロードして動的に動作拡張(任意)

HTTPメソッドとCRUD対応

GET    /devices           → 全デバイス一覧取得(Read)
GET    /devices/{id}      → 特定デバイス取得(Read)
POST   /devices           → 新規デバイス登録(Create)
PUT    /devices/{id}      → デバイス情報全更新(Update)
PATCH  /devices/{id}      → デバイス情報部分更新(Update)
DELETE /devices/{id}      → デバイス削除(Delete)

リソース設計例(IoTデバイス管理API)

# デバイス管理
GET    /api/v1/devices
POST   /api/v1/devices
GET    /api/v1/devices/{device_id}
PATCH  /api/v1/devices/{device_id}
DELETE /api/v1/devices/{device_id}

# センサーデータ
GET    /api/v1/devices/{device_id}/measurements
POST   /api/v1/devices/{device_id}/measurements
GET    /api/v1/devices/{device_id}/measurements/{id}

# ファームウェア
GET    /api/v1/firmware/latest
POST   /api/v1/devices/{device_id}/ota/trigger

# コマンド送信
POST   /api/v1/devices/{device_id}/commands
GET    /api/v1/devices/{device_id}/commands/{cmd_id}

認証方式

方式説明用途
API KeyHTTPヘッダーに固定キーを付与シンプルなデバイス認証
Bearer TokenJWTトークン(OAuth 2.0)ユーザー認証・権限管理
Basic AuthBase64(user:pass)テスト用途のみ推奨
mTLSクライアント証明書認証高セキュリティデバイス認証
HMAC署名リクエストへの署名(AWS SigV4等)AWSデバイス認証

OpenAPIによるAPI定義

openapi: 3.0.0
info:
  title: IoT Sensor API
  version: 1.0.0
paths:
  /devices/{device_id}/measurements:
    post:
      summary: センサーデータを送信
      parameters:
        - name: device_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                temperature:
                  type: number
                humidity:
                  type: number
                timestamp:
                  type: string
                  format: date-time
      responses:
        '201':
          description: 登録成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Measurement'

動作原理

ステートレス通信

RESTの核心はステートレスです。各リクエストはそれ単体で完結しており、サーバーはリクエスト間の状態を覚えません。

# 良い例:各リクエストに認証情報を含む
POST /api/v1/measurements HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Content-Type: application/json

{"temperature": 25.3, "device_id": "sensor_01"}

# 悪い例:「前回のログインセッションを使って」という暗黙の依存
POST /api/v1/measurements HTTP/1.1
Session-ID: abc123  ← セッション状態への依存(RESTではない)

HATEOASによるリンクナビゲーション

成熟したREST設計ではレスポンスに次のアクションへのリンクを含めます(HATEOAS)。

{
  "device_id": "sensor_01",
  "status": "active",
  "_links": {
    "self": {"href": "/api/v1/devices/sensor_01"},
    "measurements": {"href": "/api/v1/devices/sensor_01/measurements"},
    "commands": {"href": "/api/v1/devices/sensor_01/commands"},
    "firmware_update": {"href": "/api/v1/devices/sensor_01/ota/trigger", "method": "POST"}
  }
}

バージョニング

APIの後方互換性を維持しながら進化させるためのバージョニング戦略です。

# URLパスバージョニング(最も一般的)
/api/v1/devices
/api/v2/devices

# ヘッダーバージョニング
Accept: application/vnd.example.v2+json

# クエリパラメータバージョニング
/api/devices?version=2

用途・ユースケース

センサーデータのクラウド送信

# Raspberry Pi から REST API でデータ送信
import requests
import json
from datetime import datetime
import Adafruit_DHT

API_BASE = "https://api.iotplatform.example.com/v1"
API_KEY = "your-api-key"
DEVICE_ID = "rpi-greenhouse-01"

def send_sensor_data():
    sensor = Adafruit_DHT.DHT22
    humidity, temperature = Adafruit_DHT.read_retry(sensor, 4)
    
    payload = {
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "temperature": round(temperature, 1),
        "humidity": round(humidity, 1)
    }
    
    response = requests.post(
        f"{API_BASE}/devices/{DEVICE_ID}/measurements",
        json=payload,
        headers={"X-API-Key": API_KEY},
        timeout=10
    )
    response.raise_for_status()
    return response.json()

デバイス設定の読み書き

# デバイス設定を取得
config = requests.get(
    f"{API_BASE}/devices/{DEVICE_ID}/config",
    headers={"X-API-Key": API_KEY}
).json()

# 送信間隔を変更(部分更新)
requests.patch(
    f"{API_BASE}/devices/{DEVICE_ID}/config",
    json={"report_interval_sec": 300},
    headers={"X-API-Key": API_KEY}
)

OTAトリガー

# 新しいファームウェアバージョンを確認
latest = requests.get(f"{API_BASE}/firmware/latest").json()
current_version = "1.2.0"

if latest["version"] != current_version:
    # OTAアップデートをトリガー
    requests.post(
        f"{API_BASE}/devices/{DEVICE_ID}/ota/trigger",
        json={"firmware_version": latest["version"]},
        headers={"X-API-Key": API_KEY}
    )

コマンド送信と結果ポーリング

# コマンド送信(非同期)
resp = requests.post(
    f"{API_BASE}/devices/{DEVICE_ID}/commands",
    json={"type": "reboot", "params": {}},
    headers={"X-API-Key": API_KEY}
)
cmd_id = resp.json()["command_id"]

# 結果をポーリング
import time
for _ in range(30):
    result = requests.get(
        f"{API_BASE}/devices/{DEVICE_ID}/commands/{cmd_id}",
        headers={"X-API-Key": API_KEY}
    ).json()
    if result["status"] in ["completed", "failed"]:
        break
    time.sleep(2)

実装・開発のポイント

エラーハンドリングのベストプラクティス

RFC 7807(Problem Details for HTTP APIs)に準拠したエラーレスポンス設計が推奨されます。

{
  "type": "https://api.example.com/errors/device-offline",
  "title": "デバイスがオフラインです",
  "status": 503,
  "detail": "device_id 'sensor_01' は最後の接続から30分以上経過しています",
  "instance": "/devices/sensor_01"
}

レート制限の扱い

IoTデバイスが高頻度でAPIを叩く場合、429エラーとRetry-Afterヘッダーを適切に処理します。

// C言語での再試行処理(ESP-IDF)
int http_post_with_rate_limit(const char *url, const char *body) {
    int retry_after = 1;
    
    for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
        int status = http_post(url, body);
        
        if (status == 200 || status == 201) return 0;
        
        if (status == 429) {
            // Retry-Afterヘッダーの値を取得
            retry_after = get_retry_after_header();
            ESP_LOGW(TAG, "Rate limited. Retry after %d sec", retry_after);
            vTaskDelay(pdMS_TO_TICKS(retry_after * 1000));
        } else if (status >= 500) {
            // サーバーエラーは指数バックオフ
            vTaskDelay(pdMS_TO_TICKS(1000 * (1 << attempt)));
        } else {
            return -1;  // 4xxは再試行しない
        }
    }
    return -1;
}

冪等性の設計

PUT・DELETE・GETは冪等(同じリクエストを何度実行しても結果が変わらない)に設計します。ネットワーク障害時の再送安全性に直結します。

# 冪等なPUT(全フィールドを毎回送る)
PUT /devices/sensor_01
{"name": "温室センサー", "location": "A棟", "report_interval": 60}

# 冪等でないPOST(毎回新しいレコードが作られる可能性)
POST /devices/sensor_01/measurements
{"temperature": 25.3}
→ 重複送信防止のためidempotency-keyヘッダーを使う

他技術との比較

項目REST APIGraphQLgRPCMQTT
プロトコルHTTPHTTPHTTP/2TCP
スキーマ定義OpenAPI(任意)必須Protocol Buffersなし
ペイロードJSON/XML等JSONバイナリ任意
ストリーミング△(Webhook)○(Subscription)
学習コスト
組み込み適合

MQTTCoAPと比較してREST APIはより重量級ですが、クラウドサービスとの統合やデバッグのしやすさで優れています。JSONと組み合わせることで可読性の高いAPIを構築でき、WebSocketと組み合わせることでリアルタイム通知も実現できます。

関連用語

参考リンク