mirror of
https://github.com/nonoo/kappanhang.git
synced 2025-12-06 08:02:00 +01:00
Add internal rigctld
This commit is contained in:
parent
3a1b74ce2d
commit
2d1bbf76dc
|
|
@ -55,14 +55,13 @@ After it is connected and logged in:
|
||||||
used to record/play audio from/to the server (the transceiver). You can also
|
used to record/play audio from/to the server (the transceiver). You can also
|
||||||
set this sound card in [WSJT-X](https://physics.princeton.edu/pulsar/K1JT/wsjtx.html).
|
set this sound card in [WSJT-X](https://physics.princeton.edu/pulsar/K1JT/wsjtx.html).
|
||||||
- Starts a **TCP server** on port `4533` for exposing the **serial port**.
|
- Starts a **TCP server** on port `4533` for exposing the **serial port**.
|
||||||
- Starts `rigctld` and connects it to kappanhang's TCP serial port server.
|
- Starts an **internal rigctld** server. This can be used for controlling the
|
||||||
This can be used for controlling the server (the transceiver) with
|
server (the transceiver) with [Hamlib](https://hamlib.github.io/) (`rigctl`)
|
||||||
[Hamlib](https://hamlib.github.io/) (`rigctld`).
|
clients.
|
||||||
|
|
||||||
To use this with for example [WSJT-X](https://physics.princeton.edu/pulsar/K1JT/wsjtx.html),
|
To use this with for example [WSJT-X](https://physics.princeton.edu/pulsar/K1JT/wsjtx.html),
|
||||||
open WSJT-X settings, go to the *Radio* tab, set the *rig type* to `Hamlib
|
open WSJT-X settings, go to the *Radio* tab, set the *rig type* to `Hamlib
|
||||||
NET rigctl`, and the *Network server* to `localhost`. It is recommended to
|
NET rigctl`, and the *Network server* to `localhost`.
|
||||||
set the *poll interval* to 10 seconds.
|
|
||||||
|
|
||||||
### Virtual serial port
|
### Virtual serial port
|
||||||
|
|
||||||
|
|
|
||||||
9
args.go
9
args.go
|
|
@ -14,8 +14,7 @@ var username string
|
||||||
var password string
|
var password string
|
||||||
var serialTCPPort uint16
|
var serialTCPPort uint16
|
||||||
var enableSerialDevice bool
|
var enableSerialDevice bool
|
||||||
var rigctldModel uint
|
var rigctldPort uint16
|
||||||
var disableRigctld bool
|
|
||||||
var runCmd string
|
var runCmd string
|
||||||
var runCmdOnSerialPortCreated string
|
var runCmdOnSerialPortCreated string
|
||||||
var statusLogInterval time.Duration
|
var statusLogInterval time.Duration
|
||||||
|
|
@ -28,8 +27,7 @@ func parseArgs() {
|
||||||
p := getopt.StringLong("password", 'p', "beerbeer", "Password")
|
p := getopt.StringLong("password", 'p', "beerbeer", "Password")
|
||||||
t := getopt.Uint16Long("serial-tcp-port", 't', 4533, "Expose radio's serial port on this TCP port")
|
t := getopt.Uint16Long("serial-tcp-port", 't', 4533, "Expose radio's serial port on this TCP port")
|
||||||
s := getopt.BoolLong("enable-serial-device", 's', "Expose radio's serial port as a virtual serial port")
|
s := getopt.BoolLong("enable-serial-device", 's', "Expose radio's serial port as a virtual serial port")
|
||||||
m := getopt.UintLong("rigctld-model", 'm', 3085, "rigctld model number")
|
r := getopt.Uint16Long("rigctld-port", 'r', 4532, "Use this TCP port for the internal rigctld")
|
||||||
r := getopt.BoolLong("disable-rigctld", 'r', "Disable starting rigctld")
|
|
||||||
e := getopt.StringLong("exec", 'e', "", "Exec cmd when connected")
|
e := getopt.StringLong("exec", 'e', "", "Exec cmd when connected")
|
||||||
o := getopt.StringLong("exec-serial", 'o', "socat /tmp/kappanhang-IC-705.pty /tmp/vmware.pty", "Exec cmd when virtual serial port is created, set to - to disable")
|
o := getopt.StringLong("exec-serial", 'o', "socat /tmp/kappanhang-IC-705.pty /tmp/vmware.pty", "Exec cmd when virtual serial port is created, set to - to disable")
|
||||||
i := getopt.Uint16Long("log-interval", 'i', 100, "Status bar/log interval in milliseconds")
|
i := getopt.Uint16Long("log-interval", 'i', 100, "Status bar/log interval in milliseconds")
|
||||||
|
|
@ -48,8 +46,7 @@ func parseArgs() {
|
||||||
password = *p
|
password = *p
|
||||||
serialTCPPort = *t
|
serialTCPPort = *t
|
||||||
enableSerialDevice = *s
|
enableSerialDevice = *s
|
||||||
rigctldModel = *m
|
rigctldPort = *r
|
||||||
disableRigctld = *r
|
|
||||||
runCmd = *e
|
runCmd = *e
|
||||||
runCmdOnSerialPortCreated = *o
|
runCmdOnSerialPortCreated = *o
|
||||||
statusLogInterval = time.Duration(*i) * time.Millisecond
|
statusLogInterval = time.Duration(*i) * time.Millisecond
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -109,6 +110,8 @@ type civControlStruct struct {
|
||||||
setVFOSent bool
|
setVFOSent bool
|
||||||
setSplitSent bool
|
setSplitSent bool
|
||||||
|
|
||||||
|
mutex sync.Mutex
|
||||||
|
|
||||||
freq uint
|
freq uint
|
||||||
ptt bool
|
ptt bool
|
||||||
tune bool
|
tune bool
|
||||||
|
|
@ -141,6 +144,9 @@ func (s *civControlStruct) decode(d []byte) bool {
|
||||||
|
|
||||||
payload := d[5 : len(d)-1]
|
payload := d[5 : len(d)-1]
|
||||||
|
|
||||||
|
s.state.mutex.Lock()
|
||||||
|
defer s.state.mutex.Unlock()
|
||||||
|
|
||||||
switch d[4] {
|
switch d[4] {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
return s.decodeFreq(payload)
|
return s.decodeFreq(payload)
|
||||||
|
|
@ -799,20 +805,25 @@ func (s *civControlStruct) toggleTune() error {
|
||||||
return s.st.send([]byte{254, 254, civAddress, 224, 0x1c, 1, b, 253})
|
return s.st.send([]byte{254, 254, civAddress, 224, 0x1c, 1, b, 253})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *civControlStruct) toggleDataMode() error {
|
func (s *civControlStruct) setDataMode(enable bool) error {
|
||||||
s.state.setDataModeSent = true
|
|
||||||
var b byte
|
var b byte
|
||||||
var f byte
|
var f byte
|
||||||
if !s.state.dataMode {
|
if enable {
|
||||||
b = 1
|
b = 1
|
||||||
f = 1
|
f = 1
|
||||||
} else {
|
} else {
|
||||||
b = 0
|
b = 0
|
||||||
f = 0
|
f = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.st.send([]byte{254, 254, civAddress, 224, 0x1a, 0x06, b, f, 253})
|
return s.st.send([]byte{254, 254, civAddress, 224, 0x1a, 0x06, b, f, 253})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *civControlStruct) toggleDataMode() error {
|
||||||
|
s.state.setDataModeSent = true
|
||||||
|
return s.setDataMode(!s.state.dataMode)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *civControlStruct) incBand() error {
|
func (s *civControlStruct) incBand() error {
|
||||||
s.state.bandChanging = true
|
s.state.bandChanging = true
|
||||||
i := s.state.bandIdx + 1
|
i := s.state.bandIdx + 1
|
||||||
|
|
@ -888,29 +899,50 @@ func (s *civControlStruct) decTS() error {
|
||||||
return s.st.send([]byte{254, 254, civAddress, 224, 0x10, b, 253})
|
return s.st.send([]byte{254, 254, civAddress, 224, 0x10, b, 253})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *civControlStruct) setVFO(nr byte) error {
|
||||||
|
s.state.setVFOSent = true
|
||||||
|
return s.st.send([]byte{254, 254, civAddress, 224, 0x07, nr, 253})
|
||||||
|
}
|
||||||
|
|
||||||
func (s *civControlStruct) toggleVFO() error {
|
func (s *civControlStruct) toggleVFO() error {
|
||||||
s.state.setVFOSent = true
|
s.state.setVFOSent = true
|
||||||
var b byte
|
var b byte
|
||||||
if !s.state.vfoBActive {
|
if !s.state.vfoBActive {
|
||||||
b = 1
|
b = 1
|
||||||
}
|
}
|
||||||
return s.st.send([]byte{254, 254, civAddress, 224, 0x07, b, 253})
|
return s.setVFO(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *civControlStruct) setSplit(mode splitMode) error {
|
||||||
|
s.state.setSplitSent = true
|
||||||
|
var b byte
|
||||||
|
switch mode {
|
||||||
|
default:
|
||||||
|
b = 0x00
|
||||||
|
case splitModeOn:
|
||||||
|
b = 0x01
|
||||||
|
case splitModeDUPMinus:
|
||||||
|
b = 0x11
|
||||||
|
case splitModeDUPPlus:
|
||||||
|
b = 0x12
|
||||||
|
}
|
||||||
|
return s.st.send([]byte{254, 254, civAddress, 224, 0x0f, b, 253})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *civControlStruct) toggleSplit() error {
|
func (s *civControlStruct) toggleSplit() error {
|
||||||
s.state.setSplitSent = true
|
s.state.setSplitSent = true
|
||||||
var b byte
|
var mode splitMode
|
||||||
switch s.state.splitMode {
|
switch s.state.splitMode {
|
||||||
case splitModeOff:
|
case splitModeOff:
|
||||||
b = 0x01
|
mode = splitModeOn
|
||||||
case splitModeOn:
|
case splitModeOn:
|
||||||
b = 0x011
|
mode = splitModeDUPMinus
|
||||||
case splitModeDUPMinus:
|
case splitModeDUPMinus:
|
||||||
b = 0x12
|
mode = splitModeDUPPlus
|
||||||
default:
|
default:
|
||||||
b = 0x10
|
mode = splitModeOff
|
||||||
}
|
}
|
||||||
return s.st.send([]byte{254, 254, civAddress, 224, 0x0f, b, 253})
|
return s.setSplit(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *civControlStruct) getFreq() error {
|
func (s *civControlStruct) getFreq() error {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ type cmdRunner struct {
|
||||||
|
|
||||||
var runCmdRunner cmdRunner
|
var runCmdRunner cmdRunner
|
||||||
var serialCmdRunner cmdRunner
|
var serialCmdRunner cmdRunner
|
||||||
var rigctldRunner cmdRunner
|
|
||||||
|
|
||||||
func (c *cmdRunner) kill(cmd *exec.Cmd) {
|
func (c *cmdRunner) kill(cmd *exec.Cmd) {
|
||||||
err := cmd.Process.Kill()
|
err := cmd.Process.Kill()
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -273,12 +271,8 @@ func (s *controlStream) handleRead(r []byte) error {
|
||||||
if enableSerialDevice {
|
if enableSerialDevice {
|
||||||
serialCmdRunner.startIfNeeded(runCmdOnSerialPortCreated)
|
serialCmdRunner.startIfNeeded(runCmdOnSerialPortCreated)
|
||||||
}
|
}
|
||||||
if !disableRigctld {
|
if err := rigctld.initIfNeeded(); err != nil {
|
||||||
if _, err := exec.LookPath("rigctld"); err != nil {
|
return err
|
||||||
log.Error("can't start rigctld: ", err)
|
|
||||||
} else {
|
|
||||||
rigctldRunner.startIfNeeded(fmt.Sprint("rigctld -m ", rigctldModel, " -r :", serialTCPPort))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -150,10 +150,10 @@ exit:
|
||||||
log.Print("restarting control stream...")
|
log.Print("restarting control stream...")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rigctld.deinit()
|
||||||
serialTCPSrv.deinit()
|
serialTCPSrv.deinit()
|
||||||
runCmdRunner.stop()
|
runCmdRunner.stop()
|
||||||
serialCmdRunner.stop()
|
serialCmdRunner.stop()
|
||||||
rigctldRunner.stop()
|
|
||||||
audio.deinit()
|
audio.deinit()
|
||||||
serialPort.deinit()
|
serialPort.deinit()
|
||||||
|
|
||||||
|
|
|
||||||
387
rigctld.go
Normal file
387
rigctld.go
Normal file
|
|
@ -0,0 +1,387 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rigctldNoError = iota
|
||||||
|
rigctldInvalidParam = -1
|
||||||
|
rigctldUnsupportedCmd = -11
|
||||||
|
)
|
||||||
|
|
||||||
|
type rigctldStruct struct {
|
||||||
|
listener net.Listener
|
||||||
|
client net.Conn
|
||||||
|
|
||||||
|
clientLoopDeinitNeededChan chan bool
|
||||||
|
clientLoopDeinitFinishedChan chan bool
|
||||||
|
|
||||||
|
deinitNeededChan chan bool
|
||||||
|
deinitFinishedChan chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var rigctld rigctldStruct
|
||||||
|
|
||||||
|
func (s *rigctldStruct) disconnectClient() {
|
||||||
|
if s.client != nil {
|
||||||
|
s.client.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rigctldStruct) deinitClient() {
|
||||||
|
if s.clientLoopDeinitNeededChan != nil {
|
||||||
|
s.clientLoopDeinitNeededChan <- true
|
||||||
|
<-s.clientLoopDeinitFinishedChan
|
||||||
|
|
||||||
|
s.clientLoopDeinitNeededChan = nil
|
||||||
|
s.clientLoopDeinitFinishedChan = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rigctldStruct) send(a ...interface{}) error {
|
||||||
|
str := fmt.Sprint(a...)
|
||||||
|
_, err := s.client.Write([]byte(str))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rigctldStruct) sendReplyCode(code int) error {
|
||||||
|
str := fmt.Sprint("RPRT ", code, "\n")
|
||||||
|
_, err := s.client.Write([]byte(str))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rigctldStruct) processCmd(cmd string) (close bool, err error) {
|
||||||
|
cmdSplit := strings.Split(cmd, " ")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case cmd == "\\chk_vfo":
|
||||||
|
err = s.send("0\n")
|
||||||
|
case cmd == "\\dump_state":
|
||||||
|
err = s.send("1\n" +
|
||||||
|
"3085\n" +
|
||||||
|
"0\n" +
|
||||||
|
"30000.000000 199999999.000000 0x1401dbf -1 -1 0x10000003 0x1\n" +
|
||||||
|
"400000000.000000 470000000.000000 0x1401dbf -1 -1 0x10000003 0x1\n" +
|
||||||
|
"0 0 0 0 0 0 0\n" +
|
||||||
|
"1800000.000000 1999999.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"3500000.000000 3999999.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"5255000.000000 5405000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"7000000.000000 7300000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"10100000.000000 10150000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"14000000.000000 14350000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"18068000.000000 18168000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"21000000.000000 21450000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"24890000.000000 24990000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"28000000.000000 29700000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"50000000.000000 54000000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"144000000.000000 148000000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"430000000.000000 450000000.000000 0x10001bf 100 10000 0x10000003 0x1\n" +
|
||||||
|
"0 0 0 0 0 0 0\n" +
|
||||||
|
"0x401dbf 100\n" +
|
||||||
|
"0x401dbf 500\n" +
|
||||||
|
"0x401dbf 1000\n" +
|
||||||
|
"0x401dbf 5000\n" +
|
||||||
|
"0x401dbf 6250\n" +
|
||||||
|
"0x401dbf 8330\n" +
|
||||||
|
"0x401dbf 9000\n" +
|
||||||
|
"0x401dbf 10000\n" +
|
||||||
|
"0x401dbf 12500\n" +
|
||||||
|
"0x401dbf 20000\n" +
|
||||||
|
"0x401dbf 25000\n" +
|
||||||
|
"0x401dbf 50000\n" +
|
||||||
|
"0x401dbf 100000\n" +
|
||||||
|
"0 0\n" +
|
||||||
|
"0xc0c 3600\n" +
|
||||||
|
"0xc0c 2400\n" +
|
||||||
|
"0xc0c 1800\n" +
|
||||||
|
"0x192 500\n" +
|
||||||
|
"0x192 250\n" +
|
||||||
|
"0x82 1200\n" +
|
||||||
|
"0x110 2400\n" +
|
||||||
|
"0x400001 6000\n" +
|
||||||
|
"0x400001 3000\n" +
|
||||||
|
"0x400001 9000\n" +
|
||||||
|
"0x1020 10000\n" +
|
||||||
|
"0x1020 7000\n" +
|
||||||
|
"0x1020 15000\n" +
|
||||||
|
"0 0\n" +
|
||||||
|
"9999\n" +
|
||||||
|
"9999\n" +
|
||||||
|
"0\n" +
|
||||||
|
"0\n" +
|
||||||
|
"1 2\n" +
|
||||||
|
"20\n" +
|
||||||
|
"0xc90133fe\n" +
|
||||||
|
"0xc90133fe\n" +
|
||||||
|
"0x7f74677f3f\n" +
|
||||||
|
"0x7000677f3f\n" +
|
||||||
|
"0x35\n" +
|
||||||
|
"0x35\n" +
|
||||||
|
"vfo_ops=0x81f\n" +
|
||||||
|
"ptt_type=0x1\n" +
|
||||||
|
"targetable_vfo=0x0\n" +
|
||||||
|
"done\n")
|
||||||
|
case cmd == "q":
|
||||||
|
err = s.sendReplyCode(rigctldNoError)
|
||||||
|
close = true
|
||||||
|
case cmd == "f":
|
||||||
|
civControl.state.mutex.Lock()
|
||||||
|
defer civControl.state.mutex.Unlock()
|
||||||
|
|
||||||
|
err = s.send(civControl.state.freq, "\n")
|
||||||
|
case cmdSplit[0] == "F":
|
||||||
|
var f float64
|
||||||
|
f, err = strconv.ParseFloat(cmdSplit[1], 0)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = civControl.setFreq(uint(f))
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.sendReplyCode(rigctldNoError)
|
||||||
|
case cmd == "m":
|
||||||
|
civControl.state.mutex.Lock()
|
||||||
|
defer civControl.state.mutex.Unlock()
|
||||||
|
|
||||||
|
var mode string
|
||||||
|
if civControl.state.dataMode {
|
||||||
|
mode = "PKT"
|
||||||
|
}
|
||||||
|
mode += civOperatingModes[civControl.state.operatingModeIdx].name
|
||||||
|
|
||||||
|
// This can be queried with a CIV command for accurate values by the way.
|
||||||
|
width := "3000"
|
||||||
|
switch civControl.state.filterIdx {
|
||||||
|
case 1:
|
||||||
|
width = "2400"
|
||||||
|
case 2:
|
||||||
|
width = "1800"
|
||||||
|
}
|
||||||
|
err = s.send(mode, "\n", width, "\n")
|
||||||
|
case cmdSplit[0] == "M":
|
||||||
|
mode := cmdSplit[1]
|
||||||
|
if mode[:3] == "PKT" {
|
||||||
|
err = civControl.setDataMode(true)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mode = mode[3:]
|
||||||
|
}
|
||||||
|
var modeCode byte
|
||||||
|
var modeFound bool
|
||||||
|
for _, m := range civOperatingModes {
|
||||||
|
if m.name == mode {
|
||||||
|
modeCode = m.code
|
||||||
|
modeFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !modeFound {
|
||||||
|
err = fmt.Errorf("unknown mode %s", mode)
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var width int
|
||||||
|
width, err = strconv.Atoi(cmdSplit[2])
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var filterCode byte
|
||||||
|
if width <= 1800 {
|
||||||
|
filterCode = 2
|
||||||
|
} else if width <= 2400 {
|
||||||
|
filterCode = 1
|
||||||
|
}
|
||||||
|
err = civControl.setOperatingModeAndFilter(modeCode, filterCode)
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
} else {
|
||||||
|
_ = s.sendReplyCode(rigctldNoError)
|
||||||
|
}
|
||||||
|
case cmd == "t":
|
||||||
|
civControl.state.mutex.Lock()
|
||||||
|
defer civControl.state.mutex.Unlock()
|
||||||
|
|
||||||
|
res := "0"
|
||||||
|
if civControl.state.ptt {
|
||||||
|
res = "1"
|
||||||
|
}
|
||||||
|
err = s.send(res, "\n")
|
||||||
|
case cmdSplit[0] == "T":
|
||||||
|
if cmdSplit[1] != "0" {
|
||||||
|
err = civControl.setPTT(true)
|
||||||
|
} else {
|
||||||
|
err = civControl.setPTT(false)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
} else {
|
||||||
|
_ = s.sendReplyCode(rigctldNoError)
|
||||||
|
}
|
||||||
|
case cmdSplit[0] == "V":
|
||||||
|
if cmdSplit[1] == "VFOB" {
|
||||||
|
err = civControl.setVFO(1)
|
||||||
|
} else {
|
||||||
|
err = civControl.setVFO(0)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
} else {
|
||||||
|
_ = s.sendReplyCode(rigctldNoError)
|
||||||
|
}
|
||||||
|
case cmd == "s":
|
||||||
|
civControl.state.mutex.Lock()
|
||||||
|
defer civControl.state.mutex.Unlock()
|
||||||
|
|
||||||
|
res := "0"
|
||||||
|
if civControl.state.splitMode == splitModeOn {
|
||||||
|
res = "1"
|
||||||
|
}
|
||||||
|
err = s.send(res, "\n")
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if civControl.state.vfoBActive {
|
||||||
|
res = "VFOA"
|
||||||
|
} else {
|
||||||
|
res = "VFOB"
|
||||||
|
}
|
||||||
|
err = s.send(res, "\n")
|
||||||
|
case cmdSplit[0] == "S":
|
||||||
|
if cmdSplit[1] == "1" {
|
||||||
|
err = civControl.setSplit(splitModeOn)
|
||||||
|
} else {
|
||||||
|
err = civControl.setSplit(splitModeOff)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
_ = s.sendReplyCode(rigctldInvalidParam)
|
||||||
|
} else {
|
||||||
|
_ = s.sendReplyCode(rigctldNoError)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_ = s.sendReplyCode(rigctldUnsupportedCmd)
|
||||||
|
return false, fmt.Errorf("got unknown cmd %s", cmd)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rigctldStruct) clientLoop() {
|
||||||
|
defer func() {
|
||||||
|
s.client.Close()
|
||||||
|
log.Print("client ", s.client.RemoteAddr().String(), " disconnected")
|
||||||
|
|
||||||
|
<-s.clientLoopDeinitNeededChan
|
||||||
|
s.clientLoopDeinitFinishedChan <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Print("client ", s.client.RemoteAddr().String(), " connected")
|
||||||
|
|
||||||
|
var b [128]byte
|
||||||
|
var lineBuf bytes.Buffer
|
||||||
|
for {
|
||||||
|
n, err := s.client.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-s.clientLoopDeinitNeededChan:
|
||||||
|
s.clientLoopDeinitFinishedChan <- true
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
lineBuf.Write(b[:n])
|
||||||
|
endIndex := bytes.Index(lineBuf.Bytes(), []byte{'\n'})
|
||||||
|
if endIndex >= 0 {
|
||||||
|
lineB := make([]byte, endIndex+1)
|
||||||
|
n, err := lineBuf.Read(lineB)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n < endIndex+1 {
|
||||||
|
log.Error("short read")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n > 1 {
|
||||||
|
close, err := s.processCmd(strings.TrimSpace(string(lineB[:len(lineB)-1])))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
if close {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rigctldStruct) loop() {
|
||||||
|
for {
|
||||||
|
newClient, err := s.listener.Accept()
|
||||||
|
|
||||||
|
s.disconnectClient()
|
||||||
|
s.deinitClient()
|
||||||
|
|
||||||
|
s.clientLoopDeinitNeededChan = make(chan bool)
|
||||||
|
s.clientLoopDeinitFinishedChan = make(chan bool)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
reportError(err)
|
||||||
|
}
|
||||||
|
<-s.deinitNeededChan
|
||||||
|
s.deinitFinishedChan <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.client = newClient
|
||||||
|
|
||||||
|
go s.clientLoop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only init the serial port TCP server once, with the first device name we acquire, so apps using the
|
||||||
|
// serial port TCP server won't have issues with the interface going down while the app is running.
|
||||||
|
func (s *rigctldStruct) initIfNeeded() (err error) {
|
||||||
|
if s.listener != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.listener, err = net.Listen("tcp", fmt.Sprint(":", rigctldPort))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("starting internal rigctld on tcp port ", rigctldPort)
|
||||||
|
|
||||||
|
s.deinitNeededChan = make(chan bool)
|
||||||
|
s.deinitFinishedChan = make(chan bool)
|
||||||
|
go s.loop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *rigctldStruct) deinit() {
|
||||||
|
if s.listener != nil {
|
||||||
|
s.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.deinitNeededChan != nil {
|
||||||
|
s.deinitNeededChan <- true
|
||||||
|
<-s.deinitFinishedChan
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue