赤外線リモコンを自作する - その5クライアント編

## 目次
1. [赤外線リモコンを自作する - その1データ解析編](https://qiita.com/ayakix/items/3ad454f135fb63a92026)
1. [赤外線リモコンを自作する - その2データ送信編](https://qiita.com/ayakix/items/75adbe742c32689a3cae)
1. [赤外線リモコンを自作する - その3温度/湿度センサー編](https://qiita.com/ayakix/items/69cf14e57dec86f4415a)
1. [赤外線リモコンを自作する - その4サーバサイド編](https://qiita.com/ayakix/items/5161a70ffca3b84e51f3)
1. [赤外線リモコンを自作する - その5クライアント編](https://qiita.com/ayakix/items/ca69c61deff3fb3fdb20)

## 赤外線リモコンを自作する - その5クライアント編

今回は、Arduinoで取得した温湿度をサーバに送信し、レスポンスとして赤外線データを受信した場合の処理について説明します。

## 全体の流れ
全体の流れとしては前回の [赤外線リモコンを自作する - その4サーバサイド編](https://qiita.com/ayakix/items/5161a70ffca3b84e51f3) を参考にしてください。

## Arduinoプログラム
```c
#include <Wire.h>
#include <CRC8.h>
#include <Arduino.h>
#include <Arduino_JSON.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>

#define PIN_SEND 32
#define IR_SEND_PIN PIN_SEND
#include <IRremote.hpp>

const int KHZ = 38;
const int I2C_SDA = 14;
const int I2C_SCL = 13;
const byte AHT25_ADDR = 0x38;
const double ERROR_VALUE = 999.0;

CRC8 crc;
double temperature;
double humidity;

const char* rootCACertificate = \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n" \
"ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n" \
"b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n" \
"MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n" \
"b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n" \
"ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n" \
"9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n" \
"IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n" \
"VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n" \
"93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n" \
"jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n" \
"AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n" \
"A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n" \
"U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n" \
"N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n" \
"o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n" \
"5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n" \
"rqXRfboQnoZsG4q5WTP468SQvvG5\n" \
"-----END CERTIFICATE-----\n";

WiFiMulti WiFiMulti;

void init_aht25(void) {
  delay(100);
  Wire.beginTransmission(AHT25_ADDR);
  Wire.write(0x71);
  Wire.endTransmission();
  delay(10);

  crc.setPolynome(0x31);
  crc.setStartXOR(0xFF);
}

void update_aht25(void) {
  byte buf[7];
  uint32_t humidity_raw;
  uint32_t temperature_raw;
  byte state;

  Wire.beginTransmission(AHT25_ADDR);
  Wire.write(0xAC);
  Wire.write(0x33);
  Wire.write(0x00);
  Wire.endTransmission();
  do {
    delay(80);
    Wire.requestFrom(AHT25_ADDR, 7);
    if (Wire.available() >= 7) {
      for(int i=0; i<7; i++) {
        buf[i] = Wire.read();
      }
    }
  } while((buf[0] & 0x80) != 0);

  crc.restart();
  crc.add(buf, 6);

  if (buf[6] == crc.getCRC()) {
    state = buf[0];
    humidity_raw = ((uint32_t)buf[1] << 12)|((uint32_t)buf[2] << 4)|(((uint32_t)buf[3] >> 4) & 0x0F);
    temperature_raw = (((uint32_t)buf[3] & 0x0F) << 16)|((uint32_t)buf[4] << 8)|((uint32_t)buf[5]);

    humidity = humidity_raw / 1048576.0 * 100;
    temperature = temperature_raw / 1048576.0 * 200 - 50;
  } else {
    // error
    humidity = ERROR_VALUE;
    temperature = ERROR_VALUE;
  }
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFiMulti.addAP("YOUR_WIFI_SSID", "YOUR_WIFI_PASSWORD");

  // wait for WiFi connection
  Serial.print("Waiting for WiFi to connect...");
  while ((WiFiMulti.run() != WL_CONNECTED)) {
    Serial.print(".");
  }
  Serial.println(" connected");
}

String ipToStr(uint32_t ipAddress){
    String result = "";
    result += String((ipAddress & 0xFF), 10);
    result += ".";
    result += String((ipAddress & 0xFF00) >> 8, 10);
    result += ".";
    result += String((ipAddress & 0xFF0000) >> 16, 10);
    result += ".";
    result += String((ipAddress & 0xFF000000) >> 24, 10);
    return result;
}

void sendSensorValues() {
  if (temperature == ERROR_VALUE || humidity == ERROR_VALUE) return;

  WiFiClientSecure client;
  client.setCACert(rootCACertificate);

  HTTPClient http;
  http.setTimeout(60000);
  http.begin(client, "https://XXXXX.execute-api.ap-northeast-1.amazonaws.com/v1/send");

  JSONVar body;
  body["temperature"] = (int)temperature;
  body["humidity"] = (int)humidity;
  body["macAddress"] = (String)WiFi.macAddress();
  body["localIP"] = ipToStr(WiFi.localIP());

  String str = JSON.stringify(body);
  Serial.println(str);

  int httpResponseCode = http.POST(str);

  if (httpResponseCode <= 0) {
    Serial.print("Error code: ");
    Serial.println(httpResponseCode);
    Serial.println(http.errorToString(httpResponseCode).c_str());
    http.end();
    return;
  }

  Serial.print("HTTP Response code: ");
  Serial.println(httpResponseCode);
  String response = http.getString();
  http.end();

  Serial.println(response);
  JSONVar json = JSON.parse(response);
  int len = json["length"];
  if (len == 0) return;

  sendCommand((const char*) json["command"], len);
}

void sendCommand(String commandStr, int len) {
  uint16_t command[len];
  stringToIntValues(commandStr, command, ',' );
  IrSender.sendRaw(command, len, KHZ);
}

void stringToIntValues(String str, uint16_t value[], char delim) {
  int k = 0;
  int j = 0;
  char text[8];

  for (int i = 0; i <= str.length(); i++) {
    char c = str.charAt(i);
    if ( c == delim || i == str.length() ) {
      text[k] = '\0';
      value[j] = atoi(text);
      j++;
      k = 0;
    } else {
      text[k] = c;
      k++;
    }
  }
}

void setup(void) {
  Serial.begin(115200);
  Wire.begin(I2C_SDA, I2C_SCL);
  init_aht25();
  connectWifi();
  IrSender.begin();
}

void loop() {
  update_aht25();
  sendSensorValues();
  delay(60 * 1000);
}
```


## プログラム説明
### rootCACertificate
サーバサイドはAWS上で動いているため、SSLでの通信のために、ルート証明書をHTTPクライアントに設定する必要があります。
詳しくは[AWS IoT Core サーバー認証](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/server-authentication.html)をご覧ください。

### connectWifi
WiFiに接続します。YOUR_WIFI_SSIDとYOUR_WIFI_PASSWORDを変更してください。

### sendSensorValues
センサデータを送信します。通信先を変更してください。
センサデータを送信した結果、サーバより赤外線データを受信した際は、sendCommandを呼び出して、対象の機器を操作します。

### stringToIntValues
サーバからの赤外線データはカンマ区切りの文字列となっているため、IrSender.sendRawに引数として渡せるようにuint16_t型の配列に変換します。

## おわりに
全部で5記事と長くなってしまいましたが、赤外線通信は多くの家電で使われているため、ハックできると汎用性が高く、生活を豊かにできると思います。是非お試しあれ！