diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 643e4b43..5a1af36f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ before_script: - mkdir -p $GOPATH/.cache && ln -s $PWD/.govendor $GOPATH/.cache/govendor # Downloading go if not installed yet - apt-get update -y && apt-get install make git tar -y - - "([[ ! $(go version) =~ \"go1\\.8\" ]] && apt-get install wget -y && wget https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz && tar -C $PWD -xvzf go1.8.1.linux-amd64.tar.gz) || echo \"Expected Go toolset available in cache\"" + - "([[ ! $(go version) =~ \"version\" ]] && apt-get install wget -y && wget https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz && tar -C $PWD -xvzf go1.8.1.linux-amd64.tar.gz) || echo \"Expected Go toolset available in cache\"" # Copying the packet-forwarder in the gopath - mkdir -p $GOPATH/src/github.com/TheThingsNetwork - ln -s $PWD $GOPATH/src/github.com/TheThingsNetwork/packet_forwarder @@ -36,9 +36,20 @@ before_script: - make deps - popd +after_script: + - cp -r release release_files + - pushd release_files + # Change name of the binary + - mv packet-forwarder* packet-forwarder + # Create archive + - tar -cvzf $CI_JOB_NAME.tar.gz * + - popd + - rm -rf release/* + - cp release_files/$CI_JOB_NAME.tar.gz release + multitech-conduit-pktfwd: stage: build - image: registry.gitlab.com/thethingsindustries/packet-forwarder/multitech-toolchain + image: registry.gitlab.com/thethingsnetwork/packet_forwarder/multitech-toolchain script: # Remove the toolchain's CFLAGS - "sed 's/.*CFLAGS.*//g' /opt/mlinux/3.2.0/environment-setup-arm926ejste-mlinux-linux-gnueabi -i.bak" @@ -48,10 +59,7 @@ multitech-conduit-pktfwd: # Go to packet forwarder file - pushd $GOPATH/src/github.com/TheThingsNetwork/packet_forwarder - GOOS=linux GOARM=5 GOARCH=arm make build - - pushd release - - binary_file=$(ls packet*) - - ./../scripts/multitech/create-multitech-package.sh $binary_file - - popd + - cp scripts/multitech/* release - popd artifacts: paths: @@ -59,11 +67,24 @@ multitech-conduit-pktfwd: kerlink-iot-station-pktfwd: stage: build - image: registry.gitlab.com/thethingsindustries/packet-forwarder/klk-toolchain + image: registry.gitlab.com/thethingsnetwork/packet_forwarder/klk-toolchain script: - pushd $GOPATH/src/github.com/TheThingsNetwork/packet_forwarder - ./scripts/kerlink/build-kerlink.sh /opt - - cp scripts/kerlink/create-kerlink-package.sh release + - cp scripts/kerlink/create-package.sh release + - popd + artifacts: + paths: + - release/ + +imst-rpi-pktfwd: + stage: build + image: "ubuntu:xenial" + script: + - pushd $GOPATH/src/github.com/TheThingsNetwork/packet_forwarder + - apt install -y gcc-arm-linux-gnueabi # Installing cross-compiler + - GOOS=linux GOARM=7 GOARCH=arm PLATFORM=imst_rpi CFG_SPI=native CC=arm-linux-gnueabi-gcc make build + - cp scripts/rpi/install-systemd.sh release - popd artifacts: paths: @@ -71,6 +92,7 @@ kerlink-iot-station-pktfwd: sign: before_script: [] + after_script: [] only: - develop@thethingsnetwork/packet_forwarder - master@thethingsnetwork/packet_forwarder @@ -89,6 +111,7 @@ sign: azure-binaries: before_script: [] + after_script: [] only: - develop@thethingsnetwork/packet_forwarder - master@thethingsnetwork/packet_forwarder @@ -96,5 +119,5 @@ azure-binaries: image: registry.gitlab.com/thethingsindustries/upload script: - cd release - - export STORAGE_CONTAINER=packet-forwarder STORAGE_KEY=$AZURE_STORAGE_KEY ZIP=true TGZ=true PREFIX=$CI_BUILD_REF_NAME/ + - export STORAGE_CONTAINER=packet-forwarder STORAGE_KEY=$AZURE_STORAGE_KEY ZIP=false TGZ=false PREFIX=$CI_BUILD_REF_NAME/ - upload * diff --git a/README.md b/README.md index 90c4853a..c68bcd6a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Installation manuals are available for main available gateways: + [Kerlink IoT Station installation manual](docs/INSTALL_INSTRUCTIONS/KERLINK.md) + [Multitech Conduit installation manual](docs/INSTALL_INSTRUCTIONS/MULTITECH.md) ++ [Raspberry Pi + IMST ic880a installation manual](docs/INSTALL_INSTRUCTIONS/IMST_RPI.md) ## Build @@ -25,6 +26,7 @@ If you have a custom-made gateway, or if you want to contribute to the developme + [Kerlink IoT Station build instructions](docs/INSTALL_INSTRUCTIONS/KERLINK.md#build) + [Multitech Conduit build instructions](docs/INSTALL_INSTRUCTIONS/MULTITECH.md#build) ++ [Raspberry Pi + IMST ic880a build instructions](docs/INSTALL_INSTRUCTIONS/IMST_RPI.md#build) + [SPI environment build instructions](docs/INSTALL_INSTRUCTIONS/SPI.md) + [FTDI environment build instructions](docs/INSTALL_INSTRUCTIONS/FTDI.md) *(Experimental)* diff --git a/cmd/configure.go b/cmd/configure.go index e37ee992..8413e4d7 100644 --- a/cmd/configure.go +++ b/cmd/configure.go @@ -3,7 +3,6 @@ package cmd import ( - "fmt" "os" "github.com/TheThingsNetwork/packet_forwarder/util" @@ -21,10 +20,6 @@ The first argument is used as the storage location to the configuration file. If Run: func(cmd *cobra.Command, args []string) { ctx := util.GetLogger() - filePath := fmt.Sprintf("%s/.pktfwd.yml", os.Getenv("HOME")) - if len(args) > 0 { - filePath = args[0] - } ctx.Info("If you haven't registered your gateway yet, you can register it either with the console, or with `ttnctl`.") @@ -62,14 +57,14 @@ The first argument is used as the storage location to the configuration file. If util.GetLogger().WithError(err).Fatal("Failed to generate YAML") } - f, err := os.Create(filePath) + f, err := os.Create(cfgFile) if err != nil { util.GetLogger().WithError(err).Fatal("Failed to create file") } defer f.Close() f.Write(output) - ctx.WithField("ConfigFilePath", filePath).Info("New configuration file saved") + ctx.WithField("ConfigFilePath", cfgFile).Info("New configuration file saved") }, } diff --git a/cmd/root.go b/cmd/root.go index 894c2f16..2f90b50f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/TheThingsNetwork/packet_forwarder/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -35,6 +36,10 @@ func init() { } func initConfig() { + if cfgFile == "" { + cfgFile = util.GetConfigFile() + } + viper.SetConfigType("yaml") viper.SetConfigName(".pktfwd") viper.AddConfigPath("$HOME") @@ -46,9 +51,10 @@ func initConfig() { viper.SetConfigFile(cfgFile) } - err := viper.ReadInConfig() - if err != nil { - fmt.Println("Error when reading config file:", err) - os.Exit(1) + if _, err := os.Stat(cfgFile); err == nil { + err := viper.ReadInConfig() + if err != nil { + fmt.Println("Error when reading config file:", err, "; If the file doesn't exist yet, create .pktfwd.yml by using the `configure` command.") + } } } diff --git a/cmd/start.go b/cmd/start.go index 66a2c5ee..518d36b4 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -16,7 +16,7 @@ import ( ) // standardDownlinkSendMargin is the time we send a TX packet to the concentrator before its sending time. -const standardDownlinkSendMargin = 20 +const standardDownlinkSendMargin = 100 // downlinksMargin is specified at build. If it contains a numeric value, it is used as the number of // milliseconds of time margin. If no numeric value can be parsed, we use standardTimeMargin. @@ -52,6 +52,13 @@ var startCmd = &cobra.Command{ ctx.WithField("File", traceFilename).Info("Trace writing active for this run") } + if pin := config.GetInt("reset-pin"); pin != 0 { + ctx.WithField("ResetPin", pin).Info("Reset pin specified, resetting concentrator...") + if err := pktfwd.ResetPin(pin); err != nil { + ctx.WithError(err).Fatal("Couldn't reset pin") + } + } + ttnConfig := &pktfwd.TTNConfig{ ID: config.GetString("id"), Key: config.GetString("key"), @@ -83,6 +90,7 @@ func init() { startCmd.PersistentFlags().String("gps-path", "", "The file system path to the GPS interface, if a GPS is available (example: /dev/nmea)") startCmd.PersistentFlags().Int64("downlink-send-margin", getDefaultDownlinkSendMargin(), "The margin, in milliseconds, between a downlink is sent to a concentrator and it is being sent by the concentrator") startCmd.PersistentFlags().String("run-trace", "", "File to which write the runtime trace of the packet forwarder. Can later be read with `go tool trace `.") + startCmd.PersistentFlags().Int("reset-pin", 0, "GPIO pin associated to the reset pin of the board") startCmd.PersistentFlags().BoolP("verbose", "v", false, "Show debug logs") viper.BindPFlags(startCmd.PersistentFlags()) diff --git a/docs/IMPLEMENTATION/DOWNLINKS.md b/docs/IMPLEMENTATION/DOWNLINKS.md index a8f3b430..d1c409a8 100644 --- a/docs/IMPLEMENTATION/DOWNLINKS.md +++ b/docs/IMPLEMENTATION/DOWNLINKS.md @@ -18,11 +18,11 @@ The method we use to find `ConcentratorBootTime` is through the first uplink. Wi It is important to note that because of the uplink polling rate, `count_us` only allows us to find `ConcentratorBootTime` within 100μs. When the packet forwarder starts, it polls for uplinks every 100μs, to have a higher degree of precision for the `ConcentratorBootTime` value calculation. When the first uplink has been received, the polling frequency is diminished to every 5ms, to avoid performance issues. -* When a downlink is received, the packet forwarder schedules it in an internal queue system to be handled **20ms before `ExpectedSendingTimestamp`**. This means that the packet forwarder has then 20ms to perform its last computations on the downlink packet and to transmit it. This 20ms margin value is called `sendingTimeMargin`. +* When a downlink is received, the packet forwarder schedules it in an internal queue system to be handled **100ms before `ExpectedSendingTimestamp`**. This means that the packet forwarder has then 100ms to perform its last computations on the downlink packet and to transmit it. This 100ms margin value is called `sendingTimeMargin`. - * The value of `sendingTimeMargin` has a consequence of the gateway's downlink debit rate. Considering we need 20ms to transmit a downlink from the gateway's internal memory to emission, it means that we can only reasonably transmit 3000 downlinks per minute - and that is making the assumption that receive windows won't overlap. + * The value of `sendingTimeMargin` has a consequence of the gateway's downlink debit rate. Considering we need 100ms to transmit a downlink from the gateway's internal memory to emission, it means that we can only reasonably transmit 600 downlinks per minute - and that is making the assumption that receive windows won't overlap. - * Having a 20ms `sendingTimeMargin` allows the packet forwarder to have a comfortable margin in case of performance issues on the system, or in case of transmission issues. For systems connected to a concentrator via USB, it usually takes 10ms to perform the last computations and to transmit the packet to the concentrator. However, one improvement to the packet forwarder would be setting `sendingTimeMargin` as a build or run parameter, to make use of the higher transmission speeds on SPI-connected devices. + * Having a 100ms `sendingTimeMargin` allows the packet forwarder to have a comfortable margin in case of performance issues on the system, or in case of transmission issues. For systems connected to a concentrator via USB, it usually takes 10ms to perform the last computations and to transmit the packet to the concentrator. However, one improvement to the packet forwarder would be setting `sendingTimeMargin` as a build or run parameter, to make use of the higher transmission speeds on SPI-connected devices. *Note:* The packet forwarder doesn't support GPS concentrators yet. GPS concentrators don't rely on an internal clock, and are able to transmit absolute timestamps for an uplink - meaning it is not necessary to know their internal clock value to transmit downlinks to such devices. diff --git a/docs/INSTALL_INSTRUCTIONS/IMST_RPI.md b/docs/INSTALL_INSTRUCTIONS/IMST_RPI.md new file mode 100644 index 00000000..72a3e285 --- /dev/null +++ b/docs/INSTALL_INSTRUCTIONS/IMST_RPI.md @@ -0,0 +1,76 @@ +# Install the TTN Packet Forwarder on a Raspberry Pi with an IMST ic880a board + +To follow this manual, you must have a Raspberry Pi with an IMST ic880a board, connected through SPI. + +## Download and run + +1. Download the [Raspberry Pi + IMST build](https://ttnreleases.blob.core.windows.net/packet_forwarder/master/imst-rpi-pktfwd.zip) of the packet forwarder. + +2. Configure the packet forwarder: + +```bash +$ configure +[...] + INFO New configuration file saved ConfigFilePath=/root/.pktfwd.yml +``` + +3. Run the packet forwarder: + +```bash +$ start +``` + +### Permanent installation with systemd + +If you want a permanent installation of the packet forwarder on your Raspberry Pi, with `systemd` managing the packet forwarder on the background, we provide a basic systemd installation script, `install-systemd.sh`. + +1. Select the build, and copy it in a permanent location - such as `/usr/bin`. + +2. Create a configuration file in a permanent location, such as in a `/usr/config` directory: + +```bash +$ touch /usr/config/ttn-pkt-fwd.yml +``` + +3. Set up this configuration file: + +```bash +$ configure /usr/config/ttn-pkt-fwd.yml +``` + +4. Use the `install-systemd.sh` script, with the binary as a first argument and the config file as a second argument: + +```bash +$ ./install-systemd.sh +./install-systemd.sh: Installation of the systemd service complete. +``` + +5. Reload the systemd daemon, and start the service: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable ttn-pkt-fwd +sudo systemctl start ttn-pkt-fwd +``` + +## Build + +If want to contribute to the development of the packet forwarder, you might want to build the TTN Packet Forwarder. You will need to use a Linux environment to run the toolchain necessary for the build. + +### Getting the toolchain + +If you want to build the packet forwarder for a Raspberry Pi, you will need a **Raspberry Pi cross-compiler**. On some Linux distributions, such as Ubuntu, a toolchain is available as a package: `sudo apt install gcc-arm-linux-gnueabi -y`. + +### Building the binary + +Make sure you have [installed](https://golang.org/dl/) and [configured](https://golang.org/doc/code.html#GOPATH) your Go environment. + +Follow these commands: + +```bash +$ make dev-deps +$ make deps +$ GOOS=linux GOARCH=arm GOARM=7 CC=gcc-arm-linux-gnueabi make build +``` + +The binary will then be available in the `release/` folder. diff --git a/docs/INSTALL_INSTRUCTIONS/KERLINK.md b/docs/INSTALL_INSTRUCTIONS/KERLINK.md index 92e19c7a..ceca4e00 100644 --- a/docs/INSTALL_INSTRUCTIONS/KERLINK.md +++ b/docs/INSTALL_INSTRUCTIONS/KERLINK.md @@ -5,15 +5,17 @@ Before installing the TTN Packet Forwarder, we recommend **updating the Station to the latest firmware available**. + [Download and test the TTN Packet Forwarder](#download-test) -+ [Install the TTN Packet Forwardeer](#install) ++ [Install the TTN Packet Forwarder](#install) + [Build the TTN Packet Forwarder](#build) + [Troubleshooting](#troubleshooting) ## Download and test the TTN Packet Forwarder +*Note: Before installing the new packet forwarder, make sure you removed any other packet forwarder installed on your Kerlink IoT Station. If you don't have any important files stored on the disk, the safest way to make sure of that is to update the Station to the latest firmware available, which will reset the file system in the process.* + 1. Download the [Kerlink build](https://ttnreleases.blob.core.windows.net/packet_forwarder/master/kerlink-iot-station-pktfwd.zip) of the packet forwarder. -2. In the folder, you will find several files: a `create-kerlink-package.sh` script and a binary file, that we will call `packet-forwarder`. +2. In the folder, you will find several files: a `create-package.sh` script and a binary file, that we will call `packet-forwarder`. The binary is sufficient for a testing use - if you wish to try the TTN packet forwarder, just copy the binary on the Station, and execute: @@ -34,19 +36,19 @@ This section covers permanent installation of the TTN Packet Forwarder on a Kerl ### Packaging the TTN Packet Forwarder -Download the [Kerlink build](https://ttnreleases.blob.core.windows.net/packet-forwarder/master/kerlink-iot-station-pktfwd.zip) of the packet forwarder. Execute the `create-kerlink-package.sh` script with the binary inside as an argument: +Download the [Kerlink build](https://ttnreleases.blob.core.windows.net/packet-forwarder/master/kerlink-iot-station-pktfwd.tar.gz) of the packet forwarder. Execute the `create-package.sh` script with the binary inside as an argument: ```bash -$ ./create-kerlink-package.sh packet-forwarder +$ ./create-package.sh packet-forwarder [...] # The script will ask you several questions to configure the packet forwarder. -./create-kerlink-package.sh: Kerlink DOTA package complete. +./create-package.sh: Kerlink DOTA package complete. ``` -A `kerlink-release` folder will appear in the folder you are in: +A `kerlink-release-` folder will appear in the folder you are in: ```bash -$ cd kerlink-release && tree +$ cd kerlink-release- && tree . ├── dota_ttn-pkt-fwd.tar.gz ├── INSTALL.md diff --git a/docs/INSTALL_INSTRUCTIONS/MULTITECH.md b/docs/INSTALL_INSTRUCTIONS/MULTITECH.md index 7a11cf3f..a174d1f9 100644 --- a/docs/INSTALL_INSTRUCTIONS/MULTITECH.md +++ b/docs/INSTALL_INSTRUCTIONS/MULTITECH.md @@ -4,11 +4,20 @@ ## Download and install -1. Download the [Multitech Conduit package](https://ttnreleases.blob.core.windows.net/packet_forwarder/master/multitech-conduit-pktfwd.zip) of the packet forwarder. +*Note: Before installing the new packet forwarder, make sure you removed any other packet forwarder installed on your Multitech Conduit.* -2. In the archive, you will find an `.ipk` file, as well as the executable binary. Unless you want to test the packet forwarder before installing it, you won't need to use the binary. Copy the `.ipk` file on the Multitech mConduit, using either a USB stick or through the network. +1. Download the [Multitech Conduit package](https://ttnreleases.blob.core.windows.net/packet_forwarder/master/multitech-conduit-pktfwd.tar.gz) of the packet forwarder. -3. Install the package, configure the packet forwarder, then start it: +2. In the archive, you will find an `create-package.sh` file, a `multitech-installer.sh`, as well as the executable binary. Execute the `create-package.sh` file, with the binary as a first argument: + +```bash +$ ./create-package.sh +[...] +# Following the instructions of the wizard +./create-package.sh: package available at ttn-pkt-fwd.ipk +``` + +3. Copy the package on the Multitech Conduit, using either a USB key or `scp` if you have an SSH connection to the Multitech Conduit. Install the package, configure the packet forwarder, then start it: ```bash $ opkg install ttn-pkt-fwd.ipk @@ -63,12 +72,13 @@ The binary will then be available in the `release/` folder. ### Building the package -To build the package, use the `create-kerlink-package.sh` script: +To build the package, use the `scripts/multitech/create-package.sh` script: ```bash -$ ./scripts/create-kerlink-package.sh release/packet-forwarder-linux-arm-multitech-ftdi +$ ./scripts/multitech/create-package.sh release/ [...] -./scripts/create-kerlink-package.sh: package available at ttn-pkt-fwd-multitech.ipk +# Following the instructions of the wizard +./create-package.sh: package available at ttn-pkt-fwd.ipk ``` The package will then be available at the specified path. diff --git a/docs/INSTALL_INSTRUCTIONS/TOOLCHAINS.md b/docs/INSTALL_INSTRUCTIONS/TOOLCHAINS.md index ba652049..60242124 100644 --- a/docs/INSTALL_INSTRUCTIONS/TOOLCHAINS.md +++ b/docs/INSTALL_INSTRUCTIONS/TOOLCHAINS.md @@ -9,20 +9,20 @@ Once the images are built on your personal machine, you can set up your own CI p ## Kerlink IoT Station -To build the `registry.gitlab.com/thethingsindustries/packet-forwarder/klk-toolchain` image, you will need access to Kerlink's Wirnet Station wiki. +To build the `registry.gitlab.com/thethingsnetwork/packet_forwarder/klk-toolchain` image, you will need access to Kerlink's Wirnet Station wiki. 1. On Kerlink's Wirnet Station Wiki, click on *Resources*, scroll down to *Tools*, then download the toolchain you need, depending on the firmware of your Station. 2. In most cases, the archive will hold a `arm-2011.03-wirgrid` folder. Copy this folder in `packet-forwarder/scripts/toolchains`. -3. Build the image: `docker build . -t registry.gitlab.com/thethingsindustries/packet-forwarder/klk-toolchain -f Dockerfile.kerlink-iot-station`. The content of the toolchain will be copied to form the image. +3. Build the image: `docker build . -t registry.gitlab.com/thethingsnetwork/packet_forwarder/klk-toolchain -f Dockerfile.kerlink-iot-station`. The content of the toolchain will be copied to form the image. ## Multitech Conduit mLinux -The Multitech Conduit image, tagged `registry.gitlab.com/thethingsindustries/packet-forwarder/multitech-toolchain`, does not need access to any private resource, and can be built solely with its Dockerfile: +The Multitech Conduit image, tagged `registry.gitlab.com/thethingsnetwork/packet_forwarder/multitech-toolchain`, does not need access to any private resource, and can be built solely with its Dockerfile: ```bash -$ docker build . -t registry.gitlab.com/thethingsindustries/packet-forwarder/multitech-toolchain -f Dockerfile.multitech +$ docker build . -t registry.gitlab.com/thethingsnetwork/packet_forwarder/multitech-toolchain -f Dockerfile.multitech ``` *Note: depending on the Docker storage driver on which the image is built, the SDK extraction can fail. In that case, you might want to change the storage driver to `aufs`.* diff --git a/pktfwd/gpio.go b/pktfwd/gpio.go new file mode 100644 index 00000000..fea136e7 --- /dev/null +++ b/pktfwd/gpio.go @@ -0,0 +1,30 @@ +package pktfwd + +import ( + "time" + + "github.com/pkg/errors" + "github.com/stianeikeland/go-rpio" +) + +const gpioTimeMargin = 100 * time.Millisecond + +// ResetPin resets the specified pin +func ResetPin(pinNumber int) error { + err := rpio.Open() + if err != nil { + return errors.Wrap(err, "couldn't get GPIO access") + } + + pin := rpio.Pin(uint8(pinNumber)) + pin.Output() + time.Sleep(gpioTimeMargin) + pin.Low() + time.Sleep(gpioTimeMargin) + pin.High() + time.Sleep(gpioTimeMargin) + pin.Low() + time.Sleep(gpioTimeMargin) + + return errors.Wrap(rpio.Close(), "couldn't close GPIO access") +} diff --git a/pktfwd/gps.go b/pktfwd/gps.go index 29c00e7e..1c399f23 100644 --- a/pktfwd/gps.go +++ b/pktfwd/gps.go @@ -18,7 +18,7 @@ import ( // tries to activate it. func enableGPS(ctx log.Interface, gpsPath string) (err error) { if gpsPath == "" { - ctx.Warn("No GPS configured, ignoring") + ctx.Warn("No GPS chip configured, ignoring") return nil } diff --git a/pktfwd/manager.go b/pktfwd/manager.go index 9448275e..9a559431 100644 --- a/pktfwd/manager.go +++ b/pktfwd/manager.go @@ -40,9 +40,11 @@ type Manager struct { func NewManager(ctx log.Interface, conf util.Config, netClient NetworkClient, gpsPath string, runConfig TTNConfig) Manager { isGPS := gpsPath != "" - statusMgr := NewStatusManager(ctx, netClient.FrequencyPlan(), runConfig.GatewayDescription, isGPS) + statusMgr := NewStatusManager(ctx, netClient.FrequencyPlan(), runConfig.GatewayDescription, isGPS, netClient.DefaultLocation()) + bootTimeSetters := NewMultipleBootTimeSetter() bootTimeSetters.Add(statusMgr) + return Manager{ ctx: ctx, conf: conf, diff --git a/pktfwd/network.go b/pktfwd/network.go index 173a7bfa..6b9d07cd 100644 --- a/pktfwd/network.go +++ b/pktfwd/network.go @@ -33,6 +33,7 @@ type TTNConfig struct { } type TTNClient struct { + antennaLocation *account.AntennaLocation currentRouterConn *grpc.ClientConn ctx log.Interface uplinkStream router.UplinkStream @@ -61,6 +62,7 @@ type NetworkClient interface { Downlinks() <-chan *router.DownlinkMessage GatewayID() string Ping() (time.Duration, error) + DefaultLocation() *account.AntennaLocation Stop() } @@ -95,6 +97,10 @@ func connectToRouter(ctx log.Interface, discoveryClient discovery.Client, router return announcement.Dial() } +func (c *TTNClient) DefaultLocation() *account.AntennaLocation { + return c.antennaLocation +} + func (c *TTNClient) FrequencyPlan() string { return c.frequencyPlan } @@ -295,6 +301,7 @@ func (c *TTNClient) fetchAccountServerInfo(gatewayID string) error { if err != nil { return errors.Wrap(err, "Account server error") } + c.antennaLocation = gw.AntennaLocation c.token = gw.Token.AccessToken c.frequencyPlan = gw.FrequencyPlan return nil @@ -381,6 +388,9 @@ func (c *TTNClient) SendStatus(status gateway.Status) error { "Load15": status.GetOs().GetLoad_15(), "CpuPercentage": status.GetOs().GetCpuPercentage(), "MemoryPercentage": status.GetOs().GetMemoryPercentage(), + "Latitude": status.GetGps().GetLatitude(), + "Longitude": status.GetGps().GetLongitude(), + "Altitude": status.GetGps().GetAltitude(), }).Info("Sending status to the network server") err = c.statusStream.Send(&status) if err != nil { diff --git a/pktfwd/status.go b/pktfwd/status.go index 164ff474..c2edc1ab 100644 --- a/pktfwd/status.go +++ b/pktfwd/status.go @@ -8,6 +8,7 @@ import ( "sync/atomic" "time" + "github.com/TheThingsNetwork/go-account-lib/account" "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/packet_forwarder/util" "github.com/TheThingsNetwork/packet_forwarder/wrapper" @@ -26,10 +27,14 @@ type StatusManager interface { GenerateStatus(rtt time.Duration) (*gateway.Status, error) } -func NewStatusManager(ctx log.Interface, frequencyPlan string, gatewayDescription string, isGPS bool) StatusManager { +func NewStatusManager(ctx log.Interface, frequencyPlan string, gatewayDescription string, isGPSChip bool, antennaLocation *account.AntennaLocation) StatusManager { + if antennaLocation == nil { + ctx.Warn("Antenna location unavailable from the account server") + } return &statusManager{ + antennaLocation: antennaLocation, ctx: ctx, - isGPS: isGPS, + isGPSChip: isGPSChip, rxIn: 0, rxOk: 0, txIn: 0, @@ -40,8 +45,9 @@ func NewStatusManager(ctx log.Interface, frequencyPlan string, gatewayDescriptio } type statusManager struct { + antennaLocation *account.AntennaLocation ctx log.Interface - isGPS bool + isGPSChip bool rxIn uint32 rxOk uint32 txIn uint32 @@ -137,18 +143,33 @@ func (s *statusManager) GenerateStatus(rtt time.Duration) (*gateway.Status, erro Os: osInfo, } - if s.isGPS { // GPS enabled - gpsCoordinates, err := wrapper.GetGPSCoordinates() - if err != nil { - s.ctx.WithError(err).Warn("Unable to retrieve GPS coordinates") + coordinates := new(gateway.GPSMetadata) + if s.antennaLocation != nil { // Antenna location accessible from the account server + if s.antennaLocation.Latitude != nil { + coordinates.Latitude = float32(*s.antennaLocation.Latitude) + } + if s.antennaLocation.Longitude != nil { + coordinates.Longitude = float32(*s.antennaLocation.Longitude) + } + if s.antennaLocation.Altitude != nil { + coordinates.Altitude = int32(*s.antennaLocation.Altitude) } + } - status.Gps = &gateway.GPSMetadata{ - Latitude: float32(gpsCoordinates.Latitude), - Longitude: float32(gpsCoordinates.Longitude), - Altitude: int32(gpsCoordinates.Altitude), + if s.isGPSChip { // GPS chip available + gpsChipCoordinates, err := wrapper.GetGPSCoordinates() + if err != nil { + s.ctx.WithError(err).Warn("Unable to retrieve GPS coordinates from the GPS hardware") + } else { + coordinates = &gateway.GPSMetadata{ + Latitude: float32(gpsChipCoordinates.Latitude), + Longitude: float32(gpsChipCoordinates.Longitude), + Altitude: int32(gpsChipCoordinates.Altitude), + } } } + status.Gps = coordinates + return status, nil } diff --git a/scripts/kerlink/create-kerlink-package.sh b/scripts/kerlink/create-package.sh similarity index 56% rename from scripts/kerlink/create-kerlink-package.sh rename to scripts/kerlink/create-package.sh index 41b0eebf..fcdc8fd4 100755 --- a/scripts/kerlink/create-kerlink-package.sh +++ b/scripts/kerlink/create-package.sh @@ -12,18 +12,38 @@ RED='\033[0;31m' NC='\033[0m' # No Color -usage_str="usage: create-kerlink-package.sh [path to the Kerlink build] +usage_str="usage: create-package.sh [path to the Kerlink build] ([Gateway ID] [Gateway key]) -example: ./create-kerlink-package.sh packet-forwarder-kerlink" +example: ./create-package.sh packet-forwarder-kerlink" + +if [[ -z "$(which tar)" ]] ; then + echo "$0: tar required to run this script." + exit 1 +fi # Getting path to the kerlink binary BINARY_PATH="$1" if [[ -z "$BINARY_PATH" ]] ; then - echo "$0: $usage_str" + echo "$0: $usage_str" &> "$OUTPUT" exit 1 fi -WORKDIR="/tmp/packet-forwarder-kerlink" +if [[ $(which openssl) =~ "not found" ]] ; then + random_string="-$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;)" +else + random_string="-$(openssl rand -base64 15)" + random_string="${random_string//\/}" +fi + +gatewayID="$2" +gatewayKey="$3" +if [[ ! -z "$4" ]] ; then + OUTPUT="/dev/null" +else + OUTPUT="/dev/stdout" +fi + +WORKDIR="/tmp/packet-forwarder-kerlink$random_string" BASE="/mnt/fsuser-1/ttn-pkt-fwd" CFG_FILENAME="config.yml" PKTFWD_DESTDIR="$WORKDIR$BASE" @@ -33,8 +53,6 @@ mkdir -p "$PKTFWD_DESTDIR" cp "$BINARY_PATH" "$PKTFWD_DESTDIR/ttn-pkt-fwd" configure () { - DESTADDRESS="$1" - printf "%s: Gateway ID:\n> " "$0" read -r gatewayID @@ -42,36 +60,40 @@ configure () { printf "%s: Gateway Key:\n> " "$0" read -r -s gatewayKey - - echo "id: \"${gatewayID}\" -key: \"${gatewayKey}\"" > "$DESTADDRESS" } -echo "$0: If you haven't registered your gateway yet, register it on the console or with \`ttnctl\`, using the gateway connector protocol." - -if [[ -f "$HOME/.pktfwd.yml" ]] ; then - while true; do - read -r -p "$0: Local packet forwarder configuration found (in $HOME/.pktfwd.yml). Do you want to include it in the package? " yn - case $yn in - [Yy]* ) cp "$HOME/.pktfwd.yml" "$PKTFWD_DESTDIR/$CFG_FILENAME"; echo "$0: Local configuration included."; break;; - [Nn]* ) echo "$0: Local packet forwarder configuration not copied, please enter the new configuration."; configure "$PKTFWD_DESTDIR/$CFG_FILENAME"; break;; - * ) echo "Please answer [y]es or [n]o.";; - esac - done -else - configure "$PKTFWD_DESTDIR/$CFG_FILENAME" +if [[ -z "$gatewayID" && -z "$gatewayKey" ]] ; then + echo "$0: If you haven't registered your gateway yet, register it on the console or with \`ttnctl\`, using the gateway connector protocol." + + if [[ -f "$HOME/.pktfwd.yml" ]] ; then + while true; do + read -r -p "$0: Local packet forwarder configuration found (in $HOME/.pktfwd.yml). Do you want to include it in the package? " yn + case $yn in + [Yy]* ) cp "$HOME/.pktfwd.yml" "$PKTFWD_DESTDIR/$CFG_FILENAME"; COPIED_CONFIG="1"; echo "$0: Local configuration included."; break;; + [Nn]* ) echo "$0: Local packet forwarder configuration not copied, please enter the new configuration."; configure "$PKTFWD_DESTDIR/$CFG_FILENAME"; break;; + * ) echo "Please answer [y]es or [n]o.";; + esac + done + else + configure + fi + + echo "$0: Configuration saved - see INSTALL.md if you wish to modify this configuration later" fi -echo "$0: Configuration saved - see INSTALL.md if you wish to modify this configuration later" +if [[ -z "$COPIED_CONFIG" ]] ; then + echo "id: \"${gatewayID}\" +key: \"${gatewayKey}\"" > "$PKTFWD_DESTDIR/$CFG_FILENAME" +fi -echo "$0: Fetching TLS root certificate" +echo "$0: Fetching TLS root certificate" &> "$OUTPUT" SSL_WORKDIR="$WORKDIR/etc/ssl/certs" mkdir -p "$SSL_WORKDIR" pushd "$SSL_WORKDIR" &> /dev/null wget "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt" &> /dev/null popd &> /dev/null -echo "$0: Generating startup script" +echo "$0: Generating startup script" &> "$OUTPUT" echo "#!/bin/sh BASE=\"$BASE\" @@ -86,7 +108,7 @@ export GOGC=30 ./ttn-pkt-fwd start --config=config.yml" > "$PKTFWD_DESTDIR/ttn-pkt-fwd.sh" chmod +x "$PKTFWD_DESTDIR/ttn-pkt-fwd.sh" -echo "$0: Generating DOTA manifest" +echo "$0: Generating DOTA manifest" &> "$OUTPUT" echo " @@ -95,19 +117,20 @@ echo " " > "$PKTFWD_DESTDIR/manifest.xml" -echo "$0: Startup and init scripts, build and manifests saved. Starting packaging" +echo "$0: Startup and init scripts, build and manifests saved. Starting packaging" &> "$OUTPUT" -mkdir kerlink-release +release_folder="kerlink-release$random_string" +mkdir "$release_folder" -DOTA_ARCHIVE="dota_ttn-pkt-fwd.tar.gz" +DOTA_ARCHIVE="dota_ttn-pkt-fwd$random_string.tar.gz" pushd "$WORKDIR" &> /dev/null tar -cvzf "$DOTA_ARCHIVE" "mnt" "etc" &> /dev/null popd &> /dev/null -mv "$WORKDIR/$DOTA_ARCHIVE" kerlink-release +mv "$WORKDIR/$DOTA_ARCHIVE" "$release_folder" wget "https://cdn.rawgit.com/TheThingsNetwork/kerlink-station-firmware/16f6325e/dota/produsb.zip" &> /dev/null unzip produsb.zip &> /dev/null # Creates a produsb.sh -mv produsb.sh kerlink-release +mv produsb.sh "$release_folder" rm produsb.zip echo "# Install the TTN Packet Forwarder on a Kerlink IoT Station @@ -123,8 +146,12 @@ The Kerlink IoT Station build of the TTN packet forwarder is packaged within an ## Method 2: Network transfer 1. Copy \`$DOTA_ARCHIVE\` in the \`/mnt/fsuser-1/dota\` folder on the Station, using \`scp\`. -2. Reboot the Station with \`reboot\` to trigger the DOTA installation. Do not try to shutdown the machine until the DOTA installation is complete! You can see the progress by pushing the \"Test\" button on the Station - as long as MOD1 and MOD2 are blinking, installation is in progress. It should take between 2 and 5 minutes." > kerlink-release/INSTALL.md +2. Reboot the Station with \`reboot\` to trigger the DOTA installation. Do not try to shutdown the machine until the DOTA installation is complete! You can see the progress by pushing the \"Test\" button on the Station - as long as MOD1 and MOD2 are blinking, installation is in progress. It should take between 2 and 5 minutes." > "$release_folder/INSTALL.md" rm -rf "$WORKDIR" -printf "%s: ${RED}Kerlink DOTA package ready.${NC} The package is available in %s/kerlink-release. Consult the INSTALL.md file to know how to install the package on your Kerlink IoT Station!\n" "$0" "$PWD" +printf "%s: ${RED}Kerlink DOTA package ready.${NC} The package is available in %s/$release_folder. Consult the INSTALL.md file to know how to install the package on your Kerlink IoT Station!\n" "$0" "$PWD" &> "$OUTPUT" + +if [[ ! -z "$4" ]] ; then + printf "%s/%s/%s" "$(pwd)" "$release_folder" "$DOTA_ARCHIVE" +fi diff --git a/scripts/multitech/create-multitech-package.sh b/scripts/multitech/create-package.sh similarity index 66% rename from scripts/multitech/create-multitech-package.sh rename to scripts/multitech/create-package.sh index e4920c92..a45cecf4 100755 --- a/scripts/multitech/create-multitech-package.sh +++ b/scripts/multitech/create-package.sh @@ -21,7 +21,7 @@ if [[ ! -f "$multitech_installer_file" ]] ; then exit 1 fi -usage_str="usage: create-kerlink-package.sh [path to the Multitech build] +usage_str="usage: create-kerlink-package.sh [path to the Multitech build] ([Gateway ID] [Gateway key]) example: ./create-kerlink-package.sh packet-forwarder-multitech" @@ -32,20 +32,72 @@ if [[ -z "$INITIAL_BINARY_PATH" ]] ; then exit 1 fi -WORKDIR="/tmp/packet-forwarder-multitech" +if [[ $(which openssl) =~ "not found" ]] ; then + random_string="-$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-32};echo;)" +else + random_string="-$(openssl rand -base64 15)" + random_string="${random_string//\/}" +fi + +gatewayID="$2" +gatewayKey="$3" +if [[ ! -z "$4" ]] ; then + OUTPUT="/dev/null" +else + OUTPUT="/dev/stdout" +fi + +echo "$0: Creating file tree" &> "$OUTPUT" + +WORKDIR="/tmp/packet-forwarder-multitech-$random_string" BASE="/usr/bin" PKTFWD_DESTDIR="$WORKDIR$BASE" BINARY_NAME="ttn-pkt-fwd" -echo "$0: Creating file tree" mkdir -p "$PKTFWD_DESTDIR" # /usr/bin mkdir -p "$WORKDIR/etc/init.d" # /etc/init.d -mkdir -p "$WORKDIR/var/config/ttn-pkt-fwd" -touch "$WORKDIR/var/config/ttn-pkt-fwd/config.yml" +mkdir -p "$WORKDIR/usr/cfg" +local_config_file="/usr/cfg/config.yml" +config_file="$WORKDIR$local_config_file" +touch "$config_file" cp "$INITIAL_BINARY_PATH" "$PKTFWD_DESTDIR/$BINARY_NAME" chmod +x "$PKTFWD_DESTDIR/$BINARY_NAME" -echo "$0: Generating control file" +configure () { + printf "%s: Gateway ID:\n> " "$0" + + read -r gatewayID + + printf "%s: Gateway Key:\n> " "$0" + + read -r -s gatewayKey +} + +if [[ -z "$gatewayID" && -z "$gatewayKey" ]] ; then + echo "$0: If you haven't registered your gateway yet, register it on the console or with \`ttnctl\`, using the gateway connector protocol." &> "$OUTPUT" + + if [[ -f "$HOME/.pktfwd.yml" ]] ; then + while true; do + read -r -p "$0: Local packet forwarder configuration found (in $HOME/.pktfwd.yml). Do you want to include it in the package? " yn + case $yn in + [Yy]* ) cp "$HOME/.pktfwd.yml" "$config_file"; COPIED_CONFIG="1"; echo "$0: Local configuration included." &> "$OUTPUT"; break;; + [Nn]* ) echo "$0: Local packet forwarder configuration not copied, please enter the new configuration." &> "$OUTPUT"; configure "$PKTFWD_DESTDIR/$CFG_FILENAME"; break;; + * ) echo "Please answer [y]es or [n]o." &> "$OUTPUT";; + esac + done + else + configure + fi + + echo "$0: Configuration packaged." &> "$OUTPUT" +fi + +if [[ -z "$COPIED_CONFIG" ]] ; then + echo "id: \"${gatewayID}\" +key: \"${gatewayKey}\"" > "$config_file" +fi + +echo "$0: Generating control file" &> "$OUTPUT" echo "Package: ttn-pkt-fwd Version: 2.0.0 Description: TTN Packet Forwarder @@ -59,7 +111,7 @@ Homepage: https://github.com/TheThingsNetwork/packet_forwarder Depends: libmpsse (>= 1.3), libc6 (>= 2.19) Source: git://github.com/TheThingsNetwork/packet_forwarder.git;protocol=git" > "$WORKDIR/control" -echo "$0: Generating service script" +echo "$0: Generating service script" &> "$OUTPUT" echo "#!/bin/bash NAME=\"$BINARY_NAME\" @@ -86,7 +138,7 @@ card_found() { if [ \"\$lora_id\" = \"\$lora_us_id\" ] || [ \"\$lora_id\" = \"\$lora_eu_id\" ]; then echo \"Found lora card \$lora_id\" return 1 - else + else return 0 fi } @@ -142,8 +194,8 @@ configure() { mkdir -p \$conf_dir if [[ ! -f \"\$conf_dir/config.yml\" ]] ; then touch \"\$conf_dir/config.yml\" + \$pkt_fwd configure \"\$conf_dir/config.yml\" --config=\"\$conf_dir/config.yml\" fi - \$pkt_fwd configure \"\$conf_dir/config.yml\" --config=\"\$conf_dir/config.yml\" exit } @@ -183,14 +235,22 @@ chmod +x "$WORKDIR/postinst" cp "$multitech_installer_file" "$PKTFWD_DESTDIR" chmod +x "$PKTFWD_DESTDIR/multitech-installer.sh" +FILENAME="ttn-pkt-fwd$random_string.ipk" + pushd "$WORKDIR" &> /dev/null tar -czvf "data.tar.gz" "etc" "var" "usr" &> /dev/null tar -czvf "control.tar.gz" "control" "postinst" &> /dev/null -tar -czvf "ttn-pkt-fwd.ipk" "data.tar.gz" "control.tar.gz" &> /dev/null +tar -czvf "$FILENAME" "data.tar.gz" "control.tar.gz" &> /dev/null popd &> /dev/null -mv "$WORKDIR/ttn-pkt-fwd.ipk" "$PWD" +release_folder="multitech-release$random_string" +mkdir "$release_folder" +mv "$WORKDIR/$FILENAME" "$PWD/$release_folder" -rm -rf $WORKDIR +rm -rf "$WORKDIR" -echo "$0: package available at $PWD/ttn-pkt-fwd.ipk" +echo "$0: package available at $PWD/$release_folder/$FILENAME" &> "$OUTPUT" + +if [[ ! -z "$4" ]] ; then + printf "%s/%s/%s" "$PWD" "$release_folder" "$FILENAME" +fi diff --git a/scripts/rpi/install-systemd.sh b/scripts/rpi/install-systemd.sh new file mode 100644 index 00000000..9e0eb30d --- /dev/null +++ b/scripts/rpi/install-systemd.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +if [[ -z "$1" ]] ; then + # No binary specified + echo "$0: No binary specified." + exit 1 +fi + +binary="$1" +binary_name=`basename "$binary"` +binary_directory=`dirname "$binary"` +pushd "$binary_directory" +absolute_binary_directory="$(pwd)" +absolute_binary_path="$absolute_binary_directory/$binary_name" +popd + +config="$2" + +if [[ -z "$config" ]] ; then + echo "$0: No configuration file to use specified." + exit 1 +fi + +config_name=`basename "$config"` +config_directory=`dirname "$config"` +pushd "$config_directory" +absolute_config_directory="$(pwd)" +absolute_config_path="$absolute_config_directory/$config_name" +popd + +echo "[Unit] +Description=TTN Packet Forwarder Service + +[Install] +WantedBy=multi-user.target + +[Service] +TimeoutStartSec=infinity +Type=simple +TimeoutSec=infinity +RestartSec=10 +WorkingDirectory=$absolute_binary_directory +ExecStart=$absolute_binary_path start --config=\"$absolute_config_path\" +Restart=always +BusName=org.thethingsnetwork.ttn-pkt-fwd" > /etc/systemd/system/ttn-pkt-fwd.service + +echo "$0: Installation of the systemd service complete." diff --git a/util/system.go b/util/system.go index 33efabf4..945d9155 100644 --- a/util/system.go +++ b/util/system.go @@ -2,7 +2,13 @@ package util -import "time" +import ( + "os" + "path" + "time" + + "github.com/spf13/viper" +) // TXTimestamp allows to wrap a router.DownlinkMessage.GatewayConfiguration.Timestamp type TXTimestamp uint32 @@ -14,3 +20,34 @@ func (ts TXTimestamp) GetAsDuration() time.Duration { func TXTimestampFromDuration(d time.Duration) TXTimestamp { return TXTimestamp(d.Nanoseconds() / 1000.0) } + +func GetConfigFile() string { + flag := viper.GetString("config") + + home := os.Getenv("HOME") + homeyml := "" + homeyaml := "" + + if home != "" { + homeyml = path.Join(home, ".pktfwd.yml") + homeyaml = path.Join(home, ".pktfwd.yaml") + } + + try_files := []string{ + flag, + homeyml, + homeyaml, + } + + // find a file that exists, and use that + for _, file := range try_files { + if file != "" { + if _, err := os.Stat(file); err == nil { + return file + } + } + } + + // no file found, set up correct fallback + return homeyml +} diff --git a/vendor/vendor.json b/vendor/vendor.json index ca57c43a..01ce25da 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -488,6 +488,12 @@ "revision": "7538d73b4eb9511d85a9f1dfef202eeb8ac260f4", "revisionTime": "2017-02-17T16:38:17Z" }, + { + "checksumSHA1": "dyI8tS2bXHlHpPIp6VGZ/HXKyJQ=", + "path": "github.com/stianeikeland/go-rpio", + "revision": "896db2ee1c7f95240dd6a09e56edf9cece107a34", + "revisionTime": "2015-12-08T00:50:14Z" + }, { "checksumSHA1": "ZaU56svwLgiJD0y8JOB3+/mpYBA=", "path": "golang.org/x/crypto/ssh/terminal", diff --git a/wrapper/concentrator_halV1.go b/wrapper/concentrator_halV1.go index 547c6edf..4608fd6f 100644 --- a/wrapper/concentrator_halV1.go +++ b/wrapper/concentrator_halV1.go @@ -277,17 +277,17 @@ func SetFSKChannel(ctx log.Interface, fskChan util.ChannelConf) error { case val > 0 && val <= 7800: cFSKChan.bandwidth = loraChannelBandwidths[7800] case val > 7800 && val <= 15600: - cFSKChan.bandwidth = loraChannelBandwidths[7800] + cFSKChan.bandwidth = loraChannelBandwidths[15600] case val > 15600 && val <= 31200: - cFSKChan.bandwidth = loraChannelBandwidths[7800] + cFSKChan.bandwidth = loraChannelBandwidths[31200] case val > 31200 && val <= 62500: - cFSKChan.bandwidth = loraChannelBandwidths[7800] + cFSKChan.bandwidth = loraChannelBandwidths[62500] case val > 62500 && val <= 125000: - cFSKChan.bandwidth = loraChannelBandwidths[7800] + cFSKChan.bandwidth = loraChannelBandwidths[125000] case val > 125000 && val <= 250000: - cFSKChan.bandwidth = loraChannelBandwidths[7800] + cFSKChan.bandwidth = loraChannelBandwidths[250000] case val > 250000 && val <= 500000: - cFSKChan.bandwidth = loraChannelBandwidths[7800] + cFSKChan.bandwidth = loraChannelBandwidths[500000] } if C.lgw_rxif_setconf(9, cFSKChan) != C.LGW_HAL_SUCCESS {