diff --git a/.travis.yml b/.travis.yml index dcee6d549..b741f8186 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,14 @@ before_install: - sudo add-apt-repository -y ppa:kubuntu-ppa/backports - sudo add-apt-repository -y ppa:zoogie/sdl2-snapshots - sudo apt-get update - - sudo apt-get install --force-yes libcv-dev libcvaux-dev libhighgui-dev libopencv-dev libsdl2-dev libsdl2-image-dev libsdl2 libusb-dev xvfb libgtk2.0-0 + - sudo apt-get install --force-yes libcv-dev libcvaux-dev libhighgui-dev libopencv-dev libsdl2-dev libsdl2-image-dev libsdl2 libusb-dev xvfb unzip libgtk2.0-0 + - mkdir -p gnatsd + - cd gnatsd + - "wget https://github.com/nats-io/gnatsd/releases/download/v0.8.1/gnatsd-v0.8.1-linux-amd64.zip" + - unzip -j gnatsd-v0.8.1-linux-amd64.zip + - ./gnatsd -p 4222 & + - ./gnatsd -p 4223 --user test --pass testwd & + - cd $HOME/gopath/src/github.com/hybridgroup/gobot - go get github.com/axw/gocov/gocov - go get github.com/mattn/goveralls - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi diff --git a/examples/nats.go b/examples/nats.go new file mode 100644 index 000000000..3613b94aa --- /dev/null +++ b/examples/nats.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "time" + + "github.com/hybridgroup/gobot" + "github.com/hybridgroup/gobot/platforms/nats" +) + +func main() { + gbot := gobot.NewGobot() + + natsAdaptor := nats.NewNatsAdaptorWithAuth("nats", "localhost:4222", 1234) + + work := func() { + natsAdaptor.On("hello", func(data []byte) { + fmt.Println("hello") + }) + natsAdaptor.On("hola", func(data []byte) { + fmt.Println("hola") + }) + data := []byte("o") + gobot.Every(1*time.Second, func() { + natsAdaptor.Publish("hello", data) + }) + gobot.Every(5*time.Second, func() { + natsAdaptor.Publish("hola", data) + }) + } + + robot := gobot.NewRobot("natsBot", + []gobot.Connection{natsAdaptor}, + work, + ) + + gbot.AddRobot(robot) + + gbot.Start() +} diff --git a/platforms/nats/README.md b/platforms/nats/README.md new file mode 100644 index 000000000..b36a62761 --- /dev/null +++ b/platforms/nats/README.md @@ -0,0 +1,82 @@ +# NATS + +NATS is a lightweight messaging protocol perfect for your IoT/Robotics projects. It operates over TCP, offers a great number of features but an incredibly simple Pub Sub style model of communicating broadcast messages. NATS is blazingly fast as it is written in Go. + +This repository contains the Gobot adaptor/drivers to connect to NATS servers. It uses the NATS Go Client available at https://github.com/nats-io/nats. The NATS project is maintained by Nats.io and sponsored by Apcera. Find more information on setting up a NATS server and its capability at http://nats.io/. + +The NATS messaging protocol (http://www.nats.io/documentation/internals/nats-protocol-demo/) is really easy to work with and can be practiced by setting up a NATS server using Go or Docker. For information on setting up a server using the source code, visit https://github.com/nats-io/gnatsd. For information on the Docker image up on Docker Hub, see https://hub.docker.com/_/nats/. Getting the server set up is very easy. The server itself is Golang, can be built for different architectures and installs in a small footprint. This is an excellent way to get communications going between your IoT and Robotics projects. + +## How to Install + +Install running: + +``` +go get -d -u github.com/hybridgroup/gobot/... && go install github.com/hybridgroup/gobot/platforms/nats +``` + +## How to Use + +Before running the example, make sure you have an NATS server running somewhere you can connect to + +```go +package main + +import ( + "fmt" + "time" + + "github.com/hybridgroup/gobot" + "github.com/hybridgroup/gobot/platforms/nats" +) + +func main() { + gbot := gobot.NewGobot() + + natsAdaptor := nats.NewNatsAdaptor("nats", "localhost:4222", 1234) + + work := func() { + natsAdaptor.On("hello", func(data []byte) { + fmt.Println("hello") + }) + natsAdaptor.On("hola", func(data []byte) { + fmt.Println("hola") + }) + data := []byte("o") + gobot.Every(1*time.Second, func() { + natsAdaptor.Publish("hello", data) + }) + gobot.Every(5*time.Second, func() { + natsAdaptor.Publish("hola", data) + }) + } + + robot := gobot.NewRobot("natsBot", + []gobot.Connection{natsAdaptor}, + work, + ) + + gbot.AddRobot(robot) + + gbot.Start() +} +``` + +## Supported Features + +* Publish messages +* Respond to incoming message events + +## Upcoming Features + +* Support for Username/password +* Encoded messages (JSON) +* Exposing more NATS Features (tls) +* Simplified tests + +## Contributing + +For our contribution guidelines, please go to https://github.com/hybridgroup/gobot/blob/master/CONTRIBUTING.md + +## License + +Copyright (c) 2013-2016 The Hybrid Group. Licensed under the Apache 2.0 license. diff --git a/platforms/nats/doc.go b/platforms/nats/doc.go new file mode 100644 index 000000000..3d6af631e --- /dev/null +++ b/platforms/nats/doc.go @@ -0,0 +1,8 @@ +/* +Package nats provides Gobot adaptor for the nats message service. +Installing: + go get github.com/hybridgroup/gobot/platforms/nats +For further information refer to mqtt README: +https://github.com/hybridgroup/gobot/blob/master/platforms/nats/README.md +*/ +package nats diff --git a/platforms/nats/nats_adaptor.go b/platforms/nats/nats_adaptor.go new file mode 100644 index 000000000..7c48fe1f2 --- /dev/null +++ b/platforms/nats/nats_adaptor.go @@ -0,0 +1,94 @@ +package nats + +import ( + "github.com/nats-io/nats" +) + +// NatsAdaptor is a configuration struct for interacting with a nats server. +// Name is a logical name for the adaptor/nats server connection. +// Host is in the form "localhost:4222" which is the hostname/ip and port of the nats server. +// ClientID is a unique identifier integer that specifies the identity of the client. +type NatsAdaptor struct { + name string + Host string + clientID int + username string + password string + client *nats.Conn +} + +// NewNatsAdaptor populates a new NatsAdaptor. +func NewNatsAdaptor(name string, host string, clientID int) *NatsAdaptor { + return &NatsAdaptor{ + name: name, + Host: host, + clientID: clientID, + } +} + +// NewNatsAdaptorWithAuth populates a NatsAdaptor including username and password. +func NewNatsAdaptorWithAuth(name string, host string, clientID int, username string, password string) *NatsAdaptor { + return &NatsAdaptor{ + name: name, + Host: host, + clientID: clientID, + username: username, + password: password, + } +} + +// Name returns the logical client name. +func (a *NatsAdaptor) Name() string { return a.name } + +// Connect makes a connection to the Nats server. +func (a *NatsAdaptor) Connect() (errs []error) { + + auth := "" + if a.username != "" && a.password != "" { + auth = a.username + ":" + a.password + "@" + } + + defaultURL := "nats://" + auth + a.Host + + var err error + a.client, err = nats.Connect(defaultURL) + if err != nil { + return append(errs, err) + } + return +} + +// Disconnect from the nats server. Returns an error if the client doesn't exist. +func (a *NatsAdaptor) Disconnect() (err error) { + if a.client != nil { + a.client.Close() + } + return +} + +// Finalize is simply a helper method for the disconnect. +func (a *NatsAdaptor) Finalize() (errs []error) { + a.Disconnect() + return +} + +// Publish sends a message with the particular topic to the nats server. +func (a *NatsAdaptor) Publish(topic string, message []byte) bool { + if a.client == nil { + return false + } + a.client.Publish(topic, message) + return true +} + +// On is an event-handler style subscriber to a particular topic (named event). +// Supply a handler function to use the bytes returned by the server. +func (a *NatsAdaptor) On(event string, f func(s []byte)) bool { + if a.client == nil { + return false + } + a.client.Subscribe(event, func(msg *nats.Msg) { + f(msg.Data) + }) + return true +} diff --git a/platforms/nats/nats_adaptor_test.go b/platforms/nats/nats_adaptor_test.go new file mode 100644 index 000000000..4beace166 --- /dev/null +++ b/platforms/nats/nats_adaptor_test.go @@ -0,0 +1,69 @@ +package nats + +import ( + "fmt" + "testing" + + "github.com/hybridgroup/gobot" + "github.com/hybridgroup/gobot/gobottest" +) + +var _ gobot.Adaptor = (*NatsAdaptor)(nil) + +func TestNatsAdaptorReturnsName(t *testing.T) { + a := NewNatsAdaptor("Nats", "localhost:4222", 9999) + gobottest.Assert(t, a.Name(), "Nats") +} + +func TestNatsAdaptorPublishWhenConnected(t *testing.T) { + a := NewNatsAdaptor("Nats", "localhost:4222", 9999) + a.Connect() + data := []byte("o") + gobottest.Assert(t, a.Publish("test", data), true) +} + +func TestNatsAdaptorOnWhenConnected(t *testing.T) { + a := NewNatsAdaptor("Nats", "localhost:4222", 9999) + a.Connect() + gobottest.Assert(t, a.On("hola", func(data []byte) { + fmt.Println("hola") + }), true) +} + +func TestNatsAdaptorPublishWhenConnectedWithAuth(t *testing.T) { + a := NewNatsAdaptorWithAuth("Nats", "localhost:4222", 9999, "test", "testwd") + a.Connect() + data := []byte("o") + gobottest.Assert(t, a.Publish("test", data), true) +} + +func TestNatsAdaptorOnWhenConnectedWithAuth(t *testing.T) { + a := NewNatsAdaptorWithAuth("Nats", "localhost:4222", 9999, "test", "testwd") + a.Connect() + gobottest.Assert(t, a.On("hola", func(data []byte) { + fmt.Println("hola") + }), true) +} + +func TestNatsAdaptorConnect(t *testing.T) { + a := NewNatsAdaptor("Nats", "localhost:9999", 9999) + gobottest.Assert(t, a.Connect()[0].Error(), "nats: no servers available for connection") +} + +func TestNatsAdaptorFinalize(t *testing.T) { + a := NewNatsAdaptor("Nats", "localhost:9999", 9999) + gobottest.Assert(t, len(a.Finalize()), 0) +} + +func TestNatsAdaptorCannotPublishUnlessConnected(t *testing.T) { + a := NewNatsAdaptor("Nats", "localhost:9999", 9999) + data := []byte("o") + gobottest.Assert(t, a.Publish("test", data), false) +} + +func TestNatsAdaptorCannotOnUnlessConnected(t *testing.T) { + a := NewNatsAdaptor("Nats", "localhost:9999", 9999) + gobottest.Assert(t, a.On("hola", func(data []byte) { + fmt.Println("hola") + }), false) +}