0.缘起
我的笔记本和台式机为了能够随时远程使用,一直是开着的,这样太浪费电了。有一个同学给我推荐过这个功能;最近又看到一个同事用到这个。
我之前为了远程开关机,曾经做过一个硬件的reset开关。现在说说这个Wake On Lan的实现,事实上,你可以自己做个小嵌入式的设备来远程唤醒家里的电脑:
1.远程唤醒的机制
- 这是一个UDP包
- 不是组播,而是发往255.255.255.255的广播包
- 一个帧 = 6个连续的0xff + 目标设备的MAC地址*16
- 端口:9,7(老式设备)
2.实现
2.1 Python远程唤醒代码
import socket
def send_magic_packet(mac_address):
# 构建 Magic Packet
mac_bytes = bytes.fromhex(mac_address.replace(':', ''))
magic_packet = b'\xff' * 6 + mac_bytes * 16
# 创建 UDP 套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
# 发送 Magic Packet
sock.sendto(magic_packet, ('255.255.255.255', 9))
sock.sendto(magic_packet, ('255.255.255.255', 7))
sock.close()
# 例子:发送唤醒信号到 MAC 地址为 '00:11:22:33:44:55' 的设备
send_magic_packet('00:11:22:33:44:55')
2.2 嵌入式平台
(如果你家的路由器不支持wake on Lan),或者你没有路由器访问权限:
ESP32
2.3 外网控制
通过mqtt公网服务器中转。代码参见附录A 使用mqtt公网服务器唤醒本地台式机
3.测试
成功了,效果很惊艳。单台设备测试因为udp帧需要从另外一台设备发出,会不太方便,可以在其他设备上设定一个定时任务:
#sleep后面是延时的时间,单位是秒
#nohup不需要保持terminal处于打开状态
#stdout也倒向了/dev/null不会卡死
nohup bash -c 'sleep 90 && python3 wol_test.py' >/dev/null 2>&1 &
3.1 唤醒帧的可行性
执行完这一步,计算机休眠状态可以远程唤醒。
3.2 BIOS 部分
执行完这一步,机器关机也能唤醒。
4.总结
基于wol的远程开关机需要如下配置和参数:
- 硬件:BIOS打开网卡 wake-on-lan
- 系统:网卡PCI-E的电源管理参数修改。
- 局域网:要有一个常备的节点能够发送UDP包(如果没有服务器可以使用ESP32写一个程序)
- 这里的代码是利用了公司的一个linux服务器
- mqtt服务器,可以用免费的公网服务器。
- 软件
- mqtt服务器连接参数
- 手机或者笔记本上的mqtt客户端
- mqtt自定义topic
- mqtt自定义远程唤醒帧的处理。
附录A 使用mqtt公网服务器唤醒本地台式机
A.1 服务代码
[Unit]
Description=wol service by fengxh
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/fengxh
ExecStart=/home/fengxh/daemon_wol_fengxh.py
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
A.2 侦听远程mqtt服务器指定键值,然后调用本地wol唤醒帧的程序
#!/usr/local/bin/python3
# -*- coding: utf-8 -*-
# 获取当前脚本文件所在目录的父目录,并构建相对路径
import os
import sys
current_dir = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.join(current_dir, '..')
sys.path.append(project_path)
sys.path.append(current_dir)
import paho.mqtt.client as mqtt
import subprocess
def is_cmd_wakeup(cmd_line):
# 要检查的字符串
text = cmd_line
# 转换为小写
text_lower = text.lower()
# 检查是否包含 wakeup key
if 'xxxxx' in text_lower:
return True
else:
return False
def exec_sh(script_path):
# 获取当前工作目录
current_directory = os.getcwd()
# 构造脚本路径
script_path = os.path.join(current_directory, script_path)
# 执行脚本
try:
result = subprocess.run(['bash', script_path], capture_output=True, text=True, check=True)
print('Script output:', result.stdout)
print('Script error output:', result.stderr)
except subprocess.CalledProcessError as e:
print(f'Error executing script: {e}')
# 处理接收到的消息的回调函数
def on_message(client, userdata, msg):
print(f"Received message: {msg.payload.decode()} on topic: {msg.topic}")
if is_cmd_wakeup(msg.payload.decode()):
exec_sh('./delay3min_wol.sh');
def connected_mqtt_server_loop():
# MQTT 服务器的配置
MQTT_BROKER = 'test.mosquitto.org' # 替换为你的 MQTT 服务器地址
MQTT_PORT = 1883 # 通常为 1883
MQTT_TOPIC = 'xxxxxxxxxxx' # 可以用uuidgen生成,很难重复
# 创建 MQTT 客户端实例
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
# 设置连接和消息处理的回调函数
client.on_message = on_message
# 连接到 MQTT 服务器
client.connect(MQTT_BROKER, MQTT_PORT, 60)
# 订阅指定的 topic
client.subscribe(MQTT_TOPIC)
# 启动循环以持续接收消息
client.loop_forever()
if __name__ == "__main__":
connected_mqtt_server_loop()
A.3 调用唤醒代码,不依赖stdio的处理:
里面做了延时的处理等:
nohup bash -c 'sleep 10 && python3 wol_test.py' >/dev/null 2>&1 &