
M5Stack の ATOM Lite で WOL 射出ボタンを作った話
あらまし
楽天リベーツのポイントキャンペーンでお得だったので、新しく Lenovo のミニPCを買ったのはいいけど、機材を設置する PC ラックのデザイン上、どうしても電源ボタンが押しにくい位置にしか設置できない。しかたないので PC の Wake On LAN 機能を有効にし、小デバイスから WOL パケットを射出することで電源スイッチを入れるという荒業を行うことにした。
このノートはその備忘録。
最終コード
ATOM Lite のボタンを押すと、WiFi 経由で WOL マジックパケットがプログラム内に埋め込まれた MAC アドレスの PC に射出される。これによって WOL で起動するように設定されてる PC は無事に起動する。
以下が ATOM Lite を WOL 射出機に変身させるためのスケッチ。
// Build Switch DEBUG中なら1 最終版なら0
#define DEBUG 0
// Button & LED
#include <FastLED.h>
#define PIN_BUTTON GPIO_NUM_39
#define PIN_LED GPIO_NUM_27
#define NUM_LEDS 1
static CRGB Leds[NUM_LEDS];
#define RGB888(r,g,b) ((CRGB)(((r) << 16) | ((g) << 8) | (b)))
#define LED_BLANK RGB888(0,0,0)
#define LED_IDLE RGB888(0,32,0)
#define LED_CONNECT RGB888(0,0,128)
#define LED_ERROR RGB888(128,0,0)
// Networking
#include <WiFi.h>
#include <WiFiUdp.h>
#include <WakeOnLan.h>
const char *ssid = "@@ SSID @@"; // 無線AP の SSID 名
const char *passwd = "@@ PASSWORD @@"; // 無線AP のパスワード
const char *MACAddress = "XX:XX:XX:XX:XX:XX"; // WOLパケット射出先のPCのMACアドレス
WiFiUDP UDP;
WakeOnLan WOL(UDP);
#define DebugPrintf(...) if(DEBUG){Serial.printf(__VA_ARGS__);}
// Button & LED
void set_LED_color(CRGB c){
Leds[0] = c;
FastLED.show();
}
uint8_t get_button_state(void){
return !digitalRead(PIN_BUTTON);
}
void mySetup(void){
#if DEBUG
Serial.begin(115200);
Serial.flush();
delay(50);
#endif
pinMode(PIN_BUTTON, INPUT_PULLUP);
esp_sleep_enable_ext0_wakeup(PIN_BUTTON, LOW);
FastLED.addLeds<WS2812, PIN_LED, GRB>(Leds, NUM_LEDS);
FastLED.setBrightness(20);
}
// Networking
void setup_wifi(void) {
DebugPrintf("Wifi connecting to %s", ssid);
set_LED_color(LED_CONNECT);
WiFi.begin(ssid, passwd);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
set_LED_color(LED_BLANK);
delay(250);
DebugPrintf(".");
set_LED_color(LED_CONNECT);
}
DebugPrintf("\nConnected\n");
DebugPrintf("IP address: %s\n", WiFi.localIP().toString());
}
void wakeup_pc(void) {
DebugPrintf("Send WOL magic packet to: %s\n", MACAddress);
WOL.setRepeat(3, 100);
WOL.calculateBroadcastAddress(WiFi.localIP(), WiFi.subnetMask());
WOL.sendMagicPacket(MACAddress);
}
void disconnect_wifi(void) {
DebugPrintf("WiFi disconnecting.");
WiFi.disconnect(true, false);
while (WiFi.status() == WL_CONNECTED) {
delay(250);
DebugPrintf(".");
}
DebugPrintf("\nDisconnected\n");
}
// Deep Sleep
void go_deep_sleep(void){
while (get_button_state()){
delay(10);
}
DebugPrintf("Go into Deep Sleep\n");
set_LED_color(LED_IDLE);
delay(100);
esp_deep_sleep_start();
}
// Short press
void short_press(){
setup_wifi();
wakeup_pc();
disconnect_wifi();
}
// Long press
void long_press(){
set_LED_color(LED_ERROR); // TBD
delay(2000);
}
// System Reserved
void setup(void) {
mySetup();
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0){
int count = 200;
delay(10); // for debounce.
while (get_button_state() && count){
count --;
delay(10);
}
if (count){
short_press();
} else {
long_press();
}
}
go_deep_sleep();
}
void loop(void){
delay(100); // do nothing
}
解説
当初はエンタープライズ用 amazon dash ボタンとかで作れないかと考えたが、現在は入手できないみたいで、かつ代替品は値段が高いのでこの線はあきらめた。
他に alexa のスキルを作って WOL パケットを投げるようにしようかと思ったが、深夜に PC の電源入れようとして声張り上げることになるのもよろしくないかと思い断念。アメリカリージョンだと WOL の alexa スキルがストアに上がっているっぽいのだが、日本語ストアにはなさそう。
最終的には M5Stack の ATOM Lite を使用することにした。安さは正義。Auduino をいじるのは初めてだったけど、M5Stack なら手間いらずにいろいろ作れそうなので今後もこの周辺の知識は役立ちそうなので。
最初は loop() 内でボタンの状態を拾って押されたら WOL パケットを投げるという実装だったが、思ったよりも電流を食うらしく長時間運用しているとデバイスが温かくなっていたので、実装を変えた。起動直後に deep sleep 状態へ移行し省電力モードで待機。ボタン押下により割り込みで復帰させるという実装に。簡易な USB-C 電流メーターでの計測なのでいい加減な値ではあるが 100mA 超えだった消費電流が 20mA とかになってくれた。温かくもなくなったのでまあ良しとする。できたらもっと下がってほしいところだが、仕方ない。クロックを下げるか?でも deep sleep になっているのであまり効果はなさそう。
あと、他のブログを見ると M5Atom.h 内のサポートライブラリを使わずに FastLED ライブラリで LED を直接制御したほうがよさそうだったので、それにしたがって FastLED 直接制御するように書き換えた。LED が一つしかない ATOM Lite で、単一スレッドからしか LED の書き換えを行わないなら、FastLED で直接書き換えるほうが安くつくと思う。
ATOM Lite では LED の光量を上げすぎるとデバイスが壊れる可能性がある。サポートライブラリではこの辺りのセーフティ機構が仕込まれているが、直接制御する場合はプログラム作成側でこのケアをする必要があるので注意。ここでは光量を 20 に設定している。
ボタンの制御もサポートライブラリを使わずに直接、制御ピン情報を読み取ることにした。サポートライブラリ内でチャタリング(デバウンス)対策処理が追加されているために、現在ボタンが押されているかどうかの判定が思った通りの挙動にならなかったので。
LED と ボタンの制御を自前で作ったのでサポートライブラリが要らなくなった。少しでも軽くなったか? スケッチのサイズがあまり小さくなっていないので、裏で何か走ってるかもしれない。
将来の拡張用に、ボタンの「一度押し」と「2秒間長押し」に別の機能を設定できるようにしてある。2秒間長押しで PC をシャットダウンするようにできると仕様として美しいかな(?)と思っているが、まあそれは必要なら実装するということで。
