2014-11-04 10:30:56 +08:00
|
|
|
package mqtt
|
|
|
|
|
2016-11-08 02:52:28 +08:00
|
|
|
import (
|
2017-01-27 02:03:32 +08:00
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2023-06-15 00:27:22 +08:00
|
|
|
"fmt"
|
2023-06-11 01:25:01 +08:00
|
|
|
"os"
|
2017-01-27 02:03:32 +08:00
|
|
|
|
2016-11-08 02:52:28 +08:00
|
|
|
paho "github.com/eclipse/paho.mqtt.golang"
|
2023-11-16 03:51:52 +08:00
|
|
|
|
|
|
|
"gobot.io/x/gobot/v2"
|
2019-01-03 08:12:38 +08:00
|
|
|
)
|
|
|
|
|
2023-10-21 02:50:42 +08:00
|
|
|
// ErrNilClient is returned when a client action can't be taken because the struct has no client
|
|
|
|
var ErrNilClient = fmt.Errorf("no MQTT client available")
|
2014-11-04 10:30:56 +08:00
|
|
|
|
2017-04-19 01:51:24 +08:00
|
|
|
// Message is a message received from the broker.
|
2017-04-19 01:49:17 +08:00
|
|
|
type Message paho.Message
|
|
|
|
|
2016-11-27 01:02:17 +08:00
|
|
|
// Adaptor is the Gobot Adaptor for MQTT
|
2016-09-26 16:13:37 +08:00
|
|
|
type Adaptor struct {
|
2017-01-26 23:21:28 +08:00
|
|
|
name string
|
|
|
|
Host string
|
|
|
|
clientID string
|
|
|
|
username string
|
|
|
|
password string
|
2017-01-27 02:14:08 +08:00
|
|
|
useSSL bool
|
2017-01-27 02:03:32 +08:00
|
|
|
serverCert string
|
|
|
|
clientCert string
|
|
|
|
clientKey string
|
2017-01-26 23:21:28 +08:00
|
|
|
autoReconnect bool
|
2017-09-24 19:45:47 +08:00
|
|
|
cleanSession bool
|
2017-01-26 23:21:28 +08:00
|
|
|
client paho.Client
|
2018-12-18 01:06:34 +08:00
|
|
|
qos int
|
2014-11-04 10:30:56 +08:00
|
|
|
}
|
|
|
|
|
2016-09-26 16:13:37 +08:00
|
|
|
// NewAdaptor creates a new mqtt adaptor with specified host and client id
|
|
|
|
func NewAdaptor(host string, clientID string) *Adaptor {
|
|
|
|
return &Adaptor{
|
2017-02-02 22:29:56 +08:00
|
|
|
name: gobot.DefaultName("MQTT"),
|
2017-01-26 23:21:28 +08:00
|
|
|
Host: host,
|
|
|
|
autoReconnect: false,
|
2017-09-24 19:45:47 +08:00
|
|
|
cleanSession: true,
|
2017-01-27 02:14:08 +08:00
|
|
|
useSSL: false,
|
2017-01-26 23:21:28 +08:00
|
|
|
clientID: clientID,
|
2014-11-04 10:56:33 +08:00
|
|
|
}
|
2014-11-04 10:30:56 +08:00
|
|
|
}
|
2016-03-23 22:58:13 +08:00
|
|
|
|
2016-11-27 01:02:17 +08:00
|
|
|
// NewAdaptorWithAuth creates a new mqtt adaptor with specified host, client id, username, and password.
|
2016-09-26 16:13:37 +08:00
|
|
|
func NewAdaptorWithAuth(host, clientID, username, password string) *Adaptor {
|
|
|
|
return &Adaptor{
|
2017-01-26 23:21:28 +08:00
|
|
|
name: "MQTT",
|
|
|
|
Host: host,
|
|
|
|
autoReconnect: false,
|
2017-09-24 19:45:47 +08:00
|
|
|
cleanSession: true,
|
2017-01-27 02:14:08 +08:00
|
|
|
useSSL: false,
|
2017-01-26 23:21:28 +08:00
|
|
|
clientID: clientID,
|
|
|
|
username: username,
|
|
|
|
password: password,
|
2016-03-23 22:58:13 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-12 23:27:08 +08:00
|
|
|
// Name returns the MQTT adaptors name
|
2016-11-27 01:02:17 +08:00
|
|
|
func (a *Adaptor) Name() string { return a.name }
|
|
|
|
|
2024-02-12 23:27:08 +08:00
|
|
|
// SetName sets the MQTT adaptors name
|
2016-09-26 16:13:37 +08:00
|
|
|
func (a *Adaptor) SetName(n string) { a.name = n }
|
2014-11-04 10:30:56 +08:00
|
|
|
|
2017-01-26 03:19:15 +08:00
|
|
|
// Port returns the Host name
|
|
|
|
func (a *Adaptor) Port() string { return a.Host }
|
|
|
|
|
2017-01-26 23:21:28 +08:00
|
|
|
// AutoReconnect returns the MQTT AutoReconnect setting
|
|
|
|
func (a *Adaptor) AutoReconnect() bool { return a.autoReconnect }
|
|
|
|
|
|
|
|
// SetAutoReconnect sets the MQTT AutoReconnect setting
|
|
|
|
func (a *Adaptor) SetAutoReconnect(val bool) { a.autoReconnect = val }
|
|
|
|
|
2017-09-24 19:45:47 +08:00
|
|
|
// CleanSession returns the MQTT CleanSession setting
|
|
|
|
func (a *Adaptor) CleanSession() bool { return a.cleanSession }
|
|
|
|
|
2023-11-16 03:51:52 +08:00
|
|
|
// SetCleanSession sets the MQTT CleanSession setting. Should be false if reconnect is enabled.
|
|
|
|
// Otherwise all subscriptions will be lost
|
2017-09-24 19:45:47 +08:00
|
|
|
func (a *Adaptor) SetCleanSession(val bool) { a.cleanSession = val }
|
|
|
|
|
2017-01-27 02:14:08 +08:00
|
|
|
// UseSSL returns the MQTT server SSL preference
|
|
|
|
func (a *Adaptor) UseSSL() bool { return a.useSSL }
|
|
|
|
|
|
|
|
// SetUseSSL sets the MQTT server SSL preference
|
|
|
|
func (a *Adaptor) SetUseSSL(val bool) { a.useSSL = val }
|
|
|
|
|
2017-01-27 02:03:32 +08:00
|
|
|
// ServerCert returns the MQTT server SSL cert file
|
|
|
|
func (a *Adaptor) ServerCert() string { return a.serverCert }
|
|
|
|
|
2018-12-18 01:06:34 +08:00
|
|
|
// SetQoS sets the QoS value passed into the MTT client on Publish/Subscribe events
|
|
|
|
func (a *Adaptor) SetQoS(qos int) { a.qos = qos }
|
|
|
|
|
2017-01-27 02:03:32 +08:00
|
|
|
// SetServerCert sets the MQTT server SSL cert file
|
|
|
|
func (a *Adaptor) SetServerCert(val string) { a.serverCert = val }
|
|
|
|
|
|
|
|
// ClientCert returns the MQTT client SSL cert file
|
|
|
|
func (a *Adaptor) ClientCert() string { return a.clientCert }
|
|
|
|
|
2018-03-14 10:55:54 +08:00
|
|
|
// SetClientCert sets the MQTT client SSL cert file
|
2017-01-27 02:03:32 +08:00
|
|
|
func (a *Adaptor) SetClientCert(val string) { a.clientCert = val }
|
|
|
|
|
|
|
|
// ClientKey returns the MQTT client SSL key file
|
|
|
|
func (a *Adaptor) ClientKey() string { return a.clientKey }
|
|
|
|
|
2017-04-17 02:41:49 +08:00
|
|
|
// SetClientKey sets the MQTT client SSL key file
|
2017-01-27 02:03:32 +08:00
|
|
|
func (a *Adaptor) SetClientKey(val string) { a.clientKey = val }
|
|
|
|
|
2014-11-04 13:34:46 +08:00
|
|
|
// Connect returns true if connection to mqtt is established
|
2023-06-13 01:51:25 +08:00
|
|
|
func (a *Adaptor) Connect() error {
|
2017-01-26 23:21:28 +08:00
|
|
|
a.client = paho.NewClient(a.createClientOptions())
|
2015-04-07 06:35:11 +08:00
|
|
|
if token := a.client.Connect(); token.Wait() && token.Error() != nil {
|
2023-06-13 01:51:25 +08:00
|
|
|
return token.Error()
|
2015-04-07 06:35:11 +08:00
|
|
|
}
|
2016-02-09 07:20:25 +08:00
|
|
|
|
2023-06-13 01:51:25 +08:00
|
|
|
return nil
|
2014-11-04 10:30:56 +08:00
|
|
|
}
|
|
|
|
|
2014-11-04 13:34:46 +08:00
|
|
|
// Disconnect returns true if connection to mqtt is closed
|
2023-06-13 01:51:25 +08:00
|
|
|
func (a *Adaptor) Disconnect() error {
|
2014-11-04 13:34:46 +08:00
|
|
|
if a.client != nil {
|
|
|
|
a.client.Disconnect(500)
|
2014-11-04 11:47:41 +08:00
|
|
|
}
|
2023-06-13 01:51:25 +08:00
|
|
|
return nil
|
2014-11-04 10:30:56 +08:00
|
|
|
}
|
|
|
|
|
2016-07-14 00:44:47 +08:00
|
|
|
// Finalize returns true if connection to mqtt is finalized successfully
|
2023-06-13 01:51:25 +08:00
|
|
|
func (a *Adaptor) Finalize() error {
|
|
|
|
return a.Disconnect()
|
2014-11-04 10:30:56 +08:00
|
|
|
}
|
|
|
|
|
2014-11-05 00:41:56 +08:00
|
|
|
// Publish a message under a specific topic
|
2016-09-26 16:13:37 +08:00
|
|
|
func (a *Adaptor) Publish(topic string, message []byte) bool {
|
2019-01-03 08:12:38 +08:00
|
|
|
_, err := a.PublishWithQOS(topic, a.qos, message)
|
2023-05-19 20:16:22 +08:00
|
|
|
return err == nil
|
2014-11-04 10:30:56 +08:00
|
|
|
}
|
|
|
|
|
2020-01-07 06:46:20 +08:00
|
|
|
// PublishAndRetain publishes a message under a specific topic with retain flag
|
|
|
|
func (a *Adaptor) PublishAndRetain(topic string, message []byte) bool {
|
|
|
|
if a.client == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
a.client.Publish(topic, byte(a.qos), true, message)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2020-04-12 08:21:15 +08:00
|
|
|
// PublishWithQOS allows per-publish QOS values to be set and returns a paho.Token
|
2019-01-03 08:12:38 +08:00
|
|
|
func (a *Adaptor) PublishWithQOS(topic string, qos int, message []byte) (paho.Token, error) {
|
2017-04-17 02:41:49 +08:00
|
|
|
if a.client == nil {
|
2019-01-03 08:12:38 +08:00
|
|
|
return nil, ErrNilClient
|
|
|
|
}
|
|
|
|
|
|
|
|
token := a.client.Publish(topic, byte(qos), false, message)
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OnWithQOS allows per-subscribe QOS values to be set and returns a paho.Token
|
|
|
|
func (a *Adaptor) OnWithQOS(event string, qos int, f func(msg Message)) (paho.Token, error) {
|
|
|
|
if a.client == nil {
|
|
|
|
return nil, ErrNilClient
|
2017-04-17 02:41:49 +08:00
|
|
|
}
|
2019-01-03 08:12:38 +08:00
|
|
|
|
|
|
|
token := a.client.Subscribe(event, byte(qos), func(client paho.Client, msg paho.Message) {
|
2017-04-19 01:49:17 +08:00
|
|
|
f(msg)
|
2017-04-17 02:41:49 +08:00
|
|
|
})
|
2019-01-03 08:12:38 +08:00
|
|
|
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// On subscribes to a topic, and then calls the message handler function when data is received
|
|
|
|
func (a *Adaptor) On(event string, f func(msg Message)) bool {
|
|
|
|
_, err := a.OnWithQOS(event, a.qos, f)
|
2023-05-19 20:16:22 +08:00
|
|
|
return err == nil
|
2017-04-17 02:41:49 +08:00
|
|
|
}
|
|
|
|
|
2017-01-26 23:21:28 +08:00
|
|
|
func (a *Adaptor) createClientOptions() *paho.ClientOptions {
|
2016-09-26 16:13:37 +08:00
|
|
|
opts := paho.NewClientOptions()
|
2017-01-26 23:21:28 +08:00
|
|
|
opts.AddBroker(a.Host)
|
|
|
|
opts.SetClientID(a.clientID)
|
|
|
|
if a.username != "" && a.password != "" {
|
|
|
|
opts.SetPassword(a.password)
|
|
|
|
opts.SetUsername(a.username)
|
2016-03-23 22:58:13 +08:00
|
|
|
}
|
2017-01-26 23:21:28 +08:00
|
|
|
opts.AutoReconnect = a.autoReconnect
|
2017-09-24 19:45:47 +08:00
|
|
|
opts.CleanSession = a.cleanSession
|
2017-01-27 02:03:32 +08:00
|
|
|
|
2017-01-27 02:14:08 +08:00
|
|
|
if a.UseSSL() {
|
2017-01-27 02:03:32 +08:00
|
|
|
opts.SetTLSConfig(a.newTLSConfig())
|
|
|
|
}
|
2014-11-04 10:56:33 +08:00
|
|
|
return opts
|
2014-11-04 10:30:56 +08:00
|
|
|
}
|
2017-01-27 02:03:32 +08:00
|
|
|
|
|
|
|
// newTLSConfig sets the TLS config in the case that we are using
|
|
|
|
// an MQTT broker with TLS
|
|
|
|
func (a *Adaptor) newTLSConfig() *tls.Config {
|
|
|
|
// Import server certificate
|
|
|
|
var certpool *x509.CertPool
|
|
|
|
if len(a.ServerCert()) > 0 {
|
|
|
|
certpool = x509.NewCertPool()
|
2023-06-11 01:25:01 +08:00
|
|
|
pemCerts, err := os.ReadFile(a.ServerCert())
|
2017-01-27 02:03:32 +08:00
|
|
|
if err == nil {
|
|
|
|
certpool.AppendCertsFromPEM(pemCerts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Import client certificate/key pair
|
|
|
|
var certs []tls.Certificate
|
|
|
|
if len(a.ClientCert()) > 0 && len(a.ClientKey()) > 0 {
|
|
|
|
cert, err := tls.LoadX509KeyPair(a.ClientCert(), a.ClientKey())
|
|
|
|
if err != nil {
|
|
|
|
// TODO: proper error handling
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
certs = append(certs, cert)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create tls.Config with desired tls properties
|
|
|
|
return &tls.Config{
|
|
|
|
// RootCAs = certs used to verify server cert.
|
|
|
|
RootCAs: certpool,
|
|
|
|
// ClientAuth = whether to request cert from server.
|
|
|
|
// Since the server is set up for SSL, this happens
|
|
|
|
// anyways.
|
|
|
|
ClientAuth: tls.NoClientCert,
|
|
|
|
// ClientCAs = certs used to validate client cert.
|
|
|
|
ClientCAs: nil,
|
|
|
|
// InsecureSkipVerify = verify that cert contents
|
|
|
|
// match server. IP matches what is in cert etc.
|
2019-05-21 20:29:06 +08:00
|
|
|
InsecureSkipVerify: false,
|
2017-01-27 02:03:32 +08:00
|
|
|
// Certificates = list of certs client sends to server.
|
|
|
|
Certificates: certs,
|
2023-10-21 02:50:42 +08:00
|
|
|
// MinVersion contains the minimum TLS version that is acceptable.
|
|
|
|
// TLS 1.2 is currently used as the minimum when acting as a client.
|
|
|
|
MinVersion: tls.VersionTLS12,
|
2017-01-27 02:03:32 +08:00
|
|
|
}
|
|
|
|
}
|