Skip to content

Commit

Permalink
Publish ConnState message on gateway (un)subscribe.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brocaar committed Apr 11, 2021
1 parent 5f22f6b commit 08dc3c9
Show file tree
Hide file tree
Showing 15 changed files with 342 additions and 19 deletions.
8 changes: 8 additions & 0 deletions cmd/chirpstack-gateway-bridge/cmd/configfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ marshaler="{{ .Integration.Marshaler }}"
# Event topic template.
event_topic_template="{{ .Integration.MQTT.EventTopicTemplate }}"
# State topic template.
#
# States are sent by the gateway as retained MQTT messages so that the last
# message will be stored by the MQTT broker. When set to a blank string, this
# feature will be disabled. This feature is only supported when using the
# generic authentication type.
state_topic_template="{{ .Integration.MQTT.StateTopicTemplate }}"
# Command topic template.
command_topic_template="{{ .Integration.MQTT.CommandTopicTemplate }}"
Expand Down
1 change: 1 addition & 0 deletions cmd/chirpstack-gateway-bridge/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func init() {
viper.SetDefault("integration.mqtt.auth.type", "generic")

viper.SetDefault("integration.mqtt.event_topic_template", "gateway/{{ .GatewayID }}/event/{{ .EventType }}")
viper.SetDefault("integration.mqtt.state_topic_template", "gateway/{{ .GatewayID }}/state/{{ .StateType }}")
viper.SetDefault("integration.mqtt.command_topic_template", "gateway/{{ .GatewayID }}/command/#")
viper.SetDefault("integration.mqtt.keep_alive", 30*time.Second)
viper.SetDefault("integration.mqtt.max_reconnect_interval", time.Minute)
Expand Down
2 changes: 2 additions & 0 deletions cmd/chirpstack-gateway-bridge/cmd/root_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func run(cmd *cobra.Command, args []string) error {
log.WithField("signal", <-sigChan).Info("signal received")
log.Warning("shutting down server")

integration.GetIntegration().Stop()

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/brocaar/chirpstack-gateway-bridge
go 1.16

require (
github.com/brocaar/chirpstack-api/go/v3 v3.8.1
github.com/brocaar/chirpstack-api/go/v3 v3.9.7
github.com/brocaar/lorawan v0.0.0-20201030140234-f23da2d4a303
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/eclipse/paho.mqtt.golang v1.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/brocaar/chirpstack-api/go/v3 v3.8.1 h1:8xNpG/GZqiL8XYkkAUWYNZJu7yn5SamK6oPBx1hCQe0=
github.com/brocaar/chirpstack-api/go/v3 v3.8.1/go.mod h1:ex/wqXQaClwDMa2zDN6crp9ZiMGc1GMVQhjxiB+OJcg=
github.com/brocaar/chirpstack-api/go/v3 v3.9.7 h1:n5Zte6zIg+qbqtb4dwp3vGQwIXpXsk5nMR4WwMUcLgA=
github.com/brocaar/chirpstack-api/go/v3 v3.9.7/go.mod h1:v8AWP19nOJK4rwJsr1+weDfpUc4UNLbRh8Eygn4Oh00=
github.com/brocaar/lorawan v0.0.0-20201030140234-f23da2d4a303 h1:LkE19tFPfDaRh1HIKWLCZKSBZNonMu0rIOJPCLvEjC0=
github.com/brocaar/lorawan v0.0.0-20201030140234-f23da2d4a303/go.mod h1:CciUmQHIpUYTHHMeICtyamM7d+47VV+WBZ5ReDozpoc=
github.com/caarlos0/ctrlc v1.0.0 h1:2DtF8GSIcajgffDFJzyG15vO+1PuBWOMUdFut7NnXhw=
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type Config struct {
MQTT struct {
EventTopicTemplate string `mapstructure:"event_topic_template"`
CommandTopicTemplate string `mapstructure:"command_topic_template"`
StateTopicTemplate string `mapstructure:"state_topic_template"`
KeepAlive time.Duration `mapstructure:"keep_alive"`
MaxReconnectInterval time.Duration `mapstructure:"max_reconnect_interval"`
TerminateOnConnectError bool `mapstructure:"terminate_on_connect_error"`
Expand Down
3 changes: 3 additions & 0 deletions internal/integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ type Integration interface {
// PublishEvent publishes the given event.
PublishEvent(lorawan.EUI64, string, uuid.UUID, proto.Message) error

// PublishState publishes the given state as retained message.
PublishState(lorawan.EUI64, string, proto.Message) error

// SetDownlinkFrameFunc sets the DownlinkFrame handler func.
SetDownlinkFrameFunc(func(gw.DownlinkFrame))

Expand Down
5 changes: 5 additions & 0 deletions internal/integration/mqtt/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import (

mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/pkg/errors"

"github.com/brocaar/lorawan"
)

// Authentication defines the authentication interface.
type Authentication interface {
// Init applies the initial configuration.
Init(*mqtt.ClientOptions) error

// GetGatewayID returns the GatewayID if available.
GetGatewayID() *lorawan.EUI64

// Update updates the authentication options.
Update(*mqtt.ClientOptions) error

Expand Down
7 changes: 7 additions & 0 deletions internal/integration/mqtt/auth/azure_iot_hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/pkg/errors"

"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
"github.com/brocaar/lorawan"
)

// See:
Expand Down Expand Up @@ -201,6 +202,12 @@ func (a *AzureIoTHubAuthentication) Init(opts *mqtt.ClientOptions) error {
return nil
}

// GetGatewayID returns the GatewayID if available.
// TODO: implement.
func (a *AzureIoTHubAuthentication) GetGatewayID() *lorawan.EUI64 {
return nil
}

// Update updates the authentication options.
func (a *AzureIoTHubAuthentication) Update(opts *mqtt.ClientOptions) error {
if a.authType == authTypeSymmetric {
Expand Down
7 changes: 7 additions & 0 deletions internal/integration/mqtt/auth/gcp_cloud_iot_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"

"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
"github.com/brocaar/lorawan"
)

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

// GetGatewayID returns the GatewayID if available.
// TODO: implement.
func (a *GCPCloudIoTCoreAuthentication) GetGatewayID() *lorawan.EUI64 {
return nil
}

// Update updates the authentication options.
func (a *GCPCloudIoTCoreAuthentication) Update(opts *mqtt.ClientOptions) error {
token := jwt.NewWithClaims(a.siginingMethod, jwt.StandardClaims{
Expand Down
20 changes: 20 additions & 0 deletions internal/integration/mqtt/auth/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (

mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"

"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
"github.com/brocaar/lorawan"
)

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

// GetGatewayID returns the GatewayID if available.
func (a *GenericAuthentication) GetGatewayID() *lorawan.EUI64 {
if a.clientID == "" {
return nil
}

// Try to decode the client ID as gateway ID.
var gatewayID lorawan.EUI64
if err := gatewayID.UnmarshalText([]byte(a.clientID)); err != nil {
log.WithError(err).WithFields(log.Fields{
"client_id": a.clientID,
}).Warning("integration/mqtt/auth: could not decode client ID to gateway ID")
return nil
}

return &gatewayID
}

// Update updates the authentication options.
func (a *GenericAuthentication) Update(opts *mqtt.ClientOptions) error {
return nil
Expand Down
37 changes: 37 additions & 0 deletions internal/integration/mqtt/auth/generic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package auth

import (
"testing"

"github.com/brocaar/chirpstack-gateway-bridge/internal/config"
"github.com/brocaar/lorawan"
"github.com/stretchr/testify/require"
)

func TestGenericAuthentication(t *testing.T) {
gatewayID := lorawan.EUI64{1, 2, 3, 4, 5, 6, 7, 8}

var conf config.Config
conf.Integration.Marshaler = "json"
conf.Integration.MQTT.EventTopicTemplate = "gateway/{{ .GatewayID }}/event/{{ .EventType }}"
conf.Integration.MQTT.StateTopicTemplate = "gateway/{{ .GatewayID }}/state/{{ .StateType }}"
conf.Integration.MQTT.CommandTopicTemplate = "gateway/{{ .GatewayID }}/command/#"
conf.Integration.MQTT.Auth.Type = "generic"
conf.Integration.MQTT.Auth.Generic.Servers = []string{"tcp://localhost:1883"}
conf.Integration.MQTT.Auth.Generic.Username = "foo"
conf.Integration.MQTT.Auth.Generic.Password = "bar"
conf.Integration.MQTT.Auth.Generic.CleanSession = true
conf.Integration.MQTT.Auth.Generic.ClientID = gatewayID.String()

t.Run("New", func(t *testing.T) {
assert := require.New(t)

auth, err := NewGenericAuthentication(conf)
assert.NoError(err)

t.Run("GetGatewayID", func(t *testing.T) {
assert := require.New(t)
assert.Equal(&gatewayID, auth.GetGatewayID())
})
})
}
Loading

0 comments on commit 08dc3c9

Please sign in to comment.