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 }