Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support migration from Loriot #80

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions cmd/loriot/loriot.go
@@ -0,0 +1,21 @@
// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package loriot

import "go.thethings.network/lorawan-stack-migrate/pkg/commands"

const sourceName = "loriot"

var LoriotCmd = commands.Source(sourceName, "Export devices from Loriot")
4 changes: 3 additions & 1 deletion cmd/root.go
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/spf13/cobra"
"go.thethings.network/lorawan-stack-migrate/cmd/chirpstack"
"go.thethings.network/lorawan-stack-migrate/cmd/loriot"
"go.thethings.network/lorawan-stack-migrate/cmd/ttnv2"
"go.thethings.network/lorawan-stack-migrate/cmd/tts"
"go.thethings.network/lorawan-stack-migrate/pkg/export"
Expand Down Expand Up @@ -92,7 +93,8 @@ func init() {
Title: "Sources:",
})

rootCmd.AddCommand(chirpstack.ChirpStackCmd)
rootCmd.AddCommand(loriot.LoriotCmd)
rootCmd.AddCommand(ttnv2.TTNv2Cmd)
rootCmd.AddCommand(tts.TTSCmd)
rootCmd.AddCommand(chirpstack.ChirpStackCmd)
}
1 change: 1 addition & 0 deletions cmd/ttn-lw-migrate/main.go
Expand Up @@ -18,6 +18,7 @@ import (
"os"

_ "go.thethings.network/lorawan-stack-migrate/pkg/source/chirpstack" // ChirpStack source
_ "go.thethings.network/lorawan-stack-migrate/pkg/source/loriot" // LorIoT source
_ "go.thethings.network/lorawan-stack-migrate/pkg/source/ttnv2" // TTNv2 source
_ "go.thethings.network/lorawan-stack-migrate/pkg/source/tts" // TTS source

Expand Down
169 changes: 169 additions & 0 deletions pkg/source/loriot/api/api.go
@@ -0,0 +1,169 @@
// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
)

var (
urlPrefix = "https://"
apiURL string
apiKey string

client = &http.Client{Timeout: 10 * time.Second}
)

func SetURLPrefix(insecure bool) {
if insecure {
urlPrefix = "http://"
return
}
urlPrefix = "https://"
}

func SetAPIURL(url string) {
if s := strings.Split(url, "://"); len(s) > 1 {
url = strings.Join(s[1:], "")
}
apiURL = url
}

func SetAPIKey(key string) {
apiKey = key
}

func NewRequest(method, path string, body io.Reader) (*http.Request, error) {
switch {
case apiURL == "":
return nil, errNoAPIURL.New()

Check failure on line 57 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Code Quality

undefined: errNoAPIURL

Check failure on line 57 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Code Quality

undefined: errNoAPIURL

Check failure on line 57 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Tests

undefined: errNoAPIURL

case apiKey == "":
return nil, errNoAPIKey.New()

Check failure on line 60 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Code Quality

undefined: errNoAPIKey

Check failure on line 60 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Code Quality

undefined: errNoAPIKey

Check failure on line 60 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Tests

undefined: errNoAPIKey
}

req, err := http.NewRequest(method, urlPrefix+apiURL+path, body)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
return req, nil
}

type Device struct {
ID string `json:"_id"`
Devaddr string `json:"devaddr"`
Seqno int `json:"seqno"`
Seqdn int `json:"seqdn"`
Seqq int `json:"seqq"`
AdrCnt int `json:"adrCnt"`
Subscription int `json:"subscription"`
Txrate int `json:"txrate"`
Rxrate int `json:"rxrate"`
Devclass string `json:"devclass"`
Rxw int `json:"rxw"`
Dutycycle int `json:"dutycycle"`
Adr bool `json:"adr"`
AdrMin int `json:"adrMin"`
AdrMax int `json:"adrMax"`
AdrFix int `json:"adrFix"`
AdrCntLimit int `json:"adrCntLimit"`
Seqrelax bool `json:"seqrelax"`
Seqdnreset bool `json:"seqdnreset"`
Nonce int `json:"nonce"`
LastJoin string `json:"lastJoin"`
LastSeen int `json:"lastSeen"`
Rssi float64 `json:"rssi"`
Snr float64 `json:"snr"`
Freq int `json:"freq"`
Sf int `json:"sf"`
Bw int `json:"bw"`
Gw string `json:"gw"`
Appeui string `json:"appeui"`
LastDevStatusSeen string `json:"lastDevStatusSeen"`
Bat int `json:"bat"`
DevSnr int `json:"devSnr"`
Lorawan *Lorawan `json:"lorawan"`
}

type Lorawan struct {
Major int `json:"major"`
Minor int `json:"minor"`
Revision string `json:"revision"`
}

func GetDevice(appID, devEUI string) (*Device, error) {
d := new(Device)

req, err := NewRequest("GET", fmt.Sprintf("/app/%s/device/%s", appID, devEUI), nil)
if err != nil {
return nil, err
}

resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

return d, json.Unmarshal(body, d)
}

type PaginatedDevices struct {
Page int `json:"page"`
PerPage int `json:"perPage"`
Total int `json:"total"`
Devices []Device `json:"apps"`
}

func GetPaginatedDevices(appID string, page int) (*PaginatedDevices, error) {
req, err := NewRequest("GET", fmt.Sprintf("/app/%s/devices?page=%d", appID, page), nil)
if err != nil {
return nil, err
}

resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if sc := resp.StatusCode; sc < 200 || sc >= 300 {
return nil, errInvalidStatusCode.WithAttributes("code", sc)

Check failure on line 155 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Code Quality

undefined: errInvalidStatusCode

Check failure on line 155 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Code Quality

undefined: errInvalidStatusCode

Check failure on line 155 in pkg/source/loriot/api/api.go

View workflow job for this annotation

GitHub Actions / Go Tests

undefined: errInvalidStatusCode
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

r := new(PaginatedDevices)
if err := json.Unmarshal(body, r); err != nil {
return nil, err
}

return r, nil
}
57 changes: 57 additions & 0 deletions pkg/source/loriot/config/config.go
@@ -0,0 +1,57 @@
// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
"os"

"github.com/spf13/pflag"

"go.thethings.network/lorawan-stack-migrate/pkg/source"
)

func New() (*Config, *pflag.FlagSet) {
var (
cfg = new(Config)
flags = new(pflag.FlagSet)
)

flags.StringVar(&cfg.APIKey,
"api-key",
os.Getenv("LORIOT_API_KEY"),
"Loriot API Key")
flags.StringVar(&cfg.URL, "api-url",
os.Getenv("LORIOT_API_URL"),
"Loriot API URL")
flags.StringVar(&cfg.AppID,
"app-id",
os.Getenv("LORIOT_APP_ID"),
"Loriot APP ID")
flags.BoolVar(&cfg.Insecure,
"insecure",
os.Getenv("LORIOT_INSECURE") == "1",
"Do not connect to Loriot over TLS")

return cfg, flags
}

type Config struct {
source.Config

APIKey, URL string
Insecure bool

AppID string
}
31 changes: 31 additions & 0 deletions pkg/source/loriot/loriot.go
@@ -0,0 +1,31 @@
// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package loriot

import (
"go.thethings.network/lorawan-stack-migrate/pkg/source"
"go.thethings.network/lorawan-stack-migrate/pkg/source/loriot/config"
)

func init() {
cfg, flags := config.New()

source.RegisterSource(source.Registration{
Name: "loriot",
Description: "Migrate from Loriot LoRaWAN Network Server",
FlagSet: flags,
Create: createNewSource(cfg),
})
}