使用 ESP8266 NodeMCU 打造 WiFi 开关

在办公室希望连接到家里的电脑,内网穿透方案选的是 frp,但家里的电脑在走的时候会忘记打开,亦或者死机蓝屏的时候出现故障无法手动重启。正好手头里有两台路由器,一台 R6300 V2 刷了梅林固件,一台 WNDR 3700 V4 刷了 OpenWRT ,另外还有一块 ESP8266 NodeMcu Lua WIFI V3 ESP-12N 的板子。另外还需要一个 3.3 v/5v 的继电器,某宝上 3.9 包邮。

方案图

路由器控制 USB 电源开关

其实我第一个想到的方案就是直接通过路由器的 USB 接口控制继电器来控制 PC 主板开关,网上也有文章可以通过 echo 命令控制 USB 电源。Linux 内核官方网也有说明 Power Management for USB

Turning USB power on and off 这个是通过 GPIO 引脚驱动来控制的

# 打开电源
echo 1 > /sys/class/gpio/gpioN/value
# 关闭电源
echo 0 > /sys/class/gpio/gpioN/value

但我的 R6300V2 上没有这些 GPIO 的引脚设备文件

lede@R6300V2-5501:/tmp/home/root# tree /sys/class/gpio/
/sys/class/gpio/
└── gpio
    ├── dev
    ├── subsystem -> ../../gpio
    └── uevent
lede@R6300V2-5501:/sys/class/gpio/gpio# cat dev
254:0
lede@R6300V2-5501:/sys/class/gpio/gpio# cat uevent
MAJOR=254
MINOR=0
DEVNAME=gpio

无功而返,最后在又找到了另外一种通过命令行来控制 USB 电源开关的办法 Controlling a USB power supply (on/off) with linux

# disable external wake-up; do this only once
echo disabled > /sys/bus/usb/devices/usb1/power/wakeup

echo on > /sys/bus/usb/devices/usb1/power/level       # turn on
echo suspend > /sys/bus/usb/devices/usb1/power/level  # turn off

但我的 R6300V2 路由器里也没有这个文件,无功而返。但在这个 USB 设备文件里却发现了有意思的事情

lede@R6300V2-5501:/tmp/home/root# ls -al /sys/bus/usb/devices/ | awk '{print $9,$10,$11}'
./
../
1-0:1.0 -> ../../../devices/pci0000:00/0000:00:0b.1/usb1/1-0:1.0/
1-1 -> ../../../devices/pci0000:00/0000:00:0b.1/usb1/1-1/
1-1:1.0 -> ../../../devices/pci0000:00/0000:00:0b.1/usb1/1-1/1-1:1.0/
2-0:1.0 -> ../../../devices/pci0000:00/0000:00:0b.0/usb2/2-0:1.0/
usb1 -> ../../../devices/pci0000:00/0000:00:0b.1/usb1/
usb2 -> ../../../devices/pci0000:00/0000:00:0b.0/usb2/
lede@R6300V2-5501:/tmp/home/root#

R6300V2 支持的 USB 设备类型驱动有以下几种

lede@R6300V2-5501:/sys/bus/usb/drivers# ls
asix/        cdc_mbim/    cdc_wdm/     qmi_wwan/    usb/         usbfs/
cdc_ether/   cdc_ncm/     hub/         rndis_host/  usb-storage/ usblp/

虽然 R6300V2 号称有一个 USB3.0 和一个 USB2.0 ,但设备速度还是 480Mbps ,USB2.0 的速度,不明白这是为什么?使用 dd 测了一下速度,

lede@R6300V2-5501:/sys/devices/pci0000:00/0000:00:0b.1/usb1# cat speed
480

还是用吃灰的 ESP8266 😂

尝试了多种办法试着使用路由器的 USB 口当继电器的控制端,均无功而返,看来是不能使用路由器来搞了。正好手里还有一块 ESP8266 NodeMcu Lua WIFI V3 ESP-12N ,大二时好奇就入手了一块搞 WiFi 攻击了 😂,玩了几次一直在吃灰了,今天终于派上用场了。

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

//设置路由器和静态 IP
const char* ssid = " ";
const char* password = " ";
IPAddress staticIP(192, 168, 0, 230);
IPAddress gateway(192, 168, 0, 1);
IPAddress subnet(255, 255, 255, 0);

// web 服务器监听端口
ESP8266WebServer server(80);

// 定义相关控制针脚
const int LED = 13;
const int SW = 14;

// 定义认证用户和密码
const char* user = "esp8266";
const char* pass = "esp8266";
const char* realm = "Custom Auth Realm";
String authFailResponse = "Authentication Failed";
const char MAIN_page[] PROGMEM = R"=====(
<!DOCTYPE html><html><body><a href="pcon">PC POWER TRUN ON</a><br><a href="pcoff">PC POWER TRUN OFF</a></body></html>
)=====";

void handleRoot() {
    Serial.println("GET INDEX PAGE");
    String s = MAIN_page;
    server.send(200, "text/html", s);
}

void handlePCon() {
 Serial.println("LED on page");
 digitalWrite(LED,LOW); //LED
 digitalWrite(SW,LOW); //LED
 delay(800);
 digitalWrite(SW,HIGH);
 server.send(200, "text/html", "LED is ON");
}

void handlePCoff() {
 Serial.println("LED off page");
 digitalWrite(LED,HIGH); //LED off
 digitalWrite(SW,HIGH); //LED off
 server.send(200, "text/html", "LED is OFF"); //Send ADC value only to client ajax request
}

void setup(void) {
    pinMode(LED, OUTPUT);
    digitalWrite(LED, 0);
    pinMode(SW, OUTPUT);
    digitalWrite(SW, 1);
    Serial.begin(115200);
    WiFi.begin(ssid, password);
    WiFi.config(staticIP, subnet, gateway);
    WiFi.mode(WIFI_STA);
    Serial.println("");

  // Wait for connection
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    server.on("/", []() {
        if (!server.authenticate(user,pass))
        return server.requestAuthentication(DIGEST_AUTH, realm, authFailResponse);
        Serial.println("GET INDEX PAGE");
        String s = MAIN_page;
        server.send(200, "text/html", s);
   });
   server.on("/pcon",[]() {
        if (!server.authenticate(user,pass))
        return server.requestAuthentication(DIGEST_AUTH, realm, authFailResponse);
        Serial.println("PC POWER TRUN ON");
        digitalWrite(LED,LOW);
        digitalWrite(SW,LOW);
        delay(800);
        digitalWrite(SW,HIGH);
        server.send(200, "text/html", "PC POWER TRUN ON");
   });
   server.on("/pcoff", []() {
        if (!server.authenticate(user,pass))
        return server.requestAuthentication(DIGEST_AUTH, realm, authFailResponse);
        Serial.println("PC POWER TRUN OFF");
        digitalWrite(LED,HIGH); //LED off
        digitalWrite(SW,HIGH); //LED off
        server.send(200, "text/html", "PC POWER TRUN OFF");
   });
   server.on("/pcon", handlePCon);
   server.on("/pcoff", handlePCoff);
   server.begin();
   Serial.println("HTTP server started");
}
void loop(void) {
  server.handleClient();
}

本来第一版写的很复杂,还整了个 HTML 页面,加了个权限认证什么的,杂七杂八的的,最后还是全都去掉了,因为开机的话我习惯于命令行,还是直接在路由器上使用一个 curl 命令就能搞定(已配置好 frp 内网穿透)。另外在路由器上配置好防火墙规则,仅允许路由器本机 IP 访问 ESP8266 的 IP,这样就免去的认证的麻烦,省事儿 😂。为了安全起见,就没有把 ESP8266 监听的端口内网穿透到我的服务器。我一般都是通过 frp 连接到我家里的路由器,然后再在上面操作,当然写了别名就更方便了。