165 lines
3.9 KiB
Go
165 lines
3.9 KiB
Go
// 通过串口接收指令触发系统关机的程序
|
||
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)
|
||
}
|
||
}
|