kappanhang/rigctld.go

475 lines
11 KiB
Go
Raw Permalink Normal View History

2020-11-08 20:48:39 +01:00
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) {
2020-11-09 10:13:57 +01:00
cmdSplit := strings.Fields(cmd)
2020-11-08 20:48:39 +01:00
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
2021-05-26 19:12:21 +02:00
case cmd == "f", cmd == "\\get_freq":
2020-11-08 20:48:39 +01:00
civControl.state.mutex.Lock()
defer civControl.state.mutex.Unlock()
err = s.send(civControl.state.freq, "\n")
2021-05-26 19:12:21 +02:00
case cmdSplit[0] == "F", cmdSplit[0] == "\\set_freq":
2020-11-08 20:48:39 +01:00
var f float64
f, err = strconv.ParseFloat(cmdSplit[1], 0)
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
return
}
err = civControl.setMainVFOFreq(uint(f))
2020-11-08 20:48:39 +01:00
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
return
}
err = s.sendReplyCode(rigctldNoError)
2021-05-26 19:12:21 +02:00
case cmd == "m", cmd == "\\get_mode":
2020-11-08 20:48:39 +01:00
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")
2021-05-26 19:12:21 +02:00
case cmdSplit[0] == "M", cmdSplit[0] == "\\set_mode":
2020-11-08 20:48:39 +01:00
mode := cmdSplit[1]
2020-11-28 11:44:44 +01:00
var dataMode bool
2020-11-08 20:48:39 +01:00
if mode[:3] == "PKT" {
2020-11-28 11:44:44 +01:00
dataMode = true
2020-11-08 20:48:39 +01:00
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 {
2020-11-28 11:44:44 +01:00
err = civControl.setDataMode(dataMode)
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
return
}
2020-11-08 20:48:39 +01:00
_ = s.sendReplyCode(rigctldNoError)
}
2021-05-26 19:12:21 +02:00
case cmd == "t", cmd == "\\get_ptt":
2020-11-08 20:48:39 +01:00
civControl.state.mutex.Lock()
defer civControl.state.mutex.Unlock()
res := "0"
if civControl.state.ptt {
res = "1"
}
err = s.send(res, "\n")
2021-05-26 19:12:21 +02:00
case cmdSplit[0] == "T", cmdSplit[0] == "\\set_ptt":
2020-11-08 20:48:39 +01:00
if cmdSplit[1] != "0" {
2020-12-21 08:57:06 +01:00
if setDataModeOnTx {
if err := civControl.setDataMode(true); err != nil {
log.Error("can't enable data mode: ", err)
}
}
2020-11-08 20:48:39 +01:00
err = civControl.setPTT(true)
} else {
err = civControl.setPTT(false)
}
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
} else {
_ = s.sendReplyCode(rigctldNoError)
}
2021-05-26 19:12:21 +02:00
case cmdSplit[0] == "V", cmdSplit[0] == "\\set_vfo":
2020-11-08 20:48:39 +01:00
if cmdSplit[1] == "VFOB" {
err = civControl.setVFO(1)
} else {
err = civControl.setVFO(0)
}
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
} else {
_ = s.sendReplyCode(rigctldNoError)
}
2021-05-26 19:12:21 +02:00
case cmd == "s", cmd == "\\get_split_vfo":
2020-11-08 20:48:39 +01:00
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")
2021-05-26 19:12:21 +02:00
case cmdSplit[0] == "S", cmdSplit[0] == "\\set_split_vfo":
2020-11-08 20:48:39 +01:00
if cmdSplit[1] == "1" {
err = civControl.setSplit(splitModeOn)
} else {
err = civControl.setSplit(splitModeOff)
}
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
} else {
_ = s.sendReplyCode(rigctldNoError)
}
2021-05-26 19:12:21 +02:00
case cmd == "i", cmd == "\\get_split_freq":
2020-11-09 11:00:00 +01:00
civControl.state.mutex.Lock()
defer civControl.state.mutex.Unlock()
err = s.send(civControl.state.subFreq, "\n")
2021-05-26 19:12:21 +02:00
case cmdSplit[0] == "I", cmdSplit[0] == "\\set_split_freq":
2020-11-09 11:00:00 +01:00
var f float64
f, err = strconv.ParseFloat(cmdSplit[1], 0)
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
return
}
err = civControl.setSubVFOFreq(uint(f))
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
return
}
err = s.sendReplyCode(rigctldNoError)
2021-05-26 19:12:21 +02:00
case cmd == "x", cmd == "\\get_split_mode":
civControl.state.mutex.Lock()
defer civControl.state.mutex.Unlock()
var mode string
if civControl.state.subDataMode {
mode = "PKT"
}
mode += civOperatingModes[civControl.state.subOperatingModeIdx].name
// This can be queried with a CIV command for accurate values by the way.
width := "3000"
switch civControl.state.subFilterIdx {
case 1:
width = "2400"
case 2:
width = "1800"
}
err = s.send(mode, "\n", width, "\n")
2021-05-26 19:12:21 +02:00
case cmdSplit[0] == "X", cmdSplit[0] == "\\set_split_mode":
mode := cmdSplit[1]
var dataMode byte
if mode[:3] == "PKT" {
dataMode = 1
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.setSubVFOMode(modeCode, dataMode, filterCode)
if err != nil {
_ = s.sendReplyCode(rigctldInvalidParam)
} else {
_ = s.sendReplyCode(rigctldNoError)
}
2020-11-08 22:25:18 +01:00
case cmd == "v": // Ignore this command.
_ = s.sendReplyCode(rigctldUnsupportedCmd)
return
2020-11-08 20:48:39 +01:00
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
}
}