|
|
@@ -8,14 +8,16 @@ import (
|
|
|
"fmt"
|
|
|
"io"
|
|
|
"os"
|
|
|
+ "os/exec"
|
|
|
"os/signal"
|
|
|
+ "runtime"
|
|
|
"strings"
|
|
|
"sync/atomic"
|
|
|
"syscall"
|
|
|
"time"
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
- "hnyfkj.com.cn/rtu/linux/baseapp"
|
|
|
+ "github.com/peterh/liner"
|
|
|
)
|
|
|
|
|
|
const MODULE_NAME = "YFKJ_SSH_CLIENT"
|
|
|
@@ -23,22 +25,23 @@ const MODULE_NAME = "YFKJ_SSH_CLIENT"
|
|
|
var (
|
|
|
coupler *MQTTCoupler
|
|
|
Version = "1.0.0.1"
|
|
|
+ BuildTime = "unknown"
|
|
|
ErrBrokerAddressEmpty = errors.New("mqtt server address is empty")
|
|
|
ErrIMEINotAvailable = errors.New("device imei is not available")
|
|
|
)
|
|
|
|
|
|
func main() {
|
|
|
- if baseapp.IsArgsParam("-h") {
|
|
|
+ if IsArgsParam("-h") {
|
|
|
help()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- if baseapp.IsArgsParam("-v") {
|
|
|
- fmt.Println("程序版本:", Version, "\n构建时间:", baseapp.BuildTime)
|
|
|
+ if IsArgsParam("-v") {
|
|
|
+ fmt.Println("程序版本:", Version, "\n构建时间:", BuildTime)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- devIMEI := baseapp.GetArgsParamStr("-c", "")
|
|
|
+ devIMEI := GetArgsParamStr("-c", "")
|
|
|
if devIMEI == "" {
|
|
|
help()
|
|
|
return
|
|
|
@@ -70,15 +73,13 @@ func main() {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- fmt.Printf("[%s] 正在连接目标设备...\n", MODULE_NAME)
|
|
|
-
|
|
|
var pingState atomic.Bool
|
|
|
heartbeatLoop(&pingState) // -启动-设备在线-心跳检测-
|
|
|
for {
|
|
|
if pingState.Load() { //// 等待成功连接上目标设备卍
|
|
|
break
|
|
|
}
|
|
|
- fmt.Printf("[%s] 无法连接目标设备!!\n", MODULE_NAME)
|
|
|
+ fmt.Printf("[%s] 正在尝试连接设备...\n", MODULE_NAME)
|
|
|
time.Sleep(1 * time.Second)
|
|
|
}
|
|
|
|
|
|
@@ -93,40 +94,88 @@ func term(pingState *atomic.Bool) {
|
|
|
|
|
|
printWelcome()
|
|
|
|
|
|
- reader := bufio.NewReader(os.Stdin)
|
|
|
+ line := liner.NewLiner()
|
|
|
+ defer line.Close()
|
|
|
+ line.SetCtrlCAborts(false)
|
|
|
+ line.SetTabCompletionStyle(liner.TabCircular)
|
|
|
+
|
|
|
+ historyFile := "history.txt"
|
|
|
+ var history []string
|
|
|
+
|
|
|
+ if f, err := os.Open(historyFile); err == nil {
|
|
|
+ scanner := bufio.NewScanner(f)
|
|
|
+ for scanner.Scan() {
|
|
|
+ cmd := strings.TrimSpace(scanner.Text())
|
|
|
+ if cmd != "" {
|
|
|
+ history = append(history, cmd)
|
|
|
+ line.AppendHistory(cmd)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ f.Close()
|
|
|
+ }
|
|
|
+
|
|
|
+ defer func() {
|
|
|
+ if f, err := os.Create(historyFile); err == nil {
|
|
|
+ line.WriteHistory(f)
|
|
|
+ f.Close()
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ line.SetCompleter(func(input string) []string { // 补全
|
|
|
+ var matches []string
|
|
|
+ for _, cmd := range history {
|
|
|
+ if strings.HasPrefix(cmd, input) {
|
|
|
+ matches = append(matches, cmd)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return matches
|
|
|
+ })
|
|
|
+
|
|
|
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
|
|
|
+ prompt := fmt.Sprintf("root@%s:%s# ", coupler.imei, coupler.cwd)
|
|
|
+ input, err := line.Prompt(prompt) /// 等待用户输入指令
|
|
|
+ if err == nil {
|
|
|
+ goto inputOK
|
|
|
}
|
|
|
|
|
|
+ if err == io.EOF { ////////////////// Ctrl+D 按键处理
|
|
|
+ _, _ = coupler.quit()
|
|
|
+ fmt.Println("bye")
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("读取用户输入失败:", err)
|
|
|
+ continue
|
|
|
+
|
|
|
+ inputOK:
|
|
|
input = strings.TrimSpace(input)
|
|
|
if input == "" {
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
- switch input {
|
|
|
- case "quit":
|
|
|
+ line.AppendHistory(input) ///// 保存用户输入的历史命令
|
|
|
+
|
|
|
+ history = append(history, input) ///// 本次也立刻生效
|
|
|
+
|
|
|
+ if input == "exit" || input == "quit" {
|
|
|
_, _ = coupler.quit()
|
|
|
- os.Exit(0)
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ if input == "clear" {
|
|
|
+ clearScreen()
|
|
|
+ continue
|
|
|
}
|
|
|
|
|
|
- fmt.Print("\033[?25l") // 隐藏光标
|
|
|
executing.Store(true)
|
|
|
result, err := coupler.exec(input)
|
|
|
executing.Store(false)
|
|
|
@@ -137,11 +186,14 @@ func term(pingState *atomic.Bool) {
|
|
|
}
|
|
|
|
|
|
if result.Stdout != "" {
|
|
|
- fmt.Println(result.Stdout)
|
|
|
+ fmt.Print(result.Stdout)
|
|
|
}
|
|
|
if result.Stderr != "" {
|
|
|
fmt.Fprintln(os.Stderr, result.Stderr)
|
|
|
}
|
|
|
+ if result.ExitCode == 124 {
|
|
|
+ fmt.Println("command timeout")
|
|
|
+ }
|
|
|
|
|
|
if result.Cwd != "" {
|
|
|
coupler.cwd = result.Cwd
|
|
|
@@ -176,7 +228,6 @@ func heartbeatLoop(pingState *atomic.Bool) {
|
|
|
go func() {
|
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
|
defer ticker.Stop()
|
|
|
-
|
|
|
pingFailCount := 0
|
|
|
pong := ""
|
|
|
for range ticker.C {
|
|
|
@@ -190,7 +241,7 @@ func heartbeatLoop(pingState *atomic.Bool) {
|
|
|
if pingFailCount >= 3 { ///// 连续3次ping失败, 可以认为设备离线
|
|
|
pingState.Store(false)
|
|
|
}
|
|
|
- }
|
|
|
+ } // end if
|
|
|
} // end for
|
|
|
}()
|
|
|
}
|
|
|
@@ -204,10 +255,22 @@ func printWelcome() {
|
|
|
| |\ | | || (__| | | | | | | __/
|
|
|
|_| \_|_|\__\___|_| |_|_|_|_|\___|
|
|
|
|
|
|
-═══════════════════════════════════
|
|
|
- 云飞科技 RTU远程运维终端
|
|
|
-═══════════════════════════════════
|
|
|
-提示: 输入'quit'命令, 可退出终端模拟器
|
|
|
+══════════════════════════════════
|
|
|
+ 云飞科技RTU远程运维终端
|
|
|
+══════════════════════════════════
|
|
|
+提示: 输入'quit'命令, 退出终端
|
|
|
`
|
|
|
fmt.Println(welcome)
|
|
|
}
|
|
|
+
|
|
|
+func clearScreen() {
|
|
|
+ if runtime.GOOS == "windows" {
|
|
|
+ cmd := exec.Command("cmd", "/c", "cls")
|
|
|
+ cmd.Stdout = os.Stdout
|
|
|
+ _ = cmd.Run()
|
|
|
+ } else {
|
|
|
+ cmd := exec.Command("clear")
|
|
|
+ cmd.Stdout = os.Stdout
|
|
|
+ _ = cmd.Run()
|
|
|
+ }
|
|
|
+}
|