├── util ├── util.go ├── wait_group_wrapper.go └── net.go ├── vine_framework.png ├── vined ├── context.go ├── message.go ├── tcp.go ├── options.go ├── protocol_v1.go ├── apitcp.go ├── client.go └── vined.go ├── README.md ├── cmd └── vined │ ├── config.toml │ ├── config_test.go │ ├── vined.go │ └── config.go ├── serial ├── serial_test.go └── serial.go ├── internal └── protocol │ ├── tcp_server.go │ └── protocol.go └── LICENSE /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | -------------------------------------------------------------------------------- /vine_framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vineio/vine/HEAD/vine_framework.png -------------------------------------------------------------------------------- /vined/context.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | type context struct { 4 | vined *VINED 5 | } 6 | -------------------------------------------------------------------------------- /vined/message.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | type Message struct { 4 | NodeName string 5 | PortName string 6 | Data []byte 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **VINE** 是一个网口串口透传平台,分布式、可扩展。 2 | 3 | 它很容易配置和部署。网口端提供多种接口,使数据的访问异常灵活。比如提供http协议的restful api,tcp或udp 的json格式数据交互接口等。官方会提供多种语言的访问库(正在开发中),如果感兴趣也可以开发自己的库。 4 | 5 | ### 架构图 6 | 7 | 8 | -------------------------------------------------------------------------------- /util/wait_group_wrapper.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type WaitGroupWrapper struct { 8 | sync.WaitGroup 9 | } 10 | 11 | func (w *WaitGroupWrapper) Wrap(cb func()) { 12 | w.Add(1) 13 | go func() { 14 | cb() 15 | w.Done() 16 | }() 17 | } 18 | -------------------------------------------------------------------------------- /cmd/vined/config.toml: -------------------------------------------------------------------------------- 1 | [[serial]] 2 | PortName = "COM1" 3 | BaudRate = 115200 4 | MinimumReadSize = 4 5 | 6 | [[serial]] 7 | PortName = "COM3" 8 | BaudRate = 115200 9 | MinimumReadSize = 4 10 | 11 | [[serial]] 12 | PortName = "COM5" 13 | BaudRate = 115200 14 | MinimumReadSize = 4 -------------------------------------------------------------------------------- /serial/serial_test.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func Test_New(t *testing.T) { 9 | opts := make([]OptionSerial, 0) 10 | opts = append(opts, OptionSerial{"com1", 9600, 4}, OptionSerial{"com3", 9600, 4}) 11 | fmt.Println(opts) 12 | fmt.Println(New(opts)) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /cmd/vined/config_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type Serial struct { 8 | PortName string 9 | BaudRate int 10 | MinimumReadSize int 11 | } 12 | 13 | func Test_init(t *testing.T) { 14 | t.Log("test config") 15 | 16 | ss := make([]Serial, 0) 17 | Unmarshal("serial", &ss) 18 | 19 | t.Log(ss) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /cmd/vined/vined.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/vineio/vine/serial" 5 | "github.com/vineio/vine/vined" 6 | 7 | log "github.com/donnie4w/go-logger/logger" 8 | ) 9 | 10 | func main() { 11 | optserials := make([]serial.OptionSerial, 0) 12 | 13 | Unmarshal("serial", &optserials) 14 | if len(optserials) <= 0 { 15 | log.Error("no serial class") 16 | return 17 | } 18 | 19 | opts := vined.NewOptions() 20 | opts.Optserials = optserials 21 | 22 | v := vined.New(opts) 23 | v.Main() 24 | } 25 | -------------------------------------------------------------------------------- /vined/tcp.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/vineio/vine/util" 7 | 8 | log "github.com/donnie4w/go-logger/logger" 9 | ) 10 | 11 | type tcpServer struct { 12 | ctx *context 13 | } 14 | 15 | func (t *tcpServer) Handle(clientConn net.Conn) { 16 | portName := <-t.ctx.vined.ClientConnChan[clientConn.RemoteAddr().String()] //wait for client to connect 17 | 18 | log.Debug("tunneled the serial port to net,serial port name,net ip :", portName, clientConn.RemoteAddr().String()) 19 | util.Join(clientConn, t.ctx.vined.serialIO[portName]) //tunnel the serial to net 20 | 21 | } 22 | -------------------------------------------------------------------------------- /util/net.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | 7 | log "github.com/donnie4w/go-logger/logger" 8 | ) 9 | 10 | // will block until connection close 11 | func Join(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) { 12 | var wait sync.WaitGroup 13 | pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser) { 14 | defer to.Close() 15 | defer from.Close() 16 | defer wait.Done() 17 | 18 | var err error 19 | _, err = io.Copy(to, from) 20 | if err != nil { 21 | log.Warn("join conns error, %v", err) 22 | } 23 | } 24 | 25 | wait.Add(2) 26 | go pipe(c1, c2) 27 | go pipe(c2, c1) 28 | wait.Wait() 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /internal/protocol/tcp_server.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "net" 5 | "runtime" 6 | "strings" 7 | 8 | log "github.com/donnie4w/go-logger/logger" 9 | ) 10 | 11 | type TCPHandler interface { 12 | Handle(net.Conn) 13 | } 14 | 15 | func TCPServer(listener net.Listener, handler TCPHandler) { 16 | for { 17 | clientConn, err := listener.Accept() 18 | if err != nil { 19 | if nerr, ok := err.(net.Error); ok && nerr.Temporary() { 20 | log.Warn("temporary Accept() failure - %s", err) 21 | runtime.Gosched() 22 | continue 23 | } 24 | // theres no direct way to detect this error because it is not exposed 25 | if !strings.Contains(err.Error(), "use of closed network connection") { 26 | log.Error("listener.Accept() - %s", err) 27 | } 28 | break 29 | } 30 | go handler.Handle(clientConn) 31 | } 32 | 33 | log.Info("TCP: closing %s", listener.Addr()) 34 | } 35 | -------------------------------------------------------------------------------- /vined/options.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | import ( 4 | "crypto/md5" 5 | "hash/crc32" 6 | "io" 7 | "log" 8 | "os" 9 | 10 | "github.com/vineio/vine/serial" 11 | ) 12 | 13 | type Options struct { 14 | ID int64 `flag:"node-id" cfg:"id"` 15 | 16 | TCPAddress string `flag:"tcp-address"` 17 | ApiTCPAddress string `flag:"api-tcp-address"` 18 | ApiHTTPAddress string `flag:"api-http-address"` 19 | 20 | Optserials []serial.OptionSerial `flag:"option-serial"` 21 | 22 | DataPath string `flag:"data-path"` 23 | BroadcastAddress string `flag:"broadcast-address"` 24 | } 25 | 26 | func NewOptions() *Options { 27 | hostname, err := os.Hostname() 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | 32 | h := md5.New() 33 | io.WriteString(h, hostname) 34 | defaultID := int64(crc32.ChecksumIEEE(h.Sum(nil)) % 1024) 35 | return &Options{ 36 | ID: defaultID, 37 | TCPAddress: "0.0.0.0:4201", 38 | ApiTCPAddress: "0.0.0.0:4211", 39 | BroadcastAddress: hostname, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 vineio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vined/protocol_v1.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | import ( 4 | "net" 5 | 6 | log "github.com/donnie4w/go-logger/logger" 7 | ) 8 | 9 | const ( 10 | frameTypeResponse int32 = 0 11 | frameTypeError int32 = 1 12 | frameTypeMessage int32 = 2 13 | ) 14 | 15 | type protocolV1 struct { 16 | ctx *context 17 | } 18 | 19 | func (p *protocolV1) IOLoop(conn net.Conn) error { 20 | p.Read(conn) 21 | return nil 22 | } 23 | 24 | func (p *protocolV1) Read(conn net.Conn) { 25 | for { 26 | bs := make([]byte, 1024) 27 | n, err := conn.Read(bs) 28 | if err != nil { 29 | log.Error("(c *Client) Read:", err) 30 | return 31 | } 32 | 33 | if n <= 0 { 34 | continue 35 | } 36 | var msg Message 37 | msg.Data = bs[:n] 38 | msg.NodeName = p.ctx.vined.getOpts().BroadcastAddress 39 | msg.PortName = p.ctx.vined.ApiTCPConn[conn] 40 | 41 | p.ctx.vined.MsgTxChan[msg.PortName] <- msg.Data //put the user's data to tx buffer 42 | log.Debug("user send data:", msg) 43 | } 44 | } 45 | 46 | func (p *protocolV1) Write(conn net.Conn) { 47 | portName := p.ctx.vined.ApiTCPConn[conn] 48 | for { 49 | data := <-p.ctx.vined.MsgRxChan[portName] //send the serial data to user 50 | conn.Write(data) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cmd/vined/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | // "fmt" 8 | "log" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/spf13/viper" 13 | ) 14 | 15 | func init() { 16 | fileName := "config.toml" 17 | // cwd, _ := os.Getwd() 18 | splits := strings.Split(filepath.Base(fileName), ".") 19 | viper.SetConfigName(filepath.Base(splits[0])) 20 | // viper.AddConfigPath(cwd) 21 | viper.AddConfigPath(filepath.Dir(fileName)) 22 | err := viper.ReadInConfig() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | } 28 | 29 | func checkKey(key string) { 30 | if !viper.IsSet(key) { 31 | fmt.Printf("Configuration key %s not found; aborting \n", key) 32 | os.Exit(1) 33 | } 34 | } 35 | 36 | func MustGetString(key string) string { 37 | checkKey(key) 38 | return viper.GetString(key) 39 | } 40 | 41 | func MustGetInt(key string) int { 42 | checkKey(key) 43 | return viper.GetInt(key) 44 | } 45 | 46 | func MustGetBool(key string) bool { 47 | checkKey(key) 48 | return viper.GetBool(key) 49 | } 50 | 51 | func GetString(key string) string { 52 | return viper.GetString(key) 53 | } 54 | 55 | func Get(key string) interface{} { 56 | return viper.Get(key) 57 | } 58 | 59 | func Unmarshal(key string, rawVal interface{}) { 60 | viper.UnmarshalKey(key, rawVal) 61 | } 62 | -------------------------------------------------------------------------------- /internal/protocol/protocol.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | ) 8 | 9 | // Protocol describes the basic behavior of any protocol in the system 10 | type Protocol interface { 11 | IOLoop(conn net.Conn) error 12 | } 13 | 14 | // SendResponse is a server side utility function to prefix data with a length header 15 | // and write to the supplied Writer 16 | func SendResponse(w io.Writer, data []byte) (int, error) { 17 | err := binary.Write(w, binary.BigEndian, int32(len(data))) 18 | if err != nil { 19 | return 0, err 20 | } 21 | 22 | n, err := w.Write(data) 23 | if err != nil { 24 | return 0, err 25 | } 26 | 27 | return (n + 4), nil 28 | } 29 | 30 | // SendFramedResponse is a server side utility function to prefix data with a length header 31 | // and frame header and write to the supplied Writer 32 | func SendFramedResponse(w io.Writer, frameType int32, data []byte) (int, error) { 33 | beBuf := make([]byte, 4) 34 | size := uint32(len(data)) + 4 35 | 36 | binary.BigEndian.PutUint32(beBuf, size) 37 | n, err := w.Write(beBuf) 38 | if err != nil { 39 | return n, err 40 | } 41 | 42 | binary.BigEndian.PutUint32(beBuf, uint32(frameType)) 43 | n, err = w.Write(beBuf) 44 | if err != nil { 45 | return n + 4, err 46 | } 47 | 48 | n, err = w.Write(data) 49 | return n + 8, err 50 | } 51 | -------------------------------------------------------------------------------- /serial/serial.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "crypto/md5" 5 | "io" 6 | 7 | log "github.com/donnie4w/go-logger/logger" 8 | "github.com/jacobsa/go-serial/serial" 9 | ) 10 | 11 | type OptionSerial struct { 12 | PortName string 13 | BaudRate uint 14 | MinimumReadSize uint 15 | } 16 | 17 | func NewSerial(portnum string, baudrate uint) (io.ReadWriteCloser, error) { 18 | opt := serial.OpenOptions{ 19 | PortName: portnum, 20 | BaudRate: baudrate, 21 | DataBits: 8, 22 | StopBits: 1, 23 | ParityMode: serial.PARITY_NONE, 24 | MinimumReadSize: 8, 25 | } 26 | 27 | var err error 28 | var tempIO io.ReadWriteCloser 29 | 30 | tempIO, err = serial.Open(opt) 31 | 32 | return tempIO, err 33 | } 34 | 35 | func New(opts []OptionSerial) (map[string]io.ReadWriteCloser, map[string][md5.Size]byte, error) { 36 | // opts := make([]OptionSerial, 0) 37 | 38 | // config.Unmarshal("serial", &opts) 39 | // if len(opts) <= 0 { 40 | // return nil, errors.New("no serial class") 41 | // } 42 | 43 | tempios := make(map[string]io.ReadWriteCloser) 44 | tempmd5 := make(map[string][md5.Size]byte) 45 | 46 | for _, v := range opts { 47 | var io io.ReadWriteCloser 48 | var err error 49 | 50 | if io, err = NewSerial(v.PortName, v.BaudRate); err != nil { 51 | log.Error("NewSerial:", err) 52 | continue 53 | } 54 | tempios[v.PortName] = io 55 | tempmd5[v.PortName] = md5.Sum([]byte(v.PortName)) 56 | } 57 | 58 | return tempios, tempmd5, nil 59 | } 60 | -------------------------------------------------------------------------------- /vined/apitcp.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "io" 7 | "net" 8 | 9 | log "github.com/donnie4w/go-logger/logger" 10 | "github.com/vineio/vine/internal/protocol" 11 | ) 12 | 13 | type apiTcpServer struct { 14 | ctx *context 15 | } 16 | 17 | func (t *apiTcpServer) Handle(clientConn net.Conn) { 18 | log.Info(fmt.Sprintf("API TCP: new client(%s)", clientConn.RemoteAddr())) 19 | 20 | buf := make([]byte, 16) 21 | _, err := io.ReadFull(clientConn, buf) 22 | 23 | if err != nil { 24 | log.Info(fmt.Sprintf("failed to read protocol serial port md5 value - %s", err)) 25 | return 26 | } 27 | var protocolMagic [md5.Size]byte 28 | copy(protocolMagic[:], buf) 29 | log.Info(fmt.Sprintf("CLIENT(%v): desired protocol magic '%v'", clientConn.RemoteAddr(), protocolMagic)) 30 | 31 | var prot protocol.Protocol 32 | ok := false 33 | for portName, md5_value := range t.ctx.vined.serialMd5 { 34 | if protocolMagic == md5_value { 35 | t.ctx.vined.ApiTCPConn[clientConn] = portName //set up map for client conn and serial port name 36 | ok = true 37 | break 38 | } 39 | } 40 | if ok { 41 | prot = &protocolV1{ctx: t.ctx} 42 | } else { 43 | protocol.SendFramedResponse(clientConn, frameTypeError, []byte("E_BAD_PROTOCOL")) 44 | clientConn.Close() 45 | log.Error(fmt.Sprintf("client(%s) bad protocol serial md5 '%v'", clientConn.RemoteAddr(), protocolMagic)) 46 | return 47 | } 48 | 49 | err = prot.IOLoop(clientConn) 50 | if err != nil { 51 | log.Error(fmt.Sprintf("client(%s) - %s", clientConn.RemoteAddr(), err)) 52 | return 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vined/client.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | log "github.com/donnie4w/go-logger/logger" 8 | ) 9 | 10 | type Client struct { 11 | ctx *context 12 | } 13 | 14 | func (c *Client) Handle(ios map[string]io.ReadWriteCloser, address string) { 15 | for k, _ := range ios { 16 | conn, err := net.Dial("tcp", address) 17 | if err != nil { 18 | log.Error("util.NewTcp:", err) 19 | continue 20 | } 21 | 22 | c.ctx.vined.MsgRxChan[k] = make(chan []byte, MAX_SINGLE_CHAN_SIZE) 23 | c.ctx.vined.MsgTxChan[k] = make(chan []byte, MAX_SINGLE_CHAN_SIZE) 24 | 25 | c.ctx.vined.ClientConnChan[conn.LocalAddr().String()] = make(chan string) 26 | c.ctx.vined.ClientConnChan[conn.LocalAddr().String()] <- k 27 | log.Debug(k, conn.LocalAddr().String()) 28 | go c.IOLoop(conn, k) 29 | } 30 | 31 | } 32 | 33 | //read&wirte with net.TCPConn 34 | func (c *Client) IOLoop(conn net.Conn, portName string) { 35 | go c.Write(conn, portName) 36 | c.Read(conn, portName) 37 | 38 | } 39 | 40 | func (c *Client) Read(conn net.Conn, portName string) { 41 | for { 42 | bs := make([]byte, 1024) 43 | n, err := conn.Read(bs) 44 | if err != nil { 45 | log.Error("(c *Client) Read:", err) 46 | return 47 | } 48 | 49 | if n <= 0 { 50 | continue 51 | } 52 | var msg Message 53 | msg.Data = bs[:n] 54 | msg.NodeName = c.ctx.vined.getOpts().BroadcastAddress 55 | msg.PortName = portName 56 | 57 | c.ctx.vined.MsgRxChan[portName] <- msg.Data 58 | c.ctx.vined.MessageTotalRxChan <- msg 59 | log.Debug("get data:", msg) 60 | } 61 | } 62 | 63 | func (c *Client) Write(conn net.Conn, portName string) { 64 | for { 65 | data := <-c.ctx.vined.MsgTxChan[portName] 66 | conn.Write(data) 67 | msg := Message{ 68 | NodeName: c.ctx.vined.getOpts().BroadcastAddress, 69 | PortName: portName, 70 | Data: data, 71 | } 72 | c.ctx.vined.MessageTotalTxChan <- msg 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /vined/vined.go: -------------------------------------------------------------------------------- 1 | package vined 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "io" 7 | "net" 8 | "os" 9 | "strings" 10 | "sync/atomic" 11 | 12 | "github.com/vineio/vine/internal/protocol" 13 | "github.com/vineio/vine/serial" 14 | "github.com/vineio/vine/util" 15 | 16 | log "github.com/donnie4w/go-logger/logger" 17 | ) 18 | 19 | const ( 20 | MAX_TOTAL_CHAN_SIZE = 1024 21 | MAX_SINGLE_CHAN_SIZE = 1024 22 | ) 23 | 24 | type VINED struct { 25 | clientIDSequence int64 26 | tcpListener net.Listener 27 | apiTcpListener net.Listener 28 | opts atomic.Value 29 | waitGroup util.WaitGroupWrapper 30 | 31 | serialIO map[string]io.ReadWriteCloser 32 | serialMd5 map[string][md5.Size]byte //key string is serial port name ,value is the name's md5 value 33 | ClientConnChan map[string]chan string //key string is:client ip address,value string is serial portname 34 | MsgTxChan map[string]chan []byte //key string is:serial port name,value is rx or tx serial data 35 | MsgRxChan map[string]chan []byte 36 | MessageTotalRxChan chan Message 37 | MessageTotalTxChan chan Message 38 | ApiTCPConn map[net.Conn]string //key string si :api tcp client conn,value is serial port name 39 | } 40 | 41 | func New(opts *Options) *VINED { 42 | 43 | v := &VINED{ 44 | serialIO: make(map[string]io.ReadWriteCloser), 45 | serialMd5: make(map[string][md5.Size]byte), 46 | ClientConnChan: make(map[string]chan string), 47 | ApiTCPConn: make(map[net.Conn]string), 48 | MsgTxChan: make(map[string]chan []byte), 49 | MsgRxChan: make(map[string]chan []byte), 50 | MessageTotalRxChan: make(chan Message, MAX_TOTAL_CHAN_SIZE), 51 | MessageTotalTxChan: make(chan Message, MAX_TOTAL_CHAN_SIZE), 52 | } 53 | 54 | v.swapOpts(opts) 55 | return v 56 | } 57 | 58 | func (v *VINED) Main() { 59 | 60 | var err error 61 | var ctx = &context{v} 62 | 63 | v.tcpListener, err = net.Listen("tcp", v.getOpts().TCPAddress) 64 | if err != nil { 65 | log.Error(fmt.Sprintf("listen (%s) failed - %s", v.getOpts().TCPAddress, err)) 66 | os.Exit(1) 67 | } 68 | v.apiTcpListener, err = net.Listen("tcp", v.getOpts().ApiTCPAddress) 69 | if err != nil { 70 | log.Error(fmt.Sprintf("listen (%s) failed - %s", v.getOpts().ApiTCPAddress, err)) 71 | os.Exit(1) 72 | } 73 | 74 | log.Debug("tcp listen on:", v.getOpts().TCPAddress) 75 | tcpServer := &tcpServer{ctx: ctx} 76 | v.waitGroup.Wrap(func() { 77 | protocol.TCPServer(v.tcpListener, tcpServer) 78 | }) 79 | 80 | log.Debug("api tcp listen on:", v.getOpts().ApiTCPAddress) 81 | apiTcpServer := &apiTcpServer{ctx: ctx} 82 | v.waitGroup.Wrap(func() { 83 | protocol.TCPServer(v.apiTcpListener, apiTcpServer) 84 | }) 85 | 86 | ios, md5s, err := serial.New(v.getOpts().Optserials) 87 | if err != nil { 88 | log.Error("serial.New():", err) 89 | os.Exit(1) 90 | } 91 | log.Debug(md5s) 92 | log.Debug("serial opeded on:", v.getOpts().Optserials) 93 | 94 | v.serialMd5 = md5s 95 | v.serialIO = ios 96 | client := &Client{ctx} 97 | v.waitGroup.Wrap(func() { 98 | 99 | n := strings.Index(v.getOpts().TCPAddress, ":") 100 | tcpServerPort := v.getOpts().TCPAddress[n:] 101 | client.Handle(ios, tcpServerPort) 102 | }) 103 | 104 | v.waitGroup.Wait() 105 | } 106 | 107 | func (v *VINED) getOpts() *Options { 108 | return v.opts.Load().(*Options) 109 | } 110 | 111 | func (v *VINED) swapOpts(opts *Options) { 112 | v.opts.Store(opts) 113 | } 114 | --------------------------------------------------------------------------------