Wi-Fiを使って機能を分散する・製作編
こんにちは。
今回は、Wi-Fi機能搭載のマイコンボードと温湿度センサ、LCDディスプレイを組み合わせて、温湿度をネットワークで共有できる温湿度計を製作しました。
製作過程
温度計の仕様
前回までの記事で決めた方針を元に、温度計の仕様を下記の通りとします。
・ESP32搭載マイコンボードで、温湿度センサ(DHT11)で測定したデータを、UDP通信でサーバとして使っているRaspberry Piに送信する。
・送信は1分に1回、温湿度計測は2秒に1回行う。送信するデータは、1分間に測定したデータ(30回分)の平均値とする。
・LCDに測定データを表示する。表示するデータは、平均化したデータではなくDHT11の読取値を直接表示する。
ハードウェアの製作
接続は以下の図のように行います。
なお、LCDの信号線(SDA, SCL)を直接ESP32ボードに接続するのはあまりよろしくありません。
ESP32ボードの信号線は3.3Vで動作するようになっていますが、LCD側の信号は5Vになっています。
繋いでみたら意外と使えるらしいという記事をいくつか見つけたので(あと、めんどうだったので)今回は直接つないでいますが、本来であればレベルシフタ等で3.3V/5Vに変換する必要があります。
ある日突然故障する可能性もあるのでご注意ください。
ソフトウェアの製作
前回の記事で作成したアベレージングフィルタを使いつつ、ArduinoIDEでコードを作成します。
ESP32側のコードは下記の通りです。
#include <WiFi.h>
#include <WiFiUdp.h>
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
#include <wire.h>
#include "AveragingFilter.h"
#define DHTPIN 13 //DHT11の信号線のピン
#define DHTTYPE DHT11 //温度センサの名前
#define SAMPLE_NUM 30 //アベレージングのサンプリング数
#define OUTPUT_CYCLE 30 //RaspberryPiにデータを送る頻度
int ctGetData; //データを取得した回数のカウンタ
const char* ssid = "YOUR_SSID"; // Wi-FiのSSID
const char* password = "YOUR_PASSWORD"; // Wi-Fiのパスワード
const char* udpAddress = "YOUR_IP_ADDRESS"; //宛先IPアドレス
const int udpPort = YOUR_PORT_NO; //宛先のポート番号
//各ライブラリのインスタンス生成
WiFiUDP udp;
DHT dht11(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd(0x27,16,2);
//LCDに温度・湿度のデータを表示させる処理の実行関数
void lcd_indicateData(float temp, float humid){
char str_temp[4+1];
char str_humid[4+1];
char indicateStr[16+1];
dtostrf(temp, 4, 1, str_temp);
dtostrf(humid, 4, 1, str_humid);
sprintf(indicateStr, "%s C / %s%%", str_temp, str_humid);
lcd.setCursor(0, 1);
lcd.print(indicateStr);
lcd.setCursor(4, 1);
lcd.write(0xDF);
}
//アベレージング処理クラスのインスタンス生成
AveragingFilter temp;
AveragingFilter humid;
void setup() {
Serial.begin(115200);
// LCD初期化
lcd.init();
lcd.backlight();
//フィルタ初期化
temp.init(SAMPLE_NUM);
humid.init(SAMPLE_NUM);
// Wi-Fi接続
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
//LCD表示
lcd.setCursor(0, 0);
lcd.print("Connect to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
//LCD表示
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(WiFi.localIP());
lcd.setCursor(0, 1);
lcd.print("Initialize...");
//UDP通信の開始
udp.begin(udpPort);
// DHT初期化
dht11.begin();
ctGetData = 0;
}
void loop() {
float tempMeasuredData;
float humidMeasuredData;
float tempSendData;
float humidSendData;
//データを計測した回数が既定回数になったらRaspberryPiに
//UDP通信でデータを送信
if (ctGetData == OUTPUT_CYCLE){
tempSendData = temp.outputAveragingData();
humidSendData = humid.outputAveragingData();
Serial.print("送信データ:");
Serial.print(tempSendData);
Serial.print("/");
Serial.println(humidSendData);
udp.beginPacket(udpAddress, udpPort);
udp.print(tempSendData);
udp.print(",");
udp.print(humidSendData);
udp.endPacket();
ctGetData = 0;
}
//データ計測・アベレージング用インスタンスへの格納
tempMeasuredData = dht11.readTemperature();
humidMeasuredData = dht11.readHumidity();
temp.storeMeasuredData(tempMeasuredData);
humid.storeMeasuredData(humidMeasuredData);
//LCDに最新の計測データを表示
lcd_indicateData(tempMeasuredData, humidMeasuredData);
//データ計測回数をインクリメント
ctGetData++;
//2秒待つ
delay(2000);
}
RaspberryPi側にも、受信したデータの処理を行うコードを作成します。
コードは下記の通りです。
なお、こちらはPythonで作成しました。
import socket
import time
import datetime
import MySQLdb
import configparser
# 設定情報読み出し
inifile = configparser.ConfigParser()
inifile.read('YOUR_CONF_FILE', 'utf-8')
UDP_IP = inifile.get(CONFファイルのパラメータデータを指定)
UDP_PORT = inifile.get(CONFファイルのパラメータデータを指定)
SQL_USER = inifile.get(CONFファイルのパラメータデータを指定)
SQL_PASSWD = inifile.get(CONFファイルのパラメータデータを指定)
SQL_HOST = inifile.get(CONFファイルのパラメータデータを指定)
SQL_DATABASE = inifile.get(CONFファイルのパラメータデータを指定)
# SQL操作系
def sql_connect():
global cnx, cursor
cnx = MySQLdb.connect(
user = SQL_USER,
password = SQL_PASSWD,
host = SQL_HOST,
database = SQL_DATABASE
)
cursor = cnx.cursor()
def sql_insertDHTdata(date, time, temp, humid):
global cnx, cursor
sql = (
'''
INSERT INTO dht11
(date, time, temp, humid)
VALUES
(%s, %s, %s, %s)
'''
)
data = (date, time, temp, humid)
cursor.execute(sql, data)
cnx.commit()
cursor.close()
def sql_disconnect():
cnx.close()
# UDPソケットの作成
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, int(UDP_PORT)))
print("UDP受信開始")
while True:
# データを受信
data, addr = sock.recvfrom(1024)
temp, humid = data.decode().split(",")
dt = datetime.datetime.now()
date = dt.date().strftime("%Y-%m-%d")
time = dt.time().strftime("%H:%M:%S.%f")
print(f"受信成功 {date} {time}:{temp}℃/{humid}%")
#SQLへ保存
sql_connect()
sql_insertDHTdata(date, time, temp, humid)
sql_disconnect()
やっとこさモノづくり
パーツをくみ上げて、ESP32にソフトを書きこんで動かしてみました。
狙った通りの動作はしていそうです。
なお、LCDの1行目にはIPアドレスを表示させています。
これを表示させることには何の意味もありませんが、なんとなくIPアドレスが表示されてるとエンジニア感が出てかっこいいかなと思ってこうしました。
要するに自己満です。
次に、RaspberryPi側のコードを動かしてみます。
受信が成功すればコンソール上にメッセージが表示されるようにしてあります。
受信成功しているようですね。
というわけで、これでシステムは完成しました!
ケーシングの製作
温度計の機能としては完成しましたが、基板、配線がむき出しの状態では、置いておくには心もとありません。
そこで、基板を収納するケースを製作します。
こういう時、3Dプリンタを使ってスタイリッシュなケースを作るのが今どきのトレンドなんだと思います。
しかし、残念ながら私は3Dプリンタを持っておりません。
仕方がないので、何か別のもので代用します。
ケースになりそうなものをみつけるべく、家の中を探していると、こんなものを見つけました。
ゴルフボールです。
この箱が基板にピッタリフィットする大きさでした。
ということで、この中に収納した結果、こんな感じになりました。
予想以上にフィットしました。
画面色と箱の色も合っているような気がします。
それに、どうせ捨てるゴルフボールの箱を再利用できたので、SDGsにも貢献できたのではないでしょうか?調子に乗りすぎですかね?
個人的には満足の行く出来栄えとなりました。
まとめ
今回で、1つの目標であった、温湿度測定機能の分離が完了しました。
今後は、CO2濃度計や気圧計を追加していく予定です。
また、天気予報やニュースヘッドラインの取得など、別の目標にも手をつけていきたいです。