Bash Shell实现监控向日葵开机插座和电脑端口,检测到电脑死机后自动重启插座电源以强制重启电脑

stevehe 2023年01月29日 122次浏览

情景概述

因本人家中工作电脑日常需要24小时开机,并且会跑一些奇奇怪怪的程序,不在家中时,关机或重启电脑的需求很常见,现在问题来了:
1、电脑正常运行未死机的情况下我们可以通过ssh远程连入,通过reboot/shutdown命令来重启电脑,当然了,通过微软系统自带远程桌面RDP(mstsc)来连接操作也可以,不过对于无需交互式反馈的场景而言,效率太低,一般不用;
2、电脑死机的情况下,ssh或其他远程软件均不工作,无法远程连接并控制,只能强制重启电脑解决,如果人不在电脑旁边,物理强制重启则做不到,所幸现在智能开机插座很普遍了,价格也偏移,用智能开机插座连接主机电源,再通过远程控制插座电源的开关状态,即可对电脑实施强制重启(注意:做到这一步需要电脑主板支持来电自启设置,并把此功能设为启用状态!


解决了电脑死机通过开关插座电源来重启电脑的问题,那现在新问题又来了:能不能自动监测主力电脑的运行状态,如果发生死机情况则自动进行断电重启,而不是每次手动远程连接电脑时,才发现电脑死机了这一状况,答案当然是可以,因本人家中局域网有EXSI虚拟机控制设备,运行了NAS和软路由等多台Linux设备,其所运行的系统异常稳定。只需要在这些Linux设备上编写程序定时周期性监控主力电脑的状态和插座耗电功率,即可判断电脑是否死机,检测到死机,则发送重启插座电源的指令即可;


综上情况:本文的程序脚本就诞生了,帮助我一键监控主力电脑的运行状态,死机则执行插座断电重启。间接提高生产工作的效率!

Tips:一些需要无人值守不间断运行的程序可以设置开机自启,相关技术细节此处不再赘述!

注:本脚本程序2023年1月经测试适配“向日葵开机插座C1-2、C1-Pro”,其他未测试!
对应向日葵官网产品介绍页面网址:https://sunlogin.oray.com/hardware/socket-c2

控制向日葵插座主程序: sunlogin_plug 文件代码:

#!/bin/sh
#控制向日葵开机智能插座

SCRIPTPATH="$(dirname $0)"

requestTimeOut=3   #请求超时时间(单位:秒)

#controlHostURL="http://192.168.1.105:6767"    #发送的主机地址,可抓包获取,也可以指定插座联网的局域网IP地址,端口通常为6767
controlTime="01270152"    #接收端需要的参数time,可抓包获取
controlKey="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"   #接收端需要的参数key,可抓包获取


statusUrl="${controlHostURL}/plug?_api=get_plug_status&index=0&time=${controlTime}&key=${controlKey}"
cntdownUrl="${controlHostURL}/plug?_api=plug_cntdown_get&time=${controlTime}&key=${controlKey}&index=0"
checkVersionUrl="${controlHostURL}/plug?key=${controlKey}&_api=get_plug_version&time=${controlTime}"
electricUrl="${controlHostURL}/plug?key=${controlKey}&_api=get_plug_electric&time=${controlTime}"
powerOnUrl="${controlHostURL}/plug?_api=set_plug_status&index=0&time=${controlTime}&key=${controlKey}&status=1"
powerOffUrl="${controlHostURL}/plug?_api=set_plug_status&index=0&time=${controlTime}&key=${controlKey}&status=0"

_sendRequest() {
	local url="$1"
	local responseData=$(curl -sSf --connect-timeout $requestTimeOut "$url" 2>/dev/null)
	[ ! -z "$responseData" ] && echo "$responseData" || echo "{}"
}

#远端返回错误代码时调用(即result!=0),非请求过程本身的错误
responseInvolid() {
	local msg="【失败】:请求失败,请检查key是否已过期!"
	echo "$msg"
	cat >>$SCRIPTPATH/sunlogin-error.log<<<"[$(date +'%Y%m%d %H:%M:%S')] ${msg}"
	#exit 255
	return 255
}

appendErrorLog() {
	local logFile="$SCRIPTPATH/sunlogin-error.log"
	echo "$*"
	cat >>$logFile<<<"[$(date +'%Y-%m-%d %H:%M:%S')] appendErrorLog:$*"
}

getPlugStatus() {
	local json=$(_sendRequest "$statusUrl")
	local status=$(echo "$json"|jq -r '.result' 2>/dev/null)
	if [ -z "$status" -o "$status" = "null" ];then
		appendErrorLog "【请求错误】:请求的地址无效,请检查被控主机配置是否正确,并确保被控设备已接入网络!"
		return 99
	elif [ $status -ne 0 ];then
		responseInvolid && return
	fi
	local plugStatus=$(echo "$json"|jq '.response[]|.status')
	if [[ "${1,,}" == "--flag" ]];then  #仅返回状态标志:1->开关开启; 2->开关关闭
		echo "$plugStatus"
	else  #返回可读的信息提示文本
		if [ $plugStatus -eq 1 ];then
			echo "【开启】电源开关处在开启状态!"
		elif [];then
			echo "【关闭】电源开关处在关闭状态!"
		else
			echo "【错误】开关状态未知!"
		fi
	fi
}

getPlugVersion() {
	#获取插座固件版本信息
	local json=$(_sendRequest "$checkVersionUrl")
	local status=$(echo "$json"|jq -r '.result' 2>/dev/null)
	if [ -z "$status" -o "$status" = "null" ];then
		appendErrorLog "【请求错误】:请求的地址无效,请检查被控主机配置是否正确,并确保被控设备已接入网络!"
		return 99
	elif [ $status -ne 0 ];then
		responseInvolid && return
	fi
	echo "$json"
}

getPlugPower() {
	#获取插座当前的耗电功率!
	local json=$(_sendRequest "$electricUrl")
	local status=$(echo "$json"|jq -r '.result' 2>/dev/null)
	if [ -z "$status" -o "$status" = "null" ];then
		appendErrorLog "【请求错误】:请求的地址无效,请检查被控主机配置是否正确,并确保被控设备已接入网络!"
		return 99
	elif [ $status -ne 0 ];then
		responseInvolid && return
	fi
	if [[ "${1,,}" == "--raw" ]];then  #返回原始JSON数据
		echo "$json"
	else  #返回可读的信息提示文本
		local power=$(echo "$json"|jq -r '.power')    #当前电功率
		local current=$(echo "$json"|jq -r '.curr')   #当前电流
		local volume=$(echo "$json"|jq -r '.vol')     #当前电量
		printf "【耗电】当前电功率:%s W/h,电流:%s mA,电量:%s \n" $(echo "scale=2;$power/1000"|bc) $(echo "scale=2;$current/1000"|bc) $volume
	fi
	
}

getPlugOpenCount() {
	#获取插座今日开启次数
	#注:此接口功能待定(暂时不知道是干嘛用的...)
	local json=$(_sendRequest "$cntdownUrl")
	local status=$(echo "$json"|jq -r '.result' 2>/dev/null)
	if [ -z "$status" -o "$status" = "null" ];then
		appendErrorLog "【请求错误】:请求的地址无效,请检查被控主机配置是否正确,并确保被控设备已接入网络!"
		return 99
	elif [ $status -ne 0 ];then
		responseInvolid && return
	fi
	if [[ "${1,,}" == "--raw" ]];then  #返回原始JSON数据
		echo "$json"
	else  #返回可读的信息提示文本
		#echo "TODO..."
		echo "$json"
	fi
}

getPlugPowerTime() {
	#获取插座今日通电时长!
	local url="https://sl-api.oray.com/smartplugs/xxxxxxxxxxxx/electric?date=2023-1-27&r=0.39320349774540664"  #注意此URL可通过抓包网页控制面板获取
	local json=$(_sendRequest "$url")
	echo "$json"
}

powerOnPlug() {
	#开启插座
	local json=$(_sendRequest "$powerOnUrl")
	local status=$(echo "$json"|jq -r '.result' 2>/dev/null)
	if [ -z "$status" -o "$status" = "null" ];then
		appendErrorLog "【请求错误】:请求的地址无效,请检查被控主机配置是否正确,并确保被控设备已接入网络!"
		return 99
	elif [ $status -ne 0 ];then
		responseInvolid && return
	fi
	if [[ "${1,,}" == "--raw" ]];then  #返回原始JSON数据
		echo "$json"
	else  #返回可读的信息提示文本
		local setStatus=$(echo "$json"|jq -r '.result' 2>/dev/null||echo "1")
		[ "$setStatus" = "0" ] && echo "【成功】开启插座成功!" || echo "【失败】开启插座失败!"
	fi
	
}

powerOffPlug() {
	#关闭插座
	local json=$(_sendRequest "$powerOffUrl")
	local status=$(echo "$json"|jq -r '.result' 2>/dev/null)
	if [ -z "$status" -o "$status" = "null" ];then
		appendErrorLog "【请求错误】:请求的地址无效,请检查被控主机配置是否正确,并确保被控设备已接入网络!"
		return 99
	elif [ $status -ne 0 ];then
		responseInvolid && return
	fi
	if [[ "${1,,}" == "--raw" ]];then  #返回原始JSON数据
		echo "$json"
	else  #返回可读的信息提示文本
		local setStatus=$(echo "$json"|jq -r '.result' 2>/dev/null||echo "1")
		[ "$setStatus" = "0" ] && echo "【成功】关闭插座成功!" || echo "【失败】关闭插座失败!"
	fi
	
}

powerRebootPlug() {
	#重开插座(关闭插座后再次开启,中间间隔30秒)
	powerOffPlug
	echo "延时等待中..."
	sleep 30
	powerOnPlug
}


#——————————————————————————————————————————————————————————————
#主逻辑开始:功能函数调用:

#getPlugStatus   #获取插座开关所处状态
#getPlugOpenCount
#getPlugPower   #获取插座当前功率
#powerOnPlug    #开启插座
#powerOffPlug    #关闭插座
#powerRebootPlug  #重启插座

case "${1,,}" in 
	"on")
		echo "开启插座..."
		powerOnPlug 
	;;
	"off")
		echo "关闭插座..."
		powerOffPlug
	;;
	"reboot")
		echo "重启插座电源..."
		powerRebootPlug
	;;
	"status")
		echo "获取插座开关状态..."
		getPlugStatus
	;;
	"version")
		echo "获取插座固件版本..."
		getPlugVersion
	;;
	"power")
		echo "获取插座耗电功耗..."
		getPlugPower
	;;
	"count")
		echo "获取插座开关机次数..."
		getPlugOpenCount
	;;
	"time")
		echo "获取插座今日使用时长..."
		getPlugPowerTime
	;;
	*)
	:
	#echo "Do Nothing"
	;;
esac

sunlogin-plug运行截图:

be4fac899bca8d04fc8c385a07f93f13_176373202301291628571114385696.png

检测电脑端口和插座耗电功耗定时任务任务脚本代码 check-homepc-crash :

#!/bin/sh
#检查家中电脑是否死机奔溃
#如果崩溃,借助向日葵开机插座重新开启电脑电源
#-----------------------------------------------
#注:本脚本两次检查之间不做任何延时操作,多次检查之间的时间间隔请通过计划任务(crontab)指定
# eg:
# */10 * * * *  /path/to/check-homepc-crash   #每隔10分钟检查电脑是否死机
#另:任务时间间隔也不宜设置得过短,间隔的时间必须足够让电脑从开机到启动ssh服务有足够的时间,否则会出问题,比如设置为1分钟检查一次(很显然电脑无法保证在1分钟内把所有服务启动完成!)

pushd "$(dirname $0)" &>/dev/null
SCRIPTPATH="$(dirname $0)"

source $SCRIPTPATH/sunlogin_plug

recordFile="./homepc-status-$(date +'%Y%m%d').txt"  #失败记录标记文件,记录电脑检查失败状态,多次检查失败则考虑通过重开电源开关来重启电脑
minCheckCount=3   #检查到多少次失败时,重启电源(重开插座开关),建议值不小于2,否则会出问题,起反作用!
#(比如:电脑首次手动开机时,SSH服务还没来得及启动,就被本程序判定为电脑死机,从而导致电脑重启,电脑表示很无辜)

initRecordFile() {
	#初始化记录日志文件(文件不存在则自动创建)
	touch $recordFile
}

emptyRecordFile() {
	#清空日志文件,目标主机检查成功或无需后续检测步骤时,需要清空记录文件
	cat>$recordFile</dev/null
}

endCheckProcess() {
	#中断检查进程,退出后续操作!
	emptyRecordFile
	exit 0
}

#——————————————————————————————主逻辑开始(检查电脑是否开机,并且是否死机)——————————————————————————————————————
initRecordFile

nc -w 3 -v 192.168.1.99 22 &>/dev/null   #因本人在电脑上配置了Cygwin ssh端口开机自启动,如果端口能顺利连接则认定电脑状态OK(未死机)
homePCSSH=$?
[ $homePCSSH -eq 0 ] && echo "当前电脑Cygwin SSH在线,退出后续检查!" && endCheckProcess


echo "Cygwin SSH端口检测失败,即将通过电源插座获取电脑开机状态..."

plugStatus=$(getPlugStatus --flag)  #获取插座是否开机

[ "$plugStatus" = "0" ] && echo "插座开关未开启,退出后续检查!" && endCheckProcess
echo "当前插座为开启状态,继续获取插座设备耗电状态,以识别电脑是否处在开机状态..."

powerData=$(getPlugPower --raw)  #获取插座耗电状态
powerNumber=$(echo "$powerData"|jq -r '.power')

if [ $powerNumber -lt 70000 ];then   #插座有耗电,但功率小于70wh,则认为是其他东西在耗电,不认为是电脑开机状态,退出检查
	echo "插座耗电功率小于 70 w/h,可能电脑并未开机,退出后续检查!"
	endCheckProcess
fi

echo "电脑SSH端口检测失败,且插座耗电功率在 70wh 以上,电脑可能已经死机,将记录本次检查状态!"
cat>>$recordFile<<<"【$(date +'%Y-%m-%d %H:%M:%S')】 HOMEPC端口检查失败,且插座耗电量超过阈值!"


#查看日志记录次数,达到最小检查次数则重启插座电源
recordFileLines=$(cat $recordFile|wc -l)

if [ $recordFileLines -ge $minCheckCount ];then
	echo "失败记录次数达标,即将重启插座电源以使电脑重新启动!"
	emptyRecordFile  #清空日志文件
	#powerRebootPlug  #重启插座电源
	cat >>./homepc-reboot-log.log<<<"【$(date +'%Y-%m-%d %H:%M:%S')】执行了重启插座电源操作!"
else
	echo "当前失败记录第 $recordFileLines 次,等待下次检查!"
fi

使用说明:

sunlogin_plugcheck-homepc-crash这两文件放置于同一目录下,并赋予可执行权限(chmod a+x *),并将 check-homepc-crash加入crontab定时任务计划即可(注意任务的间隔时间不宜设置得过短)。