背景:之前使用 HACS / 网页版实现了电脑网络唤醒,但不能关机,不支持小爱

问题:不能语音关机,不支持小爱

环境:支持 WOL 的主板电脑,python 3.X 环境 (我这里用了群晖)

解法:1. 使用小爱添加第三方设备

           2. 第三方平台提供 API

          3. 找台服务器运行 Python 脚本虚拟一个开关设备,定义开关触发 bash 脚本

  • 方案评估:在评估方案时,在网上查资料最终方案为点灯科技 Blinker

  • 系统架构:一番研究后最终系统架构和步骤如下:

  •  Python 程序逻辑如下

流程、架构理清了,开始执行:

  1. 准备 python3.X 的环境,确保 pip3 命令可用,群晖没有 PIP3 可以装,但是后面很多库、依赖安装出错,最终放弃,我直接使用 docker python 环境

已经搞好的 (包含依赖环境) 上传到了:https://hub.docker.com/r/realwang/blinker-wol

 2. 在 python 环境中安装 Blinker 依赖环境,参考官方教程

  点灯科技 - 点灯物联网解决方案

  1. 手机端操作
1
2
3
4
5
6
7
1.下载个点灯科技APP,注册账户

2. 添加设备-->独立设备-->WIFI接入-->阿里云-->复制key

3. 右上角编辑-->添加按钮-->修改键名为"btn-pc1"类型勾选开关按键

4. 右拉添加个调试组件,锁定退出
  1. 编写 python 脚本,确保服务器 81 端口闲置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 本代码引用arduino论坛 南岛孤云
# 直接拿去用需要改5处,1,密匙,2,局域网电脑的固定IP,3,电脑ssh用户名,4,电脑ssh密码 5,电脑的MAC地址

from Blinker import Blinker, BlinkerButton, BlinkerNumber, BlinkerMIOT
from Blinker.BlinkerConfig import *
from Blinker.BlinkerDebug import *
from wakeonlan import send_magic_packet
import paramiko
import time
import subprocess

auth = 'xxxxxxxxxx' # 1,点灯app上获得的密匙

BLINKER_DEBUG.debugAll()

Blinker.mode("BLINKER_WIFI")
Blinker.miotType('BLINKER_MIOT_OUTLET')
Blinker.begin(auth)

staticip = "192.168.1.200" # 2,电脑局域网固定IP,用于检测电脑开关状态以及利用SSH关机,改为你的设置
pcusr = 'xxxxxx' # 3,电脑ssh用户名
pcpw = 'xxxxxx' # 4,电脑ssh密码

pcmac = 'ff.ff.ff.ff.ff.ff' # 5,MAC地址,改成你自己电脑网卡的

button1 = BlinkerButton("btn-pc1") # 数据键,在App里设置一个一样的开关,类型为 '开关按键',图标用滑动开关,其他随意,文本可为空
cmd1 = "timeout 0.1 ping -c 1 " + staticip # 电脑开关检测就是一个局域网内的ping,超时我设置为100ms,貌似太短或太长小爱都容易出错
lockbutton1 = False

oState = ''

def miotPowerState(state):
''' '''

global oState

BLINKER_LOG('need set power state: ', state)

oState = state
BlinkerMIOT.powerState(state)
BlinkerMIOT.print()
# 小爱控制的实际部分放在上报状态之后,因为电脑开机实际时间很长,小爱等久了她会以为没开
if state == 'true':
button1_callback('on')
elif state == 'false':
button1_callback('off')

def miotQuery(queryCode):
''' '''

global oState

# 问小爱电脑开了吗,ping一次获得电脑实际状态
if subprocess.call(cmd1, shell=True)==0:
oState = 'true'
else:
oState = 'false'

BLINKER_LOG('MIOT Query codes: ', queryCode)

if queryCode == BLINKER_CMD_QUERY_ALL_NUMBER :
BLINKER_LOG('MIOT Query All')
BlinkerMIOT.powerState(oState)
BlinkerMIOT.print()
elif queryCode == BLINKER_CMD_QUERY_POWERSTATE_NUMBER :
BLINKER_LOG('MIOT Query Power State')
BlinkerMIOT.powerState(oState)
BlinkerMIOT.print()
else :
BlinkerMIOT.powerState(oState)
BlinkerMIOT.print()

# 关机部分用paramiko的sshclient,不用密码的话可以改用密匙,具体查阅paramiko用法
def shutdownpc():

global staticip
global pcusr
global pcpw

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(staticip, username=pcusr, password=pcpw)
stdin, stdout, stderr = client.exec_command('shutdown -s -f -c "小爱将在10秒内关闭这个电脑" -t 10')

if client is not None:
client.close()
del client, stdin, stdout, stderr

# 开关键,集成了开关功能,状态报告给小爱,开关过程中的运行保护,开关后状态的更新。
def button1_callback(state):
""" """
global lockbutton1
global oState
global pcmac
dtimeout = 60 # 开关机超时默认60秒

if lockbutton1==False:
BLINKER_LOG('get button state: ', state)
if state=='on':
if subprocess.call(cmd1, shell=True)==0:
oState = 'true'
Blinker.print("检测到电脑已开,按钮状态已更新")
button1.text('已开机')
button1.print(state)
else:
Blinker.print("发送开机指令...")
oState = 'true'
lockbutton1 = True
tic = time.perf_counter()
toc = time.perf_counter()
send_magic_packet(pcmac) # 发魔术包开机
while subprocess.call(cmd1, shell=True)!=0 and toc-tic<dtimeout+2:
time.sleep(2)
toc = time.perf_counter()
if toc-tic >= dtimeout:
Blinker.print("开机超时!")
button1.text('已关机')
button1.print('off')
else:
button1.text('已开机')
button1.print(state)
lockbutton1 = False
elif state=='off':
if subprocess.call(cmd1, shell=True)==0:
Blinker.print("发送关机指令...")
oState = 'false'
lockbutton1 = True
tic = time.perf_counter()
toc = time.perf_counter()
shutdownpc() # 关机
while subprocess.call(cmd1, shell=True)==0 and toc-tic<dtimeout+2:
time.sleep(2)
toc = time.perf_counter()
if toc-tic >= dtimeout:
Blinker.print("关机超时!")
button1.text('已开机')
button1.print('on')
else:
button1.text('已关机')
button1.print(state)
lockbutton1 = False
else:
oState = 'false'
Blinker.print("检测到电脑已关闭,按钮状态已更新")
button1.text('已关机')
button1.print(state)
else:
Blinker.print("正在开机或关机中..")

# 心跳加入了电脑状态检测,更新按钮
def heartbeat_callback():

global oState

if subprocess.call(cmd1, shell=True)==0:
oState = 'true'
button1.text('已开机')
button1.print("on")
else:
oState = 'false'
button1.text('已关机')
button1.print("off")

button1.attach(button1_callback)
Blinker.attachHeartbeat(heartbeat_callback)

BlinkerMIOT.attachPowerState(miotPowerState)
BlinkerMIOT.attachQuery(miotQuery)

if __name__ == '__main__':

while True:
Blinker.run()
  1. 运行脚本,如果报错,缺什么补什么

  2. 电脑安装 openssh,ssh 测试一下到 win10

  1. 跑起来手机上的设备会显示在线,手动操作开机后用小爱测试下