Skip to content

Commit 08dc3c9

Browse files
committed
Publish ConnState message on gateway (un)subscribe.
This introduces a new topic hierarchy gateway/ID/state/STATE for publishing state messages. State messages are published with the retained flag. The ConnState message is published to gateway/ID/state/conn and contains the state ONLINE or OFFLINE.
1 parent 5f22f6b commit 08dc3c9

File tree

15 files changed

+342
-19
lines changed

15 files changed

+342
-19
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ marshaler="{{ .Integration.Marshaler }}"
221221
# Event topic template.
222222
event_topic_template="{{ .Integration.MQTT.EventTopicTemplate }}"
223223
224+
# State topic template.
225+
#
226+
# States are sent by the gateway as retained MQTT messages so that the last
227+
# message will be stored by the MQTT broker. When set to a blank string, this
228+
# feature will be disabled. This feature is only supported when using the
229+
# generic authentication type.
230+
state_topic_template="{{ .Integration.MQTT.StateTopicTemplate }}"
231+
224232
# Command topic template.
225233
command_topic_template="{{ .Integration.MQTT.CommandTopicTemplate }}"
226234

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ func init() {
5757
viper.SetDefault("integration.mqtt.auth.type", "generic")
5858

5959
viper.SetDefault("integration.mqtt.event_topic_template", "gateway/{{ .GatewayID }}/event/{{ .EventType }}")
60+
viper.SetDefault("integration.mqtt.state_topic_template", "gateway/{{ .GatewayID }}/state/{{ .StateType }}")
6061
viper.SetDefault("integration.mqtt.command_topic_template", "gateway/{{ .GatewayID }}/command/#")
6162
viper.SetDefault("integration.mqtt.keep_alive", 30*time.Second)
6263
viper.SetDefault("integration.mqtt.max_reconnect_interval", time.Minute)

cmd/chirpstack-gateway-bridge/cmd/root_run.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ func run(cmd *cobra.Command, args []string) error {
4747
log.WithField("signal", <-sigChan).Info("signal received")
4848
log.Warning("shutting down server")
4949

50+
integration.GetIntegration().Stop()
51+
5052
return nil
5153
}
5254

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/brocaar/chirpstack-gateway-bridge
33
go 1.16
44

55
require (
6-
github.com/brocaar/chirpstack-api/go/v3 v3.8.1
6+
github.com/brocaar/chirpstack-api/go/v3 v3.9.7
77
github.com/brocaar/lorawan v0.0.0-20201030140234-f23da2d4a303
88
github.com/dgrijalva/jwt-go v3.2.0+incompatible
99
github.com/eclipse/paho.mqtt.golang v1.3.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
5151
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
5252
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
5353
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
54-
github.com/brocaar/chirpstack-api/go/v3 v3.8.1 h1:8xNpG/GZqiL8XYkkAUWYNZJu7yn5SamK6oPBx1hCQe0=
55-
github.com/brocaar/chirpstack-api/go/v3 v3.8.1/go.mod h1:ex/wqXQaClwDMa2zDN6crp9ZiMGc1GMVQhjxiB+OJcg=
54+
github.com/brocaar/chirpstack-api/go/v3 v3.9.7 h1:n5Zte6zIg+qbqtb4dwp3vGQwIXpXsk5nMR4WwMUcLgA=
55+
github.com/brocaar/chirpstack-api/go/v3 v3.9.7/go.mod h1:v8AWP19nOJK4rwJsr1+weDfpUc4UNLbRh8Eygn4Oh00=
5656
github.com/brocaar/lorawan v0.0.0-20201030140234-f23da2d4a303 h1:LkE19tFPfDaRh1HIKWLCZKSBZNonMu0rIOJPCLvEjC0=
5757
github.com/brocaar/lorawan v0.0.0-20201030140234-f23da2d4a303/go.mod h1:CciUmQHIpUYTHHMeICtyamM7d+47VV+WBZ5ReDozpoc=
5858
github.com/caarlos0/ctrlc v1.0.0 h1:2DtF8GSIcajgffDFJzyG15vO+1PuBWOMUdFut7NnXhw=

internal/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type Config struct {
5858
MQTT struct {
5959
EventTopicTemplate string `mapstructure:"event_topic_template"`
6060
CommandTopicTemplate string `mapstructure:"command_topic_template"`
61+
StateTopicTemplate string `mapstructure:"state_topic_template"`
6162
KeepAlive time.Duration `mapstructure:"keep_alive"`
6263
MaxReconnectInterval time.Duration `mapstructure:"max_reconnect_interval"`
6364
TerminateOnConnectError bool `mapstructure:"terminate_on_connect_error"`

internal/integration/integration.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type Integration interface {
4747
// PublishEvent publishes the given event.
4848
PublishEvent(lorawan.EUI64, string, uuid.UUID, proto.Message) error
4949

50+
// PublishState publishes the given state as retained message.
51+
PublishState(lorawan.EUI64, string, proto.Message) error
52+
5053
// SetDownlinkFrameFunc sets the DownlinkFrame handler func.
5154
SetDownlinkFrameFunc(func(gw.DownlinkFrame))
5255

internal/integration/mqtt/auth/auth.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ import (
88

99
mqtt "github.com/eclipse/paho.mqtt.golang"
1010
"github.com/pkg/errors"
11+
12+
"github.com/brocaar/lorawan"
1113
)
1214

1315
// Authentication defines the authentication interface.
1416
type Authentication interface {
1517
// Init applies the initial configuration.
1618
Init(*mqtt.ClientOptions) error
1719

20+
// GetGatewayID returns the GatewayID if available.
21+
GetGatewayID() *lorawan.EUI64
22+
1823
// Update updates the authentication options.
1924
Update(*mqtt.ClientOptions) error
2025

internal/integration/mqtt/auth/azure_iot_hub.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/pkg/errors"
1616

1717
"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
18+
"github.com/brocaar/lorawan"
1819
)
1920

2021
// See:
@@ -201,6 +202,12 @@ func (a *AzureIoTHubAuthentication) Init(opts *mqtt.ClientOptions) error {
201202
return nil
202203
}
203204

205+
// GetGatewayID returns the GatewayID if available.
206+
// TODO: implement.
207+
func (a *AzureIoTHubAuthentication) GetGatewayID() *lorawan.EUI64 {
208+
return nil
209+
}
210+
204211
// Update updates the authentication options.
205212
func (a *AzureIoTHubAuthentication) Update(opts *mqtt.ClientOptions) error {
206213
if a.authType == authTypeSymmetric {

internal/integration/mqtt/auth/gcp_cloud_iot_core.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/pkg/errors"
1212

1313
"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
14+
"github.com/brocaar/lorawan"
1415
)
1516

1617
// GCPCloudIoTCoreAuthentication implements the Google Cloud IoT Core authentication.
@@ -59,6 +60,12 @@ func (a *GCPCloudIoTCoreAuthentication) Init(opts *mqtt.ClientOptions) error {
5960
return nil
6061
}
6162

63+
// GetGatewayID returns the GatewayID if available.
64+
// TODO: implement.
65+
func (a *GCPCloudIoTCoreAuthentication) GetGatewayID() *lorawan.EUI64 {
66+
return nil
67+
}
68+
6269
// Update updates the authentication options.
6370
func (a *GCPCloudIoTCoreAuthentication) Update(opts *mqtt.ClientOptions) error {
6471
token := jwt.NewWithClaims(a.siginingMethod, jwt.StandardClaims{

internal/integration/mqtt/auth/generic.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66

77
mqtt "github.com/eclipse/paho.mqtt.golang"
88
"github.com/pkg/errors"
9+
log "github.com/sirupsen/logrus"
910

1011
"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
12+
"github.com/brocaar/lorawan"
1113
)
1214

1315
// GenericAuthentication implements a generic MQTT authentication.
@@ -59,6 +61,24 @@ func (a *GenericAuthentication) Init(opts *mqtt.ClientOptions) error {
5961
return nil
6062
}
6163

64+
// GetGatewayID returns the GatewayID if available.
65+
func (a *GenericAuthentication) GetGatewayID() *lorawan.EUI64 {
66+
if a.clientID == "" {
67+
return nil
68+
}
69+
70+
// Try to decode the client ID as gateway ID.
71+
var gatewayID lorawan.EUI64
72+
if err := gatewayID.UnmarshalText([]byte(a.clientID)); err != nil {
73+
log.WithError(err).WithFields(log.Fields{
74+
"client_id": a.clientID,
75+
}).Warning("integration/mqtt/auth: could not decode client ID to gateway ID")
76+
return nil
77+
}
78+
79+
return &gatewayID
80+
}
81+
6282
// Update updates the authentication options.
6383
func (a *GenericAuthentication) Update(opts *mqtt.ClientOptions) error {
6484
return nil
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package auth
2+
3+
import (
4+
"testing"
5+
6+
"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
7+
"github.com/brocaar/lorawan"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestGenericAuthentication(t *testing.T) {
12+
gatewayID := lorawan.EUI64{1, 2, 3, 4, 5, 6, 7, 8}
13+
14+
var conf config.Config
15+
conf.Integration.Marshaler = "json"
16+
conf.Integration.MQTT.EventTopicTemplate = "gateway/{{ .GatewayID }}/event/{{ .EventType }}"
17+
conf.Integration.MQTT.StateTopicTemplate = "gateway/{{ .GatewayID }}/state/{{ .StateType }}"
18+
conf.Integration.MQTT.CommandTopicTemplate = "gateway/{{ .GatewayID }}/command/#"
19+
conf.Integration.MQTT.Auth.Type = "generic"
20+
conf.Integration.MQTT.Auth.Generic.Servers = []string{"tcp://localhost:1883"}
21+
conf.Integration.MQTT.Auth.Generic.Username = "foo"
22+
conf.Integration.MQTT.Auth.Generic.Password = "bar"
23+
conf.Integration.MQTT.Auth.Generic.CleanSession = true
24+
conf.Integration.MQTT.Auth.Generic.ClientID = gatewayID.String()
25+
26+
t.Run("New", func(t *testing.T) {
27+
assert := require.New(t)
28+
29+
auth, err := NewGenericAuthentication(conf)
30+
assert.NoError(err)
31+
32+
t.Run("GetGatewayID", func(t *testing.T) {
33+
assert := require.New(t)
34+
assert.Equal(&gatewayID, auth.GetGatewayID())
35+
})
36+
})
37+
}

0 commit comments

Comments
 (0)