機械科学生がIoT系開発に手を出して苦労した話

## これはなに
@zaurusu_aya が創造工学の授業として開発した"教室自動化大作戦!"のシステム開発の記事です。あまり正確な情報記事ではなく、単にやったことをだらだらと記述しているだけなのであまり期待せずに読んでいただけると幸いです。

### おまえは誰だ
@zaurusu_aya 鈴鹿高専機械工学科、ロボコンプロジェクトに所属しており機械班として2021,2022と高専ロボコン全国大会に出場、2023も出場予定。最近はプログラム系にも手を出し始め、その一環として今回サーバー構築に挑戦しました。
↓去年のロボコン開発記録

https://qiita.com/zaurusu_aya/items/fc6215f79d8768c16980

## "教室自動化大作戦!"とは？
教室のライトやエアコンなどの機器をwebから管理できるようにして消し忘れ防止に役立てたり、遠隔地から教室の状況を確認できるようにするシステムです。要は自分のアイデア次第で自由に作れるSwich Botみたいな感じ。

#### システム構成
サーバー:Raspberry Pi 3
このシステム全体を管理しているサーバー。

子機:ESP32 DevKitC
皆さんご存じ、超便利なArduinoライクのマイコン。これ一台でwifiもBluetoothも使えて最強。

##### 図にするとこんな感じ↓
![ず.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261082/a37d5cc5-da63-b161-3b5a-a7100fbadb3f.png)




### サーバー構築
生まれて初めてのサーバー構築。よくわからなかったので"Raspberry Pi 自作サーバー"で検索して一番上に記事が出てきたApache2で構築することにした。
Apache本体のインストール、PHPのインストールをしてApacheのドキュメントルートの設定、ファイアウォールの設定をした。ファイアウォールの設定は、外部からのVNC、SSHなどへの不正な侵入を防ぐためにグローバル側へはhttpリクエストに使われる80番,8080番ポートのみ開放。さらにローカルでのみVNCでのアクセスを可能にするため、特定のローカルIPへのみポートを開放、そのうえでノートパソコンのIPアドレスを設定したアドレスに固定した。

### ドメインの取得
ドメインの取得なんてもちろん初めてなので、とりあえず名前の聞いたことのあった「お名前ドットコム」で無料のドメインを取得した。しかしここで、情報系初心者の前にDDNSの設定という壁が立ちはだかった。当時はそもそもDDNSの「こちら側のグローバルIPアドレスが変更されたらそれをDDNSに通知する」というシステムを理解しておらず(今もいまいちわかっていないが)、お名前ドットコムではここの設定で四苦八苦していた。
わからないなりに調べていくとお名前ドットコムでDDNSを利用するにはWindows向けのソフトをサーバーで動かす必要がある??らしかったのであきらめて、DDNSの設定が楽そうなValue Domainでドメインを再取得した。

### DDNSの設定
Value Domainの設定で現在のグローバルIPとドメインを紐づけしたうえでDDNSの設定をした。Value Domainでは自分専用のDDNS設定URLがあり、サーバー側からそこにアクセスすることでこちらのグローバルIPを更新する手法をとっている。そこでサーバーにそのURLにアクセスするスクリプトを書いて、cronで1日に一回実行することでDDNSに対応させた。

### ホームページ作成
もちろん、html,css,javascriptなんて書いたことがないので基本のコーディングはChatGPTに丸投げ、リンク先など自分で設定しなければいけないところは手動で頑張って編集した。はじめはRaspberry pi内のエディタで編集していたが、大規模に編集するにはVNCで操作していたのではあまりにもラグがひどく、ストレスになってきたので途中からはサーバーフォルダごとPCに移動させてVSCodeで編集→Raspberry piに移し替える、という形態で作業していた。

![u.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261082/4854c109-c6fe-6434-0935-4ce6cbf96e7d.png)

#### web上でのボタンの切り替え

web上から現実のスイッチを操作する以上、サーバー上でスイッチの状態を保存する必要がある。そこでPHPのファイルを編集する機能で実現することにした。ON,OFFの文字列の入ったデータ保持用のtxtを作成し、操作用のページに押しボタンを作成、クリックすると飛ばされた先のページの下記のようなPHPが走ってtxtのON,OFFを書き換え、操作用のページに戻されるという仕組みにした。

~~~ruby:txtを編集するコードの例.php
<?php
            ついかしてね
?>
~~~

さらに操作用のボタンにも一工夫することで状態によって表示を切り替えるようにした。

~~~ruby:操作用ページのボタン部分.PHP
<?php
            $fg = fopen("data/buraindo.txt", "r");
            $sutetasu = fread($fg, filesize("data/denki_soreigai.txt"));
            if ($sutetasu == "ON") {
                print('<div class="button014">
                        <br>
                        <font size = 6>黒板灯</font><br><br>
                        <a href="denki_redirect">OFF</a>
                    </div>');
            } else {
                print('<div class="button015">
                        <br>
                        <font size = 6>黒板灯</font><br><br>
                        <a href="denki_redirect">ON</a>
                    </div>');
            }
            fclose($fg);
?>
~~~


### サーバー経由でESP32で動作させる
ESP32はArduinoライクのマイコンであり、ArduinoIDEを使って開発した。ここではWiFi.hというWifiを手軽に使えるライブラリを使用してネット経由でサーバーを見に行かせる。そのうえで取得した文字列に合わせてサーボを動かした。

~~~ ruby:WiFiaccess.ino
#include <HTTPClient.h>
#include <WiFi.h>

#include "Arduino.h"


#include <ESP32Servo.h>

Servo sv1;
Servo sv2;
Servo sv3;
Servo sv4;



int i = 0;

String status[] =
{
  "","","",""
};

String paststatus[] =
{
  "","","",""
};

#ifndef WIFI_SSID
#define WIFI_SSID "SSID dayooooooooooooooooooo"  // WiFi SSID (2.4GHz only)//
#endif

#ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "PASSSSSSWORRRRRRDDDDDDD"  // WiFiパスワード//
#endif

String url[] =   <!--アクセスするURLを入力-->
{
  "http:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.txt",
  "http:~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.txt",
};

void setup() { <!--ESP32Servo.hによるサーボの設定-->
  sv1.setPeriodHertz(50);
  sv2.setPeriodHertz(50);
  sv3.setPeriodHertz(50);
  sv4.setPeriodHertz(50);
  sv1.attach(32, 700, 2100);
  sv2.attach(33, 700, 2100);
  sv3.attach(4, 700, 2100);
  sv4.attach(26, 700, 2100);
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  delay(500);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  WiFi.waitForConnectResult();
  Serial.println("Wifi Connected!!");
}

void onButton() {
  for (i = 0; i < 2; i ++){
    WiFiClient client;
    HTTPClient http;

    if (!http.begin(client, url[i])) {
        Serial.println("Failed HTTPClient begin!");
        return;
    }

    // Serial.println("HTTPClient begin!");
    http.addHeader("Content-Type", "application/json");
    int responseCode = http.GET();
    if (responseCode == 200){
      Serial.println(status[i]);
      status[i] = http.getString();
    }
    http.end();
  }
}


void loop(){
  onButton();
  if (paststatus[0] != status[0]){ <!--黒板灯-->
    if (status[0] == "ON"){
      sv1.write(50);
    }

    if (status[0] == "OFF"){
      sv1.write(150);
    }
  }

  if (paststatus[1] != status[1]){ <!--それ以外-->
    if (status[1] == "ON"){
      sv2.write(50);
      sv3.write(150);
      sv4.write(150);
    }
    if (status[1] == "OFF"){
      sv2.write(150);
      sv3.write(50);
      sv4.write(50);
    }

    
  }
  for (i = 0; i < 2; i ++){
    paststatus[i] = status[i];
  }

  

  delay(500);
  sv1.write(100);
  sv2.write(100);
  sv3.write(100);
  sv4.write(100);
}

~~~

### ESP32からサーバーにデータを送る

気温、人感センサから算出される混み具合、明るさなどの情報を収集し、ESPからサーバーに送信してwebから教室の状況を確認できるようにします。
データ自体は傍受されても問題なのでhhtp postの機能を使って送信します。

ESP32側はこんな感じのソースコードでデータをサーバーに送ってます。めんどくさくなってきたのでソースコードコピペ置いておきます。

~~~ ruby:観測機.ino

#include <Arduino.h>

#include <string>

#include <HTTPClient.h>
#include <WiFi.h>

#define PIN_THERMISTOR 33  // analog read pin
#define PIN_hikari1 32
#define PIN_hikari2 35

#define PIN_Sound 19 // digital read pin
#define PIN_zinkan1 18
#define PIN_zinkan2 5
#define PIN_zinkan3 17
#define PIN_eakon 21

#define THERMISTOR_B 3431
#define THERMISTOR_R0 10000
#define THERMISTOR_T0 25

#define Control_period 1000
#define Byou_count 1000

// #ifndef WIFI_SSID
#define WIFI_SSID "SSID"  // WiFi SSID (2.4GHz only)

// #ifndef WIFI_PASSWORD
#define WIFI_PASSWORD "PASS"  // WiFiパスワード

#define RESISTOR_PULL_DOWN 10000

#ifdef ESP32
#define ANALOG_MAX 4095
#else
#define ANALOG_MAX 1023
#endif


String light_brightness0 = "on";
String light_brightness1 = "on";
float brightness0 = 0;
float brightness1 = 0;
float brightness2 = 0;
float brightness3 = 0;
String oto = "";
String oto_eakon = "";
float temperature = 0;

int zinkan1 = 0;
int zinkan2 = 0;
int zinkan3 = 0;
int eakonsw = 0;

int icount = 0;

float hikari1 = 0;
float hikari2 =0;
int soundsensor = 0;

uint32_t interval = 0;
uint32_t preinterval = 0;

String url = "http://~~~~~~~~~~~~~~~~~~~~~~~~~";

float calcTempratureByResistor(float R, int B, int R0, float T0) {
  return 1 / (log(R / R0) / B + (1.0 / (T0 + 273))) - 273;
}

float calcResistorByAnalogValue(uint16_t analog, uint16_t analogMax,
                                float resistorPullDown) {
  return (double)(analogMax - analog) / analog * resistorPullDown;
}

void setup() { 
  pinMode(PIN_Sound, INPUT);
  pinMode(PIN_zinkan1, INPUT);
  pinMode(PIN_zinkan2, INPUT);
  pinMode(PIN_zinkan3, INPUT);
  pinMode(PIN_eakon, INPUT);

  Serial.begin(115200); 

  WiFi.mode(WIFI_STA);
  delay(500);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  WiFi.waitForConnectResult();
  Serial.println("Wifi Connected!!");
}

void onButton() {
    WiFiClient client;
    HTTPClient http;

    if (!http.begin(client, String(url.c_str()))) {
        Serial.println("Failed HTTPClient begin!");
        return;
    }

    // Serial.println("HTTPClient begin!");
    http.addHeader("Content-Type", "application/json");
    int responseCode = http.GET();
    // status[i] = http.getString();
    // Serial.println(responseCode);
    if (responseCode == 200){//通信成功したらば
      
    }
    http.end();
}

void soushin() {
  if (eakonsw > 100){
    oto_eakon = "on";
  }else{
    oto_eakon = "off";
  }
  
  url = "http://zaurusuayaka.org/getdata.php?" "light_brightness0=" + light_brightness0
   + "&light_brightness1=" + light_brightness1
   + "&brightness0=" + brightness0
   + "&brightness1=" + brightness1
   + "&brightness2=" + brightness2
   + "&brightness3=" + brightness3
   + "&oto=" + oto
   + "&oto_eakon="  + oto_eakon
   + "&tempture=" + temperature
   + "&zinkan=" + (zinkan1 + zinkan2 + zinkan3);

  onButton();
  Serial.println(eakonsw);
  Serial.println(url);
  eakonsw = 0;
  icount = 0;
}

void loop() {
  // Serial.println("--------------------------------");//人感センサー確認
  zinkan1 = digitalRead(PIN_zinkan1);
  zinkan2 = digitalRead(PIN_zinkan2);
  zinkan3 = digitalRead(PIN_zinkan3);
  // Serial.println(zinkan1);
  // Serial.println(zinkan2);
  // Serial.println(zinkan3);
  // Serial.println("--------------------------------");

  if (digitalRead(PIN_eakon) == 0){
    eakonsw ++;
  }else{

  }
  // Serial.println(digitalRead(PIN_eakon));

  soundsensor = digitalRead(PIN_Sound); //音センサーでエアコンついてるか確認
  // if (soundsensor == 0){
  //   oto_eakon = "off";
  // }else{
  //   oto_eakon = "on";
  // }
  // Serial.println(soundsensor);

  hikari1 = (analogRead(PIN_hikari1) * 100) / 4096;//光センサー確認
  hikari2 = (analogRead(PIN_hikari2) * 100) / 4096;
  // Serial.println(analogRead(PIN_hikari1));
  // Serial.println(hikari1);
  brightness0 = hikari1;
  brightness1 = hikari2;


  uint16_t valAnalog = analogRead(PIN_THERMISTOR); //温度センサー確認
  float resistor =
      calcResistorByAnalogValue(valAnalog, ANALOG_MAX, RESISTOR_PULL_DOWN);
  temperature = 1.5 + calcTempratureByResistor(resistor, THERMISTOR_B,
                                               THERMISTOR_R0, THERMISTOR_T0);
  // Serial.println("Temperature: " + String(temperature) + "C");
  // Serial.println("at " + String(millis()));
  // Serial.println(temperature);
  // delay(1000);
  //ここから、getで送るurlを作成して送信する

  interval = micros() - preinterval;
  while (interval < Control_period){
    interval = micros() - preinterval;
  }

  // Serial.println(url);
 
  if (icount == Byou_count){
    soushin();
  }
  icount ++;

  preinterval = micros();
}
~~~

### サーバー側でデータを受け取る
普通にpostを受け取っています。書くのに飽きたので気が向いたら追記します。

### 初めてサーバーを構築してみて
鯖系のシステムの片鱗に触れることでサーバーの構造の理解が少しできました。
インターネット系の勉強を今まで全くしてこなかったので、すごい時間かかりましたがいい勉強になりました。特にドメイン周りの設定が本当にわからなかったのでいっぱい調べた。
いろいろな発見があるので情報系にあまり触れたことない機械系の学生はサーバー自作を体験してみるべき。