gobot.io/x/gobot@v1.16.0/platforms/mqtt/mqtt_adaptor.go (about)

     1  package mqtt
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"io/ioutil"
     7  
     8  	"gobot.io/x/gobot"
     9  
    10  	paho "github.com/eclipse/paho.mqtt.golang"
    11  	multierror "github.com/hashicorp/go-multierror"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  var (
    16  	// ErrNilClient is returned when a client action can't be taken because the struct has no client
    17  	ErrNilClient = errors.New("no MQTT client available")
    18  )
    19  
    20  // Message is a message received from the broker.
    21  type Message paho.Message
    22  
    23  // Adaptor is the Gobot Adaptor for MQTT
    24  type Adaptor struct {
    25  	name          string
    26  	Host          string
    27  	clientID      string
    28  	username      string
    29  	password      string
    30  	useSSL        bool
    31  	serverCert    string
    32  	clientCert    string
    33  	clientKey     string
    34  	autoReconnect bool
    35  	cleanSession  bool
    36  	client        paho.Client
    37  	qos           int
    38  }
    39  
    40  // NewAdaptor creates a new mqtt adaptor with specified host and client id
    41  func NewAdaptor(host string, clientID string) *Adaptor {
    42  	return &Adaptor{
    43  		name:          gobot.DefaultName("MQTT"),
    44  		Host:          host,
    45  		autoReconnect: false,
    46  		cleanSession:  true,
    47  		useSSL:        false,
    48  		clientID:      clientID,
    49  	}
    50  }
    51  
    52  // NewAdaptorWithAuth creates a new mqtt adaptor with specified host, client id, username, and password.
    53  func NewAdaptorWithAuth(host, clientID, username, password string) *Adaptor {
    54  	return &Adaptor{
    55  		name:          "MQTT",
    56  		Host:          host,
    57  		autoReconnect: false,
    58  		cleanSession:  true,
    59  		useSSL:        false,
    60  		clientID:      clientID,
    61  		username:      username,
    62  		password:      password,
    63  	}
    64  }
    65  
    66  // Name returns the MQTT Adaptor's name
    67  func (a *Adaptor) Name() string { return a.name }
    68  
    69  // SetName sets the MQTT Adaptor's name
    70  func (a *Adaptor) SetName(n string) { a.name = n }
    71  
    72  // Port returns the Host name
    73  func (a *Adaptor) Port() string { return a.Host }
    74  
    75  // AutoReconnect returns the MQTT AutoReconnect setting
    76  func (a *Adaptor) AutoReconnect() bool { return a.autoReconnect }
    77  
    78  // SetAutoReconnect sets the MQTT AutoReconnect setting
    79  func (a *Adaptor) SetAutoReconnect(val bool) { a.autoReconnect = val }
    80  
    81  // CleanSession returns the MQTT CleanSession setting
    82  func (a *Adaptor) CleanSession() bool { return a.cleanSession }
    83  
    84  // SetCleanSession sets the MQTT CleanSession setting. Should be false if reconnect is enabled. Otherwise all subscriptions will be lost
    85  func (a *Adaptor) SetCleanSession(val bool) { a.cleanSession = val }
    86  
    87  // UseSSL returns the MQTT server SSL preference
    88  func (a *Adaptor) UseSSL() bool { return a.useSSL }
    89  
    90  // SetUseSSL sets the MQTT server SSL preference
    91  func (a *Adaptor) SetUseSSL(val bool) { a.useSSL = val }
    92  
    93  // ServerCert returns the MQTT server SSL cert file
    94  func (a *Adaptor) ServerCert() string { return a.serverCert }
    95  
    96  // SetQoS sets the QoS value passed into the MTT client on Publish/Subscribe events
    97  func (a *Adaptor) SetQoS(qos int) { a.qos = qos }
    98  
    99  // SetServerCert sets the MQTT server SSL cert file
   100  func (a *Adaptor) SetServerCert(val string) { a.serverCert = val }
   101  
   102  // ClientCert returns the MQTT client SSL cert file
   103  func (a *Adaptor) ClientCert() string { return a.clientCert }
   104  
   105  // SetClientCert sets the MQTT client SSL cert file
   106  func (a *Adaptor) SetClientCert(val string) { a.clientCert = val }
   107  
   108  // ClientKey returns the MQTT client SSL key file
   109  func (a *Adaptor) ClientKey() string { return a.clientKey }
   110  
   111  // SetClientKey sets the MQTT client SSL key file
   112  func (a *Adaptor) SetClientKey(val string) { a.clientKey = val }
   113  
   114  // Connect returns true if connection to mqtt is established
   115  func (a *Adaptor) Connect() (err error) {
   116  	a.client = paho.NewClient(a.createClientOptions())
   117  	if token := a.client.Connect(); token.Wait() && token.Error() != nil {
   118  		err = multierror.Append(err, token.Error())
   119  	}
   120  
   121  	return
   122  }
   123  
   124  // Disconnect returns true if connection to mqtt is closed
   125  func (a *Adaptor) Disconnect() (err error) {
   126  	if a.client != nil {
   127  		a.client.Disconnect(500)
   128  	}
   129  	return
   130  }
   131  
   132  // Finalize returns true if connection to mqtt is finalized successfully
   133  func (a *Adaptor) Finalize() (err error) {
   134  	a.Disconnect()
   135  	return
   136  }
   137  
   138  // Publish a message under a specific topic
   139  func (a *Adaptor) Publish(topic string, message []byte) bool {
   140  	_, err := a.PublishWithQOS(topic, a.qos, message)
   141  	if err != nil {
   142  		return false
   143  	}
   144  
   145  	return true
   146  }
   147  
   148  // PublishAndRetain publishes a message under a specific topic with retain flag
   149  func (a *Adaptor) PublishAndRetain(topic string, message []byte) bool {
   150  	if a.client == nil {
   151  		return false
   152  	}
   153  
   154  	a.client.Publish(topic, byte(a.qos), true, message)
   155  	return true
   156  }
   157  
   158  // PublishWithQOS allows per-publish QOS values to be set and returns a paho.Token
   159  func (a *Adaptor) PublishWithQOS(topic string, qos int, message []byte) (paho.Token, error) {
   160  	if a.client == nil {
   161  		return nil, ErrNilClient
   162  	}
   163  
   164  	token := a.client.Publish(topic, byte(qos), false, message)
   165  	return token, nil
   166  }
   167  
   168  // OnWithQOS allows per-subscribe QOS values to be set and returns a paho.Token
   169  func (a *Adaptor) OnWithQOS(event string, qos int, f func(msg Message)) (paho.Token, error) {
   170  	if a.client == nil {
   171  		return nil, ErrNilClient
   172  	}
   173  
   174  	token := a.client.Subscribe(event, byte(qos), func(client paho.Client, msg paho.Message) {
   175  		f(msg)
   176  	})
   177  
   178  	return token, nil
   179  }
   180  
   181  // On subscribes to a topic, and then calls the message handler function when data is received
   182  func (a *Adaptor) On(event string, f func(msg Message)) bool {
   183  	_, err := a.OnWithQOS(event, a.qos, f)
   184  	if err != nil {
   185  		return false
   186  	}
   187  	return true
   188  }
   189  
   190  func (a *Adaptor) createClientOptions() *paho.ClientOptions {
   191  	opts := paho.NewClientOptions()
   192  	opts.AddBroker(a.Host)
   193  	opts.SetClientID(a.clientID)
   194  	if a.username != "" && a.password != "" {
   195  		opts.SetPassword(a.password)
   196  		opts.SetUsername(a.username)
   197  	}
   198  	opts.AutoReconnect = a.autoReconnect
   199  	opts.CleanSession = a.cleanSession
   200  
   201  	if a.UseSSL() {
   202  		opts.SetTLSConfig(a.newTLSConfig())
   203  	}
   204  	return opts
   205  }
   206  
   207  // newTLSConfig sets the TLS config in the case that we are using
   208  // an MQTT broker with TLS
   209  func (a *Adaptor) newTLSConfig() *tls.Config {
   210  	// Import server certificate
   211  	var certpool *x509.CertPool
   212  	if len(a.ServerCert()) > 0 {
   213  		certpool = x509.NewCertPool()
   214  		pemCerts, err := ioutil.ReadFile(a.ServerCert())
   215  		if err == nil {
   216  			certpool.AppendCertsFromPEM(pemCerts)
   217  		}
   218  	}
   219  
   220  	// Import client certificate/key pair
   221  	var certs []tls.Certificate
   222  	if len(a.ClientCert()) > 0 && len(a.ClientKey()) > 0 {
   223  		cert, err := tls.LoadX509KeyPair(a.ClientCert(), a.ClientKey())
   224  		if err != nil {
   225  			// TODO: proper error handling
   226  			panic(err)
   227  		}
   228  		certs = append(certs, cert)
   229  	}
   230  
   231  	// Create tls.Config with desired tls properties
   232  	return &tls.Config{
   233  		// RootCAs = certs used to verify server cert.
   234  		RootCAs: certpool,
   235  		// ClientAuth = whether to request cert from server.
   236  		// Since the server is set up for SSL, this happens
   237  		// anyways.
   238  		ClientAuth: tls.NoClientCert,
   239  		// ClientCAs = certs used to validate client cert.
   240  		ClientCAs: nil,
   241  		// InsecureSkipVerify = verify that cert contents
   242  		// match server. IP matches what is in cert etc.
   243  		InsecureSkipVerify: false,
   244  		// Certificates = list of certs client sends to server.
   245  		Certificates: certs,
   246  	}
   247  }