Raspberry pi 控制Unraid/PC远程开机

需求

由于住的破地总断电,导致NAS断联。NAS机器是自己用普通电脑配件胡乱拼的,系统用的unraid。最精彩的是主板BISO选项里有来电自启功能,但是经过测试,根本不好使,shit。

我的需求很简单,不管人在哪都能打开Unraid。

方案

最简洁的方案,使用WakeOnLan,即网络唤醒技术,让被唤醒的电脑在不开机的情况下,通过网卡或路由器发送唤醒包,让主板或电脑开机。

OpenWrt里有一个wakeonlan插件,python有一个wakeonlan库,都可以直接使用。你猜到了,我的主板唤不醒啊,睡死了,Fuxk。其实,某宝有卖一种pci开机硬件,这种不用想了,直接淘汰。

简单的方法不行,只能杀鸡用牛刀了。树莓派是来电自启的,而且本博客跑在这上,常年开机。

使用Raspberry pi 搭建Ghost博客(docker)
盆友,你猜的没错,本站用的博客就跑在树莓派上。 图样图森破: 看到没,刚创建5小时就迫不及待的发First Post,吼吼吼。 print(“Hello World!”) docker和mysql的安装就不说了,直接上 docker-compose.yml version: ‘3.1’ services: ghost: container_name: ghost image: ghost:latest restart: always ports: - 2368:2368 environment: # 更改mysql连接信息 (我用的mysql早就装在了nas机器上) database__client: mysql database__connection__host: mysql_ip database__connection__user: db_user database__connection_

翻箱倒柜找到几年前买的传感器大集合。

猜猜我这个大情种要用哪个?

你也许猜到了,没错,Relay,又叫继电器。

连接方式见图:

我这堆东西目前生存环境相当恶劣。开始在电脑房,而后去了洗衣房。为啥,吵死了,Shit,从没想过一个破铁盒子这么烦人。

PS. raspberry pi连接的那个野外露出硬盘是下载盘,为寂寞的夜晚。。。咳咳

继电器连线

继电器由树莓派供电,两个电源线,一根控制线。我忘了是哪两根了,也懒的看,后面都有+/-标记。树莓派用pinout命令。

出来的两根线接到主板的开机跳线上。我是把原有的跳线皮撸了,接了个三通。

当树莓派给信号的时候,继电器把输出端导通,相当于按下按钮。

最后上主菜,纯手撸代码,ChatGPT滚粗

俺做了一个简单网页,网页上就一个大按钮。当pi检测到机器没开机的时候,红色按钮可以点击;当pi检测到机器已开机,只显示绿色大按钮,就下图这样子。

pi_knockup_nas.py

import RPi.GPIO as GPIO
import time
import subprocess
from flask import Flask, request, jsonify, render_template

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)

unraid_host = "unraid.lan"
GPIO_NUM = 18

def is_machine_powered_on():
    try:
        result = subprocess.check_output(["ping", "-c", "1", unraid_host])
        return "1 packets transmitted, 1 received" in result.decode()
    except subprocess.CalledProcessError:
        return False
    
def poweron():
    if is_machine_powered_on() == False:
        GPIO.output(GPIO_NUM, 1)
        time.sleep(1)
        GPIO.output(GPIO_NUM, 0)
        time.sleep(120)
        if is_machine_powered_on():
            return True
        else:
            return False
    else:
        return True

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False

is_endpoint_enabled = True

@app.route('/unraid/poweron', methods=['GET'])
def unraid_poweron():
    global is_endpoint_enabled
    if is_endpoint_enabled == True:
        is_endpoint_enabled = False
        result = poweron()
        is_endpoint_enabled = True
        if result == True:
            return jsonify({"state":"ok", "result":"开机完成!"})
        else:
            return jsonify({"state":"ok", "result":"开机失败!"})
    else:
        return jsonify({"state":"pending", "result":"正在开机..."})


@app.route('/unraid/powerstatus', methods=['GET'])
def unraid_powerstatus():
    if is_machine_powered_on():
        return jsonify({"state":"ok", "result":"on"})
    else:
        return jsonify({"state":"ok", "result":"off"})

@app.route('/')
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8999, debug=True)

templates/index.html

<!DOCTYPE html>
<html>

<head>
    <title>Unraid 开机</title>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>

<body>
    <button id="p_button" class="power-button" onclick="powerOn()"></button>

    <script>
        var button = document.getElementById("p_button");

        function powerOn() {
            fetch("/unraid/poweron")
                .then(response => {
                    if (!response.ok) {
                        throw new Error("Network response was not ok");
                    }
                    return response.json();
                }).then(data => {
                    console.log(data);
                }).catch(error => {
                    console.error("Fetch error:", error);
                });
        };

        checkUnraidStatus()
        function checkUnraidStatus() {
            fetch("/unraid/powerstatus")
                .then(response => {
                    if (!response.ok) {
                        throw new Error("Network response was not ok");
                    }
                    return response.json();
                })
                .then(data => {
                    if (data.result === "off") {
                        button.style.backgroundColor = "#df1648"
                        button.textContent = "开机"
                    }
                    if (data.result === "on") {
                        button.style.backgroundColor = "#44ed0c"
                        button.textContent = ""
                    }
                })
                .catch(error => {
                    console.error("Fetch error:", error);
                });
        }
        setInterval(checkUnraidStatus, 5000)
    </script>
</body>

static/css/style.css

.power-button {
    width: 30vw;
    height: 30vw;
    min-width: 100px;
    min-height: 100px; 
    background-color: #353834;
    border: 2px solid #357934;
    border-radius: 100%;
    color: white;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    box-shadow: 0 9px #999;
}
.power-button:active {
    box-shadow: 0 5px #666;
    transform: translate(-50%, -49%);
}

访问树莓派8999端口,网页出现脸大的红按钮。

当点击大大的红色按钮,听到继电器发出(哒。。。哒)两声,电脑开机,我不知道你,反正我飞升了。


有时间好好说说我这套Pi+unraid组合。经过一段时间折腾,这套系统对于我来说已经趋于完美。

PS. 还剩3T空间,还能挺俩月。硬盘最近涨价了,Fuxk。

外网访问Pi

这一条忘了,要想从任何地点访问pi,方法很多。一言以蔽之,本人借助魔法V屁N的魔力连接到的家庭内网。看下面这里有说。

Send-Q和Recv-Q爆表,TCP连接卡死
问题 这是在电子书网站听书的时候遇到的,自己弄的Calibre-Web。 昨天上班摸鱼,听着老友记钱德勒的自传,咔,听到传神处卡死。之后刷新,重启Calibre-Web后台服务,没任何效果。 Friends, Lovers, and the Big Terrible ThingMatthew Perry 这个电子书网站只是表面,实际mp3,epub等文件都是存储在我家的NAS里的,经过各种骚操作,才能在网站上直接冒泡。 NAS(smb) -> raspberry pi(rinetd) -> zerotire -> vps(若干骚气的脚本) 这么干的好处就是成本低,大容量服务器太贵(现在都收集1T电子书了),况且这东西也就自己用,没必要。 全部配置细节等以后服务坏了,重新搭建的时候在记录。 进入侦探模式 唯一看透真相的是一个外表看似处男,智慧却过于常人的大水博主。 经过层层查找,找到一处异常,在Raspberry pi上执行命令: netstat -ant

One more thing

配置脚本为系统服务,实现自启动

/etc/systemd/system/unraid_api.service

[Unit]
Description=unraid api service
After=network.target

[Service]
ExecStart=/usr/bin/python -u /home/god/Documents/unraid/pi_knockup_nas.py
WorkingDirectory=/home/god/Documents/unraid
StandardOutput=inherit
StandardOutput=inherit
Restart=always
User=god

[Install]
WantedBy=multi-user.target

systemctl reload/enable等命令使其生效。