e-DIY
IoT

Beebotteを使ってLINEからESP8266のGPIOを制御しよう

beebotteでGPIO操作

前回の記事では、LINE Messaging APIとGASを使ってボットを作り、Beebotteにメッセージを送る前段までを作りました。

今回は、GASからのメッセージをBeebotteで受けて、ESP8266へGPIOの状態切り替え指示を送り、エアコンのリモコンを操作できるようにします。
全体イメージはこのようになっています。

LINEからエアコン制御

Beebotteとは

Beebotteは、IoTデバイス向けのクラウドベースのデータエクスチェンジとストレージプラットフォームで、MQTTブローカーとして機能します。

簡単なイメージで説明すると、Beebotteの役割は「ポスト」です。
あなたが「エアコンをつけて」という手紙を送ると、手紙は「ポスト」に届きます。
そしてESP8266に手紙が配達されると、ESP8266はその内容に従って処理を行います。

MQTTとは

MQTTとは、Message Queueing Telemetry Transportの略です。
非同期に1対多の通信ができ、HTTPと比べると、軽量で省電力なプロトコルです。
そのためモバイル機器など電池で動作するシステムに適しています。

今回の場合のイメージは下図のような感じです。

MQTTのイメージ

MQTTを使うメリット

GASから直接ESP8266にアクセスすることもできるのですが、その場合、ESP8266をサーバーとして外部公開しなければならないため、セキュリティ上の心配点があります。
MQTTを使う場合は、MQTTがサーバーでESP8266がクライアントになります。
そのため、外部公開の必要がなく、安全性が高くなります。

Beebotteの使い方

まずはこちらのページからユーザー登録をしましょう。

登録が完了したらログインし、左メニューのChannelsから新規チャンネルを作成します。

Beebotteの使い方 Beebotteの使い方

ここで設定したチャンネル名とリソース名はBeebotteにアクセスするために必要になります。

次に、登録したチャンネルをクリックし、チャンネルトークンを確認します。
Beebotteの使い方

Beebotteの使い方

チャンネルトークンはBeebotteにアクセスするときの認証として使われます。

GASのコードに追記

ここで、前回作成したGASのプログラムをもう一度確認します。

//LINEからのPOSTを処理する関数
function doPost(e) {
  //LINEから受け取ったデータをパースし、replyTokenを取り出す
  var replyToken= JSON.parse(e.postData.contents).events[0].replyToken;
  //replyTokenが存在しない場合は処理を終了
  if (typeof replyToken === 'undefined') {
    return;
  }
  //LINE Messaging APIのエンドポイント
  var url = 'https://api.line.me/v2/bot/message/reply';
  //認証に使用するチャネルトークンを入力
  var channelToken = 'あなたのチャネルトークン;

  //LINEから受け取ったデータをパースし、メッセージテキストを取り出す
  var receive_message = JSON.parse(e.postData.contents).events[0].message.text;
  //応答するメッセージのデフォルトのテキストを設定
  var reply_text = receive_message + "\n" + "は無効なメッセージです";

  //ONというメッセージを受け取った場合は「エアコンをつけます」というメッセージをLINEに返す
  if(receive_message == "ON") {
    beebottePub("ac_on"); //ac_onを引数としてbeebottePub関数を実行
    reply_text = "エアコンをつけます";

  //OFFというメッセージを受け取った場合は「エアコンを消します」というメッセージをLINEに返す
  } else if(receive_message == "OFF") {
    beebottePub("ac_off"); //ac_offを引数としてbeebottePub関数を実行
    reply_text = "エアコンを消します";
  }
  
  //LINEに対して応答メッセージを返すためのAPIコールを行う
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + channelToken,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': reply_text,
      }],
    }),
  });
  //処理が成功したことを示すレスポンスを返します。
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

function beebottePub(action) {
  var headers = {
    "Content-Type": "application/json",
    "X-Auth-Token": "トークンを入力"
  };
  var json = `{"data":"${action}"}`;
  var options = {
    "headers": headers,
    "method": "post",
    "payload": json
  };
  UrlFetchApp.fetch("https://api.beebotte.com/v1/data/publish/チャネル名/リソース名", options);
}

49行目の関数がGASからBeebotteへPostするためのものです。
52行目に先ほど確認したチャンネルトークンを記入します。
60行目はエンドポイントのURLで、ここにチャンネル名とリソース名を入力します。

21行目と26行目ではそれぞれエアコンのオンとオフに対応したメッセージを引数としてbeebottePub関数を実行しています。
これで、ac_onまたはac_offというメッセージがBeebotteへ送られます。

ESP8266のArduinoスケッチ

最後にESP8266(WROOM02)のスケッチを作成します。

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h> //MQTTクライアントライブラリ
#include <ArduinoJson.h>

#define MQTT_MAX_PACKET_SIZE 64 //MQTTメッセージの最大サイズを指定
#define onPin 14
#define offPin 12

const char* ssid = "あなたのWiFiのSSID";
const char* password = "あなたのWiFiのパスワード";

const char *clientID = "ESP8266";
const char* topic =  "AC_Control/remoteAC"; //チャネル名/リソース名
const char* host = "mqtt.beebotte.com";

//Beebotteからメッセージを受け取ったときに呼び出される関数
void callback(char* topic, byte* payload, unsigned int length) {
  char buffer[MQTT_MAX_PACKET_SIZE];
  
  snprintf(buffer, sizeof(buffer), "%s", payload);   
  Serial.println("received:");
  Serial.print("topic: ");
  Serial.println(topic);
  Serial.println(buffer);

  // 受け取ったメッセージをデコード
  StaticJsonDocument doc;
  DeserializationError error = deserializeJson(doc, buffer);

  if (error) {
    Serial.println("deserializeJson() failed");
    return;
  }

  const char* parsedPayload = doc["data"];

  if (parsedPayload != NULL) {
    Serial.print("payload: ");
    Serial.println(parsedPayload);
    if (strcmp(parsedPayload, "ac_on") == 0) {
      digitalWrite(onPin, LOW);
      delay(500); 
      digitalWrite(onPin, HIGH);
    } else if (strcmp(parsedPayload, "ac_off") == 0) {
      digitalWrite(offPin, LOW);
      delay(500); 
      digitalWrite(offPin, HIGH);
    }
  }
}

WiFiClientSecure wifiClient;
PubSubClient client(host, 8883, wifiClient);
  
void setup() {
  Serial.begin(115200);
  Serial.println();
  WiFi.mode(WIFI_STA);
  
  pinMode(onPin, OUTPUT_OPEN_DRAIN);
  pinMode(offPin, OUTPUT_OPEN_DRAIN);

  digitalWrite(onPin, HIGH);
  digitalWrite(offPin, LOW);
  delay(1000);
  digitalWrite(offPin, HIGH);
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print("connecting to ");
    Serial.print(ssid);
    Serial.println("...");
    WiFi.begin(ssid, password);

    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
      Serial.println("failed to connect");
      delay(5000);
      return;
    } else {
      Serial.println("Connected!");
      Serial.println(WiFi.localIP());
    }
  }

  //MQTTに接続
  if (!client.connected()) {
    String username = "token:";
    username += "チャンネルトークン";
    client.connect(clientID, username.c_str(), NULL);

    if (client.connected()) {
      Serial.println("MQTT connected");
      client.setCallback(callback);
      
      client.subscribe(topic);
    } else {
      Serial.print("MQTT connection failed: ");
      Serial.println(client.state());
      delay(5000);
    }
  } else {
    client.loop();
  }
}

トピックの設定

トピックは、データの送受信を行う際のエンドポイントや識別子の役割を果たします。
具体的には、Beebotteでのチャンネルとリソースを組み合わせたものをトピックとしています。

したがって、14行目にはチャネル名/リソース名という形でトピックを設定します。
これは88行目以降でBeebotteとの接続処理を行うときに使われます。

受け取ったJSONの処理

27行目からで、ArduinoJson.hを使用してJSONデータを処理しています。

StaticJsonDocument doc;
StaticJsonDocumentはArduinoJsonライブラリによって提供されるオブジェクトで、JSONデータを格納するためのものです。
JSONデータを格納するためのオブジェクト doc を作成しています。

DeserializationError error = deserializeJson(doc, buffer);
deserializeJson関数を使用して、bufferに格納されているJSON文字列を解析し、その内容をdocに展開しています。

const char* parsedPayload = doc[“data”];
JSONを解析した結果を基にして、docオブジェクトから “data” というキーに対応する値を取得し、それを parsedPayload 変数に格納しています。
この”data”は、Beebotteから受け取ったJSONメッセージの中のキーで、「ac_on」または「ac_off」が値として入っています。
下記のようなイメージです。

{
  "data": "ac_on"
}

GPIOの制御

41行目ではparsedPayloadに格納された値と「ac_on」と一致しているかをstrcmp関数でチェックしています。
parsedPayloadとac_onの差が0であれば一致していると判定されます。
一致すればGPIOのonPinをLowにします。

同様に45行目ではオフを判定しています。

GPIOとエアコンのスイッチとの接続部分(ハードウェア部分)はこちらの記事を参照してください。

Beebotteへの接続

88行目からはBeebotteへの接続の処理を行っています。

  • Beebotteへの接続状態を確認し、接続されていなければ接続を試みます。
  • 接続に成功したら、指定されたトピックを購読します。
  • すでにMQTTに接続している場合、client.loop() が呼び出され、MQTTのメッセージや通信の維持処理が行われます。

90行目にはBeebotteの管理画面で確認したチャンネルトークンを入力します。

LINEからエアコンをオン

それではArduinoスケッチを書き込んでみましょう。
シリアル出力を確認し、WiFi、MQTTが問題なく動作していることが確認できたら、LINEからエアコンをオンさせてみましょう。

このようにLINEからエアコンをオンさせることができました。
もちろん外出先からも制御することができます。

関連キーワード

フリーワード検索