// SAWACHIでは安全にデータを送受信するためにMQTT over TLSを使用します
// TLS(Transport Layer Security)は、データの送受信を暗号化するためのプロトコルです
// MQTTではクライアント側でも証明書の管理が必要になります

// 各自に発行された証明書を使ってSAWACHIに接続します
// - rootca.pem
// - certificate.pem.crt
// - private.pem.key

// ルート証明書 rootca.pem...
// ルート証明書は信頼できる認証局(CA)によって発行される証明書です
// Let's Encrypt, Sectigo, DigiCertなどが多く使われているCAです
// またAmazon, Google, Microsoftなどの大手企業もCAを持っています
// 今回使うSAWACHIのルート証明書はAmazonから発行された証明書です

// certificate.pem.crt...
// SAWACHI(ブローカー)から発行される証明書です
// 証明書には公開鍵が含まれており、公開鍵の信頼性を保証するために
// CAの署名が含まれています

// private.pem.key...
// SAWACHI(ブローカー)から発行される秘密鍵です
// 秘密鍵と公開鍵はペアで発行され、公開鍵を証明書に含めて公開します
// 公開鍵を使って暗号化されたデータは秘密鍵でのみ復号できます


// "ArduinoJson"ライブラリを使用します
// スケッチ > ライブラリを使用 > ライブラリを管理 から
// ArduinoJson を検索してインストールしてください

#include <Wire.h>
#include <WiFi.h>
#include <WiFiClientSecure.h> // 追加しました
#include <PubSubClient.h>
#include <ArduinoJson.h>

#define SDA 21 // SDAピンの設定 配線と一致していれば何番でも可
#define SCL 22 // SCLピンの設定 配線と一致していれば何番でも可

#define SHT31_ADDRESS 0x45
#define SHT31_SOFT_RESET_MSB 0x30
#define SHT31_SOFT_RESET_LSB 0xA2
#define SHT31_CLEAR_STATUS_REGISTER_MSB 0x30
#define SHT31_CLEAR_STATUS_REGISTER_LSB 0x41
#define SHT31_ON_BUILTIN_HEATER_MSB 0x30
#define SHT31_ON_BUILTIN_HEATER_LSB 0x6D
#define SHT31_OFF_BUILTIN_HEATER_MSB 0x30
#define SHT31_OFF_BUILTIN_HEATER_LSB 0x66
#define SHT31_SINGLE_MEASUREMENT_MSB 0x24
#define SHT31_SINGLE_MEASUREMENT_LSB 0x00

// const char* root_cert = "";
// const char* cert = "";
// const char* private_key = "";
// const char* mqtt_broker_host = "";
// const int mqtt_broker_port = "";
// const char* topic = "";
// const char* client_id = "";
// const char* device_id = "";
// 各自に用意されたPASTEME.txtを貼り付けてください


// WiFi settings
const char* ssid = "iopworkshop20240612";
const char* password = "iopworkshop";

//WiFiClient espClient;
WiFiClientSecure espClient;
PubSubClient client(espClient);


void setup() {

  Serial.begin(115200); // シリアル通信の速度(baud rate: ボーレート)を設定
                        // Arduino IDEの「シリアルモニタ」の速度を
                        // この数値に合わせてください

  setup_sht31(SDA, SCL); // SHT31の初期設定
                         // SDA: 27番ピン, SCL: 14番ピンを指定します

  // Wi-Fiに接続します(下に関数が定義されています)
  setup_wifi();

  // 証明書のセット
  espClient.setCACert(root_cert); // ルート証明書
  espClient.setCertificate(cert); // 証明書
  espClient.setPrivateKey(private_key); // 秘密鍵

  // MQTT Brokerを設定します
  // client.setServer("192.168.254.254", 1883); //IPアドレスとポート番号
  client.setServer(mqtt_broker_host, mqtt_broker_port);

}

void loop() {
  // Wi-Fiの接続が切れている場合は再接続を試みます
  if (!is_wifi_connected()) {
    delay(1000);
    return;
  }

  float temp, hum; // 温湿度データを格納する変数
  read_sht31(&temp, &hum); // 温湿度データを取得


  //char* topic = "iopworkshop/test";
  //char* client_id = "PromptK_Nakahira";
  //char* device_id = "some_device_id";
  char* message_id = "P00000001";
  mqtt_publish_data(topic, client_id, message_id, device_id, temp, hum); // データをMQTTブローカーに送信
  // トピック, クライアントID, デバイスID, 温度, 湿度


  // 10秒ごとにデータを取得
  delay(10000);
}

void setup_wifi() {
  delay(10); // 接続を安定させるための遅延
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  // WiFi接続が完了するまで待機
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

bool is_wifi_connected() {
  // WiFi接続が切れている場合は再接続を試みます
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi Disconnected");
    setup_wifi();
    return false;
  }
  return true;
}

void setup_sht31(uint8_t sda, uint8_t scl) {
  // 使用するピンを設定
  Wire.begin(sda, scl); // SDA: 27番ピン, SCL: 14番ピン
                        // WireはI2C通信を行うためのライブラリです


                        // SHT31のリセット
  Wire.beginTransmission(SHT31_ADDRESS); // 送信を開始するための準備
  Wire.write(SHT31_SOFT_RESET_MSB); // リセットコマンドを送信 MSB: Most Significant Bit(上位ビット)
  Wire.write(SHT31_SOFT_RESET_LSB); // リセットコマンドを送信 LSB: Least Significant Bit(下位ビット)
  Wire.endTransmission();
  /* MSB (上位ビット)、LSB (下位ビット)の順で送信します
     ここでは0x30をMSB、0xA2をLSBとして送信することで機器のリセットを行っています
     (データシート参照)
   */
  delay(500); // リセット後に500ms待機


  // レジスタ(記憶領域)のクリア
  Wire.beginTransmission(SHT31_ADDRESS);
  Wire.write(SHT31_CLEAR_STATUS_REGISTER_MSB);
  Wire.write(SHT31_CLEAR_STATUS_REGISTER_LSB);
  Wire.endTransmission();
  delay(500);

  // 内蔵ヒータOFF
  Wire.beginTransmission(SHT31_ADDRESS);
  Wire.write(SHT31_OFF_BUILTIN_HEATER_MSB);
  Wire.write(SHT31_OFF_BUILTIN_HEATER_LSB);
  Wire.endTransmission();
  delay(500);

  // シリアル文字出力
  Serial.println("SHT31 Tempreture&Humidity Measurement start!!");

}

void read_sht31(float *temp, float *hum) {
  unsigned int data[6]; // データ格納場所"data"の確保
  const unsigned int size = 6; // データサイズの設定

  // SHT31から温湿度データ取得
  Wire.beginTransmission(SHT31_ADDRESS);
  Wire.write(SHT31_SINGLE_MEASUREMENT_MSB);
  Wire.write(SHT31_SINGLE_MEASUREMENT_LSB);
  Wire.endTransmission();
  delay(300);

  // SHT31からsize=6byte=6文字のデータを受信する
  Wire.requestFrom(SHT31_ADDRESS, size);

  // データが6byte用意されるのを待つ
  while (Wire.available() != size);

  // データを読み込む
  for (unsigned int i = 0; i < size; i++) {
    data[i] = Wire.read();
  }

  // データをシリアルモニタに出力(確認用)
  // for (unsigned int i = 0; i < size; i++) {
  //   Serial.print(data[i], HEX); // データを16進数で表示
  //   Serial.print(" ");
  // }
  // Serial.println();
  // return;

  /* データは
     1. 2バイトの温度データ
     2. 1バイトのCRC(Cyclic Redundancy Check)チェックサム=データの誤り検出符号
     3. 2バイトの湿度データ
     4. 1バイトのCRCチェックサム
     のように格納されています
   */


  /* --- CRC計算と検証(今回は省略) --- */
  // uint8_t tempData[2] = {data[0], data[1]};
  // uint8_t humidityData[2] = {data[3], data[4]};

  // if (calculateCRC(tempData, 2) != data[2] || calculateCRC(humidityData, 2) != data[5]) {
  //   Serial.println("CRC error!");
  //   return;
  // }
  /* -------------------------------- */


  /* --------------------------------------------- */
  // データを可読な形に変換
  // データシートの「測定データの物理量値への換算」
  /* --------------------------------------------- */

  // 温度データを計算
  unsigned int t = (data[0] << 8) | data[1];
  *temp = -45 + (175.0 * (float)t) / 65535.0;

  // 湿度データを計算
  unsigned int h = (data[3] << 8) | data[4];
  *hum = 100.0 * (float)h / 65535.0;
}

// CRC-8計算関数
// 今回は説明を省略しますが、CRCはデータの誤り検出符号です
// 詳しくはデータシートの「CRC計算」を参照してください
uint8_t calculateCRC(uint8_t data[], uint8_t length) {
  uint8_t crc = 0xFF; // CRC初期値

  for (uint8_t i = 0; i < length; i++) {
    crc ^= data[i]; // 現在のバイトをCRCにXORする

    for (uint8_t j = 0; j < 8; j++) {
      if (crc & 0x80) {
        crc = (crc << 1) ^ 0x31; // ポリノミアル多項式 x^8 + x^5 + x^4 + 1 = 0x31
      } else {
        crc <<= 1;
      }
    }
  }

  return crc;
}

void mqtt_publish_data(const char* topic, const char* client_id, const char* message_id, const char* device_id, float temp, float humd) {
  // MQTT ブローカーとの接続が切れている場合は0.1秒ごとに再接続を試みます
  if (!client.connected()) {
    while (!client.connected()) {
      Serial.print("Attempting MQTT connection...");
      // クライアントIDを決めて接続します
      if (client.connect(client_id)) {
        Serial.println("connected");
      } else {
        Serial.print("failed, rc=");
        Serial.print(client.state());
        Serial.println(" try again in 0.1 seconds");
        delay(100);
      }
    }
  }

  /* --- ペイロードの作成 --- */
  // JSON形式でデータを格納します
  // JSONとは…
  // JavaScript Object Notationの略で、Web上でデータをやり取りする際に頻繁に使われます
  StaticJsonDocument<300> payload; // 300バイト=文字のメモリを確保

  // データ形式(Qsuプロトコル)の例
  // {
  //  "msgId" : "P00000000",
  //  "deviceId" : "SomeDevice001",
  //  "timestamp" : "2024-06-12T14:00:00.000+09:00",
  //  "data" : {
  //    "1000" : "12.3",
  //    "1001” : "200000",
  //    "1002" : "0.5"
  //  }
  // }
  // Qsuとは...
  // センサーデータをクラウドに送信するためのIoP独自のプロトコルです
  // 詳しくは配布の仕様書を参照してください

  // msgId...
  // ・接頭辞 + カウンタ
  //   9桁固定長文字列 : 接頭辞 [1文字 ]+カウンタ文字列 [8文字 ]
  // ・接頭辞の内容は以下の通り
  //   計測値(上り)   : P
  //   機器設定(下り) : C

  // deviceId...
  // ・任意の文字列

  // timestamp...
  // ・ISO8601形式で記述 : YYYY-MM-DDTHH:MM:SS.sss+09:00
  // ・+09:00は日本標準時を表します
  // ・指定がない場合はクラウド側で受信時刻を記録します

  // data...
  // ・形式は以下の通り
  // {
  //   "{Qsuアドレス }" : "{データ値 }",
  //   "{Qsuアドレス }" : "{データ値 }",
  //   ...
  // }
  // {Qsuアドレス}:10進数文字列(0~2147483647)
  // {データ値}: 文字列に変換して格納
  // ・計測データの場合、アドレスには1000〜の使用が推奨されます

  // ペイロードの作成
  payload["msgId"] = message_id;
  payload["deviceId"] = device_id;
  payload["data"]["400"] = String(1); // センサーのステータス: 1=正常, 0=異常
  payload["data"]["1000"] = String(temp);
  payload["data"]["1001"] = String(humd);

  // ペイロードを文字列に変換
  String payload_str;
  serializeJson(payload, payload_str);

  // メッセージをパブリッシュ
  Serial.print("Publishing message: ");
  Serial.println(payload_str);
  client.publish(topic, payload_str.c_str());
}

戻る