package main import ( "bufio" "context" "encoding/json" "errors" "fmt" "io" "os" "os/signal" "strings" "sync/atomic" "syscall" "time" "github.com/google/uuid" "hnyfkj.com.cn/rtu/linux/baseapp" ) const MODULE_NAME = "YFKJ_SSH_CLIENT" var ( coupler *MQTTCoupler Version = "1.0.0.1" ErrBrokerAddressEmpty = errors.New("mqtt server address is empty") ErrIMEINotAvailable = errors.New("device imei is not available") ) func main() { if baseapp.IsArgsParam("-h") { help() return } if baseapp.IsArgsParam("-v") { fmt.Println("程序版本:", Version, "\n构建时间:", baseapp.BuildTime) return } devIMEI := baseapp.GetArgsParamStr("-c", "") if devIMEI == "" { help() return } if err := loadAppConfig(); err != nil { fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, err) return } if CfgServers.MQTTSrv.Address == "" { fmt.Printf("[%s] 错误: %v!!\n", MODULE_NAME, ErrBrokerAddressEmpty) return } ctx, cancel := context.WithCancel(context.Background()) coupler = &MQTTCoupler{ ctx: ctx, cancel: cancel, broker: CfgServers.MQTTSrv.Address, username: CfgServers.MQTTSrv.Username, password: CfgServers.MQTTSrv.Password, clientID: uuid.New().String(), imei: devIMEI, cwd: "/", } if err := coupler.init2(); err != nil { fmt.Printf("[%s] 错误: %v\n!!", MODULE_NAME, err) return } var pingState atomic.Bool heartbeatLoop(&pingState) // -启动-设备在线-心跳检测- for { if pingState.Load() { //// 等待成功连接上目标设备卍 break } fmt.Printf("[%s] 尝试连接目标设备....\n", MODULE_NAME) time.Sleep(1 * time.Second) } term(&pingState) /////////////////// 启动终端模拟器卍 } func term(pingState *atomic.Bool) { var executing atomic.Bool // 是否有正在执行中的命令 var interrupted atomic.Bool // 用户是否按键取消了命令 interruptLoop(&executing, &interrupted) // Ctrl+C卍 printWelcome() reader := bufio.NewReader(os.Stdin) for { if !pingState.Load() { fmt.Printf("[%s] 目标设备连接丢失!!\n", MODULE_NAME) break } fmt.Print("\033[?25h") // 显示光标 if interrupted.Swap(false) { fmt.Println("^C") } fmt.Printf("root@%s:%s# ", coupler.imei, coupler.cwd) input, err := reader.ReadString('\n') if err != nil { if err == io.EOF { os.Exit(0) } fmt.Println("读取用户输入失败: ", err) continue } input = strings.TrimSpace(input) if input == "" { continue } switch input { case "quit": _, _ = coupler.quit() os.Exit(0) } fmt.Print("\033[?25l") // 隐藏光标 executing.Store(true) result, err := coupler.exec(input) executing.Store(false) if err != nil { fmt.Printf("执行错误: %v\n", err) continue } if result.Stdout != "" { fmt.Print(result.Stdout) } if result.Stderr != "" { fmt.Fprintln(os.Stderr, result.Stderr) } if result.ExitCode == 124 { fmt.Println("command timed out, try again later") } if result.Cwd != "" { coupler.cwd = result.Cwd } } } func help() { h := ` -h 显示帮助提示 -v 当前程序版本 -c 连接目标设备(IMEI), 例如: -c 869523059113051 ` fmt.Println(h) } func interruptLoop(executing *atomic.Bool, interrupted *atomic.Bool) { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT) go func() { for range sigCh { interrupted.Store(true) if executing.Load() { _, _ = coupler.stop() } } }() } func heartbeatLoop(pingState *atomic.Bool) { go func() { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() pingFailCount := 0 pong := "" for range ticker.C { resp, err := coupler.ping() if err == nil && resp.Error == nil && resp.Result != nil && json.Unmarshal(resp.Result, &pong) == nil && pong == "pong" { pingState.Store(true) pingFailCount = 0 } else { pingFailCount++ if pingFailCount >= 3 { ///// 连续3次ping失败, 可以认为设备离线 pingState.Store(false) } } } // end for }() } func printWelcome() { welcome := ` _ _ _ _ _ _ _ _ | \ | (_) | | | (_) | | | \| |_| |_ ___| |__ _| | | ___ | . | | __/ __| '_ \| | | |/ _ \ | |\ | | || (__| | | | | | | __/ |_| \_|_|\__\___|_| |_|_|_|_|\___| ══════════════════════════════════ 云飞科技RTU远程运维终端 ══════════════════════════════════ 提示: 输入'quit'命令, 退出终端 ` fmt.Println(welcome) }