mirror of
https://github.com/vale981/agent
synced 2025-03-05 09:31:39 -05:00
control RESTful API added
This commit is contained in:
parent
f8855ca56a
commit
51691334b3
10 changed files with 885 additions and 464 deletions
192
README.md
192
README.md
|
@ -41,6 +41,198 @@ To get usage of all parameters just run `indihub-agent -help`.
|
||||||
|
|
||||||
The latest `indihub-agent` release can be downloaded from [releases](https://github.com/indihub-space/agent/releases) or [indihub.space](https://indihub.space) Web-site.
|
The latest `indihub-agent` release can be downloaded from [releases](https://github.com/indihub-space/agent/releases) or [indihub.space](https://indihub.space) Web-site.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
There is an API-server running as part of `indihub-agent` and listening on port `:2020` (or on port specified via `-api-port=N` parameter) which provides two different APIs to control or use `indihub-agent`:
|
||||||
|
|
||||||
|
- RESTful API to get status or switch modes of the the agent
|
||||||
|
- Websocket API to control equipment via websocket-connections
|
||||||
|
|
||||||
|
By default API-server works over HTTP-protocol. You can switch it to work over TLS by providing `-api-tls` parameter. This will make `indihub-agent` to generate self-signed CA and certificate.
|
||||||
|
|
||||||
|
### RESTful API
|
||||||
|
|
||||||
|
You can use this simple RESTful API to control `indihub-agent`.
|
||||||
|
|
||||||
|
NOTE: CORS protection can be specified with comma-separated list of allowed origins via agent parameter:
|
||||||
|
|
||||||
|
`-api-origins=host1,host2,hostN`.
|
||||||
|
|
||||||
|
Also, all examples assume `indihub-agent` is running on host `raspberrypi.local`.
|
||||||
|
|
||||||
|
#### 1. Get indihub-agent status (public)
|
||||||
|
|
||||||
|
`curl -X GET http://raspberrypi.local:2020/status`
|
||||||
|
|
||||||
|
Response example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"indiProfile": "NEO-remote",
|
||||||
|
"indiServer": "raspberrypi.local:7624",
|
||||||
|
"mode": "solo",
|
||||||
|
"phd2Server": "",
|
||||||
|
"status": "running",
|
||||||
|
"supportedModes": [
|
||||||
|
"solo",
|
||||||
|
"share",
|
||||||
|
"robotic"
|
||||||
|
],
|
||||||
|
"version": "1.0.3"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Restart indihub-agent current mode (public)
|
||||||
|
|
||||||
|
`curl -X GET "http://raspberrypi.local:2020/status"`
|
||||||
|
|
||||||
|
#### 3. Switch indihub-agent mode (protected via token)
|
||||||
|
|
||||||
|
You need to do HTTP-request `POST "http://indihub-agent-host:2020/mode/{mode}"` specifying required mode in a `mode` URI-parameter and supplying your token in `Authorization` header, i.e.:
|
||||||
|
|
||||||
|
`curl -X POST "http://raspberrypi.local:2020/mode/solo" -H "Authorization: Bearer cca13ac2951efd6d912ead20a7ab4882"`
|
||||||
|
|
||||||
|
Response will have status of agent running in new mode:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"indiProfile": "NEO-remote",
|
||||||
|
"indiServer": "raspberrypi.local:7624",
|
||||||
|
"mode": "share",
|
||||||
|
"phd2Server": "",
|
||||||
|
"publicEndpoints": [
|
||||||
|
{
|
||||||
|
"name": "INDI-Server",
|
||||||
|
"addr": "node-1.indihub.io:55642"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "running",
|
||||||
|
"supportedModes": [
|
||||||
|
"share",
|
||||||
|
"robotic",
|
||||||
|
"solo"
|
||||||
|
],
|
||||||
|
"version": "1.0.3"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Websocket API
|
||||||
|
|
||||||
|
You can use `indihub-agent` to control your equipment via Websocket API, i.e. from your Web-app open int the Web-browser.
|
||||||
|
|
||||||
|
NOTE: Websocket upgrade-requests are allowed only from origins specified with comma-separated list via agent parameter:
|
||||||
|
|
||||||
|
`-api-origins=host1,host2,hostN`
|
||||||
|
|
||||||
|
#### 1. Open WS-connection to INDI-server (protected via token)
|
||||||
|
|
||||||
|
To establish new WS-connection to INDI-server you will need to use URL with format:
|
||||||
|
|
||||||
|
`ws://raspberrypi.local:2020/websocket/indiserver?token=cca13ac2951efd6d912ead20a7ab4882`
|
||||||
|
|
||||||
|
NOTE: we connect to `raspberrypi.local:2020` which is `indihub-agent` API-server and we provide token via `token` query string parameter.
|
||||||
|
|
||||||
|
This will open WS-connection to you your equipment via `indihub-agent` API-server where all outgoing messages will be INDI-protocol commands and all incoming messages will be INDI-protocol replies from your equipment.
|
||||||
|
|
||||||
|
#### 2. Message format for INDI-server Websocket connection
|
||||||
|
|
||||||
|
All commands are expected to be in JSON-format where INDI-command XML attributes translate to fields with `attr_`-prefix.
|
||||||
|
|
||||||
|
I.e. to send this INDI XML-command over WS-connection:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<getProperties version="1.7" />
|
||||||
|
```
|
||||||
|
|
||||||
|
You will need to send message over WS-connection in JSON-format:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"getProperties": {
|
||||||
|
"attr_version":"1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The INDI-protocol responses will be converted from XML to JSON-messages and sent over WS-connection as messages.
|
||||||
|
|
||||||
|
I.e. this INDI-response about telescopes installed on the mount in XML-format:
|
||||||
|
```xml
|
||||||
|
<defNumberVector device="iOptron CEM25" name="TELESCOPE_INFO" label="Scope Properties" group="Options" state="Ok" perm="rw" timeout="60" timestamp="2020-02-20T21:52:23">
|
||||||
|
<defNumber name="TELESCOPE_APERTURE" label="Aperture (mm)" format="%g" min="10" max="5000" step="0">
|
||||||
|
120
|
||||||
|
</defNumber>
|
||||||
|
<defNumber name="TELESCOPE_FOCAL_LENGTH" label="Focal Length (mm)" format="%g" min="10" max="10000" step="0">
|
||||||
|
600
|
||||||
|
</defNumber>
|
||||||
|
<defNumber name="GUIDER_APERTURE" label="Guider Aperture (mm)" format="%g" min="10" max="5000" step="0">
|
||||||
|
50
|
||||||
|
</defNumber>
|
||||||
|
<defNumber name="GUIDER_FOCAL_LENGTH" label="Guider Focal Length (mm)" format="%g" min="10" max="10000" step="0">
|
||||||
|
162
|
||||||
|
</defNumber>
|
||||||
|
</defNumberVector>
|
||||||
|
```
|
||||||
|
|
||||||
|
Will be converted in to JSON-format like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"defNumberVector": {
|
||||||
|
"attr_device":"iOptron CEM25",
|
||||||
|
"attr_group":"Options",
|
||||||
|
"attr_label":"Scope Properties",
|
||||||
|
"attr_name":"TELESCOPE_INFO",
|
||||||
|
"attr_perm":"rw",
|
||||||
|
"attr_state":"Ok",
|
||||||
|
"attr_timeout":60,
|
||||||
|
"attr_timestamp":"2020-02-20T21:52:23",
|
||||||
|
"defNumber": [
|
||||||
|
{
|
||||||
|
"#text":120,
|
||||||
|
"attr_format":"%g",
|
||||||
|
"attr_label":"Aperture (mm)",
|
||||||
|
"attr_max":5000,
|
||||||
|
"attr_min":10,
|
||||||
|
"attr_name":"TELESCOPE_APERTURE",
|
||||||
|
"attr_step":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"#text":600,
|
||||||
|
"attr_format":"%g",
|
||||||
|
"attr_label":"Focal Length (mm)",
|
||||||
|
"attr_max":10000,
|
||||||
|
"attr_min":10,
|
||||||
|
"attr_name":"TELESCOPE_FOCAL_LENGTH",
|
||||||
|
"attr_step":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"#text":50,
|
||||||
|
"attr_format":"%g",
|
||||||
|
"attr_label":"Guider Aperture (mm)",
|
||||||
|
"attr_max":5000,
|
||||||
|
"attr_min":10,
|
||||||
|
"attr_name":"GUIDER_APERTURE",
|
||||||
|
"attr_step":0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"#text":162,
|
||||||
|
"attr_format":"%g",
|
||||||
|
"attr_label":"Guider Focal Length (mm)",
|
||||||
|
"attr_max":10000,
|
||||||
|
"attr_min":10,
|
||||||
|
"attr_name":"GUIDER_FOCAL_LENGTH",
|
||||||
|
"attr_step":0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please NOTE:
|
||||||
|
|
||||||
|
- XML element attributes get converted into JSON-fields with `attr_` prefix
|
||||||
|
- XML element value get converted into JSON-field with name `#text`
|
||||||
|
- vector like child-elements get converted into JSON-arrays
|
||||||
|
|
||||||
## Building indihub-agent
|
## Building indihub-agent
|
||||||
|
|
||||||
You will need to install [Golang](https://golang.org/dl/).
|
You will need to install [Golang](https://golang.org/dl/).
|
||||||
|
|
363
apiserver/server.go
Normal file
363
apiserver/server.go
Normal file
|
@ -0,0 +1,363 @@
|
||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/indihub-space/agent/version"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/labstack/echo"
|
||||||
|
"github.com/labstack/echo/middleware"
|
||||||
|
elog "github.com/labstack/gommon/log"
|
||||||
|
|
||||||
|
"github.com/indihub-space/agent/lib"
|
||||||
|
"github.com/indihub-space/agent/logutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var allowedOrigins = map[string]bool{
|
||||||
|
"indihub.space": true,
|
||||||
|
"app.indihub.space": true,
|
||||||
|
"kids.indihub.space": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentMode provides interface to operate with agent from API-server
|
||||||
|
type AgentMode interface {
|
||||||
|
Start()
|
||||||
|
Stop()
|
||||||
|
GetStatus() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIServer struct {
|
||||||
|
token string
|
||||||
|
indiServerAddr string
|
||||||
|
phd2ServerAddr string
|
||||||
|
port uint64
|
||||||
|
isTLS bool
|
||||||
|
origins string
|
||||||
|
|
||||||
|
e *echo.Echo
|
||||||
|
upgrader websocket.Upgrader
|
||||||
|
connList []net.Conn
|
||||||
|
|
||||||
|
indiProfile string
|
||||||
|
currMode string
|
||||||
|
agentModes map[string]AgentMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIServer(token string, indiServerAddr string, phd2ServerAddr string, port uint64, isTLS bool, origins string,
|
||||||
|
currMode string, indiProfile string, agentModes map[string]AgentMode) *APIServer {
|
||||||
|
|
||||||
|
apiServer := &APIServer{
|
||||||
|
token: token,
|
||||||
|
indiServerAddr: indiServerAddr,
|
||||||
|
phd2ServerAddr: phd2ServerAddr,
|
||||||
|
port: port,
|
||||||
|
isTLS: isTLS,
|
||||||
|
e: echo.New(),
|
||||||
|
upgrader: websocket.Upgrader{},
|
||||||
|
connList: []net.Conn{},
|
||||||
|
indiProfile: indiProfile,
|
||||||
|
currMode: currMode,
|
||||||
|
agentModes: agentModes,
|
||||||
|
}
|
||||||
|
|
||||||
|
if logutil.IsDev {
|
||||||
|
allowedOrigins["localhost"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// add optional additional origins
|
||||||
|
for _, orig := range strings.Split(origins, ",") {
|
||||||
|
allowedOrigins[strings.TrimSpace(orig)] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow WS connections only from number of domains
|
||||||
|
apiServer.upgrader.CheckOrigin = func(r *http.Request) bool {
|
||||||
|
origin := r.Header["Origin"]
|
||||||
|
if len(origin) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
u, err := url.Parse(origin[0])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
host, _, err := net.SplitHostPort(u.Host)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return allowedOrigins[host]
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) newIndiConnection(c echo.Context) error {
|
||||||
|
// upgrade to WS connection
|
||||||
|
ws, err := s.upgrader.Upgrade(c.Response(), c.Request(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
// open connection to INDI-Server
|
||||||
|
conn, err := net.Dial("tcp", s.indiServerAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to connection list
|
||||||
|
s.connList = append(s.connList, conn)
|
||||||
|
|
||||||
|
// read messages from INDI-server and write them to WS
|
||||||
|
go func(indiConn net.Conn, wsConn *websocket.Conn) {
|
||||||
|
buf := make([]byte, lib.INDIServerMaxSendMsgSize, lib.INDIServerMaxSendMsgSize)
|
||||||
|
xmlFlattener := lib.NewXmlFlattener()
|
||||||
|
for {
|
||||||
|
// read from INDI-server
|
||||||
|
n, err := indiConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
indiConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonMessages := xmlFlattener.ConvertChunkToJSON(buf[:n])
|
||||||
|
|
||||||
|
// Write to WS
|
||||||
|
for _, m := range jsonMessages {
|
||||||
|
err = wsConn.WriteMessage(websocket.TextMessage, m)
|
||||||
|
if err != nil {
|
||||||
|
indiConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(conn, ws)
|
||||||
|
|
||||||
|
// read messages from WS and write them to INDI-server
|
||||||
|
xmlFlattener := lib.NewXmlFlattener()
|
||||||
|
for {
|
||||||
|
// Read from WS
|
||||||
|
_, msg, err := ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlMsg, err := xmlFlattener.ConvertJSONToXML(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not convert json '%s' to xml: %s", string(msg), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// write to INDI server
|
||||||
|
_, err = conn.Write(xmlMsg)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) newPHD2Connection(c echo.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) getRestart(c echo.Context) error {
|
||||||
|
// restart current mode
|
||||||
|
if curAgentMode, ok := s.agentModes[s.currMode]; ok {
|
||||||
|
curAgentMode.Stop()
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
curAgentMode.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) getStatus(c echo.Context) error {
|
||||||
|
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) changeMode(c echo.Context) error {
|
||||||
|
newMode := c.Param("new_mode")
|
||||||
|
|
||||||
|
// don't do anything if agent is laready in this mode
|
||||||
|
if newMode == s.currMode {
|
||||||
|
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get agent new mode
|
||||||
|
newAgentMode, ok := s.agentModes[newMode]
|
||||||
|
if !ok {
|
||||||
|
c.JSON(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
map[string]interface{}{
|
||||||
|
"message": "unknown indihub-agent mode: " + newMode,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop current mode
|
||||||
|
if curAgentMode, ok := s.agentModes[s.currMode]; ok {
|
||||||
|
curAgentMode.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// start agent in new mode
|
||||||
|
s.currMode = newMode
|
||||||
|
newAgentMode.Start()
|
||||||
|
|
||||||
|
c.JSONPretty(http.StatusOK, s.agentStatus(), " ")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) agentStatus() map[string]interface{} {
|
||||||
|
agentStatus := map[string]interface{}{
|
||||||
|
"version": version.AgentVersion,
|
||||||
|
"mode": s.currMode,
|
||||||
|
"indiProfile": s.indiProfile,
|
||||||
|
"indiServer": s.indiServerAddr,
|
||||||
|
"phd2Server": s.phd2ServerAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
supportedModes := make([]string, 0, len(s.agentModes))
|
||||||
|
for key := range s.agentModes {
|
||||||
|
supportedModes = append(supportedModes, key)
|
||||||
|
}
|
||||||
|
agentStatus["supportedModes"] = supportedModes
|
||||||
|
|
||||||
|
if agentMode, ok := s.agentModes[s.currMode]; ok {
|
||||||
|
for key, val := range agentMode.GetStatus() {
|
||||||
|
agentStatus[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return agentStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) Start() {
|
||||||
|
s.e.HideBanner = true
|
||||||
|
s.e.HidePort = true
|
||||||
|
|
||||||
|
if !logutil.IsDev {
|
||||||
|
s.e.Logger.SetLevel(elog.OFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup middle-wares
|
||||||
|
|
||||||
|
s.e.Use(middleware.Recover())
|
||||||
|
|
||||||
|
// set CORS in case browser decides to do pre-flight OPTIONS request
|
||||||
|
// default ones
|
||||||
|
allowOrigins := []string{}
|
||||||
|
for orig := range allowedOrigins {
|
||||||
|
allowOrigins = append(allowOrigins, "http://"+orig)
|
||||||
|
allowOrigins = append(allowOrigins, "https://"+orig)
|
||||||
|
}
|
||||||
|
// optional ones
|
||||||
|
for _, orig := range strings.Split(s.origins, ",") {
|
||||||
|
allowOrigins = append(allowOrigins, "http://"+strings.TrimSpace(orig))
|
||||||
|
allowOrigins = append(allowOrigins, "https://"+strings.TrimSpace(orig))
|
||||||
|
}
|
||||||
|
// add localhost for dev-mode
|
||||||
|
if logutil.IsDev {
|
||||||
|
allowOrigins = append(allowOrigins, "http://localhost:5000")
|
||||||
|
}
|
||||||
|
s.e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||||
|
AllowOrigins: allowOrigins,
|
||||||
|
AllowMethods: []string{
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPost,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
// setup routing for WS and RESTful APIs
|
||||||
|
|
||||||
|
// protected WS-API
|
||||||
|
wsGroup := s.e.Group(
|
||||||
|
"/websocket",
|
||||||
|
// set auth middleware
|
||||||
|
middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
|
||||||
|
KeyLookup: "query:token",
|
||||||
|
Validator: func(token string, eCtx echo.Context) (b bool, err error) {
|
||||||
|
return token == s.token, nil
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
wsGroup.GET("/indiserver", s.newIndiConnection)
|
||||||
|
wsGroup.GET("/phd2server", s.newPHD2Connection)
|
||||||
|
|
||||||
|
// protected RESTful API
|
||||||
|
s.e.POST(
|
||||||
|
"/mode/:new_mode",
|
||||||
|
s.changeMode,
|
||||||
|
// set auth middleware
|
||||||
|
middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
|
||||||
|
KeyLookup: "header:Authorization",
|
||||||
|
Validator: func(token string, eCtx echo.Context) (b bool, err error) {
|
||||||
|
return token == s.token, nil
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
// public RESTful API
|
||||||
|
s.e.GET("/status", s.getStatus)
|
||||||
|
s.e.GET("/restart", s.getRestart)
|
||||||
|
|
||||||
|
// start agent in a required mode
|
||||||
|
agentMode, ok := s.agentModes[s.currMode]
|
||||||
|
if !ok {
|
||||||
|
log.Println("unknown agent mode:", s.currMode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
agentMode.Start()
|
||||||
|
|
||||||
|
// check if we are running TLS
|
||||||
|
if s.isTLS {
|
||||||
|
// generate self-signed cert to serve WS over TLS
|
||||||
|
keyFile, certFile, err := getSelfSignedCert()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("could not start API-server, self-signed certificate generating failed:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// start HTTP/WS API server over TLS
|
||||||
|
err = s.e.StartTLS(fmt.Sprintf(":%d", s.port), certFile, keyFile)
|
||||||
|
if err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println("API-server error:", err)
|
||||||
|
} else {
|
||||||
|
log.Println("API-server was shutdown gracefully")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// start HTTP/WS API server
|
||||||
|
err := s.e.Start(fmt.Sprintf(":%d", s.port))
|
||||||
|
if err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println("API-server error:", err)
|
||||||
|
} else {
|
||||||
|
log.Println("API-server was shutdown gracefully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *APIServer) Stop() {
|
||||||
|
if agentMode, ok := s.agentModes[s.currMode]; ok {
|
||||||
|
agentMode.Stop()
|
||||||
|
}
|
||||||
|
for _, conn := range s.connList {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
s.e.Shutdown(context.Background())
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package websockets
|
package apiserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
|
@ -6,4 +6,8 @@ const (
|
||||||
|
|
||||||
INDIServerMaxRecvMsgSize = 49152
|
INDIServerMaxRecvMsgSize = 49152
|
||||||
INDIServerMaxSendMsgSize = 2048
|
INDIServerMaxSendMsgSize = 2048
|
||||||
|
|
||||||
|
ModeSolo = "solo"
|
||||||
|
ModeShare = "share"
|
||||||
|
ModeRobotic = "robotic"
|
||||||
)
|
)
|
||||||
|
|
291
main.go
291
main.go
|
@ -11,32 +11,25 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/indihub-space/agent/apiserver"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
_ "google.golang.org/grpc/encoding/gzip"
|
_ "google.golang.org/grpc/encoding/gzip"
|
||||||
|
|
||||||
"github.com/indihub-space/agent/config"
|
"github.com/indihub-space/agent/config"
|
||||||
"github.com/indihub-space/agent/hostutils"
|
|
||||||
"github.com/indihub-space/agent/lib"
|
"github.com/indihub-space/agent/lib"
|
||||||
"github.com/indihub-space/agent/logutil"
|
"github.com/indihub-space/agent/logutil"
|
||||||
"github.com/indihub-space/agent/manager"
|
"github.com/indihub-space/agent/manager"
|
||||||
"github.com/indihub-space/agent/proto/indihub"
|
"github.com/indihub-space/agent/proto/indihub"
|
||||||
"github.com/indihub-space/agent/proxy"
|
"github.com/indihub-space/agent/share"
|
||||||
"github.com/indihub-space/agent/solo"
|
"github.com/indihub-space/agent/solo"
|
||||||
"github.com/indihub-space/agent/version"
|
"github.com/indihub-space/agent/version"
|
||||||
"github.com/indihub-space/agent/websockets"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultWSPort uint64 = 2020
|
defaultAPIPort uint64 = 2020
|
||||||
|
|
||||||
modeSolo = "solo"
|
|
||||||
modeShare = "share"
|
|
||||||
modeRobotic = "robotic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -47,10 +40,9 @@ var (
|
||||||
flagConfFile string
|
flagConfFile string
|
||||||
flagSoloINDIServerAddr string
|
flagSoloINDIServerAddr string
|
||||||
flagCompress bool
|
flagCompress bool
|
||||||
flagWSServer bool
|
flagAPITLS bool
|
||||||
flagWSIsTLS bool
|
flagAPIPort uint64
|
||||||
flagWSPort uint64
|
flagAPIOrigins string
|
||||||
flagWSOrigins string
|
|
||||||
flagMode string
|
flagMode string
|
||||||
|
|
||||||
indiServerAddr string
|
indiServerAddr string
|
||||||
|
@ -68,7 +60,7 @@ func init() {
|
||||||
flag.StringVar(
|
flag.StringVar(
|
||||||
&flagMode,
|
&flagMode,
|
||||||
"mode",
|
"mode",
|
||||||
modeSolo,
|
lib.ModeSolo,
|
||||||
`indihub-agent mode (deafult value is "solo"), there four modes:\n
|
`indihub-agent mode (deafult value is "solo"), there four modes:\n
|
||||||
solo - equipment sharing is not possible, you are connected to INDIHUB and contributing images
|
solo - equipment sharing is not possible, you are connected to INDIHUB and contributing images
|
||||||
share - you are sharing equipment with another INDIHUB user (agent will output connection info)
|
share - you are sharing equipment with another INDIHUB user (agent will output connection info)
|
||||||
|
@ -112,35 +104,29 @@ robotic - equipment sharing is not possible, your equipment is controlled by IND
|
||||||
"Name of INDI-profile to share via indihub",
|
"Name of INDI-profile to share via indihub",
|
||||||
)
|
)
|
||||||
flag.BoolVar(
|
flag.BoolVar(
|
||||||
&flagWSServer,
|
&flagAPITLS,
|
||||||
"ws-server",
|
"api-tls",
|
||||||
true,
|
|
||||||
"launch Websocket server to control equipment via Websocket API",
|
|
||||||
)
|
|
||||||
flag.BoolVar(
|
|
||||||
&flagWSIsTLS,
|
|
||||||
"ws-tls",
|
|
||||||
false,
|
false,
|
||||||
"serve web-socket over TLS with self-signed certificate",
|
"serve API-server over TLS with self-signed certificate",
|
||||||
)
|
)
|
||||||
flag.Uint64Var(
|
flag.Uint64Var(
|
||||||
&flagWSPort,
|
&flagAPIPort,
|
||||||
"ws-port",
|
"api-port",
|
||||||
defaultWSPort,
|
defaultAPIPort,
|
||||||
"port to start web socket-server on",
|
"port to start API-server on",
|
||||||
)
|
)
|
||||||
flag.StringVar(
|
flag.StringVar(
|
||||||
&flagWSOrigins,
|
&flagAPIOrigins,
|
||||||
"ws-origins",
|
"api-origins",
|
||||||
"",
|
"",
|
||||||
"comma-separated list of origins allowed to connect to WS-server",
|
"comma-separated list of origins allowed to connect to API-server",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if flagMode != modeSolo && flagMode != modeShare && flagMode != modeRobotic {
|
if flagMode != lib.ModeSolo && flagMode != lib.ModeShare && flagMode != lib.ModeRobotic {
|
||||||
log.Fatalf("Unknown mode '%s' provided\n", flagMode)
|
log.Fatalf("Unknown mode '%s' provided\n", flagMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,9 +225,9 @@ func main() {
|
||||||
Autoconnect: indiProfile.AutoConnect,
|
Autoconnect: indiProfile.AutoConnect,
|
||||||
},
|
},
|
||||||
Drivers: make([]*indihub.INDIDriver, len(indiDrivers)),
|
Drivers: make([]*indihub.INDIDriver, len(indiDrivers)),
|
||||||
SoloMode: flagMode == modeSolo,
|
SoloMode: flagMode == lib.ModeSolo,
|
||||||
IsPHD2: flagPHD2ServerAddr != "",
|
IsPHD2: flagPHD2ServerAddr != "",
|
||||||
IsRobotic: flagMode == modeRobotic,
|
IsRobotic: flagMode == lib.ModeRobotic,
|
||||||
IsBroadcast: false,
|
IsBroadcast: false,
|
||||||
AgentVersion: version.AgentVersion,
|
AgentVersion: version.AgentVersion,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
|
@ -287,6 +273,8 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Println("...OK")
|
log.Println("...OK")
|
||||||
|
// close grpc client connection at the very end
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
indiHubClient := indihub.NewINDIHubClient(conn)
|
indiHubClient := indihub.NewINDIHubClient(conn)
|
||||||
|
|
||||||
|
@ -296,21 +284,7 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Current agent version:", version.AgentVersion)
|
version.CheckAgentVersion(regInfo.AgentVersion)
|
||||||
log.Println("Latest agent version:", regInfo.AgentVersion)
|
|
||||||
|
|
||||||
if version.AgentVersion < regInfo.AgentVersion {
|
|
||||||
yc := color.New(color.FgYellow)
|
|
||||||
yc.Println()
|
|
||||||
yc.Println(" ************************************************************")
|
|
||||||
yc.Println(" * WARNING: you version of agent is outdated! *")
|
|
||||||
yc.Println(" * *")
|
|
||||||
yc.Println(" * Please download the latest version from: *")
|
|
||||||
yc.Println(" * https://indihub.space/downloads *")
|
|
||||||
yc.Println(" * *")
|
|
||||||
yc.Println(" ************************************************************")
|
|
||||||
yc.Println(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Access token: %s\n", regInfo.Token)
|
log.Printf("Access token: %s\n", regInfo.Token)
|
||||||
log.Printf("Host session token: %s\n", regInfo.SessionIDPublic)
|
log.Printf("Host session token: %s\n", regInfo.SessionIDPublic)
|
||||||
|
@ -325,203 +299,40 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// start WS-server
|
// prepare all modes
|
||||||
wsServer := websockets.NewWsServer(
|
soloMode := solo.NewMode(indiHubClient, regInfo, indiServerAddr, ccdDrivers)
|
||||||
|
shareMode := share.NewMode(indiHubClient, regInfo, indiServerAddr, flagPHD2ServerAddr, lib.ModeShare)
|
||||||
|
roboticMode := share.NewMode(indiHubClient, regInfo, indiServerAddr, flagPHD2ServerAddr, lib.ModeRobotic)
|
||||||
|
|
||||||
|
// start API-server
|
||||||
|
apiServer := apiserver.NewAPIServer(
|
||||||
regInfo.Token,
|
regInfo.Token,
|
||||||
indiServerAddr,
|
indiServerAddr,
|
||||||
flagPHD2ServerAddr,
|
flagPHD2ServerAddr,
|
||||||
flagWSPort,
|
flagAPIPort,
|
||||||
flagWSIsTLS,
|
flagAPITLS,
|
||||||
flagWSOrigins,
|
flagAPIOrigins,
|
||||||
|
flagMode,
|
||||||
|
flagINDIProfile,
|
||||||
|
map[string]apiserver.AgentMode{
|
||||||
|
lib.ModeSolo: soloMode,
|
||||||
|
lib.ModeShare: shareMode,
|
||||||
|
lib.ModeRobotic: roboticMode,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
go wsServer.Start()
|
|
||||||
|
|
||||||
// start session
|
go func() {
|
||||||
switch flagMode {
|
sigint := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigint, os.Interrupt, os.Kill)
|
||||||
|
|
||||||
case modeSolo:
|
<-sigint
|
||||||
// solo mode - equipment sharing is not available but host still sends all images to INDIHUB
|
|
||||||
log.Println("'solo' parameter was provided. Your session is in solo-mode: equipment sharing is not available")
|
|
||||||
log.Println("Starting INDIHUB agent in solo mode!")
|
|
||||||
|
|
||||||
soloClient, err := indiHubClient.SoloMode(context.Background())
|
log.Println("Stopping API-server gracefully")
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not start agent in solo mode: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
soloAgent := solo.New(
|
// close connections to local INDI-server
|
||||||
indiServerAddr,
|
apiServer.Stop()
|
||||||
soloClient,
|
}()
|
||||||
ccdDrivers,
|
|
||||||
)
|
|
||||||
|
|
||||||
go func() {
|
// start API-server and indihub-agent in the current mode
|
||||||
sigint := make(chan os.Signal, 1)
|
apiServer.Start()
|
||||||
signal.Notify(sigint, os.Interrupt, os.Kill)
|
|
||||||
|
|
||||||
<-sigint
|
|
||||||
|
|
||||||
// stop WS-server
|
|
||||||
wsServer.Stop()
|
|
||||||
|
|
||||||
log.Println("Closing INDIHUB solo-session")
|
|
||||||
|
|
||||||
// close connections to local INDI-server and to INDI client
|
|
||||||
soloAgent.Close()
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
|
|
||||||
// close grpc client connection
|
|
||||||
conn.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// start solo mode INDI-server tcp-proxy
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
soloAgent.Start(regInfo.SessionID, regInfo.SessionIDPublic)
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
case modeShare, modeRobotic:
|
|
||||||
// main equipment sharing mode
|
|
||||||
if flagMode == modeRobotic {
|
|
||||||
log.Println("'robotic' parameter was provided. Your session is in robotic-mode: equipment sharing is not available")
|
|
||||||
}
|
|
||||||
// open INDI server tunnel
|
|
||||||
log.Println("Starting INDI-Server in the cloud...")
|
|
||||||
indiServTunnel, err := indiHubClient.INDIServer(
|
|
||||||
context.Background(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Println("...OK")
|
|
||||||
|
|
||||||
indiFilterConf := &hostutils.INDIFilterConfig{} // TODO: add reading config
|
|
||||||
indiFilter := hostutils.NewINDIFilter(indiFilterConf)
|
|
||||||
indiServerProxy := proxy.New("INDI-Server", indiServerAddr, indiServTunnel, indiFilter)
|
|
||||||
|
|
||||||
// start PHD2 server proxy if specified
|
|
||||||
var phd2ServerProxy *proxy.TcpProxy
|
|
||||||
if flagPHD2ServerAddr != "" {
|
|
||||||
// open PHD2 server tunnel
|
|
||||||
log.Println("Starting PHD2-Server in the cloud...")
|
|
||||||
phd2ServTunnel, err := indiHubClient.PHD2Server(
|
|
||||||
context.Background(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Println("...OK")
|
|
||||||
phd2ServerProxy = proxy.New("PHD2-Server", flagPHD2ServerAddr, phd2ServTunnel, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
sigint := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigint, os.Interrupt, os.Kill)
|
|
||||||
|
|
||||||
<-sigint
|
|
||||||
|
|
||||||
// stop WS-server
|
|
||||||
wsServer.Stop()
|
|
||||||
|
|
||||||
// close connections to tunnels
|
|
||||||
indiServTunnel.CloseSend()
|
|
||||||
if phd2ServerProxy != nil {
|
|
||||||
phd2ServerProxy.Tunnel.CloseSend()
|
|
||||||
}
|
|
||||||
|
|
||||||
// close grpc client connection
|
|
||||||
conn.Close()
|
|
||||||
|
|
||||||
// close connections to local INDI-server and PHD2-Server
|
|
||||||
indiServerProxy.Close()
|
|
||||||
if phd2ServerProxy != nil {
|
|
||||||
phd2ServerProxy.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
serverAddrChan := make(chan proxy.PublicServerAddr, 3)
|
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
|
|
||||||
// INDI Server Proxy start
|
|
||||||
waitNum := 1
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
indiServerProxy.Start(serverAddrChan, regInfo.SessionID, regInfo.SessionIDPublic)
|
|
||||||
}()
|
|
||||||
|
|
||||||
if flagPHD2ServerAddr != "" {
|
|
||||||
waitNum = 2
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
phd2ServerProxy.Start(serverAddrChan, regInfo.SessionID, regInfo.SessionIDPublic)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
addrData := []proxy.PublicServerAddr{}
|
|
||||||
for i := 0; i < waitNum; i++ {
|
|
||||||
sAddr := <-serverAddrChan
|
|
||||||
addrData = append(addrData, sAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
c := color.New(color.FgCyan)
|
|
||||||
gc := color.New(color.FgGreen)
|
|
||||||
yc := color.New(color.FgYellow)
|
|
||||||
rc := color.New(color.FgMagenta)
|
|
||||||
if flagMode != modeRobotic {
|
|
||||||
c.Println()
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
c.Println(" * INDIHUB public address list!! *")
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
c.Println(" ")
|
|
||||||
for _, sAddr := range addrData {
|
|
||||||
gc.Printf(" %s: %s\n", sAddr.Name, sAddr.Addr)
|
|
||||||
}
|
|
||||||
c.Println(" ")
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
c.Println()
|
|
||||||
c.Println(" Please provide your guest with this information:")
|
|
||||||
c.Println()
|
|
||||||
c.Println(" 1. Public address list from the above")
|
|
||||||
c.Println(" 2. Focal length and aperture of your main telescope")
|
|
||||||
c.Println(" 3. Focal length and aperture of your guiding telescope")
|
|
||||||
c.Println(" 4. Type of guiding you use: PHD2 or guiding via camera")
|
|
||||||
c.Println(" 5. Names of your imaging camera and guiding cameras")
|
|
||||||
c.Println()
|
|
||||||
yc.Println(" NOTE: These public addresses will be available ONLY until")
|
|
||||||
yc.Println(" agent is running! (Ctrl+C will stop the session)")
|
|
||||||
c.Println()
|
|
||||||
} else {
|
|
||||||
c.Println()
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
c.Println(" * INDIHUB robotic-session started!! *")
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
c.Println(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
c.Println()
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
c.Println(" * INDIHUB session finished!! *")
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
c.Println(" ")
|
|
||||||
if flagMode != modeRobotic {
|
|
||||||
for _, sAddr := range addrData {
|
|
||||||
rc.Printf(" %s: %s - CLOSED!!\n", sAddr.Name, sAddr.Addr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.Println(" * INDIHUB robotic-session finished. *")
|
|
||||||
c.Println(" * Thank you for your contribution! *")
|
|
||||||
}
|
|
||||||
c.Println(" ")
|
|
||||||
c.Println(" ************************************************************")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,8 @@ type TcpProxy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublicServerAddr struct {
|
type PublicServerAddr struct {
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Addr string
|
Addr string `json:"addr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(name string, addr string, tunnel INDIHubTunnel, filter *hostutils.INDIFilter) *TcpProxy {
|
func New(name string, addr string, tunnel INDIHubTunnel, filter *hostutils.INDIFilter) *TcpProxy {
|
||||||
|
|
178
share/mode.go
Normal file
178
share/mode.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
|
||||||
|
"github.com/indihub-space/agent/hostutils"
|
||||||
|
"github.com/indihub-space/agent/lib"
|
||||||
|
"github.com/indihub-space/agent/proto/indihub"
|
||||||
|
"github.com/indihub-space/agent/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mode struct {
|
||||||
|
indiHubClient indihub.INDIHubClient
|
||||||
|
regInfo *indihub.RegisterInfo
|
||||||
|
|
||||||
|
indiServerAddr string
|
||||||
|
phd2ServerAddr string
|
||||||
|
|
||||||
|
addrData []proxy.PublicServerAddr
|
||||||
|
|
||||||
|
stopCh chan struct{}
|
||||||
|
status string
|
||||||
|
mode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMode(indiHubClient indihub.INDIHubClient, regInfo *indihub.RegisterInfo, indiServerAddr string, phd2ServerAddr string, mode string) *Mode {
|
||||||
|
return &Mode{
|
||||||
|
indiHubClient: indiHubClient,
|
||||||
|
regInfo: regInfo,
|
||||||
|
indiServerAddr: indiServerAddr,
|
||||||
|
phd2ServerAddr: phd2ServerAddr,
|
||||||
|
mode: mode,
|
||||||
|
addrData: []proxy.PublicServerAddr{},
|
||||||
|
stopCh: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mode) Start() {
|
||||||
|
// main equipment sharing mode
|
||||||
|
if m.mode == lib.ModeRobotic {
|
||||||
|
log.Println("'robotic' parameter was provided. Your session is in robotic-mode: equipment sharing is not available")
|
||||||
|
}
|
||||||
|
// open INDI server tunnel
|
||||||
|
log.Println("Starting INDI-Server in the cloud...")
|
||||||
|
indiServTunnel, err := m.indiHubClient.INDIServer(
|
||||||
|
context.Background(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("...OK")
|
||||||
|
|
||||||
|
indiFilterConf := &hostutils.INDIFilterConfig{} // TODO: add reading config
|
||||||
|
indiFilter := hostutils.NewINDIFilter(indiFilterConf)
|
||||||
|
indiServerProxy := proxy.New("INDI-Server", m.indiServerAddr, indiServTunnel, indiFilter)
|
||||||
|
|
||||||
|
// start PHD2 server proxy if specified
|
||||||
|
var phd2ServerProxy *proxy.TcpProxy
|
||||||
|
if m.phd2ServerAddr != "" {
|
||||||
|
// open PHD2 server tunnel
|
||||||
|
log.Println("Starting PHD2-Server in the cloud...")
|
||||||
|
phd2ServTunnel, err := m.indiHubClient.PHD2Server(
|
||||||
|
context.Background(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("...OK")
|
||||||
|
phd2ServerProxy = proxy.New("PHD2-Server", m.phd2ServerAddr, phd2ServTunnel, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-m.stopCh
|
||||||
|
|
||||||
|
log.Printf("Closing %s-session\n", m.mode)
|
||||||
|
|
||||||
|
// close connections to tunnels
|
||||||
|
indiServTunnel.CloseSend()
|
||||||
|
if phd2ServerProxy != nil {
|
||||||
|
phd2ServerProxy.Tunnel.CloseSend()
|
||||||
|
}
|
||||||
|
|
||||||
|
// close connections to local INDI-server and PHD2-Server
|
||||||
|
indiServerProxy.Close()
|
||||||
|
if phd2ServerProxy != nil {
|
||||||
|
phd2ServerProxy.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
serverAddrChan := make(chan proxy.PublicServerAddr, 3)
|
||||||
|
|
||||||
|
// INDI Server Proxy start
|
||||||
|
go indiServerProxy.Start(serverAddrChan, m.regInfo.SessionID, m.regInfo.SessionIDPublic)
|
||||||
|
sAddr := <-serverAddrChan
|
||||||
|
if m.mode != lib.ModeRobotic {
|
||||||
|
m.addrData = append(m.addrData, sAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHD2 Server proxy start
|
||||||
|
if m.phd2ServerAddr != "" {
|
||||||
|
go phd2ServerProxy.Start(serverAddrChan, m.regInfo.SessionID, m.regInfo.SessionIDPublic)
|
||||||
|
sAddr := <-serverAddrChan
|
||||||
|
if m.mode != lib.ModeRobotic {
|
||||||
|
m.addrData = append(m.addrData, sAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := color.New(color.FgCyan)
|
||||||
|
gc := color.New(color.FgGreen)
|
||||||
|
yc := color.New(color.FgYellow)
|
||||||
|
if m.mode != lib.ModeRobotic {
|
||||||
|
c.Println()
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
c.Println(" * INDIHUB public address list!! *")
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
c.Println(" ")
|
||||||
|
for _, sAddr := range m.addrData {
|
||||||
|
gc.Printf(" %s: %s\n", sAddr.Name, sAddr.Addr)
|
||||||
|
}
|
||||||
|
c.Println(" ")
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
c.Println()
|
||||||
|
c.Println(" Please provide your guest with this information:")
|
||||||
|
c.Println()
|
||||||
|
c.Println(" 1. Public address list from the above")
|
||||||
|
c.Println(" 2. Focal length and aperture of your main telescope")
|
||||||
|
c.Println(" 3. Focal length and aperture of your guiding telescope")
|
||||||
|
c.Println(" 4. Type of guiding you use: PHD2 or guiding via camera")
|
||||||
|
c.Println(" 5. Names of your imaging camera and guiding cameras")
|
||||||
|
c.Println()
|
||||||
|
yc.Println(" NOTE: These public addresses will be available ONLY until")
|
||||||
|
yc.Println(" agent is running! (Ctrl+C will stop the session)")
|
||||||
|
c.Println()
|
||||||
|
} else {
|
||||||
|
c.Println()
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
c.Println(" * INDIHUB robotic-session started!! *")
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
c.Println(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.status = "running"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mode) Stop() {
|
||||||
|
m.status = "stopped"
|
||||||
|
m.stopCh <- struct{}{}
|
||||||
|
|
||||||
|
c := color.New(color.FgCyan)
|
||||||
|
rc := color.New(color.FgMagenta)
|
||||||
|
c.Println()
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
c.Println(" * INDIHUB session finished!! *")
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
c.Println(" ")
|
||||||
|
if m.mode != lib.ModeRobotic {
|
||||||
|
for _, sAddr := range m.addrData {
|
||||||
|
rc.Printf(" %s: %s - CLOSED!!\n", sAddr.Name, sAddr.Addr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.Println(" * INDIHUB robotic-session finished. *")
|
||||||
|
c.Println(" * Thank you for your contribution! *")
|
||||||
|
}
|
||||||
|
c.Println(" ")
|
||||||
|
c.Println(" ************************************************************")
|
||||||
|
|
||||||
|
m.addrData = []proxy.PublicServerAddr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mode) GetStatus() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"status": m.status,
|
||||||
|
"publicEndpoints": m.addrData,
|
||||||
|
}
|
||||||
|
}
|
70
solo/mode.go
Normal file
70
solo/mode.go
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
package solo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/indihub-space/agent/proto/indihub"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mode struct {
|
||||||
|
indiServerAddr string
|
||||||
|
indiHubClient indihub.INDIHubClient
|
||||||
|
regInfo *indihub.RegisterInfo
|
||||||
|
ccdDrivers []string
|
||||||
|
|
||||||
|
stopCh chan struct{}
|
||||||
|
status string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMode(indiHubClient indihub.INDIHubClient, regInfo *indihub.RegisterInfo, indiServerAddr string, ccdDrivers []string) *Mode {
|
||||||
|
return &Mode{
|
||||||
|
indiServerAddr: indiServerAddr,
|
||||||
|
indiHubClient: indiHubClient,
|
||||||
|
regInfo: regInfo,
|
||||||
|
ccdDrivers: ccdDrivers,
|
||||||
|
stopCh: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Mode) Start() {
|
||||||
|
// solo mode - equipment sharing is not available but host still sends all images to INDIHUB
|
||||||
|
log.Println("'solo' parameter was provided. Your session is in solo-mode: equipment sharing is not available")
|
||||||
|
log.Println("Starting INDIHUB agent in solo mode!")
|
||||||
|
|
||||||
|
soloClient, err := s.indiHubClient.SoloMode(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not start agent in solo mode: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
soloAgent := New(
|
||||||
|
s.indiServerAddr,
|
||||||
|
soloClient,
|
||||||
|
s.ccdDrivers,
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-s.stopCh
|
||||||
|
log.Println("Closing INDIHUB solo-session")
|
||||||
|
// close connections to local INDI-server
|
||||||
|
soloAgent.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// start agent in solo-mode
|
||||||
|
go func() {
|
||||||
|
soloAgent.Start(s.regInfo.SessionID, s.regInfo.SessionIDPublic)
|
||||||
|
}()
|
||||||
|
|
||||||
|
s.status = "running"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Mode) Stop() {
|
||||||
|
s.status = "stopped"
|
||||||
|
s.stopCh <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Mode) GetStatus() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"status": s.status,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,27 @@
|
||||||
package version
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
)
|
||||||
|
|
||||||
var AgentVersion = "1.0.2"
|
var AgentVersion = "1.0.2"
|
||||||
|
|
||||||
|
func CheckAgentVersion(latestVer string) {
|
||||||
|
log.Println("Current agent version:", AgentVersion)
|
||||||
|
log.Println("Latest agent version:", latestVer)
|
||||||
|
|
||||||
|
if AgentVersion < latestVer {
|
||||||
|
yc := color.New(color.FgYellow)
|
||||||
|
yc.Println()
|
||||||
|
yc.Println(" ************************************************************")
|
||||||
|
yc.Println(" * WARNING: you version of agent is outdated! *")
|
||||||
|
yc.Println(" * *")
|
||||||
|
yc.Println(" * Please download the latest version from: *")
|
||||||
|
yc.Println(" * https://indihub.space/downloads *")
|
||||||
|
yc.Println(" * *")
|
||||||
|
yc.Println(" ************************************************************")
|
||||||
|
yc.Println(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,221 +0,0 @@
|
||||||
package websockets
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/labstack/echo"
|
|
||||||
"github.com/labstack/echo/middleware"
|
|
||||||
elog "github.com/labstack/gommon/log"
|
|
||||||
|
|
||||||
"github.com/indihub-space/agent/lib"
|
|
||||||
"github.com/indihub-space/agent/logutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
var allowedOrigins = map[string]bool{
|
|
||||||
"indihub.space": true,
|
|
||||||
"app.indihub.space": true,
|
|
||||||
"kids.indihub.space": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
type WsServer struct {
|
|
||||||
token string
|
|
||||||
indiServerAddr string
|
|
||||||
phd2ServerAddr string
|
|
||||||
wsPort uint64
|
|
||||||
isTLS bool
|
|
||||||
origins string
|
|
||||||
|
|
||||||
e *echo.Echo
|
|
||||||
upgrader websocket.Upgrader
|
|
||||||
connList []net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWsServer(token string, indiServerAddr string, phd2ServerAddr string, wsPort uint64, isTLS bool, origins string) *WsServer {
|
|
||||||
wsServer := &WsServer{
|
|
||||||
token: token,
|
|
||||||
indiServerAddr: indiServerAddr,
|
|
||||||
phd2ServerAddr: phd2ServerAddr,
|
|
||||||
wsPort: wsPort,
|
|
||||||
isTLS: isTLS,
|
|
||||||
e: echo.New(),
|
|
||||||
upgrader: websocket.Upgrader{},
|
|
||||||
connList: []net.Conn{},
|
|
||||||
}
|
|
||||||
|
|
||||||
if logutil.IsDev {
|
|
||||||
allowedOrigins["localhost"] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// add optional additional origins
|
|
||||||
for _, orig := range strings.Split(origins, ",") {
|
|
||||||
allowedOrigins[strings.TrimSpace(orig)] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow WS connections only from number of domains
|
|
||||||
wsServer.upgrader.CheckOrigin = func(r *http.Request) bool {
|
|
||||||
origin := r.Header["Origin"]
|
|
||||||
if len(origin) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
u, err := url.Parse(origin[0])
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
host, _, err := net.SplitHostPort(u.Host)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return allowedOrigins[host]
|
|
||||||
}
|
|
||||||
|
|
||||||
return wsServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WsServer) newIndiConnection(c echo.Context) error {
|
|
||||||
// upgrade to WS connection
|
|
||||||
ws, err := s.upgrader.Upgrade(c.Response(), c.Request(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ws.Close()
|
|
||||||
|
|
||||||
// open connection to INDI-Server
|
|
||||||
conn, err := net.Dial("tcp", s.indiServerAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add to connection list
|
|
||||||
s.connList = append(s.connList, conn)
|
|
||||||
|
|
||||||
// read messages from INDI-server and write them to WS
|
|
||||||
go func(indiConn net.Conn, wsConn *websocket.Conn) {
|
|
||||||
buf := make([]byte, lib.INDIServerMaxSendMsgSize, lib.INDIServerMaxSendMsgSize)
|
|
||||||
xmlFlattener := lib.NewXmlFlattener()
|
|
||||||
for {
|
|
||||||
// read from INDI-server
|
|
||||||
n, err := indiConn.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
indiConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonMessages := xmlFlattener.ConvertChunkToJSON(buf[:n])
|
|
||||||
|
|
||||||
// Write to WS
|
|
||||||
for _, m := range jsonMessages {
|
|
||||||
err = wsConn.WriteMessage(websocket.TextMessage, m)
|
|
||||||
if err != nil {
|
|
||||||
indiConn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(conn, ws)
|
|
||||||
|
|
||||||
// read messages from WS and write them to INDI-server
|
|
||||||
xmlFlattener := lib.NewXmlFlattener()
|
|
||||||
for {
|
|
||||||
// Read from WS
|
|
||||||
_, msg, err := ws.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
xmlMsg, err := xmlFlattener.ConvertJSONToXML(msg)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("could not convert json '%s' to xml: %s", string(msg), err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// write to INDI server
|
|
||||||
_, err = conn.Write(xmlMsg)
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WsServer) newPHD2Connection(c echo.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WsServer) Start() {
|
|
||||||
s.e.HideBanner = true
|
|
||||||
s.e.HidePort = true
|
|
||||||
|
|
||||||
if !logutil.IsDev {
|
|
||||||
s.e.Logger.SetLevel(elog.OFF)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.e.Use(middleware.Recover())
|
|
||||||
|
|
||||||
// set CORS in case browser decides to do pre-flight OPTIONS request
|
|
||||||
// default ones
|
|
||||||
allowOrigins := []string{}
|
|
||||||
for orig := range allowedOrigins {
|
|
||||||
allowOrigins = append(allowOrigins, "http://"+orig)
|
|
||||||
allowOrigins = append(allowOrigins, "https://"+orig)
|
|
||||||
}
|
|
||||||
// optional ones
|
|
||||||
for _, orig := range strings.Split(s.origins, ",") {
|
|
||||||
allowOrigins = append(allowOrigins, "http://"+strings.TrimSpace(orig))
|
|
||||||
allowOrigins = append(allowOrigins, "https://"+strings.TrimSpace(orig))
|
|
||||||
}
|
|
||||||
// localhost for dev-mode
|
|
||||||
if logutil.IsDev {
|
|
||||||
allowOrigins = append(allowOrigins, "http://localhost:5000")
|
|
||||||
}
|
|
||||||
s.e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
|
||||||
AllowOrigins: allowOrigins,
|
|
||||||
AllowMethods: []string{http.MethodGet},
|
|
||||||
}))
|
|
||||||
|
|
||||||
// set auth middleware
|
|
||||||
s.e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
|
|
||||||
KeyLookup: "query:token",
|
|
||||||
Validator: func(token string, eCtx echo.Context) (b bool, err error) {
|
|
||||||
return token == s.token, nil
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
s.e.GET("/indiserver", s.newIndiConnection)
|
|
||||||
s.e.GET("/phd2server", s.newPHD2Connection)
|
|
||||||
|
|
||||||
// check if we are running TLS
|
|
||||||
if s.isTLS {
|
|
||||||
// generate self-signed cert to serve WS over TLS
|
|
||||||
keyFile, certFile, err := getSelfSignedCert()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("could not start WSS server, cert generation failed:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = s.e.StartTLS(fmt.Sprintf(":%d", s.wsPort), certFile, keyFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("WSS server error:", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// run WS
|
|
||||||
err := s.e.Start(fmt.Sprintf(":%d", s.wsPort))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("WSS server error:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *WsServer) Stop() {
|
|
||||||
for _, conn := range s.connList {
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
s.e.Shutdown(context.Background())
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue