はじめに:なぜ大学のエアコンは我々の実験の邪魔するのか?
私の研究室では、量子物理学に基づく精密な測定実験を昼夜問わず行っています。
この実験は非常に気温にシビアで、温度が変わって使用するレーザーの「モード」のロックが外れると、欲しい周波数が出なくなり実験データは完全にゴミになります。温度調節用のペルチェでフィードバック制御して温度を一定に保っていますが、室温が20℃と25℃辺りで温度の制御設定を変える必要があります。
ちなみに連続測定時のデータ量はバイナリにも関わらず約1.5GB/日。もしレーザーのモードロックが外れてしまったら、この大容量の無駄なデータがただただ垂れ流しになるという地獄が出来上がります(1敗)。
しかも研究室の空調は節電のため、18時と0時に集中管理で一度OFFになるという酷い仕様になっています。当然用があれば付けっぱでもよいので、他の研究室の教授なんて0時にエアコン押してから毎晩帰ってるらしいですよ。
といった具合で、夏は基本的に実験がハードです。神聖な研究を何だと思っているんだ…
さてこの問題について、モードロックが外れたことを検知する機能を作ってもよいですが、根本的な解決にはなりません。根本的な原因は室温の変化ですし、週末の夜2時とかに通知が来た所で寝てますし普通に。
解決方法として現在のレーザーから気温が変わってもモードが変わらないレーザーに交換、もしくは現在のレーザーのまま室温を制御、が上げられます。
前者の方法は問題の完全解決ではありますが、制御回路等を新作しないといけませんし、レーザーの性能の評価だって必要です。仕事量としてもレーザーの新規作成は立派な卒論を書けるような大変なものです。お金だってそれなりにかかりますしね。
それに対し後者は楽チンです。誰かが夜な夜なエアコンの電源を入れるだけですから。
ということで、指定時刻に物理ボタンを押すだけのスタンドアローンな装置を2025年の夏前に自作しました。
SwitchBotなどのスマートリモコンの導入も考えましたが、以下の理由で見送りました。
- 実験室にWi-Fiを置いていない
- 弊研究室でWi-FiルーターはEMI源と見なされています
- 欲しいのは「特定の時間に1回ボタンを押す」という単機能だけ
後は自作したほうが安そうですしね。
要件定義
- 機能:毎日 18:05 と 00:05 にサーボモーターを動かし、「0°→60°→0°」のような軌道でエアコンのリモコンの「運転」ボタンを押下する。
- 通信:一切無し
- 時刻管理:ある程度高精度な電池バックアップ付きのRTCモジュール
- 復帰要件: USB抜き差し後もRTC保持で自律復帰
-
安全・運用:
- 外部電源はUSB 5Vのみ。
- エアコンのリモコンに両面テープで直接貼り付けて固定するため、押し込みすぎて反発力で剥がれ落ちないストローク調整が必要。
- サーボは動作時だけ駆動(
detach()を使い、待機時の唸り音防止と省電力を両立)。
構成(部品と役割)
-
MCU:Seeed Studio XIAO RP2040
小さくて安くて軽くてそこそこ賢い、USB Type-C給電ができる良いマイコン。 -
RTC:Adafruit ADA-3013 DS3231
小型低ドリフトのRTCモジュール。I2C通信可能。CR2032電池でバックアップ。 -
サーボモーター:SG90
いつもの。
【配線】
- DS3231:VCC=3.3V、GND、SDA=D4、SCL=D5
- SG90:信号=D10、電源=5V (VBUS)、GND共通
サーボ用にキャパシタを入れたい人はより安定するでしょうが、この程度の負荷なら無くても十分安定して動作します。
RTCだけでMCUを3個買えるのでRTCとMCUはジャンパでつなぎ、一番安いサーボのケーブルはちょん切って裂いてMCUの裏面にハンダで接続。
【配置】
3DPで筐体作っても良かったのですが、売り物でもないので適当固定です。
サーボのネジ穴にRTCを共締めし、サーボのボックスに分厚い両面テープでMCUを貼り付けるだけの簡単なお仕事。
実装 1:Arduino化とUF2書き込み
XIAO RP2040はArduino IDEで開発できます。
ボードマネージャから XIAO RP2040 を追加し、書き込みはBOOTボタンを押しながらPCに繋ぎ、マウントされた RPI-RP2 ドライブにコンパイル済みの UF2 ファイルをドラッグ&ドロップするだけでもOKです。まずはLチカで生存確認を済ませます。
実装 2:最大のハマりどころ
今回一番時間を溶かしたところです。
XIAO RP2040(Arduino環境)内で
digitalWrite(10) は D10ピン ではありません。
RP2040では「数字だけ」を指定すると内部のGPIO番号扱いになり、シルク印刷されているピン番号と盛大にズレて混乱します。
デジタルピン(Dピン)を指定したいときは、必ず digitalWrite(D10) のように“D付きの定数”で指定してください。私はこれに気づかず、テスターで全ピンの出力をチェックする羽目になりました。
実装 3:DS3231の時刻合わせ(PC時刻に合わせる)
ネットワークを持たないため、最初にPCから現在時刻をRTCに書き込む必要があります。
PCからISO8601形式の文字列をシリアル通信で送り、Arduino側でパースして rtc.adjust() する仕組みを作りました。
ちなみにコードの大半はChatGPTが生成しています。物理工学徒はそこまでコードは書けないのです。良い時代に生まれたものだ…
【Arduino側:時刻書き込み用スケッチ】
#include <Wire.h>
#include <RTClib.h>
RTC_DS3231 rtc;
bool parseISO(const String& s, DateTime& out) {
// 期待するフォーマット: "YYYY-MM-DDTHH:MM:SS"
if (s.length() < 19) return false;
int y = s.substring(0,4).toInt();
int m = s.substring(5,7).toInt();
int d = s.substring(8,10).toInt();
int hh= s.substring(11,13).toInt();
int mm= s.substring(14,16).toInt();
int ss= s.substring(17,19).toInt();
if (!y || !m || !d) return false;
out = DateTime(y,m,d,hh,mm,ss); // PCからの信号のタイムゾーンはJST(日本標準時)としてそのまま扱う
return true;
}
void setup() {
Serial.begin(115200);
Wire.begin(); // XIAO既定: SDA=D4, SCL=D5
if (!rtc.begin()) { Serial.println("RTC NOT FOUND"); while(1); }
Serial.println("Send PC time as ISO8601, e.g. 2025-08-06T23:10:00");
// 1行受信して設定
while (!Serial.available()) { delay(10); }
String line = Serial.readStringUntil('\n'); line.trim();
DateTime dt;
if (parseISO(line, dt)) {
rtc.adjust(dt);
Serial.print("RTC updated to: "); Serial.println(line);
} else {
Serial.println("Bad format. Expected YYYY-MM-DDTHH:MM:SS");
}
}
void loop() {
// 確認用に1秒ごとに現在時刻を出力
DateTime now = rtc.now();
Serial.printf("%04d-%02d-%02d %02d:%02d:%02d\n",
now.year(), now.month(), now.day(),
now.hour(), now.minute(), now.second());
delay(1000);
}
【Python側:時刻送信スクリプト】
上記のスケッチを書き込んだ後、以下のPythonスクリプトを実行してPCの正確な時刻と同期します。
# pip install pyserial
import serial, time
from datetime import datetime, timedelta
PORT = "COM4" # ← XIAOのポートに合わせて書き換え(デバイスマネージャー等で確認)
BAUD = 115200
def main():
# 次のちょうどの秒に合わせるための待機処理
now = datetime.now()
target = (now + timedelta(seconds=1)).replace(microsecond=0)
time.sleep((target - now).total_seconds())
iso = target.isoformat() # "YYYY-MM-DDTHH:MM:SS"
print("Sending:", iso)
with serial.Serial(PORT, BAUD, timeout=2) as ser:
# Arduino側スケッチは起動直後に待ち受けている想定
ser.write((iso + "\n").encode())
try:
print("Device:", ser.readline().decode().strip())
except:
pass
if __name__ == "__main__":
main()
一度時刻を書き込めばボタン電池があるから、電源を繋がなくても年単位でRTCが時を刻み続けてくれます。卒業するまで余裕で安泰だね。
※(修論書き終わっても1分も時間はズレてなく余裕でしたね)
実装 4:サーボの制御
ここが本機の重要な部分です。
両面テープでリモコンに貼り付けるだけなので、勢いよく「ガッ」と押しすぎると、モーターのトルクで装置本体がリモコンから剥がれ落ちます。
これを防ぐため、以下の対策を入れました。
-
起動時の暴れ防止:
attach()直後に現在角度をwrite(currentAngle)で指定する。 -
滑らかな押し込み: 角度を少しずつ刻んで動かす(
STEP_DEG,MOVE_MS)。 -
脱力: 動作が終わったら即座に
detach()してサーボの力を抜き、ジーという唸り音を防ぐ。
要は落ちないようにゆっくり押下するだけです。
角度とかはいい感じに調整するか、あとはドラレコ用の強力な両面テープでも買ってくださいな。
完成コード(本番稼働用)
時刻設定が終わったら、以下のコードを書き込んで完成です。USBで給電さえすれば毎日18:05と00:05に勝手にボタンを押してくれます。RTCの電池が切れるまでUSBをいくら抜き差ししても大丈夫です。
#include <Wire.h>
#include <RTClib.h>
#include <Servo.h>
RTC_DS3231 rtc;
Servo s;
const int SERVO_PIN = D10;
const int PRESS_ANGLE = 60; // 押し角(環境に合わせて調整)
const int MOVE_MS = 900; // 0→60° の移動時間(遅くしたいほど増やす)
const int STEP_DEG = 2; // 1〜3推奨
const int DWELL_MS = 250; // 押し当て時間
int currentAngle = 0;
void moveSmooth(int target, int duration_ms) {
int delta = target - currentAngle;
int steps = max(1, abs(delta) / STEP_DEG);
float stepDelay = (float)duration_ms / steps;
int dir = (delta >= 0) ? STEP_DEG : -STEP_DEG;
for (int i = 0; i < steps; i++) {
currentAngle += dir;
s.write(currentAngle);
delay((int)stepDelay);
}
currentAngle = target;
s.write(currentAngle);
}
void pressSequence() {
s.attach(SERVO_PIN);
s.write(currentAngle); // attach直後の“ガッ”軽減
delay(50);
moveSmooth(PRESS_ANGLE, MOVE_MS);
delay(DWELL_MS);
moveSmooth(0, MOVE_MS);
s.detach(); // 静音・省電力・反発落下防止
}
void setup() {
Serial.begin(115200);
Wire.begin(); // XIAO既定: SDA=D4, SCL=D5
if (!rtc.begin()) {
Serial.println("RTC NOT FOUND");
while (1);
}
}
void loop() {
DateTime now = rtc.now();
// 18:05:00 と 00:05:00 に発動
bool t1 = (now.hour() == 18 && now.minute() == 5 && now.second() == 0);
bool t2 = (now.hour() == 0 && now.minute() == 5 && now.second() == 0);
if (t1 || t2) {
Serial.printf("[%02d:%02d:%02d] press\n", now.hour(), now.minute(), now.second());
pressSequence();
delay(1200); // 同一秒内での二重実行防止
}
delay(100);
}
動作の様子
テストとして温度ボタンを押してみましょう。
実験は成功だ!!
このgifでは温度が2回変わってる?ON/OFFボタンではちゃんとできたからヨシ!
さいごに
とりあえずこれで夜間にエアコンが切れて大量ののゴミデータが生成される悲劇は回避できるようになりました。最先端の量子物理学の実験環境が、テープで貼り付けた安いSG90によって守られています。立派なおもちゃです。
両面テープで貼ってるだけなので、夜間に実験しない時は剥がしてしまえば元通りです。節電にも配慮できている、ヨシ!!
今後は余っているピンを使って、エアコンのON/OFFを示す緑LEDの点灯状態をフォトトランジスタで読み取ってON/OFF判定を行ったり、BME280のような温度センサを追加して純粋な気温ログを取ったりといった拡張をしても面白いかもしれません。
