Compare commits

..

No commits in common. "main" and "v0.8" have entirely different histories.
main ... v0.8

19 changed files with 151 additions and 688 deletions

4
.gitignore vendored
View File

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

View File

@ -1,61 +0,0 @@
#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 Normal file
View File

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

View File

@ -1,21 +1,10 @@
# 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都支持)
## 运行示例 ## 运行示例
@ -36,19 +25,3 @@
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,38 +14,23 @@ type Command struct {
function func() function func()
} }
var (
commands []Command
args []string
)
func cmdhelp() { func cmdhelp() {
var page = 0 var page = 0
strout(out, config.outputCode, fmt.Sprintf(">-------Help(%v)-------<\n", page)) fmt.Printf(">-------Help(%v)-------<\n", page)
for i := 0; i < len(commands); i++ { for i := 0; i < len(commands); i++ {
strout(out, config.outputCode, fmt.Sprintf(" %-10v --%v\n", commands[i].name, commands[i].description)) strout(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() {
strout(out, config.outputCode, fmt.Sprintf(">-------Args(%v)-------<\n", len(args)-1)) fmt.Printf(">-------Args(%v)-------<\n", len(args)-1)
strout(out, config.outputCode, fmt.Sprintf("%q\n", args[1:])) fmt.Printf("%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() {
strout(out, config.outputCode, fmt.Sprintf(">-----Hex Send-----<\n")) fmt.Printf(">-----Hex Send-----<\n")
strout(out, config.outputCode, fmt.Sprintf("%q\n", args[1:])) fmt.Printf("%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 {
@ -58,7 +43,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: ".ctrl", description: "发送Ctrl组合键", function: cmdctrl}) commands = append(commands, Command{name: ".args", description: "参数信息", function: cmdargs})
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,68 +1,14 @@
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
forWard []int parityBit 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)
}
} }

234
flag.go
View File

@ -1,32 +1,17 @@
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
@ -34,7 +19,6 @@ type Val struct {
bool bool
float64 float64
float32 float32
extdef string
} }
type Flag struct { type Flag struct {
v ptrVal v ptrVal
@ -52,23 +36,10 @@ var (
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"}, "终端换行符"}
logExt = Flag{v: ptrVal{ext: &config.logFilePath}, sStr: "l", lStr: "log", dv: Val{extdef: "./%s-$s.txt", string: ""}, help: "日志保存路径"} enableLog = Flag{ptrVal{bool: &config.enableLog}, "l", "log", Val{bool: false}, "是否启用日志保存"}
timeExt = Flag{v: ptrVal{ext: &config.timesFmt}, sStr: "t", lStr: "time", dv: Val{extdef: "[06-01-02 15:04:05.000]", string: ""}, help: "时间戳格式化字段"} logFilePath = Flag{ptrVal{string: &config.logFilePath}, "P", "Path", Val{string: "./Log.txt"}, "日志保存路径"}
forWard = Flag{ptrVal{il: &config.forWard}, "f", "forward", Val{int: 0}, "转发模式(0: 无 1:TCP-C 2:UDP-C 支持多次传入)"}
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校验)"} 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} flags = []Flag{portName, baudRate, dataBits, stopBits, outputCode, inputCode, endStr, enableLog, logFilePath, parityBit}
)
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
@ -78,7 +49,6 @@ const (
stringVal stringVal
intVal intVal
boolVal boolVal
extVal
) )
func printUsage(ports []string) { func printUsage(ports []string) {
@ -98,9 +68,6 @@ 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) {
@ -111,202 +78,21 @@ 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 {
pflag.StringVarP(f.v.string, f.lStr, f.sStr, f.dv.string, f.help) flag.StringVar(f.v.string, f.sStr, f.dv.string, "")
flag.StringVar(f.v.string, f.lStr, f.dv.string, f.help)
} }
if f.v.bool != nil { if f.v.bool != nil {
pflag.BoolVarP(f.v.bool, f.lStr, f.sStr, f.dv.bool, f.help) flag.BoolVar(f.v.bool, f.sStr, f.dv.bool, "")
flag.BoolVar(f.v.bool, f.lStr, f.dv.bool, f.help)
} }
if f.v.int != nil { if f.v.int != nil {
pflag.IntVarP(f.v.int, f.lStr, f.sStr, f.dv.int, f.help) flag.IntVar(f.v.int, f.sStr, f.dv.int, "")
} 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,53 +3,12 @@ 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
github.com/creack/pty v1.1.21 // indirect golang.org/x/sys v0.4.0 // indirect
github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect golang.org/x/text v0.6.0 // 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
) )

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1001 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 633 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

137
main.go
View File

@ -1,13 +1,44 @@
package main package main
import ( import (
"bufio"
"flag"
"fmt" "fmt"
"github.com/spf13/pflag" "github.com/zimolab/charsetconv"
"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 {
@ -16,41 +47,91 @@ func init() {
cmdinit() cmdinit()
} }
func main() { func input() {
pflag.Parse() input := bufio.NewScanner(os.Stdin)
flagExt() var ok = false
if config.portName == "" { for input.Scan() {
getCliFlag() 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() {
flag.Parse()
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,
checkLogOpen() StopBits: serial.StopBits(config.stopBits),
DataBits: config.dataBits,
//串口设备开启 Parity: serial.Parity(config.parityBit),
OpenSerial()
defer CloseSerial()
// 打开文件服务
OpenTrzsz()
defer CloseTrzsz()
//开启转发
OpenForwarding()
// 获取终端输入
go input(in)
if len(outs) != 1 {
out = io.MultiWriter(outs...)
} }
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()
if config.enableLog {
f, err := os.OpenFile(config.logFilePath, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
log.Fatal(err)
}
out := io.MultiWriter(os.Stdout, f)
for { for {
output() output(out)
}
} else {
for {
output(os.Stdout)
}
} }
} }

View File

@ -1,85 +0,0 @@
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
View File

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