Skip to content

Commit 7fc9b2d

Browse files
committed
Make mqtt topics configurable.
1 parent 359742a commit 7fc9b2d

File tree

9 files changed

+149
-34
lines changed

9 files changed

+149
-34
lines changed

cmd/lora-gateway-bridge/cmd/configfile.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ skip_crc_check = {{ .PacketForwarder.SkipCRCCheck }}
3434
3535
# Configuration for the MQTT backend.
3636
[backend.mqtt]
37+
# MQTT topic templates for the different MQTT topics.
38+
#
39+
# The meaning of these topics are documented at:
40+
# https://docs.loraserver.io/lora-gateway-bridge/use/data/
41+
#
42+
# The default values match the default expected configuration of the
43+
# LoRa Server MQTT backend. Therefore only change these values when
44+
# absolutely needed.
45+
# Use "{{ "{{ .MAC }}" }}" as an substitution for the LoRa gateway MAC.
46+
uplink_topic_template="gateway/{{ "{{ .MAC }}" }}/rx"
47+
downlink_topic_template="gateway/{{ "{{ .MAC }}" }}/tx"
48+
stats_topic_template="gateway/{{ "{{ .MAC }}" }}/stats"
49+
ack_topic_template="gateway/{{ "{{ .MAC }}" }}/ack"
50+
3751
# MQTT server (e.g. scheme://host:port where scheme is tcp, ssl or ws)
3852
server="{{ .Backend.MQTT.Server }}"
3953

cmd/lora-gateway-bridge/cmd/root.go

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ func init() {
3737
rootCmd.PersistentFlags().Int("log-level", 4, "debug=5, info=4, error=2, fatal=1, panic=0")
3838

3939
// for backwards compatibility
40-
rootCmd.PersistentFlags().String("udp-bind", "0.0.0.0:1700", "ip:port to bind the UDP listener to")
41-
rootCmd.PersistentFlags().String("mqtt-server", "tcp://127.0.0.1:1883", "mqtt server (e.g. scheme://host:port where scheme is tcp, ssl or ws)")
42-
rootCmd.PersistentFlags().String("mqtt-username", "", "mqtt server username (optional)")
43-
rootCmd.PersistentFlags().String("mqtt-password", "", "mqtt server password (optional)")
44-
rootCmd.PersistentFlags().String("mqtt-ca-cert", "", "mqtt CA certificate file (optional)")
40+
rootCmd.PersistentFlags().String("udp-bind", "", "")
41+
rootCmd.PersistentFlags().String("mqtt-server", "", "")
42+
rootCmd.PersistentFlags().String("mqtt-username", "", "")
43+
rootCmd.PersistentFlags().String("mqtt-password", "", "")
44+
rootCmd.PersistentFlags().String("mqtt-ca-cert", "", "")
4545
rootCmd.PersistentFlags().String("mqtt-tls-cert", "", "")
4646
rootCmd.PersistentFlags().String("mqtt-tls-key", "", "")
47-
rootCmd.PersistentFlags().Bool("skip-crc-check", false, "skip the CRC status-check of received packets")
47+
rootCmd.PersistentFlags().Bool("skip-crc-check", false, "")
4848
rootCmd.PersistentFlags().MarkHidden("udp-bind")
4949
rootCmd.PersistentFlags().MarkHidden("mqtt-server")
5050
rootCmd.PersistentFlags().MarkHidden("mqtt-username")
@@ -76,6 +76,15 @@ func init() {
7676
viper.BindPFlag("backend.mqtt.tls_cert", rootCmd.PersistentFlags().Lookup("mqtt-tls-cert"))
7777
viper.BindPFlag("backend.mqtt.tls_key", rootCmd.PersistentFlags().Lookup("mqtt-tls-key"))
7878

79+
// default values
80+
viper.SetDefault("packet_forwarder.udp_bind", "0.0.0.0:1700")
81+
82+
viper.SetDefault("backend.mqtt.uplink_topic_template", "gateway/{{ .MAC }}/rx")
83+
viper.SetDefault("backend.mqtt.downlink_topic_template", "gateway/{{ .MAC }}/tx")
84+
viper.SetDefault("backend.mqtt.stats_topic_template", "gateway/{{ .MAC }}/stats")
85+
viper.SetDefault("backend.mqtt.ack_topic_template", "gateway/{{ .MAC }}/ack")
86+
viper.SetDefault("backend.mqtt.server", "tcp://127.0.0.1:1883")
87+
7988
rootCmd.AddCommand(versionCmd)
8089
rootCmd.AddCommand(configCmd)
8190
}
@@ -99,7 +108,18 @@ func run(cmd *cobra.Command, args []string) error {
99108
var pubsub *mqttpubsub.Backend
100109
for {
101110
var err error
102-
pubsub, err = mqttpubsub.NewBackend(config.C.Backend.MQTT.Server, config.C.Backend.MQTT.Username, config.C.Backend.MQTT.Password, config.C.Backend.MQTT.CACert, config.C.Backend.MQTT.TLSCert, config.C.Backend.MQTT.TLSKey)
111+
pubsub, err = mqttpubsub.NewBackend(
112+
config.C.Backend.MQTT.Server,
113+
config.C.Backend.MQTT.Username,
114+
config.C.Backend.MQTT.Password,
115+
config.C.Backend.MQTT.CACert,
116+
config.C.Backend.MQTT.TLSCert,
117+
config.C.Backend.MQTT.TLSKey,
118+
config.C.Backend.MQTT.UplinkTopicTemplate,
119+
config.C.Backend.MQTT.DownlinkTopicTemplate,
120+
config.C.Backend.MQTT.StatsTopicTemplate,
121+
config.C.Backend.MQTT.AckTopicTemplate,
122+
)
103123
if err == nil {
104124
break
105125
}

docs/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ googleAnalytics = "UA-3512995-9"
3232
weight = 4
3333

3434
[params]
35-
version = "2.3.0"
35+
version = "2.3.1"

docs/content/install/config.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,20 @@ skip_crc_check = false
9797

9898
# Configuration for the MQTT backend.
9999
[backend.mqtt]
100+
# MQTT topic templates for the different MQTT topic.
101+
#
102+
# The meaning of these topics are documented at:
103+
# https://docs.loraserver.io/lora-gateway-bridge/use/data/
104+
#
105+
# The default values match the default expected configuration of the
106+
# LoRa Server MQTT backend. Therefore only change these values when
107+
# absolutely needed.
108+
# Use "{{ .MAC }}" as an substitution for the LoRa gateway MAC.
109+
uplink_topic_template="gateway/{{ .MAC }}/rx"
110+
downlink_topic_template="gateway/{{ .MAC }}/tx"
111+
stats_topic_template="gateway/{{ .MAC }}/stats"
112+
ack_topic_template="gateway/{{ .MAC }}/ack"
113+
100114
# MQTT server (e.g. scheme://host:port where scheme is tcp, ssl or ws)
101115
server="tcp://127.0.0.1:1883"
102116

docs/content/overview/changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ menu:
88

99
## Changelog
1010

11+
### 2.3.1
12+
13+
**Improvements:**
14+
15+
* MQTT topics are now configurable through the configuration file.
16+
See [Configuration](https://docs.loraserver.io/lora-gateway-bridge/install/config/).
17+
1118
### 2.3.0
1219

1320
**Features:**

internal/backend/mqttpubsub/backend.go

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package mqttpubsub
22

33
import (
4+
"bytes"
45
"crypto/tls"
56
"crypto/x509"
67
"encoding/json"
78
"fmt"
89
"io/ioutil"
910
"sync"
11+
"text/template"
1012
"time"
1113

1214
"github.com/brocaar/loraserver/api/gw"
1315
"github.com/brocaar/lorawan"
1416
"github.com/eclipse/paho.mqtt.golang"
17+
"github.com/pkg/errors"
1518
log "github.com/sirupsen/logrus"
1619
)
1720

@@ -21,15 +24,42 @@ type Backend struct {
2124
txPacketChan chan gw.TXPacketBytes
2225
gateways map[lorawan.EUI64]struct{}
2326
mutex sync.RWMutex
27+
28+
UplinkTemplate *template.Template
29+
DownlinkTemplate *template.Template
30+
StatsTemplate *template.Template
31+
AckTemplate *template.Template
2432
}
2533

2634
// NewBackend creates a new Backend.
27-
func NewBackend(server, username, password, cafile, certFile, certKeyFile string) (*Backend, error) {
35+
func NewBackend(server, username, password, cafile, certFile, certKeyFile, uplinkTopic, downlinkTopic, statsTopic, ackTopic string) (*Backend, error) {
36+
var err error
37+
2838
b := Backend{
2939
txPacketChan: make(chan gw.TXPacketBytes),
3040
gateways: make(map[lorawan.EUI64]struct{}),
3141
}
3242

43+
b.UplinkTemplate, err = template.New("uplink").Parse(uplinkTopic)
44+
if err != nil {
45+
return nil, errors.Wrap(err, "parse uplink template error")
46+
}
47+
48+
b.DownlinkTemplate, err = template.New("downlink").Parse(downlinkTopic)
49+
if err != nil {
50+
return nil, errors.Wrap(err, "parse downlink template error")
51+
}
52+
53+
b.StatsTemplate, err = template.New("stats").Parse(statsTopic)
54+
if err != nil {
55+
return nil, errors.Wrap(err, "parse stats template error")
56+
}
57+
58+
b.AckTemplate, err = template.New("ack").Parse(ackTopic)
59+
if err != nil {
60+
return nil, errors.Wrap(err, "parse ack template error")
61+
}
62+
3363
opts := mqtt.NewClientOptions()
3464
opts.AddBroker(server)
3565
opts.SetUsername(username)
@@ -114,9 +144,13 @@ func (b *Backend) SubscribeGatewayTX(mac lorawan.EUI64) error {
114144
defer b.mutex.Unlock()
115145
b.mutex.Lock()
116146

117-
topic := fmt.Sprintf("gateway/%s/tx", mac.String())
118-
log.WithField("topic", topic).Info("backend: subscribing to topic")
119-
if token := b.conn.Subscribe(topic, 0, b.txPacketHandler); token.Wait() && token.Error() != nil {
147+
topic := bytes.NewBuffer(nil)
148+
if err := b.DownlinkTemplate.Execute(topic, struct{ MAC lorawan.EUI64 }{mac}); err != nil {
149+
return errors.Wrap(err, "execute uplink template error")
150+
}
151+
152+
log.WithField("topic", topic.String()).Info("backend: subscribing to topic")
153+
if token := b.conn.Subscribe(topic.String(), 0, b.txPacketHandler); token.Wait() && token.Error() != nil {
120154
return token.Error()
121155
}
122156
b.gateways[mac] = struct{}{}
@@ -129,9 +163,13 @@ func (b *Backend) UnSubscribeGatewayTX(mac lorawan.EUI64) error {
129163
defer b.mutex.Unlock()
130164
b.mutex.Lock()
131165

132-
topic := fmt.Sprintf("gateway/%s/tx", mac.String())
133-
log.WithField("topic", topic).Info("backend: unsubscribing from topic")
134-
if token := b.conn.Unsubscribe(topic); token.Wait() && token.Error() != nil {
166+
topic := bytes.NewBuffer(nil)
167+
if err := b.DownlinkTemplate.Execute(topic, struct{ MAC lorawan.EUI64 }{mac}); err != nil {
168+
return errors.Wrap(err, "execute uplink template error")
169+
}
170+
171+
log.WithField("topic", topic.String()).Info("backend: unsubscribing from topic")
172+
if token := b.conn.Unsubscribe(topic.String()); token.Wait() && token.Error() != nil {
135173
return token.Error()
136174
}
137175
delete(b.gateways, mac)
@@ -140,29 +178,31 @@ func (b *Backend) UnSubscribeGatewayTX(mac lorawan.EUI64) error {
140178

141179
// PublishGatewayRX publishes a RX packet to the MQTT broker.
142180
func (b *Backend) PublishGatewayRX(mac lorawan.EUI64, rxPacket gw.RXPacketBytes) error {
143-
topic := fmt.Sprintf("gateway/%s/rx", mac.String())
144-
return b.publish(topic, rxPacket)
181+
return b.publish(mac, b.UplinkTemplate, rxPacket)
145182
}
146183

147184
// PublishGatewayStats publishes a GatewayStatsPacket to the MQTT broker.
148185
func (b *Backend) PublishGatewayStats(mac lorawan.EUI64, stats gw.GatewayStatsPacket) error {
149-
topic := fmt.Sprintf("gateway/%s/stats", mac.String())
150-
return b.publish(topic, stats)
186+
return b.publish(mac, b.StatsTemplate, stats)
151187
}
152188

153189
// PublishGatewayTXAck publishes a TX ack to the MQTT broker.
154190
func (b *Backend) PublishGatewayTXAck(mac lorawan.EUI64, ack gw.TXAck) error {
155-
topic := fmt.Sprintf("gateway/%s/ack", mac.String())
156-
return b.publish(topic, ack)
191+
return b.publish(mac, b.AckTemplate, ack)
157192
}
158193

159-
func (b *Backend) publish(topic string, v interface{}) error {
194+
func (b *Backend) publish(mac lorawan.EUI64, topicTemplate *template.Template, v interface{}) error {
195+
topic := bytes.NewBuffer(nil)
196+
if err := topicTemplate.Execute(topic, struct{ MAC lorawan.EUI64 }{mac}); err != nil {
197+
return errors.Wrap(err, "execute template error")
198+
}
199+
160200
bytes, err := json.Marshal(v)
161201
if err != nil {
162202
return err
163203
}
164-
log.WithField("topic", topic).Info("backend: publishing packet")
165-
if token := b.conn.Publish(topic, 0, false, bytes); token.Wait() && token.Error() != nil {
204+
log.WithField("topic", topic.String()).Info("backend: publishing packet")
205+
if token := b.conn.Publish(topic.String(), 0, false, bytes); token.Wait() && token.Error() != nil {
166206
return token.Error()
167207
}
168208
return nil

internal/backend/mqttpubsub/backend_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66
"time"
77

8+
"github.com/brocaar/lora-gateway-bridge/internal/config"
89
"github.com/brocaar/loraserver/api/gw"
910
"github.com/eclipse/paho.mqtt.golang"
1011
. "github.com/smartystreets/goconvey/convey"
@@ -22,7 +23,16 @@ func TestBackend(t *testing.T) {
2223
defer c.Disconnect(0)
2324

2425
Convey("Given a new Backend", func() {
25-
backend, err := NewBackend(conf.Server, conf.Username, conf.Password, "", "", "")
26+
backend, err := NewBackend(
27+
conf.Server,
28+
conf.Username,
29+
conf.Password,
30+
"", "", "",
31+
config.C.Backend.MQTT.UplinkTopicTemplate,
32+
config.C.Backend.MQTT.DownlinkTopicTemplate,
33+
config.C.Backend.MQTT.StatsTopicTemplate,
34+
config.C.Backend.MQTT.AckTopicTemplate,
35+
)
2636
So(err, ShouldBeNil)
2737
defer backend.Close()
2838

internal/backend/mqttpubsub/base_test.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,27 @@ package mqttpubsub
33
import (
44
"os"
55

6+
"github.com/brocaar/lora-gateway-bridge/internal/config"
67
log "github.com/sirupsen/logrus"
78
)
89

910
func init() {
1011
log.SetLevel(log.ErrorLevel)
1112
}
1213

13-
type config struct {
14+
type conf struct {
1415
Server string
1516
Username string
1617
Password string
1718
}
1819

19-
func getConfig() *config {
20-
c := &config{
20+
func getConfig() *conf {
21+
config.C.Backend.MQTT.DownlinkTopicTemplate = "gateway/{{ .MAC }}/tx"
22+
config.C.Backend.MQTT.UplinkTopicTemplate = "gateway/{{ .MAC }}/rx"
23+
config.C.Backend.MQTT.StatsTopicTemplate = "gateway/{{ .MAC }}/stats"
24+
config.C.Backend.MQTT.AckTopicTemplate = "gateway/{{ .MAC }}/ack"
25+
26+
c := &conf{
2127
Server: "tcp://127.0.0.1:1883",
2228
}
2329

internal/config/config.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ type Config struct {
1313

1414
Backend struct {
1515
MQTT struct {
16-
Server string
17-
Username string
18-
Password string
19-
CACert string `mapstructure:"ca_cert"`
20-
TLSCert string `mapstructure:"tls_cert"`
21-
TLSKey string `mapstructure:"tls_key"`
16+
Server string
17+
Username string
18+
Password string
19+
CACert string `mapstructure:"ca_cert"`
20+
TLSCert string `mapstructure:"tls_cert"`
21+
TLSKey string `mapstructure:"tls_key"`
22+
UplinkTopicTemplate string `mapstructure:"uplink_topic_template"`
23+
DownlinkTopicTemplate string `mapstructure:"downlink_topic_template"`
24+
StatsTopicTemplate string `mapstructure:"stats_topic_template"`
25+
AckTopicTemplate string `mapstructure:"ack_topic_template"`
2226
}
2327
}
2428
}

0 commit comments

Comments
 (0)