背景:之前使用 HACS / 网页版实现了电脑网络唤醒,但不能关机,不支持小爱
问题:不能语音关机,不支持小爱
环境:支持 WOL 的主板电脑,python 3.X 环境 (我这里用了群晖)
解法:1. 使用小爱添加第三方设备
2. 第三方平台提供 API,
3. 找台服务器运行 Python 脚本虚拟一个开关设备,定义开关触发 bash 脚本
- 方案评估:在评估方案时,在网上查资料最终方案为点灯科技 Blinker



流程、架构理清了,开始执行:
- 准备 python3.X 的环境,确保 pip3 命令可用,群晖没有 PIP3 可以装,但是后面很多库、依赖安装出错,最终放弃,我直接使用 docker python 环境
已经搞好的 (包含依赖环境) 上传到了:https://hub.docker.com/r/realwang/blinker-wol
2. 在 python 环境中安装 Blinker 依赖环境,参考官方教程
点灯科技 - 点灯物联网解决方案
- 手机端操作
1 2 3 4 5 6 7
| 1.下载个点灯科技APP,注册账户 2. 添加设备-->独立设备-->WIFI接入-->阿里云-->复制key 3. 右上角编辑-->添加按钮-->修改键名为"btn-pc1"类型勾选开关按键 4. 右拉添加个调试组件,锁定退出
|
- 编写 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()
|
运行脚本,如果报错,缺什么补什么
电脑安装 openssh,ssh 测试一下到 win10

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