|
|
@@ -0,0 +1,214 @@
|
|
|
+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")
|
|
|
+ }
|
|
|
+
|
|
|
+ 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)
|
|
|
+}
|