From 0c543393d8f95446af7e5c9e06b5d9b4d36288f1 Mon Sep 17 00:00:00 2001 From: Torsten Harenberg Date: Wed, 5 Feb 2025 16:18:05 +0100 Subject: [PATCH] Option to use a TCP socket instead of a serial port. Closes #2. --- README.md | 2 +- ptc.go | 213 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 112 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 8c33493..c32560a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ vara_mode: false | Variable | Meaning | |-------------------|-----------------------------------------------------------------------------------------------------------------------------| -| `device` | Path to the serial device where the modem is connected | +| `device` | Path to the serial device where the modem is connected **or** `tcp://address:port` (useful for Android) | | `baudrate` | baud rate of the modem | | `mycall` | The callsign to be used on HF | | `server_address` | server socket address for the **commands** | diff --git a/ptc.go b/ptc.go index 7d25b9c..74c2dbf 100644 --- a/ptc.go +++ b/ptc.go @@ -11,6 +11,7 @@ import ( "github.com/albenik/go-serial/v2" "log" "math/bits" + "net" "os" "regexp" "runtime" @@ -46,13 +47,10 @@ type pflags struct { type pmux struct { device sync.Mutex - pactor sync.Mutex write sync.Mutex read sync.Mutex close sync.Mutex bufLen sync.Mutex - // sendbuf sync.Mutex - // recvbuf sync.Mutex } type Modem struct { @@ -63,14 +61,12 @@ type Modem struct { state State - device *serial.Port - mux pmux - wg sync.WaitGroup - flags pflags - goodChunks int - // recvBuf bytes.Buffer - // cmdBuf chan string - // sendBuf bytes.Buffer + device *serial.Port + tcpdevice net.Conn + mux pmux + wg sync.WaitGroup + flags pflags + goodChunks int packetcounter bool chanbusy bool cmdlineinit string @@ -149,17 +145,24 @@ func OpenModem(path string, baudRate int, myCall string, initfile string, cmdlin } writeDebug("Initialising pactor modem", 1) - if err := p.checkSerialDevice(); err != nil { - writeDebug(err.Error(), 1) - return nil, err - } - //Setup serial device - if p.device, err = serial.Open(p.devicePath, serial.WithBaudrate(baudRate), serial.WithReadTimeout(SerialTimeout)); err != nil { - writeDebug(err.Error(), 1) - return nil, err + if strings.HasPrefix(p.devicePath, "tcp://") { + if p.tcpdevice, err = net.Dial("tcp", strings.Replace(p.devicePath, "tcp://", "", 1)); err != nil { + writeDebug(err.Error(), 0) + return nil, err + } + } else { + // Check if serial device exists + if err := p.checkSerialDevice(); err != nil { + writeDebug(err.Error(), 0) + return nil, err + } + //Setup serial device + if p.device, err = serial.Open(p.devicePath, serial.WithBaudrate(baudRate), serial.WithReadTimeout(SerialTimeout)); err != nil { + writeDebug(err.Error(), 1) + return nil, err + } } - err = p.init() if err != nil { return nil, err @@ -410,10 +413,11 @@ func (p *Modem) stophostmode() { for ok == false { buff := make([]byte, 100) - p.device.Write([]byte{0xaa, 0xaa, 0x00, 0x01, 0x05, 0x4a, 0x48, 0x4f, 0x53, 0x54, 0x30, 0xfb, 0x3d}) + //var n int + _ = p.write(string([]byte{0xaa, 0xaa, 0x00, 0x01, 0x05, 0x4a, 0x48, 0x4f, 0x53, 0x54, 0x30, 0xfb, 0x3d})) time.Sleep(100 * time.Millisecond) for { - n, err := p.device.Read(buff) + n, _, err := p.read(100) if err != nil { log.Fatal(err) break @@ -421,12 +425,11 @@ func (p *Modem) stophostmode() { if n == 0 { break } - //fmt.Printf("%v", string(buff[:n])) } - p.device.Write([]byte("\rRESTART\r")) + p.write("\rRESTART\r") time.Sleep(1000 * time.Millisecond) for { - n, err := p.device.Read(buff) + n, a, err := p.read(100) if err != nil { log.Fatal(err) break @@ -434,6 +437,7 @@ func (p *Modem) stophostmode() { if n == 0 { break } + copy(buff, a) //fmt.Printf("%v", string(buff[:n])) } ok, err = regexp.Match("cmd:", buff) @@ -481,16 +485,12 @@ func (p *Modem) close() (err error) { writeDebug("PACTOR close called", 1) } - //p.disconnect() writeDebug("signal modem thread to stop", 1) p.flags.stopmodem = true writeDebug("waiting for all threads to exit", 1) p.wg.Wait() writeDebug("modem thread stopped", 1) - //p.hostmodeQuit() - // will not close the serial port as we reuse it - //p.device.Close() p.stophostmode() p.setState(Closed) writeDebug("PACTOR close() finished", 1) @@ -641,7 +641,7 @@ func (p *Modem) writeAndGetResponse(msg string, ch int, isCommand bool, chunkSiz var n int var str string if ch < 0 { - err = p._write(msg + "\r") + err = p.write(msg + "\r") if err != nil { return 0, "", err } @@ -649,7 +649,7 @@ func (p *Modem) writeAndGetResponse(msg string, ch int, isCommand bool, chunkSiz i := 0 var tmp []byte for { - n, tmp, err = p._read(chunkSize) + n, tmp, err = p.read(chunkSize) str = string(tmp) if err == nil { break @@ -685,7 +685,7 @@ func (p *Modem) writeAndGetResponse(msg string, ch int, isCommand bool, chunkSiz return 0, "", err } } - br, b, err := p._read(1) + br, b, err := p.read(1) if err != nil { writeDebug("ERROR at _read: "+error.Error(err), 1) } @@ -737,56 +737,6 @@ func (p *Modem) Busy() bool { return p.chanbusy } -// Write to serial connection (thread safe) -// -// No other read/write operation allowed during this time -func (p *Modem) write(cmd string) error { - p.mux.pactor.Lock() - defer p.mux.pactor.Unlock() - return p._write(cmd) -} - -// Write to serial connection (NOT thread safe) -// -// If used, make shure to lock/unlock p.mux.pactor mutex! -func (p *Modem) _write(cmd string) error { - if err := p.checkSerialDevice(); err != nil { - writeDebug(err.Error(), 1) - return err - } - - writeDebug("write: \n"+hex.Dump([]byte(cmd)), 3) - - p.mux.device.Lock() - defer p.mux.device.Unlock() - out := cmd + "\r" - for { - - // check if the serial line is clear-to-send - status, err := cts(p.device) - if err != nil { - writeDebug("GetModemStatusBits failed. cmd: "+cmd+" Error: "+err.Error(), 1) - return err - } - - if status { - for { - sent, err := p.device.Write([]byte(out)) - if err == nil { - break - } else { - // log.Errorf("ERROR while sending serial command: %s\n", out) - writeDebug(err.Error(), 2) - out = out[sent:] - } - } - break - } - } - - return nil -} - // *** Helper functions for the CRC hostmode // Although these functions are small, I prefer to keep their functionality // separate. They follow the steps in the SCS documentation @@ -908,27 +858,86 @@ func (p *Modem) writeChannel(msg string, ch int, isCommand bool) error { // read: Read from serial connection (thread safe) func (p *Modem) read(chunkSize int) (int, []byte, error) { - p.mux.pactor.Lock() - defer p.mux.pactor.Unlock() - return p._read(chunkSize) -} - -// _read: Read from serial connection (NOT thread safe). To be used from read -func (p *Modem) _read(chunkSize int) (int, []byte, error) { - if err := p.checkSerialDevice(); err != nil { - writeDebug(err.Error(), 1) - return 0, []byte{}, err - } - buf := make([]byte, chunkSize) + if strings.HasPrefix(p.devicePath, "tcp://") { + p.tcpdevice.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + n, err := p.tcpdevice.Read(buf) + if err != nil { + writeDebug("Error received during read: "+err.Error(), 1) + return 0, []byte{}, err + } + return n, buf[0:n], nil + } else { + if err := p.checkSerialDevice(); err != nil { + writeDebug(err.Error(), 1) + return 0, []byte{}, err + } - p.mux.device.Lock() - defer p.mux.device.Unlock() - n, err := p.device.Read(buf) - if err != nil { - writeDebug("Error received during read: "+err.Error(), 1) - return 0, []byte{}, err + p.mux.device.Lock() + defer p.mux.device.Unlock() + n, err := p.device.Read(buf) + if err != nil { + writeDebug("Error received during read: "+err.Error(), 1) + return 0, []byte{}, err + } + + return n, buf[0:n], nil + } +} + +// Write to serial connection (thread safe) +// +// No other read/write operation allowed during this time +func (p *Modem) write(cmd string) error { + if strings.HasPrefix(p.devicePath, "tcp://") { + + //TCP connection + out := cmd + "\r" + sent, err := p.tcpdevice.Write([]byte(out)) + if err == nil { + return err + } else { + writeDebug(err.Error(), 2) + out = out[sent:] + return nil + } + + } else { + + //Serial connection + if err := p.checkSerialDevice(); err != nil { + writeDebug(err.Error(), 1) + return err + } + + writeDebug("write: \n"+hex.Dump([]byte(cmd)), 3) + + p.mux.device.Lock() + defer p.mux.device.Unlock() + out := cmd + "\r" + for { + + // check if the serial line is clear-to-send + status, err := cts(p.device) + if err != nil { + writeDebug("GetModemStatusBits failed. cmd: "+cmd+" Error: "+err.Error(), 1) + return err + } + + if status { + for { + sent, err := p.device.Write([]byte(out)) + if err == nil { + break + } else { + writeDebug(err.Error(), 2) + out = out[sent:] + } + } + break + } + } + + return nil } - - return n, buf[0:n], nil }