茱萸note

電子工作の備忘録と旅行の記録

Alexa Arduino AWS ESP8266 IoT マイコン 電子工作

Alexa で ESP8266 を制御する ―― プログラムの解説編【Alexa×Arduino その3】

投稿日:2020年12月23日 更新日:

Alexa で ESP8266 を制御するプログラムの解説です。


目次


今回は【Alexa × Arduino】第3弾ということでやっていきます。前々回と前回の記事では、Alexa Smart Home Skill と AWS IoT を組み合わせることで Alexa から ESP8266 に接続した LED を操作する方法を具体的に説明しました。

今回はこれらで用いたプログラムの簡単な解説をしたいと思います。今回解説するのは、

  • ESP8266 のプログラム(Arduino スケッチ)
  • AWS Lambda のプログラム

です。

プログラムは、以下のレポジトリにアップロードしてあります。

ESP8266 のプログラム

Arduino スケッチを読んでいただければ大体の流れが分かると思いますが、AWS IoT Core とのやりとりに関わる部分だけ抜粋します。

まずは初期処理の部分です。このライブラリでは IAM ユーザー認証方式での接続を試みています。connectAWSIoTCore が AWS IoT Core サーバーへの接続をする関数です。起動してから接続まで5秒程度以上待ってから接続を開始しないと、Exception (9) が発生するという原因がよく分からないトラブルに見舞われたので、ここでは 10秒待ってから接続しています。接続が完了したら、$aws/things/ESP8266/shadow/update/delta を Subscribe して、mqttCallback をコールバック関数として登録しています。

C++💩/*** AWS IoT Core Initialization ***/
awsWSclient.setAWSRegion(aws_region);
awsWSclient.setAWSDomain(aws_endpoint);
awsWSclient.setAWSKeyID(aws_iam_key);
awsWSclient.setAWSSecretKey(aws_iam_secret_key);
awsWSclient.setUseSSL(true);
awsWSclient.setCA(ca);
awsWSclient.setUseAmazonTimestamp(false);

connectAWSIoTCore();
void connectAWSIoTCore() {
  if ( mqttClient.connected() ) {
    mqttClient.disconnect();
  }
  Serial.print("Waiting for AWS IoT Core connection.");

  delay(10000); // Important ! To Avoid Exception (9)
  
  mqttClient.setServer(aws_endpoint, aws_port);
  if ( ! mqttClient.connect("HogeHoge") ){
    Serial.println(" Failed !");
    return;
  }
  Serial.println(" Finished !");

  mqttClient.setCallback(mqttCallback);
  mqttClient.subscribe(aws_topic_shadow_delta);
  Serial.println("Subscribed !");  
}

$aws/things/ESP8266/shadow/update/delta が変化すると、ただちに mqttCallback が呼ばれます。mqttCallback では、メッセージの解読がおこなわれ、LED の ON/OFF をします。メッセージは JSON 形式で送信されるので、それをパースして情報を抽出します。

void mqttCallback(char *topic, byte *payload, unsigned int length) {
  if (strcmp(topic, aws_topic_shadow_delta) == 0) {
    // Parse received message (JSON Format)
    JSONVar json = JSON.parse( (char *)payload );
    Serial.println( JSON.stringify(json) );
    JSONVar state = json["state"];

    if ( state.hasOwnProperty("led") ){
      // Obtain desired LED state
      bool led = state["led"];
      // LED ON/OFF
      digitalWrite(LED, led ? HIGH : LOW);
      // Report current LED state
      reportLedState(led);
    }
  }
}

受信される JSON の形式は次のようになっています。

json💩{
  "state" : {
    "led" : true
  }
}

LED の ON/OFF を切り替えたら、それを Thing Shadow に反映させるために $aws/things/ESP8266/shadow/update に Publish します。

C++💩void reportLedState(bool led) {
  char buf[100];
  sprintf(buf, "{\"state\":{\"reported\":{\"led\": %s}}}", led ? "true" : "false");
  mqttClient.publish(aws_topic_shadow, buf);
}

送信する JSON の形式は次のようになっています。

json💩{
  "state" : {
    "reported" : {
      "led" : true
    }
  }
}

Thing Shadow の復讐

Thing Shadow は、JSON で表すと以下のような構造です。

{
  "desired": {
    "led": false
  },
  "reported": {
    "led": false
  }
}

Thing Shadow の更新と受信は、 $aws/things/.../shadow/update という名前の Topic でおこないます。例えば、$aws/things/.../shadow/update に、

{
  "state" : {
    "desired" : {
      "led" : true
    }
  }
}

を Publish すると、Thing Shadow は

{
  "desired": {
    "led": true
  },
  "reported": {
    "led": false
  }
}

に変化します。$aws/things/.../shadow/update を購読していた場合、Publish した内容と全く同じものが受信されます。

さて、$aws/things/.../shadow/update/delta を購読していたとします。.../delta は、 desiredreported に差分が生じた時に通知がいきます。先ほどの場合、"desired" : { "led" : true }"reported" : { "led" : false } なので差分があり、

{
  "state" : {
    "led" : true
  }
}

が受信されます。この差分が生じた状態を解消するには、reported を変化させます。今回の場合、$aws/things/.../shadow/update

{
  "state" : {
    "reported" : {
      "led" : true
    }
  }
}

を Publish することで、Thing Shadow が

{
  "desired": {
    "led": true
  },
  "reported": {
    "led": true
  }
}

となり、desiredreported の差がなくなってめでたしめでたしです。

AWS Lambda のプログラム

AWS Lambda では Alexa Smart Home Skill の実装と、Thing Shadow の desired の更新をおこなっています。

まずは、Thing Shadow の更新部分です。

python💩client = boto3.client('iot-data', region_name='us-west-2')

def update_thing_shadow(name, value) :
    obj = {
        "state": {
            "desired": {
                name : value
            }
        }
    }
    
    topic_shadow_name = "$aws/things/ESP8266/shadow/update"
    client.publish(
        topic=topic_shadow_name,
        qos=0,
        payload=json.dumps(obj)
    )

ここでは、$aws/things/ESP8266/shadow/update

json💩{
  "state" : {
    "desired" : {
      "led" : true
    }
  }
}

を Publish することで、Thing Shadow の desired の更新をしています。

次に、Alexa Smart Home Skill です。雑に説明します。

まずは、Alexa がスマートホームデバイスを探す「Discovery」中の処理です。

python💩if namespace == 'Alexa.Discovery':
	if name == 'Discover':
		adr = AlexaResponse(namespace='Alexa.Discovery', name='Discover.Response')
		capability_alexa = adr.create_payload_endpoint_capability()
		capability_alexa_powercontroller = adr.create_payload_endpoint_capability(
			interface='Alexa.PowerController',
			supported=[{'name': 'powerState'}])
		adr.add_payload_endpoint(
			endpoint_id='LED',
			friendly_name='LED',
			description='これはただのLEDです。',
			display_categories=['LIGHT'],
			capabilities=[capability_alexa, capability_alexa_powercontroller])
		return send_response(adr.get())

Capability は、そのスマートホームデバイスが持つ機能です。例えば、スマートプラグやライトは ON/OFF ができるので、下に示すように「PowerController」という属性を付与しています。

capability_alexa_powercontroller = adr.create_payload_endpoint_capability(
	interface='Alexa.PowerController',
	supported=[{'name': 'powerState'}])

friendly_name は Alexa アプリに表示されるデバイス名です。日本語でもかまいません。display_categoriesここの一覧表の中から適切に設定する必要があります。

adr.add_payload_endpoint(
	endpoint_id='LED',
	friendly_name='LED',
	description='これはただのLEDです。',
	display_categories=['LIGHT'],
	capabilities=[capability_alexa, capability_alexa_powercontroller])

次に、「アレクサ、〇〇を消して!」と言った時の処理の部分です。

if namespace == 'Alexa.PowerController':
	# Note: This sample always returns a success response for either a request to TurnOff or TurnOn
	endpoint_id = request['directive']['endpoint']['endpointId']
	power_state_value = 'OFF' if name == 'TurnOff' else 'ON'
	correlation_token = request['directive']['header']['correlationToken']

	apcr = AlexaResponse(correlation_token=correlation_token)
	apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value)

	# Update Thing Shadow Desired Value
	update_thing_shadow("led", False if power_state_value == 'OFF' else True)

	return send_response(apcr.get())

「アレクサ、〇〇つけて!」と言うと、name が TurnOn になるようです。

power_state_value = 'OFF' if name == 'TurnOff' else 'ON'

update_thing_shadow 関数で Thing Shadow の desired の値を更新します。

update_thing_shadow("led", False if power_state_value == 'OFF' else True)

最後に、アレクサに「〇〇を ON/OFF にした」という返信をします。

apcr = AlexaResponse(correlation_token=correlation_token)
apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value)

return send_response(apcr.get())


雑に説明するとこんな感じです。

今度は、Raspberry Pi を AWS IoT Core に接続してみたいと思います。ESP8266 のときより簡単だと思います。

-電子工作
-, , , , , ,


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

【Ubuntu】NVIDIAドライバ・CUDA・CUDNNをインストールして深層学習環境を整える

Ubuntuで Nvidiaドライバ、CUDA、CUDNN をインストールし深層学習環境を整える方法を説明します。

WSLでGUIアプリを起動させる

WSLでGUIアプリを動かすためにVcXsrvというX Window サーバーをインストールします。

Tp-Link のスマートプラグ『Tapo P105』を PC・Raspberry Pi から直接操作する

Tp-Link のスマートプラグ『Tapo P105』をアプリや IFTTT といった外部サービスを用いずに、PC・Raspberry Pi から直接操作してみます。

AVR32を使う

Atmel製のマイコン「AVR32」を使ってみた感想です。

ESP8266 で Tp-Link のスマートプラグ『Tapo P105』を直接操作する

Tp-Link のスマートプラグ『Tapo P105』をアプリや IFTTT といった外部サービスを用いずに、ESP8266 から直接操作してみます。