请选择 进入手机版 | 继续访问电脑版

『瀚思彼岸』» 智能家居技术论坛

 找回密码
 立即注册
查看: 11355|回复: 23

esp8266远程开关电脑

[复制链接]

1

主题

10

帖子

148

积分

注册会员

Rank: 2

积分
148
金钱
138
HASS币
0
发表于 2020-11-26 19:38:38 | 显示全部楼层 |阅读模式
本帖最后由 kappa8086 于 2020-11-26 20:38 编辑

搜英文资料时发现有不少人弄过这个,至于为什么不用 WOL 而用另外的唤醒方案,每个人给出的理由都不太一样,我说说我的:

1 关机功耗,在主板打开 WOL 功能时,网卡在关机时要处于运转状态,大概连带着主板很多元件要一起工作,导致关机功耗超过 5W(随主板和电源的不同而不同,见过高达十几 W的),而这个额外的解决方案,0.5W 都用不到,可以把 BIOS 里的相关设置统统关掉;
2 WOL 失败的情况很多,比如多数主板断电后就无法唤醒了;又比如我是双系统,切换就可能导致 WOL 状态不在;休眠状态大概率无法唤醒;
3 WOL 只能用于有线网络;
4 ESP 方案可以把电脑的开关机状态集成到 Hass,避免远程开机后忘了关,同时可以快速的一键关机,不管运行的是什么 OS。

好了,1234条这么多,其实就是折腾玩具也需要一个主题。技术人员的玩具很便宜,零件加起来20元左右,淘宝运费都可能比这贵。

材料清单:
1 ESP-01S模块一个,淘宝卖的全是这个,6-7元。最好不要是 ESP-01,否则可能就需要额外的手术了;
2 3.3V 稳压模块一个,1-2元;
3 面包板适配器一个,1元;
4 5K,10K 电阻各一个,成本几分钱。。。嗯,大概没人会单个卖,100个起步,2-4元;
5 杜邦线若干,如果有几条50cm公对母,有些特别的连接会很方便;
6 最后是esp-01专用TTL烧写器,8-9元,写固件用。
materials.jpg


主板电源开机的原理很简单,让 PWR SW 两脚短接一下就行。最自然的考虑是用继电器,但继电器块头相对比较大,而且有大材小用之感,关键是 esp-01 定制的继电器模块,根本都没有给其他的 GPIO 留余地,改造这东西看上去很蠢。
我经过测量发现,PWR SW 正极的电平 3V 而不是 5V,就是说直接怼到 GPIO 上完全没问题。短接 PWR SW 再换个说法就是把它的正极拉到 GND 那么一瞬间,对于 GPIO 来说就是输出 LOW 几百毫秒的样子。嗯,就这么干。

然后是状态判断,有两种选择,1 是接同在前面板的 PWR LED,2 是接任意一个 +5V,但要保证独立于 +5VSB。比如 USB 就不太行,在有些时候关机是有电的。
所以初看起来 PWR LED 是个好选择,但问题是这一路的电阻并不能确定,导致电压可能介于 3V-5V 之间变化,我测量过我的主板,开机状态切断 LED 就是5V,连通降为 3V,是限流电阻的效果,但这也意味着落在 GPIO 输入上也会有一瞬间是5V,而原则上讲这不安全,反而要在稳压电路上动点心思了。再加一个降压模块有点难看,而且往往这东西会要求输入比输出至少高 1V,输入端降到 3V 左右的时候反而就失败了。
所以后来选择了和供电一起,直接从 ATX24针电源插头上偷电。


为什么要说“偷”电呢?。。。那个,我的意思是。。。窃电不能算偷。
atx24vs3pin.jpg
知道公对母线的公头用在哪了吧-_-

一开始,我发现我的 USB 关机是有电的,所以把从前面板 USB 端口取电的排针都焊好了,然后我发现只要我是从 linux 系统关机的,USB 就没电了。。。这事居然还和 BIOS 设定无关,是 Windows10 的快速开机干出来的,我把这电源选项关了,然后关机连最后 1W 的耗电也没有了,同时 USB 就彻底关机无用了。
这使得我放弃了从 USB 取电的方法,转而动起了直接走电源的心思。不过这样很好,这个方法和主板/BIOS 无关,任何电源都是适用的。


连接针脚:(插头背面视角)
atx24_vs_3.png
红的都是+5V,黑的全是 GND,除 +5VSB 只有一个针脚可用其他两条连哪个都行。

一般机箱的前面板都可以拆开来里面有一点空间,放这个足够了。而刚好电源按钮也在这里,如果你不打算从主板那边多扯条线过来,完全可以在这里对线动点手术(但要用表确定下哪边是正极,负极=GND)。
pc-frp.jpg

(如果你的机箱没有这个空间,那可能要考虑外挂。别想把这东西就挂在电源插头上跑,它是不能放在机箱里面的,因为电磁屏蔽,无线网络会连接不上。)

如果是在前几年,把这个功能直接产品化到机箱里,我估摸着会很受欢迎。所额外需要的就是带一个 ATX24 双向转接头,让装机者把它插在电源和主板之间,光明正大的窃...呃,取电。现在这个从菊花端强行插入的方法,连接稳固性需要注意一下。

回复

使用道具 举报

1

主题

10

帖子

148

积分

注册会员

Rank: 2

积分
148
金钱
138
HASS币
0
 楼主| 发表于 2020-11-26 20:08:08 | 显示全部楼层
本帖最后由 kappa8086 于 2020-11-26 20:35 编辑

模块整体连接图
esp_sys_draft.png

10K电阻给 RX(GPIO3)端口做下拉,8266 多数 GPIO 引脚都没有下拉电阻配备,甚至,nodemcu 的 API定义都没有下拉模式,只能外部电路手动;
5K电阻配合下拉给输入分压。

至于控制开关的为什么是 GPIO2 而不是 GPIO0,用 IO0 也可以,但 ESP-01S 上IO2 的上拉接有 LED,无论主动还是被动接通它都会亮,这很好,尤其对于调试。如果你想的话,GPIO0 还可以接一路 RESET SW,不过我暂时用不到,多条线多分乱,暂不开发这个功能。真要遇到死机重启的情况,还有长按4秒这个杀着呢。

实物手工

esp_sys_prod.jpg
咳,品相上这已经算不错了,我是万能的热融胶党,平时自认还算心灵手巧的我,拿起电烙铁就怀疑自己是帕金森。




固件用 nodemcu,延时这种东西还是需要写点代码控制的,不能依赖网络的命令间隔。如果你不想因为一个丢包就让电源开关变成长按的话。
(固件在 https://nodemcu-build.com/ 定制,注意比最小运行至少要多选一个 mqtt)

控制代码 [init.lua]
macaddr=wifi.sta.getmac()
print('MAC Address: ',macaddr)
print('Chip ID: ',node.chipid())
print('Heap Size: ',node.heap(),'\n')

function createAP()
    wifi.setmode(wifi.SOFTAP,false)
    wifi.ap.config({ssid='NODEPCTR_'..string.sub(string.gsub(macaddr,':',''),7)},false)
    wifi.ap.setip({ip='192.168.1.1',netmask='255.255.255.0',gateway='192.168.1.1'})
    print("AP created.")
    --5分钟后务必自动重启,防止和路由同时掉电后长期失联
    tmr.create():alarm(300000, tmr.ALARM_SINGLE, function() node.restart() end)
end

netcfg=file.open('net.cfg')
if netcfg == nil then
    createAP()
    tmr.create():alarm(1000, tmr.ALARM_SINGLE, function() startServer() end)
else
    local cfgitems={}
    for cl in string.gmatch(netcfg:read(),'[%S]+') do table.insert(cfgitems,cl) end
    netcfg:close()
    ssid=cfgitems[1] wifipasswd=cfgitems[2]
    mqttip=cfgitems[3] mqttport=cfgitems[4]
    mqtopic=cfgitems[5]
    tochkpwr=cfgitems[6]=='on'  --使用GPIO3的判断条件,否则模块将失去调试及更新能力

    wifi.setmode(wifi.STATION)
    wifi.sta.config({ssid=ssid,pwd=wifipasswd})
    wifi.sta.autoconnect(1)

    print("Connecting to AP...")
    tmr1=tmr.create()
    wifiretries=5
    tmr1:alarm(3000, tmr.ALARM_AUTO, function()
        if wifi.sta.getip() ~= nil then
            ip, nm, gw=wifi.sta.getip()
            print("Connected using IP ",ip,'#',nm,'->',gw)
            tmr1:unregister()
            startServer()
        else
            wifiretries=wifiretries-1
            if wifiretries <= 0 then
                mqttip=''
                createAP()
                tmr1:unregister()
                startServer()
            end
        end
    end)
end

trigger_pin=4 --3=IO0,4=IO2,10=IO1/TX,9=IO3/RX
chkpw_pin=9
if tochkpwr then
    gpio.mode(chkpw_pin,gpio.INPUT)
    powstat = gpio.read(chkpw_pin)==gpio.HIGH and 'ON' or 'OFF'
    print('Init Power is '..powstat)
end
pow_hold_time=200          --短按
powforce_hold_time=4500    --长按
gpio.mode(trigger_pin,gpio.INPUT)
tmr2=tmr.create()

function trigger_pow(force)
    gpio.write(trigger_pin,gpio.LOW)
    gpio.mode(trigger_pin,gpio.OUTPUT)
    print("Relay on...")
    ontime=tmr.now()
    tmr2:alarm(force and powforce_hold_time or pow_hold_time, tmr.ALARM_SINGLE, function()
        gpio.mode(trigger_pin,gpio.INPUT)
        gpio.write(trigger_pin,gpio.HIGH)
        el=(tmr.now()-ontime)/1000
        print("Relay off in "..el.." ms.")
    end)
end

configpage=string.format([[<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /><title>Network Config</title><style>label{display:inline-block;width:120px;}</style></head>
<body>
    <form method="post" enctype="text/plain">
        <label>MAC addr: </label>%s<br />
        <label>SSID: </label><input type="text" name="ssid" value="%s" /><br />
        <label>Password: </label><input type="text" name="pwd" value="%s" /><br />
        <br />
        <label>MQTT server: </label><input type="text" name="mqttip" value="%s" /><br />
        <label>MQTT port: </label><input type="text" name="mqttport" value="%d" size="5" /><br />
        <label>MQTT topic: </label><input type="text" name="mqtopic" value="%s" />(/ctrl)<br />
        <label>Check Power: </label><input type="checkbox" name="chkpwr" value="on" /><br />
        <br />
        <input type="submit" value="Save" />
    </form>
</body>
</html>]],macaddr,'','','',1883,'home/pc/id1')

function httpResponse(content,ctype,httpcode)
    if ctype == 'html' then ctype = 'text/html; charset=UTF-8'
    elseif ctype == 'text' or ctype == 'plain' then ctype = 'text/plain' end
    return string.format([[HTTP/1.1 %s
Cache-Control: no-cache
Content-Type: %s
Content-Length: %d

%s]],httpcode or '200 OK',ctype or 'text/plain',#content,content)
end

function startServer()
    srv=net.createServer(net.TCP,10)
    srv:listen(80,function(conn)
        conn:on("receive",function(conn,payload)
            local _,ip = conn:getpeer()
            local _,_,method,url = string.find(payload, '^([A-Z]+) (/%S+)')
            print('['..ip..']',method,url)
            if method ~= 'GET' and method ~= 'POST' then
                print('Illegel request.')
                conn:close()
                return
            end
            if url == '/test' then 
                print("Interface test OK.")
                conn:send(httpResponse("Interface test OK."))
            elseif method == 'GET' and url == '/stat' then
                conn:send(httpResponse(powstat or 'UNK'))
            elseif method == 'POST' and url == '/pow' then
                trigger_pow()
                print("Power triggered.")
                conn:send(httpResponse("OK"))
            elseif method == 'POST' and url == '/powforce' then
                trigger_pow(true)
                print("Power triggered.")
                conn:send(httpResponse("OK"))
            elseif method == 'GET' and url == '/config' then
                conn:send(httpResponse(configpage,'html'))
            elseif method == 'POST' and url == '/config' then
                local params={}
                local _,cp=string.find(payload,'\r\n\r\n')
                payload=string.sub(payload,cp+1)
                for k,v in string.gmatch(payload,'(%w+)=([^%c]*)') do params[k]=v print(k,v) end
                if params['ssid'] == nil or params['pwd'] == nil or params['ssid'] == '' or params['wifipasswd'] == '' then
                    conn:send(httpResponse('Wrong parameters.'),'plain','400')
                else
                    file.open('net.cfg','w')
                    file.writeline(params['ssid']) file.writeline(params['pwd'])
                    file.writeline(params['mqttip'] or '') file.writeline(params['mqttport'] or '') file.writeline(params['mqtopic'] or '')
                    file.writeline(params['chkpwr'] or '')
                    file.close()
                    conn:send(httpResponse('Node is restarting.'))
                    tmr2:alarm(3000, tmr.ALARM_SINGLE, function() node.restart() end)
                end
            else
                conn:send(httpResponse("ERROR",'404'))
            end
        end)
    conn:on("sent", function(conn) conn:close() end)
    end)
    print('Server listening at port 80...')

    if mqttip ~= nil and mqttip ~= '' then
        mc=mqtt.Client('ESP'..node.chipid(),60)
        mc:connect(mqttip,mqttport,false, function()
            print('MQTT server connected.')
            mc:subscribe(mqtopic..'/ctrl',0)
            if powstat ~= nil then mc:publish(mqtopic,powstat,0,1) end
        end)
        mc:on("message", function(client,topic,message)
            if message == 'pow' or message == 'powforce' then
                trigger_pow(message=='powforce')
                print("Power triggered.")
            end
        end)
    end
    if tochkpwr then
        gpio.mode(chkpw_pin,gpio.INT)
        gpio.trig(chkpw_pin,'both',function(level,when,ec)
            powstat = gpio.read(chkpw_pin)==gpio.HIGH and 'ON' or 'OFF'
            print('Power is '..powstat)
            if mc ~= nil then mc:publish(mqtopic,powstat,0,1) end
        end)
    end
end
(烧写上传过程略。)

这个脚本集合了 WIFI 连接和热点,HTTP 协议接口,MQTT 协议接口,以及参数配置页面。配置用最简单的方法分行存储在文件中,其中最后一项比较特别,因为RX 引脚被用做 GPIO4 去读取电源状态了,如果还打算用 TTL 调试及更新脚本的话,就要进入 /config 页重新配置,保持这项不选。


配置及测试
粘贴,连接,PC上电。如果还没有任何配置,很快会看到一个名为 NODEPCTR_xxxxxx的热点,没有密码,连接后浏览器打开 http://192.168.1.1/config,把网络配置好。
config.png
如果你不打算使用MQTT / Hass,除 SSID 和密码外都可以不填,那么 MAC 地址你应该记一下并到路由器里给它分配一个静态 IP;反之用 MQTT 的话它本身的 IP 就不重要了。
HTTP 接口的开机要用 POST(curl $节点IP/pow -d ''),当然不爽这一点的话也可以改一下,我并没有写触发按钮页面。

HA 的 MQTT开关配置例
switch:
  - platform: mqtt
    unique_id: pcpwrctrl_mqtt_1
    name: "PC Power MQTT"
    command_topic: "home/pc/work/ctrl"
    payload_on: "pow"
    payload_off: "pow"
    state_topic: "home/pc/work"
    state_on: "ON"
    state_off: "OFF"
按上面代码的实现 state_topic 就是配置页刚填的 MQTT Topic,command_topic 加上一个 /ctrl 后缀,开关控制和状态不同不区分 ON/OFF,都是"pow"。然后 HA 就可以集成显示和控制 PC 开关了。
回复

使用道具 举报

71

主题

1145

帖子

6286

积分

论坛元老

Rank: 8Rank: 8

积分
6286
金钱
5136
HASS币
30
发表于 2020-11-26 20:22:24 | 显示全部楼层
为了几瓦的用电,完全关机,也不是很方便。现在使用电脑还是习惯了用休眠模式,这样唤醒后,都能保持原样。
回复

使用道具 举报

1

主题

10

帖子

148

积分

注册会员

Rank: 2

积分
148
金钱
138
HASS币
0
 楼主| 发表于 2020-11-26 20:29:10 | 显示全部楼层
xuyang 发表于 2020-11-26 20:22
为了几瓦的用电,完全关机,也不是很方便。现在使用电脑还是习惯了用休眠模式,这样唤醒后,都能保持原样。 ...

经常远程开关机的明显不是这个用法,大概也并不需要什么原样吧
回复

使用道具 举报

1

主题

10

帖子

148

积分

注册会员

Rank: 2

积分
148
金钱
138
HASS币
0
 楼主| 发表于 2020-11-26 20:44:27 | 显示全部楼层
本帖最后由 kappa8086 于 2020-12-1 19:04 编辑

开关的处理的额外安全说明。短接电源按钮要输出低电平(0),但除此之外的时间不是保持输出高电平(1),而是应该切换至 INPUT 模式,换句话说,其实是要在 INPUT PULLUP 和 OUTPUT LOW这两个状态中切换。
不同于输入模式,输出 1 时针脚和 Vcc 之间是没有电阻的,一旦被物理开关接通GND,烧电源可能不会,但ESP片子和稳压模块其中之一可能会烧毁,掉电是一定的。以下电路图按我的理解推测。

circuit1.png
INPUT模式开关被按下,安全,LED 亮,PWR ON 信号触发


circuit2.png
OUTPUT模式输出 0,安全,LED亮,PWR ON 信号触发。此时开关按不按下并无区别


circuit.png
OUTPUT 模式输出 1,针脚绕过上拉电阻连接 Vcc,此时前面板开关被按下,短路!

除此之外我发现 GPIO2 的 LED 有个“微亮”的状态,先设置为 OUTPUT 模式默认低电平因此 LED 亮,再改为 INPUT 模式结果 LED 并未全灭,除非对其写入 1(不管是不是处在 OUTPUT 模式),不知算是电路缺陷还是什么。


后补安全说明:
因为这两天重做了一些关于esp8266 GPIO的资料研究,才发现其限流12mA,而不少方案因此加了三极管(当然也有和我一样莽的),因此对比之下这个方案算是没有安全保护,没烧坏基于一个假设:PWR SW短路电流不超过10mA,也就是说主板相关电路限流的等效电阻至少要超过300欧。但主板对于这条线路有没有什么设计要求我没查到,于是能跑起来多少有点凭运气,哈,如果量产考虑保险的话还是用继电器模块的好


回复

使用道具 举报

19

主题

682

帖子

3398

积分

论坛元老

Rank: 8Rank: 8

积分
3398
金钱
2711
HASS币
20
发表于 2020-11-27 09:09:10 | 显示全部楼层
本帖最后由 ms2 于 2020-11-27 09:13 编辑

这搞复杂了吧,我是这样做的:反正电脑也要接电,那直接上个DC1,电脑关机状态下功率小于一定值,连电都断开了,不是更省电,开机更简单了,直接DC1插口加电,电脑设置来电开机,多简单,还包括电脑显示器的插座都有了。不过楼主这代码写得还是挺有水平的,这个得顶。
回复

使用道具 举报

1

主题

10

帖子

148

积分

注册会员

Rank: 2

积分
148
金钱
138
HASS币
0
 楼主| 发表于 2020-11-27 10:03:53 | 显示全部楼层
ms2 发表于 2020-11-27 09:09
这搞复杂了吧,我是这样做的:反正电脑也要接电,那直接上个DC1,电脑关机状态下功率小于一定值,连电都断 ...

也可以,几年前还是用小K开关时就这么干过,远程时显示器不用管,不过就是这个开关只允许“开”而不允许“关”,关机得进系统,得温柔
另外还有一点担心就是PC长时间处于不上电状态,CMOS电池会掉很快
回复

使用道具 举报

19

主题

682

帖子

3398

积分

论坛元老

Rank: 8Rank: 8

积分
3398
金钱
2711
HASS币
20
发表于 2020-11-27 10:31:06 | 显示全部楼层
本帖最后由 ms2 于 2020-11-27 10:34 编辑
kappa8086 发表于 2020-11-27 10:03
也可以,几年前还是用小K开关时就这么干过,远程时显示器不用管,不过就是这个开关只允许“开”而不允许 ...

我电脑关机功率小时,直接自动关DC1总开关的,也就是连显示器一起关了,关机论坛里有帖子,可以直接通过网络给电脑发关机指令,联动到按键上就可以了,只是我感觉没这个必要,因为电脑如果不动设置了自动休眠,然后DC1也会关总开关,所以没搞。CMOS电池如果主板没问题,用个几年是可以的,换个电池倒也不难。不过你主要是找折腾的乐趣,我现在尽量是越简单越好,所以全线能用涂鸦的就用涂鸦,因为可以用涂鸦APP自动化很多事,少了HA的事。
回复

使用道具 举报

40

主题

3057

帖子

1万

积分

超级版主

Nero

Rank: 8Rank: 8

积分
11135
金钱
8028
HASS币
182
发表于 2020-11-27 11:09:35 | 显示全部楼层
家里没有台式机~泪流满面路过
Nero
回复

使用道具 举报

74

主题

1942

帖子

7885

积分

元老级技术达人

积分
7885
金钱
5893
HASS币
430

活跃会员教程狂人

发表于 2020-11-27 11:25:39 | 显示全部楼层
太秀了。给技术大佬端茶。
所有过往,皆为序章。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian

GMT+8, 2024-3-28 18:04 , Processed in 0.882740 second(s), 35 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表