|
@@ -0,0 +1,164 @@
|
|
|
|
|
+package mqtt
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "context"
|
|
|
|
|
+ "crypto/tls"
|
|
|
|
|
+ "errors"
|
|
|
|
|
+
|
|
|
|
|
+ paho "github.com/eclipse/paho.mqtt.golang"
|
|
|
|
|
+ "github.com/google/uuid"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+type ConnHandler interface {
|
|
|
|
|
+ ConnectionLostHandler(err error)
|
|
|
|
|
+ OnConnectHandler()
|
|
|
|
|
+ GetWill() (topic string, payload string)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Client for talking using mqtt
|
|
|
|
|
+type Client struct {
|
|
|
|
|
+ Options ClientOptions // The options that were used to create this client
|
|
|
|
|
+ client paho.Client
|
|
|
|
|
+ router *router
|
|
|
|
|
+ connHandler ConnHandler
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ClientOptions is the list of options used to create a client
|
|
|
|
|
+type ClientOptions struct {
|
|
|
|
|
+ Servers []string // The list of broker hostnames to connect to
|
|
|
|
|
+ ClientID string // If left empty a uuid will automatically be generated
|
|
|
|
|
+ Username string // If not set then authentication will not be used
|
|
|
|
|
+ Password string // Will only be used if the username is set
|
|
|
|
|
+
|
|
|
|
|
+ AutoReconnect bool // If the client should automatically try to reconnect when the connection is lost
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// QOS describes the quality of service of an mqtt publish
|
|
|
|
|
+type QOS byte
|
|
|
|
|
+
|
|
|
|
|
+const (
|
|
|
|
|
+ // AtMostOnce means the broker will deliver at most once to every subscriber - this means message delivery is not guaranteed
|
|
|
|
|
+ AtMostOnce QOS = iota
|
|
|
|
|
+ // AtLeastOnce means the broker will deliver a message at least once to every subscriber
|
|
|
|
|
+ AtLeastOnce
|
|
|
|
|
+ // ExactlyOnce means the broker will deliver a message exactly once to every subscriber
|
|
|
|
|
+ ExactlyOnce
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+var (
|
|
|
|
|
+ // ErrMinimumOneServer means that at least one server should be specified in the client options
|
|
|
|
|
+ ErrMinimumOneServer = errors.New("mqtt: at least one server needs to be specified")
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+func handle(callback MessageHandler) paho.MessageHandler {
|
|
|
|
|
+ return func(client paho.Client, message paho.Message) {
|
|
|
|
|
+ if callback != nil {
|
|
|
|
|
+ callback(Message{message: message})
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// NewClient creates a new client with the specified options
|
|
|
|
|
+func NewClient(options ClientOptions, connhandler ConnHandler) (*Client, error) {
|
|
|
|
|
+ pahoOptions := paho.NewClientOptions()
|
|
|
|
|
+
|
|
|
|
|
+ // brokers
|
|
|
|
|
+ if options.Servers != nil && len(options.Servers) > 0 {
|
|
|
|
|
+ for _, server := range options.Servers {
|
|
|
|
|
+ pahoOptions.AddBroker(server)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return nil, ErrMinimumOneServer
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // client id
|
|
|
|
|
+ if options.ClientID == "" {
|
|
|
|
|
+ options.ClientID = uuid.New().String()
|
|
|
|
|
+ }
|
|
|
|
|
+ pahoOptions.SetClientID(options.ClientID)
|
|
|
|
|
+
|
|
|
|
|
+ t := &tls.Config{
|
|
|
|
|
+ InsecureSkipVerify: true,
|
|
|
|
|
+ }
|
|
|
|
|
+ pahoOptions.SetTLSConfig(t)
|
|
|
|
|
+
|
|
|
|
|
+ // auth
|
|
|
|
|
+ if options.Username != "" {
|
|
|
|
|
+ pahoOptions.SetUsername(options.Username)
|
|
|
|
|
+ pahoOptions.SetPassword(options.Password)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // auto reconnect
|
|
|
|
|
+ pahoOptions.SetAutoReconnect(options.AutoReconnect)
|
|
|
|
|
+
|
|
|
|
|
+ pahoOptions.SetCleanSession(false)
|
|
|
|
|
+
|
|
|
|
|
+ var client Client
|
|
|
|
|
+ pahoOptions.SetConnectionLostHandler(client.ConnectionLostHandler) //断连
|
|
|
|
|
+ pahoOptions.SetOnConnectHandler(client.OnConnectHandler) //连接
|
|
|
|
|
+ if t, m := connhandler.GetWill(); t != "" {
|
|
|
|
|
+ pahoOptions.SetWill(t, m, 0, false) //遗嘱消息
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pahoClient := paho.NewClient(pahoOptions)
|
|
|
|
|
+ router := newRouter()
|
|
|
|
|
+ pahoClient.AddRoute("#", handle(func(message Message) {
|
|
|
|
|
+ routes := router.match(&message)
|
|
|
|
|
+ for _, route := range routes {
|
|
|
|
|
+ m := message
|
|
|
|
|
+ m.vars = route.vars(&message)
|
|
|
|
|
+ route.handler(m)
|
|
|
|
|
+ }
|
|
|
|
|
+ }))
|
|
|
|
|
+
|
|
|
|
|
+ client.client = pahoClient
|
|
|
|
|
+ client.Options = options
|
|
|
|
|
+ client.router = router
|
|
|
|
|
+ client.connHandler = connhandler
|
|
|
|
|
+
|
|
|
|
|
+ return &client, nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Connect tries to establish a connection with the mqtt servers
|
|
|
|
|
+func (c *Client) Connect(ctx context.Context) error {
|
|
|
|
|
+ // try to connect to the client
|
|
|
|
|
+ token := c.client.Connect()
|
|
|
|
|
+ return tokenWithContext(ctx, token)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Connect tries to establish a connection with the mqtt servers
|
|
|
|
|
+func (c *Client) IsConnected() bool {
|
|
|
|
|
+ // try to connect to the client
|
|
|
|
|
+ return c.client.IsConnected()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// DisconnectImmediately will immediately close the connection with the mqtt servers
|
|
|
|
|
+func (c *Client) DisconnectImmediately() {
|
|
|
|
|
+ c.client.Disconnect(0)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func tokenWithContext(ctx context.Context, token paho.Token) error {
|
|
|
|
|
+ completer := make(chan error)
|
|
|
|
|
+
|
|
|
|
|
+ // TODO: This go routine will not be removed up if the ctx is cancelled or a the ctx timeout passes
|
|
|
|
|
+ go func() {
|
|
|
|
|
+ token.Wait()
|
|
|
|
|
+ completer <- token.Error()
|
|
|
|
|
+ }()
|
|
|
|
|
+
|
|
|
|
|
+ for {
|
|
|
|
|
+ select {
|
|
|
|
|
+ case <-ctx.Done():
|
|
|
|
|
+ return ctx.Err()
|
|
|
|
|
+ case err := <-completer:
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+func (c *Client) ConnectionLostHandler(client paho.Client, err error) {
|
|
|
|
|
+ c.connHandler.ConnectionLostHandler(err)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+func (c *Client) OnConnectHandler(client paho.Client) {
|
|
|
|
|
+ c.connHandler.OnConnectHandler()
|
|
|
|
|
+}
|