Commit 03273255 authored by Orne Brocaar's avatar Orne Brocaar
Browse files

Update LoRaCloud integration with Modem & Geoloc Services.

parent ce8c8d83
......@@ -928,7 +928,12 @@ func (a *ApplicationAPI) UpdateLoRaCloudIntegration(ctx context.Context, in *pb.
return nil, helpers.ErrToRPCError(err)
}
conf := loracloud.Config{
var conf loracloud.Config
if err := json.Unmarshal(integration.Settings, &conf); err != nil {
return nil, helpers.ErrToRPCError(err)
}
conf = loracloud.Config{
Geolocation: in.GetIntegration().Geolocation,
GeolocationToken: in.GetIntegration().GeolocationToken,
GeolocationBufferTTL: int(in.GetIntegration().GeolocationBufferTtl),
......
......@@ -9,7 +9,9 @@ import (
"net/http"
"time"
"github.com/brocaar/chirpstack-application-server/internal/logging"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
const (
......@@ -60,8 +62,13 @@ func (c *Client) apiRequest(ctx context.Context, endpoint string, v, resp interf
reqCtx, cancel := context.WithTimeout(ctx, c.requestTimeout)
defer cancel()
req = req.WithContext(reqCtx)
log.WithFields(log.Fields{
"ctx_id": ctx.Value(logging.ContextIDKey),
"endpoint": endpoint,
}).Debug("integration/das/das: making API request")
httpResp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "http request error")
......
......@@ -14,6 +14,8 @@ import (
"github.com/brocaar/chirpstack-api/go/v3/common"
"github.com/brocaar/chirpstack-api/go/v3/gw"
"github.com/brocaar/chirpstack-application-server/internal/logging"
log "github.com/sirupsen/logrus"
)
const (
......@@ -35,21 +37,27 @@ type Client struct {
uri string
token string
requestTimeout time.Duration
migrated bool
}
// New creates a new Geolocation client.
func New(uri string, token string) *Client {
func New(migrated bool, uri string, token string) *Client {
return &Client{
uri: uri,
token: token,
requestTimeout: time.Second,
migrated: migrated,
}
}
// TDOASingleFrame request.
func (c *Client) TDOASingleFrame(ctx context.Context, rxInfo []*gw.UplinkRXInfo) (common.Location, error) {
req := NewTDOASingleFrameRequest(rxInfo)
resp, err := c.apiRequest(ctx, tdoaSingleFrameEndpoint, req)
endpoint := tdoaSingleFrameEndpoint
if c.migrated {
endpoint = "%s/api/v1/solve/tdoa"
}
resp, err := c.apiRequest(ctx, endpoint, req)
if err != nil {
return common.Location{}, errors.Wrap(err, "api request error")
}
......@@ -60,7 +68,11 @@ func (c *Client) TDOASingleFrame(ctx context.Context, rxInfo []*gw.UplinkRXInfo)
// TDOAMultiFrame request.
func (c *Client) TDOAMultiFrame(ctx context.Context, rxInfo [][]*gw.UplinkRXInfo) (common.Location, error) {
req := NewTDOAMultiFrameRequest(rxInfo)
resp, err := c.apiRequest(ctx, tdoaMultiFrameEndpoint, req)
endpoint := tdoaMultiFrameEndpoint
if c.migrated {
endpoint = "%s/api/v1/solve/tdoaMultiframe"
}
resp, err := c.apiRequest(ctx, endpoint, req)
if err != nil {
return common.Location{}, errors.Wrap(err, "api request error")
}
......@@ -71,7 +83,11 @@ func (c *Client) TDOAMultiFrame(ctx context.Context, rxInfo [][]*gw.UplinkRXInfo
// RSSISingleFrame request.
func (c *Client) RSSISingleFrame(ctx context.Context, rxInfo []*gw.UplinkRXInfo) (common.Location, error) {
req := NewRSSISingleFrameRequest(rxInfo)
resp, err := c.apiRequest(ctx, rssiSingleFrameEndpoint, req)
endpoint := rssiSingleFrameEndpoint
if c.migrated {
endpoint = "%s/api/v1/solve/rssi"
}
resp, err := c.apiRequest(ctx, endpoint, req)
if err != nil {
return common.Location{}, errors.Wrap(err, "api request error")
}
......@@ -82,7 +98,11 @@ func (c *Client) RSSISingleFrame(ctx context.Context, rxInfo []*gw.UplinkRXInfo)
// RSSIMultiFrame request.
func (c *Client) RSSIMultiFrame(ctx context.Context, rxInfo [][]*gw.UplinkRXInfo) (common.Location, error) {
req := NewRSSIMultiFrameRequest(rxInfo)
resp, err := c.apiRequest(ctx, rssiMultiFrameEndpoint, req)
endpoint := rssiMultiFrameEndpoint
if c.migrated {
endpoint = "%s/api/v1/solve/rssiMultiframe"
}
resp, err := c.apiRequest(ctx, endpoint, req)
if err != nil {
return common.Location{}, errors.Wrap(err, "api request error")
}
......@@ -93,7 +113,11 @@ func (c *Client) RSSIMultiFrame(ctx context.Context, rxInfo [][]*gw.UplinkRXInfo
// WifiTDOASingleFrame request.
func (c *Client) WifiTDOASingleFrame(ctx context.Context, rxInfo []*gw.UplinkRXInfo, aps []WifiAccessPoint) (common.Location, error) {
req := NewWifiTDOASingleFrameRequest(rxInfo, aps)
resp, err := c.apiRequest(ctx, wifiTDOASingleFrameEndpoint, req)
endpoint := wifiTDOASingleFrameEndpoint
if c.migrated {
endpoint = "%s/api/v1/solve/loraWifi"
}
resp, err := c.apiRequest(ctx, endpoint, req)
if err != nil {
return common.Location{}, errors.Wrap(err, "api request error")
}
......@@ -104,7 +128,11 @@ func (c *Client) WifiTDOASingleFrame(ctx context.Context, rxInfo []*gw.UplinkRXI
// GNSSLR1110SingleFrame request.
func (c *Client) GNSSLR1110SingleFrame(ctx context.Context, rxInfo []*gw.UplinkRXInfo, useRxTime bool, pl []byte) (common.Location, error) {
req := NewGNSSLR1110SingleFrameRequest(rxInfo, useRxTime, pl)
resp, err := c.v3APIRequest(ctx, gnssLR1110SingleFrameEndpoint, req)
endpoint := gnssLR1110SingleFrameEndpoint
if c.migrated {
endpoint = "%s/api/v1/solve/gnss_lr1110_singleframe"
}
resp, err := c.v3APIRequest(ctx, endpoint, req)
if err != nil {
return common.Location{}, errors.Wrap(err, "api request error")
}
......@@ -167,12 +195,23 @@ func (c *Client) apiRequest(ctx context.Context, endpoint string, v interface{})
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Ocp-Apim-Subscription-Key", c.token)
if c.migrated {
req.Header.Set("Authorization", c.token)
} else {
req.Header.Set("Ocp-Apim-Subscription-Key", c.token)
}
reqCtx, cancel := context.WithTimeout(ctx, c.requestTimeout)
defer cancel()
req = req.WithContext(reqCtx)
log.WithFields(log.Fields{
"ctx_id": ctx.Value(logging.ContextIDKey),
"endpoint": endpoint,
"migrated": c.migrated,
}).Debug("integration/das/geolocation: making API request")
httpResp, err := http.DefaultClient.Do(req)
if err != nil {
return resp, errors.Wrap(err, "http request error")
......@@ -210,8 +249,14 @@ func (c *Client) v3APIRequest(ctx context.Context, endpoint string, v interface{
reqCtx, cancel := context.WithTimeout(ctx, c.requestTimeout)
defer cancel()
req = req.WithContext(reqCtx)
log.WithFields(log.Fields{
"ctx_id": ctx.Value(logging.ContextIDKey),
"endpoint": endpoint,
"migrated": c.migrated,
}).Debug("integration/das/geolocation: making API request")
httpResp, err := http.DefaultClient.Do(req)
if err != nil {
return resp, errors.Wrap(err, "http request error")
......
......@@ -29,7 +29,7 @@ type ClientTestSuite struct {
func (ts *ClientTestSuite) SetupSuite() {
ts.server = httptest.NewServer(http.HandlerFunc(ts.apiHandler))
ts.client = New(ts.server.URL, "foobar")
ts.client = New(true, ts.server.URL, "foobar")
}
func (ts *ClientTestSuite) TearDownSuite() {
......
......@@ -6,6 +6,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"time"
"github.com/golang/protobuf/ptypes"
......@@ -61,57 +62,71 @@ type Integration struct {
func New(conf Config) (*Integration, error) {
return &Integration{
config: conf,
geolocationURI: "https://gls.loracloud.com",
dasURI: "https://das.loracloud.com",
geolocationURI: os.Getenv("LORACLOUD_GEOLOCATION_URI"), // for testing
dasURI: os.Getenv("LORACLOUD_DAS_URI"), // for testing
}, nil
}
func (i *Integration) getGeolocationURI() string {
// for testing
if i.geolocationURI != "" {
return i.geolocationURI
}
return "https://gls.loracloud.com"
}
func (i *Integration) getDasURI() string {
// for testing
if i.dasURI != "" {
return i.dasURI
}
return "https://das.loracloud.com"
}
// HandleUplinkEvent handles the Uplinkevent.
func (i *Integration) HandleUplinkEvent(ctx context.Context, ii models.Integration, vars map[string]string, pl pb.UplinkEvent) error {
var devEUI lorawan.EUI64
copy(devEUI[:], pl.DevEui)
// handle geolocation
if i.config.Geolocation {
err := func() error {
// update and get geoloc buffer
geolocBuffer, err := i.updateGeolocBuffer(ctx, devEUI, pl)
if err != nil {
return errors.Wrap(err, "update geolocation buffer error")
}
err := func() error {
// update and get geoloc buffer
geolocBuffer, err := i.updateGeolocBuffer(ctx, devEUI, pl)
if err != nil {
return errors.Wrap(err, "update geolocation buffer error")
}
// do geolocation
uplinkIDs, loc, err := i.geolocation(ctx, devEUI, geolocBuffer, pl)
if err != nil {
return errors.Wrap(err, "geolocation error")
}
// do geolocation
uplinkIDs, loc, err := i.geolocation(ctx, devEUI, geolocBuffer, pl)
if err != nil {
return errors.Wrap(err, "geolocation error")
}
// if it resolved to a location, send it to integrations
if loc != nil {
var fCnt uint32
if len(uplinkIDs) == 0 {
fCnt = pl.FCnt
}
if err := ii.HandleLocationEvent(ctx, vars, pb.LocationEvent{
ApplicationId: pl.ApplicationId,
ApplicationName: pl.ApplicationName,
DeviceName: pl.DeviceName,
DevEui: pl.DevEui,
Tags: pl.Tags,
Location: loc,
UplinkIds: uplinkIDs,
FCnt: fCnt,
PublishedAt: ptypes.TimestampNow(),
}); err != nil {
return errors.Wrap(err, "handle location event error")
}
// if it resolved to a location, send it to integrations
if loc != nil {
var fCnt uint32
if len(uplinkIDs) == 0 {
fCnt = pl.FCnt
}
if err := ii.HandleLocationEvent(ctx, vars, pb.LocationEvent{
ApplicationId: pl.ApplicationId,
ApplicationName: pl.ApplicationName,
DeviceName: pl.DeviceName,
DevEui: pl.DevEui,
Tags: pl.Tags,
Location: loc,
UplinkIds: uplinkIDs,
FCnt: fCnt,
PublishedAt: ptypes.TimestampNow(),
}); err != nil {
return errors.Wrap(err, "handle location event error")
}
return nil
}()
if err != nil {
log.WithError(err).Error("integration/loracloud: geolocation error")
}
return nil
}()
if err != nil {
log.WithError(err).Error("integration/loracloud: geolocation error")
}
// handle das
......@@ -200,23 +215,20 @@ func (i *Integration) Close() error {
}
func (i *Integration) updateGeolocBuffer(ctx context.Context, devEUI lorawan.EUI64, pl pb.UplinkEvent) ([][]*gw.UplinkRXInfo, error) {
// Do not trigger geolocation if there are less than 3 gateways.
if len(pl.RxInfo) < 3 {
return nil, nil
}
// read the geoloc buffer
geolocBuffer, err := GetGeolocBuffer(ctx, devEUI, time.Duration(i.config.GeolocationBufferTTL)*time.Second)
if err != nil {
return nil, errors.Wrap(err, "get geoloc buffer error")
}
geolocBuffer = append(geolocBuffer, pl.RxInfo)
// if the uplink was received by at least 3 gateways, append the metadata
// to the buffer
if len(pl.RxInfo) >= 3 {
geolocBuffer = append(geolocBuffer, pl.RxInfo)
}
// Save the buffer when there are > 0 items.
if len(geolocBuffer) != 0 {
if err := SaveGeolocBuffer(ctx, devEUI, geolocBuffer, time.Duration(i.config.GeolocationBufferTTL)*time.Second); err != nil {
return nil, errors.Wrap(err, "save geoloc buffer error")
}
if err := SaveGeolocBuffer(ctx, devEUI, geolocBuffer, time.Duration(i.config.GeolocationBufferTTL)*time.Second); err != nil {
return nil, errors.Wrap(err, "save geoloc buffer error")
}
return geolocBuffer, nil
......@@ -313,7 +325,14 @@ func (i *Integration) geolocation(ctx context.Context, devEUI lorawan.EUI64, geo
}
func (i *Integration) tdoaGeolocation(ctx context.Context, devEUI lorawan.EUI64, geolocBuffer [][]*gw.UplinkRXInfo) (*common.Location, error) {
client := geolocation.New(i.geolocationURI, i.config.GeolocationToken)
token := i.config.GeolocationToken
migrated := false
if i.config.DASToken != "" {
token = i.config.DASToken
migrated = true
}
client := geolocation.New(migrated, i.getGeolocationURI(), token)
start := time.Now()
var loc common.Location
......@@ -342,7 +361,14 @@ func (i *Integration) tdoaGeolocation(ctx context.Context, devEUI lorawan.EUI64,
}
func (i *Integration) rssiGeolocation(ctx context.Context, devEUI lorawan.EUI64, geolocBuffer [][]*gw.UplinkRXInfo) (*common.Location, error) {
client := geolocation.New(i.geolocationURI, i.config.GeolocationToken)
token := i.config.GeolocationToken
migrated := false
if i.config.DASToken != "" {
token = i.config.DASToken
migrated = true
}
client := geolocation.New(migrated, i.getGeolocationURI(), token)
start := time.Now()
var loc common.Location
......@@ -372,7 +398,14 @@ func (i *Integration) rssiGeolocation(ctx context.Context, devEUI lorawan.EUI64,
}
func (i *Integration) gnssLR1110Geolocation(ctx context.Context, devEUI lorawan.EUI64, rxInfo []*gw.UplinkRXInfo, pl []byte) (*common.Location, error) {
client := geolocation.New(i.geolocationURI, i.config.GeolocationToken)
token := i.config.GeolocationToken
migrated := false
if i.config.DASToken != "" {
token = i.config.DASToken
migrated = true
}
client := geolocation.New(migrated, i.getGeolocationURI(), token)
start := time.Now()
loc, err := client.GNSSLR1110SingleFrame(ctx, rxInfo, i.config.GeolocationGNSSUseRxTime, pl)
......@@ -390,7 +423,14 @@ func (i *Integration) gnssLR1110Geolocation(ctx context.Context, devEUI lorawan.
}
func (i *Integration) wifiTDOAGeolocation(ctx context.Context, devEUI lorawan.EUI64, rxInfo []*gw.UplinkRXInfo, aps []geolocation.WifiAccessPoint) (*common.Location, error) {
client := geolocation.New(i.geolocationURI, i.config.GeolocationToken)
token := i.config.GeolocationToken
migrated := false
if i.config.DASToken != "" {
token = i.config.DASToken
migrated = true
}
client := geolocation.New(migrated, i.getGeolocationURI(), token)
start := time.Now()
loc, err := client.WifiTDOASingleFrame(ctx, rxInfo, aps)
......@@ -413,7 +453,7 @@ func (i *Integration) dasJoin(ctx context.Context, devEUI lorawan.EUI64, pl pb.J
"ctx_id": ctx.Value(logging.ContextIDKey),
}).Info("integration/das: forwarding join notification")
client := das.New(i.dasURI, i.config.DASToken)
client := das.New(i.getDasURI(), i.config.DASToken)
start := time.Now()
_, err := client.UplinkSend(ctx, das.UplinkRequest{
......@@ -439,7 +479,7 @@ func (i *Integration) dasModem(ctx context.Context, vars map[string]string, devE
"ctx_id": ctx.Value(logging.ContextIDKey),
}).Info("integration/loracloud: forwarding das modem message")
client := das.New(i.dasURI, i.config.DASToken)
client := das.New(i.getDasURI(), i.config.DASToken)
start := time.Now()
resp, err := client.UplinkSend(ctx, das.UplinkRequest{
......@@ -472,7 +512,7 @@ func (i *Integration) dasGNSS(ctx context.Context, vars map[string]string, devEU
"ctx_id": ctx.Value(logging.ContextIDKey),
}).Info("integration/loracloud: forwarding das gnss message")
client := das.New(i.dasURI, i.config.DASToken)
client := das.New(i.getDasURI(), i.config.DASToken)
start := time.Now()
msg := das.UplinkMsgGNSS{
......@@ -523,7 +563,7 @@ func (i *Integration) dasUplinkMetaData(ctx context.Context, vars map[string]str
"ctx_id": ctx.Value(logging.ContextIDKey),
}).Info("integration/das: forwarding uplink meta-data to das")
client := das.New(i.dasURI, i.config.DASToken)
client := das.New(i.getDasURI(), i.config.DASToken)
start := time.Now()
resp, err := client.UplinkSend(ctx, das.UplinkRequest{
......@@ -698,7 +738,7 @@ func (i *Integration) streamGeolocWorkaround(ctx context.Context, vars map[strin
msg.GNSSAssistAltitude = loc.Altitude
}
client := das.New(i.dasURI, i.config.DASToken)
client := das.New(i.getDasURI(), i.config.DASToken)
resp, err := client.UplinkSend(ctx, das.UplinkRequest{
helpers.EUI64(devEUI): msg,
})
......@@ -718,7 +758,7 @@ func (i *Integration) streamGeolocWorkaround(ctx context.Context, vars map[strin
Timestamp: float64(helpers.GetTimestamp(pl.RxInfo).UnixNano()) / float64(time.Second),
}
client := das.New(i.dasURI, i.config.DASToken)
client := das.New(i.getDasURI(), i.config.DASToken)
resp, err := client.UplinkSend(ctx, das.UplinkRequest{
helpers.EUI64(devEUI): msg,
})
......@@ -743,7 +783,7 @@ func (i *Integration) streamGeolocWorkaround(ctx context.Context, vars map[strin
Timestamp: float64(helpers.GetTimestamp(pl.RxInfo).UnixNano()) / float64(time.Second),
}
client := das.New(i.dasURI, i.config.DASToken)
client := das.New(i.getDasURI(), i.config.DASToken)
resp, err := client.UplinkSend(ctx, das.UplinkRequest{
helpers.EUI64(devEUI): msg,
})
......
......@@ -20,6 +20,7 @@ class CreateLoRaCloudIntegration extends Component {
render() {
let obj = {
das: true,
dasGNSSPort: 198,
dasModemPort: 199,
};
......@@ -28,7 +29,7 @@ class CreateLoRaCloudIntegration extends Component {
<Grid container spacing={4}>
<Grid item xs={12}>
<Card>
<CardHeader title="Add Semtech LoRa Cloud integration" />
<CardHeader title="Add Semtech LoRa Cloud&trade; integration" />
<CardContent>
<LoRaCloudIntegrationForm submitLabel="Add integration" onSubmit={this.onSubmit} object={obj} />
</CardContent>
......
......@@ -37,10 +37,10 @@ class LoRaCloudCard extends Component {
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
Semtech LoRa Cloud
Semtech LoRa Cloud&trade;
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
The Semtech LoRa Cloud integration provides Geolocation and Device & Application Services.
The Semtech LoRa Cloud integration provides Modem & Geolocation Services.
</Typography>
</CardContent>
<CardActions>
......
......@@ -41,7 +41,7 @@ class UpdateLoRaCloudIntegration extends Component {
<Grid container spacing={4}>
<Grid item xs={12}>
<Card>
<CardHeader title="Update Semtech LoRa Cloud integration" />
<CardHeader title="Update Semtech LoRa Cloud&trade; integration" />
<CardContent>
<LoRaCloudIntegrationForm submitLabel="Update integration" onSubmit={this.onSubmit} object={this.state.object} />
</CardContent>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment