2020-02-24 23:40:55 -05:00
|
|
|
package solo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2020-02-28 15:03:02 -05:00
|
|
|
"fmt"
|
2020-04-08 00:10:30 -04:00
|
|
|
"io"
|
2020-02-24 23:40:55 -05:00
|
|
|
"log"
|
|
|
|
"net"
|
2020-04-01 15:32:45 -04:00
|
|
|
"sync"
|
2020-02-28 15:03:02 -05:00
|
|
|
|
|
|
|
"github.com/clbanning/mxj"
|
2020-02-24 23:40:55 -05:00
|
|
|
"github.com/fatih/color"
|
|
|
|
|
|
|
|
"github.com/indihub-space/agent/lib"
|
|
|
|
"github.com/indihub-space/agent/proto/indihub"
|
|
|
|
)
|
|
|
|
|
2020-04-08 00:10:30 -04:00
|
|
|
const queueSize = 4096
|
|
|
|
|
2020-02-24 23:40:55 -05:00
|
|
|
var (
|
2020-04-01 15:32:45 -04:00
|
|
|
getProperties = []byte("<getProperties version='1.7'/>")
|
|
|
|
getCCDProperties = "<getProperties device='%s' version='1.7'/>"
|
2020-04-08 00:10:30 -04:00
|
|
|
enableBLOBNever = "<enableBLOB device='%s'>Never</enableBLOB>"
|
2020-04-01 15:32:45 -04:00
|
|
|
enableBLOBOnly = "<enableBLOB device='%s'>Only</enableBLOB>"
|
2020-02-28 15:03:02 -05:00
|
|
|
|
2020-03-19 00:08:08 -04:00
|
|
|
setBLOBVector = []byte("<setBLOBVector")
|
|
|
|
defNumberVector = []byte("<defNumberVector")
|
2020-02-24 23:40:55 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type INDIHubSoloTunnel interface {
|
|
|
|
Send(response *indihub.Response) error
|
|
|
|
CloseAndRecv() (*indihub.SoloSummary, error)
|
|
|
|
}
|
|
|
|
|
2020-02-28 15:03:02 -05:00
|
|
|
type Agent struct {
|
|
|
|
indiServerAddr string
|
|
|
|
indiConn net.Conn
|
|
|
|
tunnel INDIHubSoloTunnel
|
|
|
|
shouldExit bool
|
2020-04-01 15:32:45 -04:00
|
|
|
|
|
|
|
ccdConnMap map[string]net.Conn
|
|
|
|
ccdConnMapMu sync.Mutex
|
|
|
|
|
|
|
|
sessionID uint64
|
|
|
|
sessionToken string
|
2020-04-08 00:10:30 -04:00
|
|
|
|
|
|
|
respPool *sync.Pool
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
|
|
|
|
2020-03-19 00:08:08 -04:00
|
|
|
func New(indiServerAddr string, tunnel INDIHubSoloTunnel) *Agent {
|
2020-02-28 15:03:02 -05:00
|
|
|
return &Agent{
|
|
|
|
indiServerAddr: indiServerAddr,
|
|
|
|
tunnel: tunnel,
|
2020-04-01 15:32:45 -04:00
|
|
|
ccdConnMap: make(map[string]net.Conn),
|
2020-04-08 00:10:30 -04:00
|
|
|
respPool: &sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
return &indihub.Response{
|
|
|
|
Data: make([]byte, lib.INDIServerMaxRecvMsgSize),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-28 15:06:29 -05:00
|
|
|
func (p *Agent) Start(sessionID uint64, sessionToken string) error {
|
2020-04-01 15:32:45 -04:00
|
|
|
p.sessionID = sessionID
|
|
|
|
p.sessionToken = sessionToken
|
|
|
|
|
2020-02-28 15:03:02 -05:00
|
|
|
// open connection to real INDI-server
|
2020-02-24 23:40:55 -05:00
|
|
|
var err error
|
2020-04-08 00:10:30 -04:00
|
|
|
p.indiConn, err = p.connectToINDI()
|
2020-02-24 23:40:55 -05:00
|
|
|
if err != nil {
|
2020-02-28 15:03:02 -05:00
|
|
|
log.Printf("could not connect to INDI-server in solo-mode: %s\n", err)
|
|
|
|
return err
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
2020-02-28 15:03:02 -05:00
|
|
|
defer p.indiConn.Close()
|
2020-02-24 23:40:55 -05:00
|
|
|
|
2020-04-08 00:10:30 -04:00
|
|
|
// run response sending queue
|
|
|
|
respCh := make(chan *indihub.Response, queueSize)
|
|
|
|
defer close(respCh)
|
|
|
|
go p.sendResponses(respCh)
|
2020-02-24 23:40:55 -05:00
|
|
|
|
2020-02-28 15:03:02 -05:00
|
|
|
// listen INDI-server for data
|
2020-04-08 00:10:30 -04:00
|
|
|
buf := make([]byte, lib.INDIServerMaxSendMsgSize)
|
2020-02-28 15:03:02 -05:00
|
|
|
xmlFlattener := lib.NewXmlFlattener()
|
2020-04-01 15:32:45 -04:00
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
var connNum uint32
|
2020-02-24 23:40:55 -05:00
|
|
|
for {
|
2020-02-28 15:03:02 -05:00
|
|
|
if p.shouldExit {
|
2020-02-24 23:40:55 -05:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-02-28 15:03:02 -05:00
|
|
|
n, err := p.indiConn.Read(buf)
|
2020-04-08 00:10:30 -04:00
|
|
|
if err == io.EOF {
|
|
|
|
// reconnect
|
|
|
|
if p.indiConn, err = p.connectToINDI(); err != nil {
|
|
|
|
log.Printf("Failed to re-connect to INDI-server is solo mode: %s", err)
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
n, err = p.indiConn.Read(buf)
|
|
|
|
}
|
|
|
|
}
|
2020-02-24 23:40:55 -05:00
|
|
|
if err != nil {
|
2020-02-28 15:03:02 -05:00
|
|
|
log.Println("could not read from INDI-server in solo-mode:", err)
|
2020-02-24 23:40:55 -05:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2020-02-28 15:03:02 -05:00
|
|
|
// subscribe to BLOBs and catch images
|
|
|
|
xmlCommands := xmlFlattener.FeedChunk(buf[:n])
|
2020-02-24 23:40:55 -05:00
|
|
|
|
2020-02-28 15:03:02 -05:00
|
|
|
for _, xmlCmd := range xmlCommands {
|
2020-03-19 00:08:08 -04:00
|
|
|
// subscribe to BLOBs from CCDs by catching defNumberVector property with name="CCD_EXPOSURE"
|
|
|
|
if !bytes.HasPrefix(xmlCmd, defNumberVector) {
|
2020-02-28 15:03:02 -05:00
|
|
|
continue
|
|
|
|
}
|
2020-02-24 23:40:55 -05:00
|
|
|
|
2020-02-28 15:03:02 -05:00
|
|
|
mapVal, err := mxj.NewMapXml(xmlCmd, true)
|
|
|
|
if err != nil {
|
|
|
|
log.Println("could not parse XML chunk in solo-mode:", err)
|
|
|
|
continue
|
|
|
|
}
|
2020-03-19 00:08:08 -04:00
|
|
|
defNumberVectorMap, _ := mapVal.ValueForKey("defNumberVector")
|
|
|
|
if defNumberVectorMap == nil {
|
2020-02-28 15:03:02 -05:00
|
|
|
continue
|
|
|
|
}
|
2020-03-19 00:08:08 -04:00
|
|
|
defNumberVectorVal := defNumberVectorMap.(map[string]interface{})
|
|
|
|
|
|
|
|
if nameStr, ok := defNumberVectorVal["attr_name"].(string); ok && nameStr == "CCD_EXPOSURE" {
|
2020-04-01 15:32:45 -04:00
|
|
|
if deviceStr, ok := defNumberVectorVal["attr_device"].(string); ok && p.getConnCCD(deviceStr) == nil {
|
|
|
|
// disable receiving BLOBs on main connection
|
|
|
|
if _, err := p.indiConn.Write([]byte(fmt.Sprintf(enableBLOBNever, deviceStr))); err != nil {
|
2020-03-19 00:08:08 -04:00
|
|
|
log.Printf("could not write to INDI-server in solo-mode: %s\n", err)
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
2020-04-01 15:32:45 -04:00
|
|
|
|
|
|
|
// launch Go-routine with connection per CCD and only BLOB enabled
|
|
|
|
connNum++
|
|
|
|
wg.Add(1)
|
2020-04-08 00:10:30 -04:00
|
|
|
go func(ccdName string, cNum uint32, ch chan *indihub.Response) {
|
2020-04-01 15:32:45 -04:00
|
|
|
defer wg.Done()
|
2020-04-08 00:10:30 -04:00
|
|
|
p.readFromCCD(ccdName, cNum, ch)
|
|
|
|
}(deviceStr, connNum, respCh)
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
|
|
|
}
|
2020-02-28 15:03:02 -05:00
|
|
|
}
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
2020-02-28 15:03:02 -05:00
|
|
|
|
2020-04-01 15:32:45 -04:00
|
|
|
wg.Wait()
|
|
|
|
|
2020-02-24 23:40:55 -05:00
|
|
|
// close connections to tunnel
|
2020-02-28 15:03:02 -05:00
|
|
|
if summary, err := p.tunnel.CloseAndRecv(); err == nil {
|
2020-02-24 23:40:55 -05:00
|
|
|
gc := color.New(color.FgGreen)
|
|
|
|
gc.Println()
|
|
|
|
gc.Println(" ************************************************************")
|
|
|
|
gc.Println(" * INDIHUB solo session finished!! *")
|
|
|
|
gc.Println(" ************************************************************")
|
|
|
|
gc.Println(" ")
|
|
|
|
|
|
|
|
gc.Printf(" "+
|
|
|
|
"Processed %d images. Thank you for your contribution!\n",
|
|
|
|
summary.ImagesNum,
|
|
|
|
)
|
|
|
|
gc.Println(" ************************************************************")
|
|
|
|
} else {
|
|
|
|
log.Printf("Error getting solo-session summary: %v", err)
|
|
|
|
}
|
2020-02-28 15:03:02 -05:00
|
|
|
|
|
|
|
return nil
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
|
|
|
|
2020-04-08 00:10:30 -04:00
|
|
|
func (p *Agent) connectToINDI() (net.Conn, error) {
|
|
|
|
log.Println("Connecting to INDI-server in solo mode...")
|
|
|
|
conn, err := net.Dial("tcp", p.indiServerAddr)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("could not connect to INDI-server in solo-mode: %s\n", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// set connection to receive data
|
|
|
|
if _, err := conn.Write(getProperties); err != nil {
|
|
|
|
log.Printf("could not write to INDI-server in solo-mode: %s\n", err)
|
|
|
|
conn.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// disable BLOBs for CCDs if any
|
|
|
|
names := p.getCurrCCD()
|
|
|
|
for _, ccdName := range names {
|
|
|
|
if _, err := conn.Write([]byte(fmt.Sprintf(enableBLOBNever, ccdName))); err != nil {
|
|
|
|
log.Printf("could not write to INDI-server in solo-mode: %s\n", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("...OK")
|
|
|
|
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Agent) getCurrCCD() []string {
|
|
|
|
p.ccdConnMapMu.Lock()
|
|
|
|
defer p.ccdConnMapMu.Unlock()
|
|
|
|
names := []string{}
|
|
|
|
for ccdName := range p.ccdConnMap {
|
|
|
|
names = append(names, ccdName)
|
|
|
|
}
|
|
|
|
|
|
|
|
return names
|
|
|
|
}
|
|
|
|
|
2020-04-01 15:32:45 -04:00
|
|
|
func (p *Agent) getConnCCD(ccdName string) net.Conn {
|
|
|
|
p.ccdConnMapMu.Lock()
|
|
|
|
defer p.ccdConnMapMu.Unlock()
|
|
|
|
return p.ccdConnMap[ccdName]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Agent) setConnCCD(ccdName string, conn net.Conn) {
|
|
|
|
p.ccdConnMapMu.Lock()
|
|
|
|
defer p.ccdConnMapMu.Unlock()
|
|
|
|
p.ccdConnMap[ccdName] = conn
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|
|
|
|
|
2020-04-08 00:10:30 -04:00
|
|
|
func (p *Agent) readFromCCD(ccdName string, cNum uint32, ch chan *indihub.Response) {
|
2020-04-01 15:32:45 -04:00
|
|
|
// open connection
|
2020-04-08 00:10:30 -04:00
|
|
|
conn, err := p.connectToCCD(ccdName)
|
2020-04-01 15:32:45 -04:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
// read data from INDI-server and send to tunnel
|
2020-04-08 00:10:30 -04:00
|
|
|
buf := make([]byte, lib.INDIServerMaxSendMsgSize)
|
2020-04-01 15:32:45 -04:00
|
|
|
for {
|
|
|
|
if p.shouldExit {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
n, err := conn.Read(buf)
|
2020-04-08 00:10:30 -04:00
|
|
|
if err == io.EOF {
|
|
|
|
// reconnect
|
|
|
|
if conn, err = p.connectToCCD(ccdName); err != nil {
|
|
|
|
log.Printf("Failed to re-connect to INDI-server for %s is solo mode: %s\n", ccdName, err)
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
n, err = conn.Read(buf)
|
|
|
|
}
|
|
|
|
}
|
2020-04-01 15:32:45 -04:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("could not read from INDI-server for %s in solo-mode: %s\n", ccdName, err)
|
|
|
|
break
|
|
|
|
}
|
2020-02-24 23:40:55 -05:00
|
|
|
|
2020-04-01 15:32:45 -04:00
|
|
|
// send data to tunnel
|
2020-04-08 00:10:30 -04:00
|
|
|
resp := p.respPool.Get().(*indihub.Response)
|
|
|
|
resp.Conn = cNum
|
|
|
|
resp.SessionToken = p.sessionToken
|
|
|
|
resp.SessionID = p.sessionID
|
|
|
|
resp.Data = resp.Data[:n]
|
|
|
|
copy(resp.Data, buf[:n])
|
|
|
|
ch <- resp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Agent) sendResponses(respCh chan *indihub.Response) {
|
|
|
|
for resp := range respCh {
|
2020-04-01 15:32:45 -04:00
|
|
|
if err := p.tunnel.Send(resp); err != nil {
|
2020-04-08 00:10:30 -04:00
|
|
|
log.Printf("Failed to send a response to tunnel in solo-mode: %s", err)
|
2020-04-01 15:32:45 -04:00
|
|
|
}
|
2020-04-08 00:10:30 -04:00
|
|
|
p.respPool.Put(resp)
|
2020-04-01 15:32:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-08 00:10:30 -04:00
|
|
|
func (p *Agent) connectToCCD(ccdName string) (net.Conn, error) {
|
|
|
|
// open connection
|
|
|
|
log.Println("Connecting to INDI-device:", ccdName)
|
|
|
|
conn, err := net.Dial("tcp", p.indiServerAddr)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("could not connect to INDI-server for CCD '%s' in solo-mode: %s\n",
|
|
|
|
ccdName, err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
log.Println("...OK")
|
|
|
|
|
|
|
|
// set connection to receive data
|
|
|
|
if _, err := conn.Write([]byte(fmt.Sprintf(getCCDProperties, ccdName))); err != nil {
|
|
|
|
log.Printf("getProperties: could not write to INDI-server for %s in solo-mode: %s\n", ccdName, err)
|
|
|
|
conn.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// enable BLOBS only
|
|
|
|
if _, err := conn.Write([]byte(fmt.Sprintf(enableBLOBOnly, ccdName))); err != nil {
|
|
|
|
log.Printf("getProperties: could not write to INDI-server for %s in solo-mode: %s\n", ccdName, err)
|
|
|
|
conn.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
p.setConnCCD(ccdName, conn)
|
|
|
|
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
2020-04-01 15:32:45 -04:00
|
|
|
func (p *Agent) Close() {
|
|
|
|
// close connections to CCDs
|
|
|
|
p.ccdConnMapMu.Lock()
|
|
|
|
defer p.ccdConnMapMu.Unlock()
|
|
|
|
for ccdName, conn := range p.ccdConnMap {
|
|
|
|
log.Println("Closing connection to:", ccdName)
|
|
|
|
conn.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// close main connection
|
|
|
|
p.indiConn.Close()
|
|
|
|
p.shouldExit = true
|
2020-02-24 23:40:55 -05:00
|
|
|
}
|