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 }