Skip to content

Commit

Permalink
Merge pull request #12 from synfinatic/discovery
Browse files Browse the repository at this point in the history
Add Alpaca Remote Disovery protocol support
  • Loading branch information
synfinatic authored Jan 16, 2021
2 parents e533609 + aad4fcc commit 1a4407a
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 31 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

## Unreleased

## v0.0.4 - 2021-01-16

Added:

- Info about viruses and how to build on Windows
- Git workflow for building & testing
- Add Linux-ARM binary for RasPi
- Add support for Alpaca discovery via: --alpaca-host auto
- Add support for choosing from between multiple telescopes via --telescope-id

Fixed:

Expand Down
16 changes: 7 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ endif
BUILDINFOSDET ?=
PROGRAM_ARGS ?=

PROJECT_VERSION := 0.0.3
PROJECT_VERSION := 0.0.4
DOCKER_REPO := synfinatic
PROJECT_NAME := alpacascope
PROJECT_TAG := $(shell git describe --tags 2>/dev/null $(git rev-list --tags --max-count=1))
Expand Down Expand Up @@ -113,38 +113,36 @@ precheck: test test-fmt test-tidy ## Run all tests that happen in a PR
# Build targets for our supported plaforms
windows: $(WINDOWS_BIN) ## Build 64bit Windows binary

GO_BUILD_DEPS: cmd/*.go alpaca/*.go telelscope/*.go .prepare

$(WINDOWS_BIN): $(GO_BUILD_DEPS)
$(WINDOWS_BIN): $(wildcard */*.go) .prepare
GOARCH=amd64 GOOS=windows go build -ldflags='$(LDFLAGS)' -o $(WINDOWS_BIN) cmd/*.go
@echo "Created: $(WINDOWS_BIN)"

windows32: $(WINDOWS32_BIN) ## Build 32bit Windows binary

$(WINDOWS32_BIN): $(GO_BUILD_DEPS)
$(WINDOWS32_BIN): $(wildcard */*.go) .prepare
GOARCH=386 GOOS=windows go build -ldflags='$(LDFLAGS)' -o $(WINDOWS32_BIN) cmd/*.go
@echo "Created: $(WINDOWS32_BIN)"

linux: $(LINUX_BIN) ## Build Linux/x86_64 binary

$(LINUX_BIN): $(GO_BUILD_DEPS)
$(LINUX_BIN): $(wildcard */*.go) .prepare
GOARCH=amd64 GOOS=linux go build -ldflags='$(LDFLAGS)' -o $(LINUX_BIN) cmd/*.go
@echo "Created: $(LINUX_BIN)"

linux-arm64: $(LINUXARM64_BIN) ## Build Linux/arm64 binary

$(LINUXARM64_BIN): $(GO_BUILD_DEPS)
$(LINUXARM64_BIN): $(wildcard */*.go) .prepare
GOARCH=arm64 GOOS=linux go build -ldflags='$(LDFLAGS)' -o $(LINUXARM64_BIN) cmd/*.go
@echo "Created: $(LINUXARM64_BIN)"

linux-arm32: $(LINUXARM32_BIN) ## Build Linux/arm64 binary

$(LINUXARM32_BIN): $(GO_BUILD_DEPS)
$(LINUXARM32_BIN): $(wildcard */*.go) .prepare
GOARCH=arm GOOS=linux go build -ldflags='$(LDFLAGS)' -o $(LINUXARM32_BIN) cmd/*.go
@echo "Created: $(LINUXARM32_BIN)"

darwin: $(DARWIN_BIN) ## Build MacOS/x86_64 binary

$(DARWIN_BIN): $(GO_BUILD_DEPS)
$(DARWIN_BIN): $(wildcard */*.go) .prepare
GOARCH=amd64 GOOS=darwin go build -ldflags='$(LDFLAGS)' -o $(DARWIN_BIN) cmd/*.go
@echo "Created: $(DARWIN_BIN)"
43 changes: 30 additions & 13 deletions alpaca/alpaca.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type Alpaca struct {
url_base string
ClientId uint32
transactionId uint32
ErrorNumber int // last error
ErrorMessage string // last error
}

func NewAlpaca(clientid uint32, ip string, port int32) *Alpaca {
Expand Down Expand Up @@ -63,6 +65,10 @@ func (a *Alpaca) GetString(device string, id uint32, api string) (string, error)
if err != nil {
return "", err
}
if resp.IsError() {
a.ErrorNumber = resp.StatusCode()
a.ErrorMessage = resp.String()
}
result := (resp.Result().(*stringResponse))
return result.Value, nil
}
Expand All @@ -84,6 +90,10 @@ func (a *Alpaca) GetStringList(device string, id uint32, api string) ([]string,
if err != nil {
return []string{""}, err
}
if resp.IsError() {
a.ErrorNumber = resp.StatusCode()
a.ErrorMessage = resp.String()
}
result := (resp.Result().(*stringlistResponse))
return result.Value, nil
}
Expand All @@ -105,6 +115,10 @@ func (a *Alpaca) GetBool(device string, id uint32, api string) (bool, error) {
if err != nil {
return false, err
}
if resp.IsError() {
a.ErrorNumber = resp.StatusCode()
a.ErrorMessage = resp.String()
}
result := (resp.Result().(*boolResponse))
return result.Value, nil
}
Expand All @@ -126,6 +140,10 @@ func (a *Alpaca) GetInt32(device string, id uint32, api string) (int32, error) {
if err != nil {
return 0, err
}
if resp.IsError() {
a.ErrorNumber = resp.StatusCode()
a.ErrorMessage = resp.String()
}
result := (resp.Result().(*int32Response))
return result.Value, nil
}
Expand All @@ -147,6 +165,10 @@ func (a *Alpaca) GetFloat64(device string, id uint32, api string) (float64, erro
if err != nil {
return 0, err
}
if resp.IsError() {
a.ErrorNumber = resp.StatusCode()
a.ErrorMessage = resp.String()
}
result := (resp.Result().(*float64Response))
return result.Value, nil
}
Expand All @@ -168,6 +190,10 @@ func (a *Alpaca) GetListUint32(device string, id uint32, api string) ([]uint32,
if err != nil {
return []uint32{}, err
}
if resp.IsError() {
a.ErrorNumber = resp.StatusCode()
a.ErrorMessage = resp.String()
}
result := (resp.Result().(*listUint32Response))
return result.Value, nil

Expand Down Expand Up @@ -219,19 +245,10 @@ func (a *Alpaca) Put(device string, id uint32, api string, form map[string]strin
return err
}

/*
// Explore response object
fmt.Println("Response Info:")
fmt.Println(" Error :", err)
fmt.Println(" Status Code:", resp.StatusCode())
fmt.Println(" Status :", resp.Status())
fmt.Println(" Proto :", resp.Proto())
fmt.Println(" Time :", resp.Time())
fmt.Println(" Received At:", resp.ReceivedAt())
fmt.Println(" Body :\n", resp)
fmt.Println()
*/

if resp.IsError() {
a.ErrorNumber = resp.StatusCode()
a.ErrorMessage = resp.String()
}
result := (resp.Result().(*putResponse))
if result.ErrorNumber != 0 {
return fmt.Errorf("%d: %s", result.ErrorNumber, result.ErrorMessage)
Expand Down
132 changes: 132 additions & 0 deletions alpaca/discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package alpaca

import (
"encoding/json"
"fmt"
"net"
"strings"
"time"

log "github.com/sirupsen/logrus"
)

const (
ALPACA_DISCOVERY_VERSION = 1
)

type AlpacaDiscoveryMessage struct {
Fixed []byte // must be 'alpacadiscovery'
Version byte
Reserved []byte
}

type AlpacaResponseMessage struct {
AlpacaPort uint16 `json:"AlpacaPort"`
}

func (a *AlpacaDiscoveryMessage) Bytes() []byte {
var buf []byte = []byte{}

buf = append(buf[:], a.Fixed[:]...)
buf = append(buf[:], a.Version)
buf = append(buf[:], a.Reserved[:]...)
return buf
}

func NewAlpacaDiscoveryMessage(version int) *AlpacaDiscoveryMessage {
a := AlpacaDiscoveryMessage{
Fixed: []byte("alpacadiscovery"),
Version: byte(fmt.Sprintf("%d", version)[0]),
Reserved: make([]byte, 48),
}
return &a
}

// checks if server is on localhost. returns IP or empty string
func IsRunningLocal(port int32) string {
log.Infof("Looking for Alpaca Remote Server locally on port %d...", port)
addrs, err := net.InterfaceAddrs()
if err != nil {
log.Errorf("Unable to determine local interface addresses: %s", err.Error())
return ""
}
log.Debugf("local addrs: %v", addrs)
for _, addr := range addrs {
ips := strings.Split(addr.String(), "/")
ip := net.ParseIP(ips[0])
if ip.To4() == nil {
continue // skip non-IPv4
}

if tryAlpaca(ip.String(), port) {
log.Infof("Found Alpaca on %s:%d", ip.String(), port)
return ip.String()
} else {
log.Debugf("Alpaca is not running on %s:%d", ip.String(), port)
}
}
log.Info("No local Alpaca Remote Servers found")
return ""
}

// Send a discovery packet to the given IP to see if it's Alpaca
func tryAlpaca(ip string, port int32) bool {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), time.Second*1)
if err != nil {
return false
}
conn.Close()
return true
}

// Discover any alpaca servers. returns IP as string and port
func DiscoverServer(tries int) (string, int32, error) {
pc, err := net.ListenPacket("udp4", ":32227")
if err != nil {
return "", 0, fmt.Errorf("Unable to open Alpaca discovery listen socket: %s", err.Error())
}
defer pc.Close()

send_addr, err := net.ResolveUDPAddr("udp4", "255.255.255.255:32227")
if err != nil {
return "", 0, fmt.Errorf("Unable to resolve Alpaca broadcast address: %s", err.Error())
}

msg := NewAlpacaDiscoveryMessage(ALPACA_DISCOVERY_VERSION)
msg_bytes := msg.Bytes()
buf := make([]byte, 1024)

for i := 0; i < tries; i++ {
_, err = pc.WriteTo(msg_bytes, send_addr)
if err != nil {
return "", 0, fmt.Errorf("Unable to send Alpaca discovery message: %s", err.Error())
}

deadline := time.Now().Add(time.Second * 1)
for true {
pc.SetReadDeadline(deadline)
err = nil
n, addr, err := pc.ReadFrom(buf)
if err != nil {
log.Warnf("Failed to discover Alpaca server: %s", err.Error())
break // don't try reading again this cycle
} else if n == 64 || string(buf) == fmt.Sprintf("alpacadiscovery%d", ALPACA_DISCOVERY_VERSION) {
log.Debug("Skipping packet we sent")
continue // this is the message we sent
}

ip := strings.Split(addr.String(), ":")
log.Debugf("receved %d bytes via discovery: %v", n, buf[:n])
var a AlpacaResponseMessage
err = json.Unmarshal(buf[:n], &a)
if err != nil {
log.Warnf("Unable to decode message: %s", err.Error())
break
} else {
log.Infof("Discovered Alpaca Server on %s:%d", ip[0], a.AlpacaPort)
return ip[0], int32(a.AlpacaPort), nil
}
}
}
return "", 0, fmt.Errorf("No reply from Alpaca Server")
}
40 changes: 31 additions & 9 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ var Buildinfos = "unknown"
var Tag = "NO-TAG"
var CommitID = "unknown"

type TeleComms int

const (
NexStar int = iota
NexStar TeleComms = iota
LX200
)

Expand All @@ -27,27 +29,33 @@ func main() {
var lip string // listen IP
var clientid uint32 // alpaca client id
var debug bool // enable debugging
var info bool // enable info
var sport int32 // Alpaca server port
var shost string // Alpaca server IP
var version bool // Version info
var _mode string // Comms mode
var mode int
var mode TeleComms
var telescopeId uint32 // Alpaca telescope id. Usually 0-10

flag.StringVar(&shost, "alpaca-host", "127.0.0.1", "FQDN or IP address of Alpaca server")
flag.StringVar(&shost, "alpaca-host", "auto", "FQDN or IP address of Alpaca server")
flag.Int32Var(&sport, "alpaca-port", 11111, "TCP port of the Alpaca server")
flag.Uint32Var(&clientid, "clientid", 1, "Alpaca ClientID used for debugging")
flag.Int32Var(&lport, "listen-port", 4030, "TCP port to listen on for clients")
flag.StringVar(&lip, "listen-ip", "0.0.0.0", "IP to listen on for clients")
flag.BoolVar(&debug, "debug", false, "Enable debug logging")
flag.BoolVar(&info, "info", false, "Enable info logging")
flag.BoolVar(&version, "version", false, "Print version and exit")
flag.StringVar(&_mode, "mode", "nexstar", "Comms mode: [nexstar|lx200]")
flag.Uint32Var(&telescopeId, "telescope-id", 0, "Alpaca Telescope ID")

flag.Parse()

// turn on debugging?
if debug == true {
log.SetReportCaller(true)
log.SetLevel(log.DebugLevel)
} else if info == true {
log.SetLevel(log.InfoLevel)
} else {
log.SetLevel(log.WarnLevel)
}
Expand All @@ -72,22 +80,34 @@ func main() {
log.Fatalf("Error listening on %s: %s", listen, err.Error())
}

if shost == "auto" {
// first look locally since we can't rely on UDP broadcast to work locally on windows
shost = alpaca.IsRunningLocal(sport)
if shost == "" {
shost, sport, err = alpaca.DiscoverServer(3)
if err != nil {
log.Fatalf("Unable to auto discover Alpaca Remote Server. Please specify --alpaca-host and --alpaca-port")
}
}
}

a := alpaca.NewAlpaca(clientid, shost, sport)
telescope := alpaca.NewTelescope(0, a)
telescope := alpaca.NewTelescope(telescopeId, a)

connected, err := telescope.GetConnected()
if err != nil {
log.Fatalf("Unable to determine status of telescope: %s", err.Error())
}

if !connected {
log.Fatalf("Unable to connect to telescope ID %d: %s", telescopeId, a.ErrorMessage)
}

name, err := telescope.GetName()
if err != nil {
log.Fatalf("Unable to determine name of telescope: %s", err.Error())
}
if connected {
log.Infof("Connected to telescope: %s", name)
log.Warnf("Unable to determine name of telescope: %s", err.Error())
} else {
log.Warnf("Not connected to telescope: %s", name)
log.Infof("Connected to telescope %d: %s", telescopeId, name)
}

actions, err := telescope.GetSupportedActions()
Expand All @@ -111,6 +131,8 @@ func main() {
}
}

fmt.Printf("Waiting for %s clients on %s:%d\n", _mode, lip, lport)

for {
conn, err := ln.Accept()
if err != nil {
Expand Down

0 comments on commit 1a4407a

Please sign in to comment.