Compare commits

...

20 Commits
v0.8 ... main

Author SHA1 Message Date
jixishi f6eff2da22 Fix img名称错误 2024-06-12 14:46:29 +08:00
jixishi 2fc4b4f41a UP 多服同步 2024-06-12 14:43:43 +08:00
jixishi c841dfeae4 UP 功能建议更新 2024-06-03 20:54:23 +08:00
jixishi 88285df82b UP README.md 2024-05-30 16:43:21 +08:00
jixishi 67e2f5a1c8 UP README.md 2024-05-30 16:35:29 +08:00
jixishi 17950c05dc UP releaser 2024-05-30 16:23:53 +08:00
jixishi 9b374fc42d Merge branch 'dev' 2024-05-30 16:04:55 +08:00
jixishi 82ec65958e 时间戳 文件传输 支持 2024-05-30 15:56:52 +08:00
jixishi 68f285b5ed up 2024-04-26 22:33:08 +08:00
jixishi d2f8c8a268 增加Ctrl组合键发送指令.ctrl 如.ctrl c 2024-04-26 22:29:16 +08:00
jixishi 8d4273df77 增加交互式配置支持 2024-04-26 22:29:00 +08:00
jixishi 92c92e67e1 增加交互式配置支持 2024-04-26 22:23:32 +08:00
jixishi 604e5bb4ad 修复帧指定的错误参数指向 2024-04-26 12:49:32 +08:00
jixishi e3415ae05a 去除TCP服务端连接 2024-04-26 11:55:26 +08:00
jixishi d19c09e4cd TCP|UDP 客户端数据转发支持更新 帧长设置支持 2024-04-26 11:03:44 +08:00
jixishi 5bf90d1b63 TCP|UDP 客户端数据转发支持更新 2024-04-19 22:42:28 +08:00
jixishi dffb269247 RT 2024-04-19 17:03:28 +08:00
JiXieShi d450a8a019 多平台构建 2024-04-19 16:18:03 +08:00
JiXieShi 8b1e5bfb06 多平台构建 2024-04-19 16:15:28 +08:00
JiXieShi 437f309ab9 多平台构建 2024-04-19 16:08:55 +08:00
19 changed files with 687 additions and 150 deletions

4
.gitignore vendored
View File

@ -1 +1,5 @@
/build/ /build/
.idea
dist/
/go.sum
/view/*

61
.goreleaser.yaml Normal file
View File

@ -0,0 +1,61 @@
#file: noinspection YAMLSchemaValidation
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 1
before:
hooks:
# You may remove this if you don't use go modules.
# - go mod tidy
# you may remove this if you don't need go generate
# - go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
ldflags:
- -s -w
upx:
- enabled: true
goos:
- windows
goarch:
- amd64
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: 'v1.0.0-snapshot'
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,10 +1,21 @@
# SerialTerminalForWindowsTerminal # SerialTerminalForWindowsTerminal
编写这个项目之前在windows Terminal中对串口设备的支持不是很棒 开始这个项目之前我发现Windows Terminal对串口设备的支持并不理想。
用过一段时间的 我试用了一段时间[Zhou-zhi-peng的SerialPortForWindowsTerminal](https://github.com/Zhou-zhi-peng/SerialPortForWindowsTerminal/)项目。
[SerialPortForWindowsTerminal](https://github.com/Zhou-zhi-peng/SerialPortForWindowsTerminal/)
项目。 然而,这个项目存在着编码转换的问题,导致数据显示乱码,并且作者目前并没有进行后续支持。因此,我决定创建了这个项目。
此项目没有编码转换能力在使用过程中有乱码的问题,并且作者截至目前并没有进行后续支持,于是我便编写了此项目
## 功能进展
* [x] Hex接收发送(大写hex与原文同显)
* [x] 双向编码转换
* [x] 活动端口探测
* [x] 数据日志保存
* [x] Hex断帧设置
* [x] UDP数据转发(支持多服)
* [x] TCP数据转发(支持多服)
* [x] 参数交互配置
* [x] Ctrl组合键
* [x] 文件接收发送(trzsz lrzsz都支持)
## 运行示例 ## 运行示例
@ -25,3 +36,19 @@
5. Hex发送 `./COM -p COM8 -b 115200` 5. Hex发送 `./COM -p COM8 -b 115200`
![img5.png](image/img5.png) ![img5.png](image/img5.png)
6. 交互配置 `./COM`
![img6.png](image/img6.png)
7. Ctrl组合键发送指令.ctrl `.ctrl c`
![img7.png](image/img7.png)
8. 文件上传演示 `index.html`
![img8.png](image/img8.png)
内容对比
![img11.png](image/img11.png)
9. 时间戳 `./COM -p COM8 -t`
![img9.png](image/img9.png)
10. 格式修改 `./COM -p COM11 -t='<2006-01-02 15:04:05>'`
![img10.png](image/img10.png)
11. 多服同步转发 `./COM -p COM11 -f 1 -a 127.0.0.1:23456 -f 1 -a 127.0.0.1:23457`
![img12.png](image/img12.png)

View File

@ -14,23 +14,38 @@ type Command struct {
function func() function func()
} }
var (
commands []Command
args []string
)
func cmdhelp() { func cmdhelp() {
var page = 0 var page = 0
fmt.Printf(">-------Help(%v)-------<\n", page) strout(out, config.outputCode, fmt.Sprintf(">-------Help(%v)-------<\n", page))
for i := 0; i < len(commands); i++ { for i := 0; i < len(commands); i++ {
strout(config.outputCode, fmt.Sprintf(" %-10v --%v\n", commands[i].name, commands[i].description)) strout(out, config.outputCode, fmt.Sprintf(" %-10v --%v\n", commands[i].name, commands[i].description))
} }
} }
func cmdexit() { func cmdexit() {
CloseTrzsz()
CloseSerial()
os.Exit(0) os.Exit(0)
} }
func cmdargs() { func cmdargs() {
fmt.Printf(">-------Args(%v)-------<\n", len(args)-1) strout(out, config.outputCode, fmt.Sprintf(">-------Args(%v)-------<\n", len(args)-1))
fmt.Printf("%q\n", args[1:]) strout(out, config.outputCode, fmt.Sprintf("%q\n", args[1:]))
}
func cmdctrl() {
var err error
b := []byte(args[1])
x := []byte{b[0] & 0x1f}
_, err = serialPort.Write(x)
ErrorF(err)
strout(out, config.outputCode, fmt.Sprintf("Ctrl+%s\n", b))
} }
func cmdhex() { func cmdhex() {
fmt.Printf(">-----Hex Send-----<\n") strout(out, config.outputCode, fmt.Sprintf(">-----Hex Send-----<\n"))
fmt.Printf("%q\n", args[1:]) strout(out, config.outputCode, fmt.Sprintf("%q\n", args[1:]))
s := strings.Join(args[1:], "") s := strings.Join(args[1:], "")
b, err := hex.DecodeString(s) b, err := hex.DecodeString(s)
if err != nil { if err != nil {
@ -43,7 +58,7 @@ func cmdhex() {
} }
func cmdinit() { func cmdinit() {
commands = append(commands, Command{name: ".help", description: "帮助信息", function: cmdhelp}) commands = append(commands, Command{name: ".help", description: "帮助信息", function: cmdhelp})
commands = append(commands, Command{name: ".args", description: "参数信息", function: cmdargs}) commands = append(commands, Command{name: ".ctrl", description: "发送Ctrl组合键", function: cmdctrl})
commands = append(commands, Command{name: ".hex", description: "发送Hex", function: cmdhex}) commands = append(commands, Command{name: ".hex", description: "发送Hex", function: cmdhex})
commands = append(commands, Command{name: ".exit", description: "退出终端", function: cmdexit}) commands = append(commands, Command{name: ".exit", description: "退出终端", function: cmdexit})
} }

View File

@ -1,14 +1,68 @@
package main package main
import (
"fmt"
"log"
"net"
"os"
"time"
)
type Config struct { type Config struct {
portName string portName string
baudRate int baudRate int
dataBits int dataBits int
stopBits int stopBits int
parityBit int
outputCode string outputCode string
inputCode string inputCode string
endStr string endStr string
enableLog bool enableLog bool
logFilePath string logFilePath string
parityBit int forWard []int
frameSize int
timesTamp bool
timesFmt string
address []string
}
type FoeWardMode int
const (
NOT FoeWardMode = iota
TCPC
UDPC
)
var config Config
func setForWardClient(mode FoeWardMode, add string) (conn net.Conn) {
var err error
switch mode {
case NOT:
case TCPC:
conn, err = net.Dial("tcp", add)
if err != nil {
log.Fatal(err)
}
case UDPC:
conn, err = net.Dial("udp", add)
if err != nil {
log.Fatal(err)
}
default:
panic("未知模式设置")
}
return conn
}
func checkLogOpen() {
if config.enableLog {
path := fmt.Sprintf(config.logFilePath, config.portName, time.Now().Format("2006_01_02T150405"))
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Fatal(err)
}
outs = append(outs, f)
}
} }

250
flag.go
View File

@ -1,17 +1,32 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"github.com/charmbracelet/bubbles/key"
inf "github.com/fzdwx/infinite"
"github.com/fzdwx/infinite/color"
"github.com/fzdwx/infinite/components"
"github.com/fzdwx/infinite/components/input/text"
"github.com/fzdwx/infinite/components/selection/confirm"
"github.com/fzdwx/infinite/components/selection/singleselect"
"github.com/fzdwx/infinite/style"
"github.com/fzdwx/infinite/theme"
"github.com/spf13/pflag"
"go.bug.st/serial"
"log"
"strconv"
"strings" "strings"
) )
type ptrVal struct { type ptrVal struct {
*string *string
sl *[]string
*int *int
il *[]int
*bool *bool
*float64 *float64
*float32 *float32
ext *string
} }
type Val struct { type Val struct {
string string
@ -19,6 +34,7 @@ type Val struct {
bool bool
float64 float64
float32 float32
extdef string
} }
type Flag struct { type Flag struct {
v ptrVal v ptrVal
@ -29,17 +45,30 @@ type Flag struct {
} }
var ( var (
portName = Flag{ptrVal{string: &config.portName}, "p", "port", Val{string: ""}, "要连接的串口\t(/dev/ttyUSB0、COMx)"} portName = Flag{ptrVal{string: &config.portName}, "p", "port", Val{string: ""}, "要连接的串口\t(/dev/ttyUSB0、COMx)"}
baudRate = Flag{ptrVal{int: &config.baudRate}, "b", "baud", Val{int: 115200}, "波特率"} baudRate = Flag{ptrVal{int: &config.baudRate}, "b", "baud", Val{int: 115200}, "波特率"}
dataBits = Flag{ptrVal{int: &config.dataBits}, "d", "data", Val{int: 8}, "数据位"} dataBits = Flag{ptrVal{int: &config.dataBits}, "d", "data", Val{int: 8}, "数据位"}
stopBits = Flag{ptrVal{int: &config.stopBits}, "s", "stop", Val{int: 0}, "停止位停止位(0: 1停止 1:1.5停止 2:2停止)"} stopBits = Flag{ptrVal{int: &config.stopBits}, "s", "stop", Val{int: 0}, "停止位停止位(0: 1停止 1:1.5停止 2:2停止)"}
outputCode = Flag{ptrVal{string: &config.outputCode}, "o", "out", Val{string: "UTF-8"}, "输出编码"} outputCode = Flag{ptrVal{string: &config.outputCode}, "o", "out", Val{string: "UTF-8"}, "输出编码"}
inputCode = Flag{ptrVal{string: &config.inputCode}, "i", "in", Val{string: "UTF-8"}, "输入编码"} inputCode = Flag{ptrVal{string: &config.inputCode}, "i", "in", Val{string: "UTF-8"}, "输入编码"}
endStr = Flag{ptrVal{string: &config.endStr}, "e", "end", Val{string: "\n"}, "终端换行符"} endStr = Flag{ptrVal{string: &config.endStr}, "e", "end", Val{string: "\n"}, "终端换行符"}
enableLog = Flag{ptrVal{bool: &config.enableLog}, "l", "log", Val{bool: false}, "是否启用日志保存"} logExt = Flag{v: ptrVal{ext: &config.logFilePath}, sStr: "l", lStr: "log", dv: Val{extdef: "./%s-$s.txt", string: ""}, help: "日志保存路径"}
logFilePath = Flag{ptrVal{string: &config.logFilePath}, "P", "Path", Val{string: "./Log.txt"}, "日志保存路径"} timeExt = Flag{v: ptrVal{ext: &config.timesFmt}, sStr: "t", lStr: "time", dv: Val{extdef: "[06-01-02 15:04:05.000]", string: ""}, help: "时间戳格式化字段"}
parityBit = Flag{ptrVal{int: &config.parityBit}, "v", "verify", Val{int: 0}, "奇偶校验(0:无校验、1:奇校验、2:偶校验、3:1校验、4:0校验)"} forWard = Flag{ptrVal{il: &config.forWard}, "f", "forward", Val{int: 0}, "转发模式(0: 无 1:TCP-C 2:UDP-C 支持多次传入)"}
flags = []Flag{portName, baudRate, dataBits, stopBits, outputCode, inputCode, endStr, enableLog, logFilePath, parityBit} address = Flag{ptrVal{sl: &config.address}, "a", "address", Val{string: "127.0.0.1:12345"}, "转发服务地址(支持多次传入)"}
frameSize = Flag{ptrVal{int: &config.frameSize}, "F", "Frame", Val{int: 16}, "帧大小"}
parityBit = Flag{ptrVal{int: &config.parityBit}, "v", "verify", Val{int: 0}, "奇偶校验(0:无校验、1:奇校验、2:偶校验、3:1校验、4:0校验)"}
flags = []Flag{portName, baudRate, dataBits, stopBits, outputCode, inputCode, endStr, forWard, address, frameSize, parityBit, logExt, timeExt}
)
var (
bauds = []string{"自定义", "300", "600", "1200", "2400", "4800", "9600",
"14400", "19200", "38400", "56000", "57600", "115200", "128000",
"256000", "460800", "512000", "750000", "921600", "1500000"}
datas = []string{"5", "6", "7", "8"}
stops = []string{"1", "1.5", "2"}
paritys = []string{"无校验", "奇校验", "偶校验", "1校验", "0校验"}
forwards = []string{"No", "TCP-C", "UDP-C"}
) )
type ValType int type ValType int
@ -49,6 +78,7 @@ const (
stringVal stringVal
intVal intVal
boolVal boolVal
extVal
) )
func printUsage(ports []string) { func printUsage(ports []string) {
@ -68,6 +98,9 @@ func flagFindValue(v ptrVal) ValType {
if v.int != nil { if v.int != nil {
return intVal return intVal
} }
if v.ext != nil {
return extVal
}
return notVal return notVal
} }
func flagprint(f Flag) { func flagprint(f Flag) {
@ -78,21 +111,202 @@ func flagprint(f Flag) {
fmt.Printf("\t-%v -%v %T \n\t %v\t默认值:%v\n", f.sStr, f.lStr, f.dv.int, f.help, f.dv.int) fmt.Printf("\t-%v -%v %T \n\t %v\t默认值:%v\n", f.sStr, f.lStr, f.dv.int, f.help, f.dv.int)
case boolVal: case boolVal:
fmt.Printf("\t-%v -%v %T \n\t %v\t默认值:%v\n", f.sStr, f.lStr, f.dv.bool, f.help, f.dv.bool) fmt.Printf("\t-%v -%v %T \n\t %v\t默认值:%v\n", f.sStr, f.lStr, f.dv.bool, f.help, f.dv.bool)
case extVal:
fmt.Printf("\t-%v -%v %T \n\t %v\t默认值:%v\n", f.sStr, f.lStr, f.dv.extdef, f.help, f.dv.extdef)
default: default:
panic("unhandled default case") panic("unhandled default case")
} }
} }
func flagInit(f *Flag) { func flagInit(f *Flag) {
if f.v.string != nil { if f.v.string != nil {
flag.StringVar(f.v.string, f.sStr, f.dv.string, "") pflag.StringVarP(f.v.string, f.lStr, f.sStr, f.dv.string, f.help)
flag.StringVar(f.v.string, f.lStr, f.dv.string, f.help)
} }
if f.v.bool != nil { if f.v.bool != nil {
flag.BoolVar(f.v.bool, f.sStr, f.dv.bool, "") pflag.BoolVarP(f.v.bool, f.lStr, f.sStr, f.dv.bool, f.help)
flag.BoolVar(f.v.bool, f.lStr, f.dv.bool, f.help)
} }
if f.v.int != nil { if f.v.int != nil {
flag.IntVar(f.v.int, f.sStr, f.dv.int, "") pflag.IntVarP(f.v.int, f.lStr, f.sStr, f.dv.int, f.help)
flag.IntVar(f.v.int, f.lStr, f.dv.int, f.help) }
if f.v.ext != nil {
pflag.StringVarP(f.v.ext, f.lStr, f.sStr, f.dv.string, f.help)
pflag.Lookup(f.lStr).NoOptDefVal = f.dv.extdef
}
if f.v.sl != nil {
pflag.StringArrayVarP(f.v.sl, f.lStr, f.sStr, []string{f.dv.string}, f.help)
}
if f.v.il != nil {
pflag.IntSliceVarP(f.v.il, f.lStr, f.sStr, []int{f.dv.int}, f.help)
} }
} }
func flagExt() {
if config.logFilePath != "" {
config.enableLog = true
}
if config.timesFmt != "" {
config.timesTamp = true
}
}
func getCliFlag() {
ports, err := serial.GetPortsList()
if err != nil {
log.Fatal(err)
}
inputs := components.NewInput()
inputs.Prompt = "Filtering: "
inputs.PromptStyle = style.New().Bold().Italic().Fg(color.LightBlue)
selectKeymap := singleselect.DefaultSingleKeyMap()
selectKeymap.Confirm = key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "finish select"),
)
selectKeymap.Choice = key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "finish select"),
)
selectKeymap.NextPage = key.NewBinding(
key.WithKeys("right"),
key.WithHelp("->", "next page"),
)
selectKeymap.PrevPage = key.NewBinding(
key.WithKeys("left"),
key.WithHelp("<-", "prev page"),
)
s, _ := inf.NewSingleSelect(
ports,
singleselect.WithKeyBinding(selectKeymap),
singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs),
).Display("选择串口")
config.portName = ports[s]
s, _ = inf.NewSingleSelect(
bauds,
singleselect.WithKeyBinding(selectKeymap),
singleselect.WithPageSize(4),
).Display("选择波特率")
if s != 0 {
config.baudRate, _ = strconv.Atoi(bauds[s])
} else {
b, _ := inf.NewText(
text.WithPrompt("BaudRate:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue("115200"),
).Display()
config.baudRate, _ = strconv.Atoi(b)
}
v, _ := inf.NewConfirmWithSelection(
confirm.WithPrompt("启用Hex"),
).Display()
if v {
config.inputCode = "hex"
b, _ := inf.NewText(
text.WithPrompt("Frames:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue("16"),
).Display()
config.frameSize, _ = strconv.Atoi(b)
}
v, _ = inf.NewConfirmWithSelection(
confirm.WithPrompt("启用时间戳"),
).Display()
config.timesTamp = v
if v {
b, _ := inf.NewText(
text.WithPrompt("格式化字段:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(timeExt.dv.extdef),
).Display()
config.timesFmt = b
}
v, _ = inf.NewConfirmWithSelection(
confirm.WithPrompt("启用高级配置"),
).Display()
if v {
s, _ = inf.NewSingleSelect(
datas,
singleselect.WithKeyBinding(selectKeymap),
singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs),
).Display("选择数据位")
config.dataBits, _ = strconv.Atoi(datas[s])
s, _ = inf.NewSingleSelect(
stops,
singleselect.WithKeyBinding(selectKeymap),
singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs),
).Display("选择停止位")
config.stopBits = s
s, _ = inf.NewSingleSelect(
paritys,
singleselect.WithKeyBinding(selectKeymap),
singleselect.WithPageSize(4),
singleselect.WithFilterInput(inputs),
).Display("选择校验位")
config.parityBit = s
t, _ := inf.NewText(
text.WithPrompt("换行符:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(endStr.dv.string),
).Display()
config.endStr = t
v, _ = inf.NewConfirmWithSelection(
confirm.WithDefaultYes(),
confirm.WithPrompt("启用编码转换"),
).Display()
if v {
t, _ = inf.NewText(
text.WithPrompt("输入编码:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(inputCode.dv.string),
).Display()
config.inputCode = t
t, _ = inf.NewText(
text.WithPrompt("输出编码:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(outputCode.dv.string),
).Display()
config.outputCode = t
}
G_F_mode:
s, _ = inf.NewSingleSelect(
forwards,
singleselect.WithKeyBinding(selectKeymap),
singleselect.WithPageSize(3),
singleselect.WithFilterInput(inputs),
).Display("选择转发模式")
if s != 0 {
config.forWard = append(config.forWard, s)
t, _ = inf.NewText(
text.WithPrompt("地址:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue(address.dv.string),
).Display()
config.address = append(config.address, t)
goto G_F_mode
}
e, _ := inf.NewConfirmWithSelection(
confirm.WithDefaultYes(),
confirm.WithPrompt("启用日志"),
).Display()
config.enableLog = e
if e {
t, _ = inf.NewText(
text.WithPrompt("Path:"),
text.WithPromptStyle(theme.DefaultTheme.PromptStyle),
text.WithDefaultValue("./%s-$s.txt"),
).Display()
config.logFilePath = t
}
}
}

45
go.mod
View File

@ -3,12 +3,53 @@ module COM
go 1.22 go 1.22
require ( require (
github.com/charmbracelet/bubbles v0.18.0
github.com/fzdwx/infinite v0.12.1
github.com/gobwas/ws v1.4.0
github.com/spf13/pflag v1.0.5
github.com/trzsz/trzsz-go v1.1.7
github.com/zimolab/charsetconv v0.1.2 github.com/zimolab/charsetconv v0.1.2
go.bug.st/serial v1.6.2 go.bug.st/serial v1.6.2
golang.org/x/term v0.19.0
) )
require ( require (
github.com/UserExistsError/conpty v0.1.2 // indirect
github.com/akavel/rsrc v0.10.2 // indirect
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/creack/goselect v0.1.2 // indirect github.com/creack/goselect v0.1.2 // indirect
golang.org/x/sys v0.4.0 // indirect github.com/creack/pty v1.1.21 // indirect
golang.org/x/text v0.6.0 // indirect github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect
github.com/duke-git/lancet/v2 v2.2.1 // indirect
github.com/fzdwx/iter v0.0.0-20230511075109-0afee9319312 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/josephspurrier/goversioninfo v1.4.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/zenity v0.10.10 // indirect
github.com/randall77/makefat v0.0.0-20210315173500-7ddd0e42c844 // indirect
github.com/rivo/uniseg v0.4.6 // indirect
github.com/rotisserie/eris v0.5.4 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/trzsz/go-arg v1.5.3 // indirect
github.com/trzsz/promptui v0.10.5 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
) )

BIN
image/img10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
image/img11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 KiB

BIN
image/img12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 41 KiB

BIN
image/img6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

BIN
image/img7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
image/img8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

BIN
image/img9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

137
main.go
View File

@ -1,44 +1,13 @@
package main package main
import ( import (
"bufio"
"flag"
"fmt" "fmt"
"github.com/zimolab/charsetconv" "github.com/spf13/pflag"
"go.bug.st/serial"
"io" "io"
"log" "log"
"os" "os"
"strings"
) )
var (
config Config
commands []Command
serialPort serial.Port
err error
args []string
)
func checkPortAvailability(name string) ([]string, error) {
ports, err := serial.GetPortsList()
if err != nil {
log.Fatal(err)
}
if len(ports) == 0 {
return nil, fmt.Errorf("无串口")
}
if name == "" {
return ports, fmt.Errorf("串口未指定")
}
for _, port := range ports {
if strings.Compare(port, name) == 0 {
return ports, nil
}
}
return ports, fmt.Errorf("串口 " + name + " 未在线")
}
func init() { func init() {
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile | log.Lmsgprefix) log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile | log.Lmsgprefix)
for _, f := range flags { for _, f := range flags {
@ -47,91 +16,41 @@ func init() {
cmdinit() cmdinit()
} }
func input() {
input := bufio.NewScanner(os.Stdin)
var ok = false
for input.Scan() {
ok = false
args = strings.Split(input.Text(), " ")
for _, cmd := range commands {
if strings.Compare(strings.TrimSpace(args[0]), cmd.name) == 0 {
cmd.function()
ok = true
}
}
if !ok {
_, err := io.WriteString(serialPort, input.Text())
if err != nil {
log.Fatal(err)
}
_, err = io.WriteString(serialPort, config.endStr)
if err != nil {
log.Fatal(err)
}
}
err := serialPort.Drain()
if err != nil {
log.Fatal(err)
}
}
}
func strout(cs, str string) {
err = charsetconv.EncodeWith(strings.NewReader(str), os.Stdout, charsetconv.Charset(cs), false)
if err != nil {
log.Fatal(err)
}
}
func output(out io.Writer) {
if strings.Compare(config.inputCode, "hex") == 0 {
b, _ := bufio.NewReader(io.LimitReader(serialPort, 16)).Peek(16)
_, err = fmt.Fprintf(out, "% X %q \n", b, b)
} else {
err = charsetconv.ConvertWith(io.LimitReader(serialPort, 1024), charsetconv.Charset(config.inputCode), out, charsetconv.Charset(config.outputCode), false)
}
if err != nil {
log.Fatal(err)
}
}
func main() { func main() {
flag.Parse() pflag.Parse()
flagExt()
if config.portName == "" {
getCliFlag()
}
ports, err := checkPortAvailability(config.portName) ports, err := checkPortAvailability(config.portName)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
printUsage(ports) printUsage(ports)
os.Exit(0) os.Exit(0)
} }
mode := &serial.Mode{
BaudRate: config.baudRate,
StopBits: serial.StopBits(config.stopBits),
DataBits: config.dataBits,
Parity: serial.Parity(config.parityBit),
}
serialPort, err = serial.Open(config.portName, mode)
if err != nil {
log.Fatal(err)
}
defer func(port serial.Port) {
err := port.Close()
if err != nil {
}
}(serialPort)
go input() // 日志文件输出检测
checkLogOpen()
if config.enableLog { //串口设备开启
f, err := os.OpenFile(config.logFilePath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) OpenSerial()
if err != nil {
log.Fatal(err) defer CloseSerial()
} // 打开文件服务
out := io.MultiWriter(os.Stdout, f) OpenTrzsz()
for {
output(out) defer CloseTrzsz()
}
} else { //开启转发
for { OpenForwarding()
output(os.Stdout)
} // 获取终端输入
go input(in)
if len(outs) != 1 {
out = io.MultiWriter(outs...)
}
for {
output()
} }
} }

85
mutual.go Normal file
View File

@ -0,0 +1,85 @@
package main
import (
"bufio"
"fmt"
"github.com/trzsz/trzsz-go/trzsz"
"github.com/zimolab/charsetconv"
"go.bug.st/serial"
"io"
"log"
"os"
"strings"
"time"
)
var (
serialPort serial.Port
in io.Reader = os.Stdin
out io.Writer = os.Stdout
outs = []io.Writer{os.Stdout}
trzszFilter *trzsz.TrzszFilter
clientIn *io.PipeReader
stdoutPipe *io.PipeReader
stdinPipe *io.PipeWriter
clientOut *io.PipeWriter
)
func input(in io.Reader) {
var err error
input := bufio.NewScanner(in)
var ok = false
for {
input.Scan()
ok = false
args = strings.Split(input.Text(), " ")
for _, cmd := range commands {
if strings.Compare(strings.TrimSpace(args[0]), cmd.name) == 0 {
cmd.function()
ok = true
}
}
if !ok {
_, err := io.WriteString(stdinPipe, input.Text())
if err != nil {
log.Fatal(err)
}
_, err = io.WriteString(stdinPipe, config.endStr)
if err != nil {
log.Fatal(err)
}
}
err = serialPort.Drain()
ErrorF(err)
}
}
func strout(out io.Writer, cs, str string) {
err := charsetconv.EncodeWith(strings.NewReader(str), out, charsetconv.Charset(cs), false)
ErrorF(err)
}
func output() {
var err error
if strings.Compare(config.inputCode, "hex") == 0 {
b := make([]byte, config.frameSize)
r, _ := io.LimitReader(stdoutPipe, int64(config.frameSize)).Read(b)
if r != 0 {
if config.timesTamp {
strout(out, config.outputCode, fmt.Sprintf("%v % X %q \n", time.Now().Format(config.timesFmt), b, b))
} else {
strout(out, config.outputCode, fmt.Sprintf("% X %q \n", b, b))
}
}
} else {
if config.timesTamp {
line, _, _ := bufio.NewReader(stdoutPipe).ReadLine()
if line != nil {
strout(out, config.outputCode, fmt.Sprintf("%v %s\n", time.Now().Format(config.timesFmt), line))
}
} else {
err = charsetconv.ConvertWith(stdoutPipe, charsetconv.Charset(config.inputCode), out, charsetconv.Charset(config.outputCode), false)
}
}
ErrorP(err)
}

125
utils.go Normal file
View File

@ -0,0 +1,125 @@
package main
import (
"fmt"
"github.com/trzsz/trzsz-go/trzsz"
"go.bug.st/serial"
"golang.org/x/term"
"io"
"log"
"net"
"os"
"os/signal"
"runtime"
"strings"
)
func checkPortAvailability(name string) ([]string, error) {
ports, err := serial.GetPortsList()
if err != nil {
log.Fatal(err)
}
if len(ports) == 0 {
return nil, fmt.Errorf("无串口")
}
if name == "" {
return ports, fmt.Errorf("串口未指定")
}
for _, port := range ports {
if strings.Compare(port, name) == 0 {
return ports, nil
}
}
return ports, fmt.Errorf("串口 " + name + " 未在线")
}
func OpenSerial() {
var err error
mode := &serial.Mode{
BaudRate: config.baudRate,
StopBits: serial.StopBits(config.stopBits),
DataBits: config.dataBits,
Parity: serial.Parity(config.parityBit),
}
serialPort, err = serial.Open(config.portName, mode)
ErrorF(err)
return
}
func CloseSerial() {
err := serialPort.Close()
ErrorF(err)
return
}
var termch chan os.Signal
// OpenTrzsz create a TrzszFilter to support trzsz ( trz / tsz ).
//
// ┌────────┐ stdinPipe ┌────────┐ ClientIn ┌─────────────┐ SerialIn ┌────────┐
// │ ├─────────────►│ ├─────────────►│ ├─────────────►│ │
// │ mutual │ │ Client │ │ TrzszFilter │ │ Serial │
// │ │◄─────────────│ │◄─────────────┤ │◄─────────────┤ │
// └────────┘ stdoutPipe └────────┘ ClientOut └─────────────┘ SerialOut └────────┘
func OpenTrzsz() {
fd := int(os.Stdin.Fd())
width, _, err := term.GetSize(fd)
if err != nil {
if runtime.GOOS != "windows" {
fmt.Printf("term get size failed: %s\n", err)
return
}
width = 80
}
clientIn, stdinPipe = io.Pipe()
stdoutPipe, clientOut = io.Pipe()
trzszFilter = trzsz.NewTrzszFilter(clientIn, clientOut, serialPort, serialPort,
trzsz.TrzszOptions{TerminalColumns: int32(width), EnableZmodem: true})
trzsz.SetAffectedByWindows(false)
termch = make(chan os.Signal, 1)
go func() {
for range termch {
width, _, err := term.GetSize(fd)
if err != nil {
fmt.Printf("term get size failed: %s\n", err)
continue
}
trzszFilter.SetTerminalColumns(int32(width))
}
}()
}
func CloseTrzsz() {
signal.Stop(termch)
close(termch)
}
func OpenForwarding() {
for i, mode := range config.forWard {
if FoeWardMode(mode) != NOT {
conn := setForWardClient(FoeWardMode(mode), config.address[i])
outs = append(outs, conn)
go func() {
defer func(conn net.Conn) {
err := conn.Close()
if err != nil {
log.Fatal(err)
}
}(conn)
input(conn)
}()
}
}
}
func ErrorP(err error) {
if err != nil {
fmt.Fprint(os.Stderr, err)
}
}
func ErrorF(err error) {
if err != nil {
log.Fatal(err)
}
}