Skip to content

Commit

Permalink
feat: add discover
Browse files Browse the repository at this point in the history
  • Loading branch information
MoonLiightz committed Mar 26, 2020
1 parent fd0ff10 commit d9bfc3e
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 3 deletions.
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func main() {
Commands: []*cli.Command{
appcli.Command.Color,
appcli.Command.State,
appcli.Command.Discover,
},
}

Expand Down
46 changes: 44 additions & 2 deletions internal/appcli/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import (
"fmt"
"net"
"strings"
"time"

"github.com/moonliightz/magic-home/internal/util"
magichome "github.com/moonliightz/magic-home/pkg"
"github.com/urfave/cli/v2"
)

type command struct {
Color *cli.Command
State *cli.Command
Color *cli.Command
State *cli.Command
Discover *cli.Command
}

// Command are the commands for the CLI
Expand Down Expand Up @@ -96,6 +98,46 @@ var Command = command{
cli.ShowCommandHelpAndExit(c, "state", 1)
}

return nil
},
},
Discover: &cli.Command{
Name: "discover",
Aliases: []string{"d"},
Usage: "Discover for Magic Home devices on the network",
ArgsUsage: "",
Flags: []cli.Flag{Flag.BroadcastAddr, Flag.Timeout},
Action: func(c *cli.Context) error {
fmt.Print("Discovering")
go func() {
for {
fmt.Print(".")
time.Sleep(100 * time.Millisecond)
}
}()

devices, err := magichome.Discover(magichome.DiscoverOptions{
BroadcastAddr: c.String("broadcastaddr"),
Timeout: uint8(c.Int("timeout")),
})
if err != nil {
return err
}

if len(*devices) >= 1 {
fmt.Println()
fmt.Println("Discovered the following devices:")
fmt.Println()
fmt.Println("Address \t| ID \t| Model")
fmt.Println("---------------------------------------")
for _, device := range *devices {
fmt.Printf("%s\t| %s\t| %s\n", device.IP, device.ID, device.Model)
}
} else {
fmt.Println()
fmt.Println("No devices discovered.")
}

return nil
},
},
Expand Down
20 changes: 19 additions & 1 deletion internal/appcli/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package appcli
import "github.com/urfave/cli/v2"

type flag struct {
Port *cli.IntFlag
Port *cli.IntFlag
BroadcastAddr *cli.StringFlag
Timeout *cli.IntFlag
}

// Flag contains the options for the CLI
Expand All @@ -16,4 +18,20 @@ var Flag = flag{
Value: 5577,
Required: false,
},
BroadcastAddr: &cli.StringFlag{
Name: "broadcastaddr",
Aliases: []string{"b"},
Usage: "broadcast address of the netwerk",
DefaultText: "255.255.255.255",
Value: "255.255.255.255",
Required: false,
},
Timeout: &cli.IntFlag{
Name: "timeout",
Aliases: []string{"t"},
Usage: "discover search timeout",
DefaultText: "1 second",
Value: 1,
Required: false,
},
}
109 changes: 109 additions & 0 deletions pkg/discover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package magichome

import (
"fmt"
"net"
"strings"
"time"
)

// Device represents a Magic Home device that answered the udp broadcast
type Device struct {
IP net.IP
ID string
Model string
}

// DiscoverOptions are used to configure the discovering
type DiscoverOptions struct {
BroadcastAddr string
Timeout uint8
}

// Discover searches for Magic Home devices on the network
func Discover(options DiscoverOptions) (*[]Device, error) {
if options.BroadcastAddr == "" {
options.BroadcastAddr = "255.255.255.255"
}

broadcastAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:48899", options.BroadcastAddr))
if err != nil {
return nil, err
}

localAddr, err := net.ResolveUDPAddr("udp4", ":0")
if err != nil {
return nil, err
}

conn, err := net.ListenUDP("udp4", localAddr)
if err != nil {
return nil, err
}
defer conn.Close()

receivedDevice := make(chan *Device)
done := make(chan bool, 1)
var devices []Device

go receive(conn, receivedDevice)
go send(conn, broadcastAddr)
go timeout(options.Timeout, done)

for {
select {
case device, ok := <-receivedDevice:
if ok {
devices = append(devices, *device)
}
case <-done:
done = nil
}

if done == nil {
break
}
}

close(receivedDevice)

return &devices, nil
}

func send(conn *net.UDPConn, broadcastAddr *net.UDPAddr) {
_, err := conn.WriteToUDP([]byte("HF-A11ASSISTHREAD"), broadcastAddr)
if err != nil {
fmt.Println("Error: ", err)
}
}

func receive(conn *net.UDPConn, receivedDevice chan<- *Device) {
for {
buf := make([]byte, 1024)
n, _, err := conn.ReadFromUDP(buf)
if err != nil {
break
}

data := strings.Split(string(buf[:n]), ",")
if len(data) == 3 {
receivedDevice <- &Device{
IP: net.ParseIP(data[0]),
ID: data[1],
Model: data[2],
}
}
}
}

func timeout(seconds uint8, done chan<- bool) {
if seconds < 1 {
seconds = 1
} else if seconds > 30 {
seconds = 30
}

time.Sleep(time.Duration(seconds) * time.Second)

done <- true
}

0 comments on commit d9bfc3e

Please sign in to comment.