first commit
This commit is contained in:
237
poweroff_linux/change_port.sh
Normal file
237
poweroff_linux/change_port.sh
Normal file
@ -0,0 +1,237 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# ============================================
|
||||
# 串口关机控制器 - 更换串口脚本
|
||||
# ============================================
|
||||
|
||||
INSTALL_DIR="/opt/ttyshutdown"
|
||||
CONFIG_FILE="${INSTALL_DIR}/config.json"
|
||||
SIGNAL_SCRIPT="/usr/lib/systemd/system-shutdown/send_signal.sh"
|
||||
SERVICE_NAME="ttyshutdown"
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# 检查 root 权限
|
||||
check_root() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
error "请使用 root 权限运行此脚本: sudo bash change_port.sh"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查是否已安装
|
||||
check_installed() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
error "未找到配置文件 $CONFIG_FILE,请先运行 install.sh 安装"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 读取当前配置
|
||||
read_current_config() {
|
||||
CURRENT_PORT=$(grep -oP '"port"\s*:\s*"\K[^"]+' "$CONFIG_FILE")
|
||||
CURRENT_BAUD=$(grep -oP '"baudRate"\s*:\s*\K[0-9]+' "$CONFIG_FILE")
|
||||
CURRENT_WORD=$(grep -oP '"shutdownWord"\s*:\s*"\K[^"]+' "$CONFIG_FILE")
|
||||
|
||||
echo -e "${CYAN}=== 当前配置 ===${NC}"
|
||||
echo " 串口设备: $CURRENT_PORT"
|
||||
echo " 波特率: $CURRENT_BAUD"
|
||||
echo " 关机指令: $CURRENT_WORD"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 扫描可用串口
|
||||
scan_serial_ports() {
|
||||
local ports=()
|
||||
for pattern in "ttyUSB*" "ttyACM*" "ttyS*"; do
|
||||
for dev in /dev/$pattern; do
|
||||
if [ -e "$dev" ]; then
|
||||
ports+=("$dev")
|
||||
fi
|
||||
done
|
||||
done
|
||||
echo "${ports[@]}"
|
||||
}
|
||||
|
||||
# 交互选择串口
|
||||
select_serial_port() {
|
||||
local ports
|
||||
ports=$(scan_serial_ports)
|
||||
|
||||
if [ -z "$ports" ]; then
|
||||
warn "未检测到串口设备"
|
||||
echo -e "请手动输入串口设备路径 (如 ${YELLOW}/dev/ttyUSB0${NC}): "
|
||||
read -r SELECTED_PORT
|
||||
if [ ! -e "$SELECTED_PORT" ]; then
|
||||
warn "设备 $SELECTED_PORT 当前不存在,仍将配置(设备可能稍后接入)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
local port_array=($ports)
|
||||
echo -e "检测到以下串口设备:${GREEN}"
|
||||
local i=1
|
||||
for p in "${port_array[@]}"; do
|
||||
local desc=""
|
||||
case "$p" in
|
||||
/dev/ttyUSB*) desc="USB转串口" ;;
|
||||
/dev/ttyACM*) desc="USB CDC虚拟串口" ;;
|
||||
/dev/ttyS*) desc="原生串口(COM口)" ;;
|
||||
esac
|
||||
local marker=""
|
||||
if [ "$p" = "$CURRENT_PORT" ]; then
|
||||
marker=" ${YELLOW}<-- 当前${NC}"
|
||||
fi
|
||||
echo " [$i] $p ($desc)${marker}"
|
||||
((i++))
|
||||
done
|
||||
echo -e "${NC}"
|
||||
|
||||
local last_index=${#port_array[@]}
|
||||
echo " [$((last_index + 1))] 手动输入其他路径"
|
||||
echo ""
|
||||
|
||||
local choice
|
||||
while true; do
|
||||
read -rp "请选择串口 [1-$((last_index + 1))]: " choice
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$((last_index + 1))" ]; then
|
||||
break
|
||||
fi
|
||||
error "无效选择,请重新输入"
|
||||
done
|
||||
|
||||
if [ "$choice" -le "$last_index" ]; then
|
||||
SELECTED_PORT="${port_array[$((choice - 1))]}"
|
||||
else
|
||||
read -rp "请输入串口设备路径: " SELECTED_PORT
|
||||
if [ -z "$SELECTED_PORT" ]; then
|
||||
error "路径不能为空"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 更新 config.json
|
||||
update_config() {
|
||||
local port="$1"
|
||||
local baud_rate="$2"
|
||||
local shutdown_word="$3"
|
||||
|
||||
cat > "$CONFIG_FILE" <<EOF
|
||||
{
|
||||
"port": "${port}",
|
||||
"baudRate": ${baud_rate},
|
||||
"shutdownWord": "${shutdown_word}"
|
||||
}
|
||||
EOF
|
||||
info "已更新配置文件: $CONFIG_FILE"
|
||||
}
|
||||
|
||||
# 更新 send_signal.sh
|
||||
update_send_signal() {
|
||||
local port="$1"
|
||||
local baud_rate="$2"
|
||||
local shutdown_word="$3"
|
||||
|
||||
cat > "$SIGNAL_SCRIPT" <<EOF
|
||||
#!/bin/bash
|
||||
|
||||
TTY_DEVICE="${port}"
|
||||
|
||||
ACTION=\$1
|
||||
|
||||
if [ "\$ACTION" = "poweroff" ] ; then
|
||||
if [ -w "\$TTY_DEVICE" ]; then
|
||||
stty -F "\$TTY_DEVICE" ${baud_rate} cs8 -cstopb -parenb
|
||||
printf "ok\r\n" > "\$TTY_DEVICE"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$SIGNAL_SCRIPT"
|
||||
info "已更新关机钩子: $SIGNAL_SCRIPT"
|
||||
}
|
||||
|
||||
# 重启服务
|
||||
restart_service() {
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
info "已重启服务: $SERVICE_NAME"
|
||||
}
|
||||
|
||||
# 主流程
|
||||
main() {
|
||||
check_root
|
||||
check_installed
|
||||
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo -e "${CYAN} 串口关机控制器 - 更换串口${NC}"
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
# 1. 显示当前配置
|
||||
read_current_config
|
||||
|
||||
# 2. 选择新串口
|
||||
select_serial_port
|
||||
|
||||
if [ "$SELECTED_PORT" = "$CURRENT_PORT" ]; then
|
||||
warn "选择的新串口与当前串口相同,无需更改"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "新串口: $SELECTED_PORT"
|
||||
|
||||
# 3. 可选修改波特率和关机指令
|
||||
local new_baud="$CURRENT_BAUD"
|
||||
local new_word="$CURRENT_WORD"
|
||||
|
||||
echo ""
|
||||
read -rp "波特率 [当前 $CURRENT_BAUD,回车保持]: " input_baud
|
||||
if [ -n "$input_baud" ]; then
|
||||
new_baud="$input_baud"
|
||||
fi
|
||||
|
||||
read -rp "关机指令 [当前 $CURRENT_WORD,回车保持]: " input_word
|
||||
if [ -n "$input_word" ]; then
|
||||
new_word="$input_word"
|
||||
fi
|
||||
|
||||
# 4. 确认
|
||||
echo ""
|
||||
echo -e "${CYAN}=== 确认更改 ===${NC}"
|
||||
echo " 串口设备: $CURRENT_PORT -> $SELECTED_PORT"
|
||||
[ "$new_baud" != "$CURRENT_BAUD" ] && echo " 波特率: $CURRENT_BAUD -> $new_baud"
|
||||
[ "$new_word" != "$CURRENT_WORD" ] && echo " 关机指令: $CURRENT_WORD -> $new_word"
|
||||
echo ""
|
||||
read -rp "确认更改? [Y/n]: " confirm
|
||||
if [[ "$confirm" =~ ^[Nn] ]]; then
|
||||
warn "已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 5. 执行更改
|
||||
update_config "$SELECTED_PORT" "$new_baud" "$new_word"
|
||||
update_send_signal "$SELECTED_PORT" "$new_baud" "$new_word"
|
||||
restart_service
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}串口更换完成!${NC},重启或重启ttyshutdown服务后生效"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e " 新串口: ${CYAN}$SELECTED_PORT${NC}"
|
||||
echo -e " 波特率: ${CYAN}$new_baud${NC}"
|
||||
echo -e " 服务状态: ${CYAN}systemctl status ttyshutdown${NC}"
|
||||
echo -e " 查看日志: ${CYAN}journalctl -u ttyshutdown -f${NC}"
|
||||
}
|
||||
|
||||
main
|
||||
5
poweroff_linux/config.json
Normal file
5
poweroff_linux/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"port": "/dev/ttyUSB0",
|
||||
"baudRate": 115200,
|
||||
"shutdownWord": "SHUTDOWN"
|
||||
}
|
||||
10
poweroff_linux/go.mod
Normal file
10
poweroff_linux/go.mod
Normal file
@ -0,0 +1,10 @@
|
||||
module shutdown
|
||||
|
||||
go 1.25.6
|
||||
|
||||
require (
|
||||
github.com/creack/goselect v0.1.2 // indirect
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 // indirect
|
||||
go.bug.st/serial v1.6.4 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
)
|
||||
8
poweroff_linux/go.sum
Normal file
8
poweroff_linux/go.sum
Normal file
@ -0,0 +1,8 @@
|
||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
234
poweroff_linux/install.sh
Normal file
234
poweroff_linux/install.sh
Normal file
@ -0,0 +1,234 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# ============================================
|
||||
# 串口关机控制器 - 安装脚本
|
||||
# ============================================
|
||||
|
||||
INSTALL_DIR="/opt/ttyshutdown"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# 检查 root 权限
|
||||
check_root() {
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
error "请使用 root 权限运行此脚本: sudo bash install.sh"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 扫描可用串口
|
||||
scan_serial_ports() {
|
||||
local ports=()
|
||||
for pattern in "ttyUSB*" "ttyACM*" "ttyS*"; do
|
||||
for dev in /dev/$pattern; do
|
||||
if [ -e "$dev" ]; then
|
||||
ports+=("$dev")
|
||||
fi
|
||||
done
|
||||
done
|
||||
echo "${ports[@]}"
|
||||
}
|
||||
|
||||
# 交互选择串口
|
||||
select_serial_port() {
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo -e "${CYAN} 串口关机控制器 - 安装向导${NC}"
|
||||
echo -e "${CYAN}========================================${NC}"
|
||||
echo ""
|
||||
|
||||
local ports
|
||||
ports=$(scan_serial_ports)
|
||||
|
||||
if [ -z "$ports" ]; then
|
||||
warn "未检测到串口设备"
|
||||
echo -e "请手动输入串口设备路径 (如 ${YELLOW}/dev/ttyUSB0${NC}): "
|
||||
read -r SELECTED_PORT
|
||||
if [ ! -e "$SELECTED_PORT" ]; then
|
||||
warn "设备 $SELECTED_PORT 当前不存在,仍将配置(设备可能稍后接入)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
# 转为数组
|
||||
local port_array=($ports)
|
||||
echo -e "检测到以下串口设备:${GREEN}"
|
||||
local i=1
|
||||
for p in "${port_array[@]}"; do
|
||||
local desc=""
|
||||
case "$p" in
|
||||
/dev/ttyUSB*) desc="USB转串口" ;;
|
||||
/dev/ttyACM*) desc="USB CDC虚拟串口" ;;
|
||||
/dev/ttyS*) desc="原生串口(COM口)" ;;
|
||||
esac
|
||||
echo " [$i] $p ($desc)"
|
||||
((i++))
|
||||
done
|
||||
echo -e "${NC}"
|
||||
|
||||
local last_index=${#port_array[@]}
|
||||
echo " [$((last_index + 1))] 手动输入其他路径"
|
||||
echo ""
|
||||
|
||||
local choice
|
||||
while true; do
|
||||
read -rp "请选择串口 [1-$((last_index + 1))]: " choice
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$((last_index + 1))" ]; then
|
||||
break
|
||||
fi
|
||||
error "无效选择,请重新输入"
|
||||
done
|
||||
|
||||
if [ "$choice" -le "$last_index" ]; then
|
||||
SELECTED_PORT="${port_array[$((choice - 1))]}"
|
||||
else
|
||||
read -rp "请输入串口设备路径: " SELECTED_PORT
|
||||
if [ -z "$SELECTED_PORT" ]; then
|
||||
error "路径不能为空"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 生成 config.json
|
||||
generate_config() {
|
||||
local port="$1"
|
||||
local baud_rate="$2"
|
||||
local shutdown_word="$3"
|
||||
|
||||
cat > "${INSTALL_DIR}/config.json" <<EOF
|
||||
{
|
||||
"port": "${port}",
|
||||
"baudRate": ${baud_rate},
|
||||
"shutdownWord": "${shutdown_word}"
|
||||
}
|
||||
EOF
|
||||
info "已生成配置文件: ${INSTALL_DIR}/config.json"
|
||||
}
|
||||
|
||||
# 生成 send_signal.sh
|
||||
generate_send_signal() {
|
||||
local port="$1"
|
||||
local baud_rate="$2"
|
||||
local shutdown_word="$3"
|
||||
|
||||
cat > "/usr/lib/systemd/system-shutdown/send_signal.sh" <<EOF
|
||||
#!/bin/bash
|
||||
|
||||
TTY_DEVICE="${port}"
|
||||
|
||||
ACTION=\$1
|
||||
|
||||
if [ "\$ACTION" = "poweroff" ] ; then
|
||||
if [ -w "\$TTY_DEVICE" ]; then
|
||||
stty -F "\$TTY_DEVICE" ${baud_rate} cs8 -cstopb -parenb
|
||||
printf "ok\r\n" > "\$TTY_DEVICE"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "/usr/lib/systemd/system-shutdown/send_signal.sh"
|
||||
info "已安装关机钩子: /usr/lib/systemd/system-shutdown/send_signal.sh"
|
||||
}
|
||||
|
||||
# 生成 systemd service
|
||||
generate_service() {
|
||||
cat > /etc/systemd/system/ttyshutdown.service <<EOF
|
||||
[Unit]
|
||||
Description=ttyshutdown
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=${INSTALL_DIR}
|
||||
ExecStart=${INSTALL_DIR}/shutdown
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
info "已生成 systemd 服务: /etc/systemd/system/ttyshutdown.service"
|
||||
}
|
||||
|
||||
# 主安装流程
|
||||
main() {
|
||||
check_root
|
||||
|
||||
# 1. 选择串口
|
||||
select_serial_port
|
||||
info "已选择串口: $SELECTED_PORT"
|
||||
|
||||
# 2. 可选参数
|
||||
local baud_rate=115200
|
||||
local shutdown_word="SHUTDOWN"
|
||||
echo ""
|
||||
read -rp "波特率 [默认 115200]: " input_baud
|
||||
if [ -n "$input_baud" ]; then
|
||||
baud_rate="$input_baud"
|
||||
fi
|
||||
|
||||
read -rp "关机指令 [默认 SHUTDOWN]: " input_word
|
||||
if [ -n "$input_word" ]; then
|
||||
shutdown_word="$input_word"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}=== 确认配置 ===${NC}"
|
||||
echo " 串口设备: $SELECTED_PORT"
|
||||
echo " 波特率: $baud_rate"
|
||||
echo " 关机指令: $shutdown_word"
|
||||
echo " 安装目录: $INSTALL_DIR"
|
||||
echo ""
|
||||
read -rp "确认安装? [Y/n]: " confirm
|
||||
if [[ "$confirm" =~ ^[Nn] ]]; then
|
||||
warn "已取消安装"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 3. 检查二进制文件
|
||||
if [ ! -f "${SCRIPT_DIR}/shutdown" ]; then
|
||||
error "未找到预编译的 shutdown 二进制文件"
|
||||
echo "请将 shutdown 二进制文件复制到 ${SCRIPT_DIR} 目录下"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 4. 安装文件
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
cp "${SCRIPT_DIR}/shutdown" "${INSTALL_DIR}/shutdown"
|
||||
chmod +x "${INSTALL_DIR}/shutdown"
|
||||
|
||||
# 5. 生成配置
|
||||
generate_config "$SELECTED_PORT" "$baud_rate" "$shutdown_word"
|
||||
|
||||
# 6. 安装关机钩子
|
||||
mkdir -p /usr/lib/systemd/system-shutdown
|
||||
generate_send_signal "$SELECTED_PORT" "$baud_rate" "$shutdown_word"
|
||||
|
||||
# 7. 生成并启用 systemd 服务
|
||||
generate_service
|
||||
systemctl daemon-reload
|
||||
systemctl enable ttyshutdown.service
|
||||
systemctl start ttyshutdown.service
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} 安装完成!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e " 服务状态: ${CYAN}systemctl status ttyshutdown${NC}"
|
||||
echo -e " 查看日志: ${CYAN}journalctl -u ttyshutdown -f${NC}"
|
||||
echo -e " 停止服务: ${CYAN}systemctl stop ttyshutdown${NC}"
|
||||
echo -e " 卸载: ${CYAN}sudo bash ${SCRIPT_DIR}/uninstall.sh${NC}"
|
||||
}
|
||||
|
||||
main
|
||||
14
poweroff_linux/send_signal.sh
Normal file
14
poweroff_linux/send_signal.sh
Normal file
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
TTY_DEVICE="/dev/ttyACM0"
|
||||
# 参数为 "poweroff", "halt", "reboot"等
|
||||
ACTION=$1
|
||||
|
||||
# 当关机(poweroff)或重启(reboot)时才发送
|
||||
if [ "$ACTION" = "poweroff" ] ; then
|
||||
if [ -w "$TTY_DEVICE" ]; then
|
||||
stty -F "$TTY_DEVICE" 115200 cs8 -cstopb -parenb
|
||||
printf "ok\r\n" > "$TTY_DEVICE"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
BIN
poweroff_linux/shutdown
Normal file
BIN
poweroff_linux/shutdown
Normal file
Binary file not shown.
164
poweroff_linux/shutdown.go
Normal file
164
poweroff_linux/shutdown.go
Normal file
@ -0,0 +1,164 @@
|
||||
// 通过串口接收指令触发系统关机的程序
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.bug.st/serial"
|
||||
)
|
||||
|
||||
// Config 配置结构体,从 config.json 文件读取
|
||||
type Config struct {
|
||||
SerialPort string `json:"port"` // 串口设备路径,如 COM1 或 /dev/ttyUSB0
|
||||
BaudRate int `json:"baudRate"` // 串口波特率
|
||||
ShutdownWord string `json:"shutdownWord"` // 触发关机的指令文本
|
||||
}
|
||||
|
||||
// loadConfig 从指定路径加载配置文件并解析为 Config 结构体
|
||||
func loadConfig(configPath string) (*Config, error) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
err = json.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 加载配置文件
|
||||
config, err := loadConfig("config.json")
|
||||
if err != nil {
|
||||
log.Fatalf("加载配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("已监听串口 %s (波特率 %d),等待指令 '%s'...", config.SerialPort, config.BaudRate, config.ShutdownWord)
|
||||
|
||||
// 串口读取缓冲区和数据接收缓冲区
|
||||
buf := make([]byte, 1024)
|
||||
var buffer bytes.Buffer
|
||||
|
||||
// 外层循环:持续尝试连接串口
|
||||
for {
|
||||
mode := &serial.Mode{
|
||||
BaudRate: config.BaudRate,
|
||||
}
|
||||
|
||||
// 尝试打开串口
|
||||
port, err := serial.Open(config.SerialPort, mode)
|
||||
if err != nil {
|
||||
log.Printf("打开串口 %s 失败: %v,5秒后重试...", config.SerialPort, err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
defer port.Close()
|
||||
log.Printf("串口 %s 已连接", config.SerialPort)
|
||||
|
||||
// 设置读取超时为1秒,避免阻塞
|
||||
port.SetReadTimeout(time.Second * 1)
|
||||
|
||||
// 串口初始化后发送 start 指令
|
||||
_, err = port.Write([]byte("start\r\n"))
|
||||
if err != nil {
|
||||
log.Printf("发送 start 失败: %v", err)
|
||||
port.Close()
|
||||
continue
|
||||
}
|
||||
log.Printf("已发送 start")
|
||||
|
||||
// 启动定时发送 blive的协程
|
||||
sendDone := make(chan struct{})
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_, err := port.Write([]byte("blive\r\n"))
|
||||
if err != nil {
|
||||
log.Printf("发送 blive 失败: %v", err)
|
||||
return
|
||||
}
|
||||
case <-sendDone:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 内层循环:读取串口数据
|
||||
for {
|
||||
n, err := port.Read(buf)
|
||||
if err != nil {
|
||||
if err.Error() == "timeout" {
|
||||
continue
|
||||
}
|
||||
log.Printf("读取串口错误: %v,重新连接...", err)
|
||||
close(sendDone)
|
||||
port.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
// 将接收到的数据写入缓冲区
|
||||
buffer.Write(buf[:n])
|
||||
|
||||
data := buffer.String()
|
||||
// 检查是否接收到完整的一行数据(以 \r\n 或 \n 结尾)
|
||||
if strings.Contains(data, "\r\n") || strings.Contains(data, "\n") {
|
||||
var lines []string
|
||||
if strings.Contains(data, "\r\n") {
|
||||
lines = strings.Split(dataAfter(data, "\r\n"), "\r\n")
|
||||
} else {
|
||||
lines = strings.Split(dataAfter(data, "\n"), "\n")
|
||||
}
|
||||
|
||||
// 处理每一行数据
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
log.Printf("收到: %s", line)
|
||||
// 如果匹配到关机指令,执行关机
|
||||
if line == config.ShutdownWord {
|
||||
executeShutdown()
|
||||
}
|
||||
}
|
||||
buffer.Reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dataAfter 获取字符串中最后一个分隔符之前的内容
|
||||
// 用于处理跨数据包的行边界问题,保留最后一个不完整的行
|
||||
func dataAfter(s, sep string) string {
|
||||
idx := strings.LastIndex(s, sep)
|
||||
if idx == -1 {
|
||||
return s
|
||||
}
|
||||
return s[:idx]
|
||||
}
|
||||
|
||||
// executeShutdown 执行系统关机命令
|
||||
func executeShutdown() {
|
||||
log.Println("触发关机指令!")
|
||||
log.Println("执行关机命令: shutdown -h now")
|
||||
cmd := exec.Command("shutdown", "-h", "now")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Fatalf("关机命令执行失败: %v", err)
|
||||
}
|
||||
}
|
||||
BIN
poweroff_linux/shutdown_go.zip
Normal file
BIN
poweroff_linux/shutdown_go.zip
Normal file
Binary file not shown.
13
poweroff_linux/ttyshutdown.service
Normal file
13
poweroff_linux/ttyshutdown.service
Normal file
@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description= ttyshutdown
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/home/lyrobot010/shutdown
|
||||
ExecStart=/home/lyrobot010/shutdown/shutdown
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
35
poweroff_linux/uninstall.sh
Normal file
35
poweroff_linux/uninstall.sh
Normal file
@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
|
||||
INSTALL_DIR="/opt/ttyshutdown"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo -e "${RED}[ERROR]${NC} 请使用 root 权限运行: sudo bash uninstall.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}即将卸载串口关机控制器${NC}"
|
||||
read -rp "确认卸载? [y/N]: " confirm
|
||||
if [[ ! "$confirm" =~ ^[Yy] ]]; then
|
||||
info "已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
systemctl stop ttyshutdown.service 2>/dev/null || true
|
||||
systemctl disable ttyshutdown.service 2>/dev/null || true
|
||||
rm -f /etc/systemd/system/ttyshutdown.service
|
||||
systemctl daemon-reload
|
||||
|
||||
rm -f /usr/lib/systemd/system-shutdown/send_signal.sh
|
||||
|
||||
rm -rf "$INSTALL_DIR"
|
||||
|
||||
info "卸载完成"
|
||||
243
poweroff_linux/串口关机文档.md
Normal file
243
poweroff_linux/串口关机文档.md
Normal file
@ -0,0 +1,243 @@
|
||||
# 串口关机控制器
|
||||
|
||||
通过串口接收指令触发 x86 Ubuntu 系统关机的程序。
|
||||
|
||||
## 功能概述
|
||||
|
||||
本程序持续监听指定的串口,当接收到特定的关机指令时,自动执行系统关机操作。适用于嵌入式 x86 Ubuntu 系统中需要通过外部设备(如 MCU/Arduino)控制关机的场景。
|
||||
|
||||
程序启动后会主动发送 `start` 握手指令,并每秒定时发送 `blive` 心跳包,用于告知对端设备本机处于在线状态。
|
||||
|
||||
## 主要特性
|
||||
|
||||
- 持续监听串口数据
|
||||
- 支持配置文件自定义串口参数
|
||||
- 自动重连机制(串口断开后每 5 秒重新连接)
|
||||
- 按行解析接收到的数据
|
||||
- 串口连接后发送 `start` 握手指令
|
||||
- 每秒定时发送 `blive` 心跳包
|
||||
- 执行系统关机命令
|
||||
|
||||
## 通信协议
|
||||
|
||||
### 本机发送(→ 对端)
|
||||
|
||||
| 指令 | 触发时机 | 说明 |
|
||||
|------|----------|------|
|
||||
| `start\r\n` | 串口连接成功后立即发送 | 握手指令,通知对端本机已就绪 |
|
||||
| `blive\r\n` | 每秒定时发送 | 心跳包,通知对端本机仍在运行 |
|
||||
| `ok\r\n` | 系统关机(poweroff)时发送 | 关机钩子脚本发送,通知对端本机即将关机 |
|
||||
|
||||
### 本机接收(← 对端)
|
||||
|
||||
| 指令 | 说明 |
|
||||
|------|------|
|
||||
| `shutdownWord`(配置项) | 收到后立即触发系统关机,默认为 `SHUTDOWN` |
|
||||
|
||||
## 配置说明
|
||||
|
||||
配置文件为 `config.json`,与可执行文件放在同一目录下,包含以下参数:
|
||||
|
||||
| 参数 | 类型 | 说明 | 默认值 |
|
||||
|------|------|------|--------|
|
||||
| port | string | 串口设备路径 | `/dev/ttyUSB0` |
|
||||
| baudRate | int | 串口波特率 | 115200 |
|
||||
| shutdownWord | string | 触发关机的指令文本 | `SHUTDOWN` |
|
||||
|
||||
### 串口设备路径
|
||||
|
||||
x86 Ubuntu 下常见的串口设备路径格式:
|
||||
|
||||
| 设备类型 | 路径格式 | 说明 |
|
||||
|----------|----------|------|
|
||||
| USB 转串口 | `/dev/ttyUSBx` | USB-RS232/USB-TTL 转接器(如 CH340、CP2102、FT232) |
|
||||
| USB CDC ACM | `/dev/ttyACMx` | USB 虚拟串口设备(使用USB时) |
|
||||
| 原生串口 | `/dev/ttySx` | 主板自带 RS232 串口(COM 口) |
|
||||
|
||||
> `x` 为设备编号,从 0 开始。可通过 `ls /dev/tty*` 或 `dmesg | grep tty` 查看系统中可用的串口设备。
|
||||
|
||||
### 配置示例
|
||||
使用 USB 转串口:
|
||||
```json
|
||||
{
|
||||
"port": "/dev/ttyUSB0",
|
||||
"baudRate": 115200,
|
||||
"shutdownWord": "SHUTDOWN"
|
||||
}
|
||||
```
|
||||
|
||||
使用 USB CDC:
|
||||
|
||||
```json
|
||||
{
|
||||
"port": "/dev/ttyACM0",
|
||||
"baudRate": 115200,
|
||||
"shutdownWord": "SHUTDOWN"
|
||||
}
|
||||
```
|
||||
|
||||
使用主板原生串口:
|
||||
|
||||
```json
|
||||
{
|
||||
"port": "/dev/ttyS0",
|
||||
"baudRate": 115200,
|
||||
"shutdownWord": "SHUTDOWN"
|
||||
}
|
||||
```
|
||||
|
||||
## 交叉编译
|
||||
|
||||
本程序在 Windows 上开发,需要交叉编译为 x86 Linux 可执行文件:
|
||||
|
||||
```bash
|
||||
set GOOS=linux
|
||||
set GOARCH=amd64
|
||||
set CGO_ENABLED=0
|
||||
go build -o shutdown shutdown.go
|
||||
```
|
||||
|
||||
PowerShell 环境:
|
||||
|
||||
```powershell
|
||||
$env:GOOS = "linux"
|
||||
$env:GOARCH = "amd64"
|
||||
$env:CGO_ENABLED = "0"
|
||||
go build -o shutdown shutdown.go
|
||||
```
|
||||
|
||||
编译产物 `shutdown` 为 Linux x86_64 可执行文件,需上传至目标 Ubuntu 系统运行。
|
||||
|
||||
## 部署与运行
|
||||
|
||||
### 方式一:使用安装脚本(推荐)
|
||||
|
||||
项目提供 `install.sh` 安装脚本,可自动完成串口选择、配置生成、服务安装等全部流程。
|
||||
|
||||
#### 上传项目到目标系统
|
||||
|
||||
```bash
|
||||
scp -r ./ user@<目标IP>:/home/user/shutdown/
|
||||
```
|
||||
|
||||
#### 运行安装脚本
|
||||
|
||||
```bash
|
||||
cd /home/user/shutdown
|
||||
sudo bash install.sh
|
||||
```
|
||||
|
||||
#### 安装流程
|
||||
|
||||
1. **选择串口** - 脚本自动扫描系统中的串口设备(`ttyUSB*`、`ttyACM*`、`ttyS*`),列出设备并标明类型,交互选择或手动输入路径
|
||||
2. **配置参数** - 可自定义波特率(默认 115200)和关机指令(默认 SHUTDOWN)
|
||||
3. **确认安装** - 显示完整配置供确认
|
||||
4. **自动编译** - 检测到 Go 环境则交叉编译,否则使用预编译二进制
|
||||
5. **安装文件** - 二进制和配置文件安装到 `/opt/ttyshutdown/`
|
||||
6. **关机钩子** - 生成 `/usr/lib/systemd/system-shutdown/send_signal.sh`,系统关机时通过串口发送 `ok\r\n` 通知对端
|
||||
7. **systemd 服务** - 生成并启用 `ttyshutdown.service`
|
||||
|
||||
#### 安装后管理
|
||||
|
||||
```bash
|
||||
# 查看服务状态
|
||||
systemctl status ttyshutdown
|
||||
|
||||
# 查看实时日志
|
||||
journalctl -u ttyshutdown -f
|
||||
|
||||
# 停止服务
|
||||
systemctl stop ttyshutdown
|
||||
|
||||
# 更换串口
|
||||
sudo bash change_port.sh
|
||||
|
||||
# 卸载
|
||||
sudo bash uninstall.sh
|
||||
```
|
||||
|
||||
### 更换串口
|
||||
|
||||
当需要更换串口设备时(如更换 USB 端口、更换转接器等),使用 `change_port.sh` 脚本可自动完成配置更新和服务重启。
|
||||
|
||||
```bash
|
||||
sudo bash change_port.sh
|
||||
```
|
||||
|
||||
#### 更换流程
|
||||
|
||||
1. **显示当前配置** - 展示当前串口设备、波特率、关机指令
|
||||
2. **选择新串口** - 自动扫描系统中可用串口,当前使用的串口会标注 `<-- 当前`,交互选择或手动输入新路径
|
||||
3. **可选修改参数** - 波特率和关机指令可直接回车保持不变,或输入新值修改
|
||||
4. **确认更改** - 显示新旧配置对比供确认
|
||||
5. **执行更改** - 同时更新 `config.json` 和 `send_signal.sh`,并自动重启 `ttyshutdown` 服务
|
||||
|
||||
> 脚本会同时更新 `config.json` 和 `send_signal.sh` 中的串口配置,确保两者一致。
|
||||
|
||||
### 方式二:手动部署
|
||||
|
||||
#### 1. 上传到目标系统
|
||||
|
||||
```bash
|
||||
scp shutdown config.json user@<目标IP>:/home/user/shutdown/
|
||||
```
|
||||
|
||||
#### 2. 添加执行权限
|
||||
|
||||
```bash
|
||||
chmod +x shutdown
|
||||
```
|
||||
|
||||
#### 3. 运行程序
|
||||
|
||||
```bash
|
||||
sudo ./shutdown
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 程序需要 root 权限才能执行系统关机命令
|
||||
2. 串口断开后会每 5 秒尝试重新连接
|
||||
3. 接收的数据以换行符(`\n` 或 `\r\n`)为单位进行解析
|
||||
4. 关机指令必须与配置文件中的 `shutdownWord` 完全匹配(区分大小写)
|
||||
5. 串口重连后 `start` 握手和 `blive` 心跳会自动重新启动
|
||||
6. 串口设备路径在设备重新插拔后可能变化(ttyUSB0 → ttyUSB1),建议使用 udev 规则绑定固定设备名
|
||||
7. 安装脚本会将选定的串口设备同时写入 `config.json` 和 `send_signal.sh`,确保两者一致
|
||||
|
||||
|
||||
## 日志输出
|
||||
|
||||
程序运行时会输出以下日志信息:
|
||||
|
||||
- 配置加载状态
|
||||
- 串口连接状态
|
||||
- `start` 握手指令发送状态
|
||||
- 接收到的数据内容
|
||||
- 关机指令触发和执行状态
|
||||
- 串口错误和重连状态
|
||||
|
||||
## 关机钩子
|
||||
|
||||
系统关机时,`/usr/lib/systemd/system-shutdown/send_signal.sh` 会被 systemd 调用,在系统执行 poweroff 前通过串口向对端发送 `ok\r\n`,通知对端本机即将关机。
|
||||
|
||||
该脚本由安装脚本自动生成,其中 `TTY_DEVICE` 与 `config.json` 中的 `port` 保持一致。
|
||||
|
||||
## 文件说明
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `shutdown.go` | 主程序源码 |
|
||||
| `config.json` | 串口配置文件(与二进制同目录) |
|
||||
| `send_signal.sh` | systemd 关机钩子脚本,安装到 `/usr/lib/systemd/system-shutdown/` |
|
||||
| `ttyshutdown.service` | systemd 服务单元文件 |
|
||||
| `install.sh` | 安装脚本 |
|
||||
| `uninstall.sh` | 卸载脚本 |
|
||||
| `change_port.sh` | 更换串口脚本 |
|
||||
|
||||
## 技术实现
|
||||
|
||||
- 编程语言:Go
|
||||
- 目标平台:x86 Ubuntu (linux/amd64)
|
||||
- 依赖库:`go.bug.st/serial`(串口通信)
|
||||
- 关机命令:`shutdown -h now`
|
||||
- 交叉编译:CGO_ENABLED=0 静态编译,无外部依赖
|
||||
Reference in New Issue
Block a user