gobot.io/x/gobot/v2@v2.1.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/v2"
     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  	return err == nil
   142  }
   143  
   144  // PublishAndRetain publishes a message under a specific topic with retain flag
   145  func (a *Adaptor) PublishAndRetain(topic string, message []byte) bool {
   146  	if a.client == nil {
   147  		return false
   148  	}
   149  
   150  	a.client.Publish(topic, byte(a.qos), true, message)
   151  	return true
   152  }
   153  
   154  // PublishWithQOS allows per-publish QOS values to be set and returns a paho.Token
   155  func (a *Adaptor) PublishWithQOS(topic string, qos int, message []byte) (paho.Token, error) {
   156  	if a.client == nil {
   157  		return nil, ErrNilClient
   158  	}
   159  
   160  	token := a.client.Publish(topic, byte(qos), false, message)
   161  	return token, nil
   162  }
   163  
   164  // OnWithQOS allows per-subscribe QOS values to be set and returns a paho.Token
   165  func (a *Adaptor) OnWithQOS(event string, qos int, f func(msg Message)) (paho.Token, error) {
   166  	if a.client == nil {
   167  		return nil, ErrNilClient
   168  	}
   169  
   170  	token := a.client.Subscribe(event, byte(qos), func(client paho.Client, msg paho.Message) {
   171  		f(msg)
   172  	})
   173  
   174  	return token, nil
   175  }
   176  
   177  // On subscribes to a topic, and then calls the message handler function when data is received
   178  func (a *Adaptor) On(event string, f func(msg Message)) bool {
   179  	_, err := a.OnWithQOS(event, a.qos, f)
   180  	return err == nil
   181  }
   182  
   183  func (a *Adaptor) createClientOptions() *paho.ClientOptions {
   184  	opts := paho.NewClientOptions()
   185  	opts.AddBroker(a.Host)
   186  	opts.SetClientID(a.clientID)
   187  	if a.username != "" && a.password != "" {
   188  		opts.SetPassword(a.password)
   189  		opts.SetUsername(a.username)
   190  	}
   191  	opts.AutoReconnect = a.autoReconnect
   192  	opts.CleanSession = a.cleanSession
   193  
   194  	if a.UseSSL() {
   195  		opts.SetTLSConfig(a.newTLSConfig())
   196  	}
   197  	return opts
   198  }
   199  
   200  // newTLSConfig sets the TLS config in the case that we are using
   201  // an MQTT broker with TLS
   202  func (a *Adaptor) newTLSConfig() *tls.Config {
   203  	// Import server certificate
   204  	var certpool *x509.CertPool
   205  	if len(a.ServerCert()) > 0 {
   206  		certpool = x509.NewCertPool()
   207  		pemCerts, err := ioutil.ReadFile(a.ServerCert())
   208  		if err == nil {
   209  			certpool.AppendCertsFromPEM(pemCerts)
   210  		}
   211  	}
   212  
   213  	// Import client certificate/key pair
   214  	var certs []tls.Certificate
   215  	if len(a.ClientCert()) > 0 && len(a.ClientKey()) > 0 {
   216  		cert, err := tls.LoadX509KeyPair(a.ClientCert(), a.ClientKey())
   217  		if err != nil {
   218  			// TODO: proper error handling
   219  			panic(err)
   220  		}
   221  		certs = append(certs, cert)
   222  	}
   223  
   224  	// Create tls.Config with desired tls properties
   225  	return &tls.Config{
   226  		// RootCAs = certs used to verify server cert.
   227  		RootCAs: certpool,
   228  		// ClientAuth = whether to request cert from server.
   229  		// Since the server is set up for SSL, this happens
   230  		// anyways.
   231  		ClientAuth: tls.NoClientCert,
   232  		// ClientCAs = certs used to validate client cert.
   233  		ClientCAs: nil,
   234  		// InsecureSkipVerify = verify that cert contents
   235  		// match server. IP matches what is in cert etc.
   236  		InsecureSkipVerify: false,
   237  		// Certificates = list of certs client sends to server.
   238  		Certificates: certs,
   239  	}
   240  }