├── CONFIGURATION.md ├── Dockerfile.template ├── Dockerfile.template.metering ├── README.md ├── dev └── build.sh ├── gwexporter.tgz ├── run.py ├── start.sh └── start.sh.metering /CONFIGURATION.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | ## Required global variables 3 | * GW_ID required 4 | * GW_KEY required 5 | * This gateway ID and gateway Key for TTN will be used to fetch the gateway's information form the TTN console. When SERVER_TTN is true, this will also be used to conenct and forward packets to TTN. 6 | 7 | ## Optional global variables 8 | * GW_EUI - by default an EUI will be generated from the Raspberry Pi's ethernet MAC address. 9 | * The unique identifier for this gateway. It is used in LoRaWAN networks to identify where the packet was received and to address where a downlink packet needs to be sent from. 10 | * GW_CONTACT_EMAIL - default an empty string 11 | * The gateway owner's contact information. Will be overridden by the value from the TTN console. 12 | * GW_DESCRIPTION optional - default an empty string 13 | * A description of this gateway. Will be overridden by the value from the TTN console. 14 | * GW_RESET_PIN - default 22 15 | * The physical pin number on the Raspberry Pi to which the concentrator's reset is connected. See the [README](README.md) file for a description and a list of common values. 16 | * GW_GPS optional - default False 17 | * If true, use the hardware GPS. 18 | * If false, 19 | * use either fake gps if a location was configured in the TTN console, 20 | * otherwise try using fake gps with the reference location as set via environment variables, 21 | * otherwise don't send coordinates. 22 | * GW_GPS_PORT optional - default /dev/ttyAMA0 23 | * The UART to which the hardware GPS is connected to. 24 | * GW_REF_LATITUDE optional - default 0 25 | * The latitude to use for fake gps if the coordinates are not set in the TTN console. 26 | * GW_REF_LONGITUDE optional - default 0 27 | * The longitude to use for fake gps if the coordinates are not set in the TTN console. 28 | * GW_REF_ALTITUDE optional - default 0 29 | * The altitude to use for fake gps if the coordinates are not set in the TTN console. 30 | * GW_LOGGER optional - default false 31 | * Write a line to the terminal whenever a packet is received. ex: `08:54:37 INFO: [stats] received packet with valid CRC from mote: 26011C51 (fcnt=7)` 32 | * GW_FWD_CRC_ERR optional - default false 33 | * Forward packets with an invalid CRC. 34 | * GW_FWD_CRC_VAL optional - default true. 35 | * Forward packets with a valid CRC. 36 | * GW_DOWNSTREAM optional - default true. 37 | * Globally enable (or disable) transmissions for this gateway. 38 | * GW_ANTENNA_GAIN optional - default 0. 39 | * Set this to the dBd gain of your antenna. The dBd value is the dBi value minus 2.15dB, ie. dBd = dBi-2.15. This is used to reduce the TX power of the concentrator to stay within the legal limits. 40 | * GW_PUSH_TIMEOUT optional - default 100. 41 | * Sets the timeout in milliseconds to wait for an acknowledgement of PUSH messages from the upstream server. If your gateway is located in South Africa this needs to be set to at least more than 170ms to compensate for the internet latency to Europe. 42 | * GW_AUTOQUIT_THRESHOLD optional - default 0/disabled. 43 | * The number of allowed PULL_ACKs to be missed on Semtech UDP connections before the packet forwarder restarts. 44 | * GW_LOGGER optional - default false. 45 | * Log received LoRaWAN packets to the console. This will increase the amount of logging to Balena and increase data usage. 46 | * FREQ_PLAN_URL optional - default `https://account.thethingsnetwork.org/api/v2/frequency-plans/EU_863_870` 47 | * The URL where the base configuration file and frequency plan should be downloaded from. This is overwritten by the URL given by the TTN account server when using the TTN gateway connector protocol. 48 | * SPI_SPEED optional - default 8000000. 49 | * The SPI bus speed in Herz to use to communicate with the concentrator. 50 | 51 | ## Server variables 52 | All server variables are optional, but when a server is enabled, it is recommended to set all variables to configure it completely. 53 | * SERVER_TTN optional - default true 54 | Should the gateway connect to the TTN backend 55 | * SERVER_TTN_DOWNLINK - default true 56 | Enable downlink transmissions for this server 57 | * ACCOUNT_SERVER_DOMAIN optional - default `account.thethingsnetwork.org` 58 | Domain of the account server to fetch the information from 59 | * ROUTER_MQTT_ADDRESS optional 60 | Override the address of the MQTT broker to connect to - e.g. `router.eu.thethings.network:1883` 61 | 62 | * SERVER_1_ENABLED optional - default false 63 | * SERVER_1_TYPE - default "semtech" 64 | * SERVER_1_ADDRESS 65 | * SERVER_1_PORTUP - only when using type semtech 66 | * SERVER_1_PORTDOWN - only when using type semtech 67 | * SERVER_1_GWID - only when using type ttn 68 | * SERVER_1_GWKEY - only when using type ttn 69 | * SERVER_1_DOWNLINK - default false 70 | 71 | * SERVER_2_ENABLED optional - default false 72 | * SERVER_2_TYPE - default "semtech" 73 | * SERVER_2_ADDRESS 74 | * SERVER_2_PORTUP - only when using type semtech 75 | * SERVER_2_PORTDOWN - only when using type semtech 76 | * SERVER_2_GWID 77 | * SERVER_2_GWKEY 78 | * SERVER_2_DOWNLINK - default false 79 | 80 | * SERVER_3_ENABLED optional - default false 81 | * SERVER_3_TYPE - default "semtech" 82 | * SERVER_3_ADDRESS 83 | * SERVER_3_PORTUP - only when using type semtech 84 | * SERVER_3_PORTDOWN - only when using type semtech 85 | * SERVER_3_GWID 86 | * SERVER_3_GWKEY 87 | * SERVER_3_DOWNLINK - default false 88 | 89 | As long as `SERVER_TTN` is set to false, you can also: 90 | * SERVER_0_ENABLED optional - default false 91 | * SERVER_0_TYPE - default "semtech" 92 | * SERVER_0_ADDRESS 93 | * SERVER_0_PORTUP - only when using type semtech 94 | * SERVER_0_PORTDOWN - only when using type semtech 95 | * SERVER_0_GWID 96 | * SERVER_0_GWKEY 97 | * SERVER_0_DOWNLINK - default false 98 | 99 | ## Example for using only legacy forwarder 100 | 101 | | Variable | Value | 102 | | ----------------- | ----- | 103 | | SERVER_TTN | false | 104 | | SERVER_1_ADDRESS | bridge.eu.thethings.network | 105 | | SERVER_1_ENABLED | true | 106 | | SERVER_1_PORTDOWN | 1700 | 107 | | SERVER_1_PORTUP | 1700 | 108 | | FREQ_PLAN_URL | https://account.thethingsnetwork.org/api/v2/frequency-plans/EU_863_870 | 109 | | GW_REF_LATITUDE | -33.1 | 110 | | GW_REF_LONGITUDE | 18.9 | 111 | | GW_REF_ALTITUDE | 190 | 112 | 113 | ## Note about boolean values 114 | 115 | Use `true` and `false` as lower case words to enable or disable features via environment variables. Any other format will not be interpreted correctly. 116 | 117 | # Logal debugging 118 | ``` 119 | docker run --device /dev/ttyAMA0:/dev/ttyAMA0 --device /dev/mem:/dev/mem --privileged -e GW_TYPE="imst-ic880a-spi" -e GW_DESCRIPTION="test gateway" -e GW_CONTACT_EMAIL="" -e GW_ID="" -e GW_KEY="" newforwarder 120 | 121 | 122 | ``` 123 | Make a copy of `Dockerfile.template` to `Dockerfile`. 124 | ``` 125 | FROM resin/raspberrypi-buildpack-deps 126 | ... 127 | CMD ["bash"] 128 | ``` 129 | -------------------------------------------------------------------------------- /Dockerfile.template: -------------------------------------------------------------------------------- 1 | FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:latest-build AS buildstep 2 | 3 | WORKDIR /opt/ttn-gateway/ 4 | 5 | # Install required packages 6 | RUN install_packages \ 7 | wget \ 8 | build-essential \ 9 | libc6-dev \ 10 | git pkg-config \ 11 | protobuf-compiler \ 12 | libprotobuf-dev \ 13 | libprotoc-dev \ 14 | automake \ 15 | libtool \ 16 | autoconf \ 17 | python-dev \ 18 | python-rpi.gpio 19 | 20 | COPY dev dev 21 | RUN ./dev/build.sh 22 | 23 | FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:latest-run 24 | 25 | ENV UDEV=off 26 | 27 | WORKDIR /opt/ttn-gateway 28 | 29 | RUN install_packages python-rpi.gpio 30 | 31 | COPY --from=buildstep /opt/ttn-gateway/mp_pkt_fwd . 32 | COPY --from=buildstep /usr/local/lib/libpaho-embed-* /usr/lib/ 33 | COPY --from=buildstep /usr/lib/libttn* /usr/lib/ 34 | 35 | COPY run.py ./ 36 | COPY start.sh ./ 37 | 38 | # run when container lands on device 39 | CMD ["bash", "start.sh"] 40 | -------------------------------------------------------------------------------- /Dockerfile.template.metering: -------------------------------------------------------------------------------- 1 | FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:latest-build AS buildstep 2 | 3 | # Install required packages 4 | RUN install_packages \ 5 | wget \ 6 | build-essential \ 7 | libc6-dev \ 8 | git pkg-config \ 9 | protobuf-compiler \ 10 | libprotobuf-dev \ 11 | libprotoc-dev \ 12 | automake \ 13 | libtool \ 14 | autoconf \ 15 | python-dev \ 16 | python-rpi.gpio 17 | 18 | ENV UDEV=off 19 | 20 | WORKDIR /etc 21 | 22 | # versions 23 | ENV NODE_EXPORTER_VERSION 0.16.0 24 | ENV DIST_ARCH linux-armv6 25 | RUN wget https://github.com/prometheus/node_exporter/releases/download/v${NODE_EXPORTER_VERSION}/node_exporter-${NODE_EXPORTER_VERSION}.${DIST_ARCH}.tar.gz \ 26 | && tar xvfz node_exporter-${NODE_EXPORTER_VERSION}.${DIST_ARCH}.tar.gz \ 27 | && rm node_exporter-${NODE_EXPORTER_VERSION}.${DIST_ARCH}.tar.gz 28 | 29 | COPY gwexporter.tgz /opt/ttn-gateway/gwexporter.tgz 30 | WORKDIR /opt/gwexporter 31 | RUN tar xvzf /opt/ttn-gateway/gwexporter.tgz 32 | RUN wget https://nodejs.org/dist/v8.8.1/node-v8.8.1-linux-armv6l.tar.gz \ 33 | && tar xvzf node-v8.8.1-linux-armv6l.tar.gz \ 34 | && mv node-v8.8.1-linux-armv6l/* . \ 35 | && rm -rf node-v8.8.1-linux-armv6l 36 | 37 | WORKDIR /opt/ttn-gateway/ 38 | COPY dev dev 39 | RUN ./dev/build.sh 40 | 41 | FROM balenalib/%%BALENA_MACHINE_NAME%%-debian:latest-run 42 | 43 | # Install required packages 44 | RUN install_packages python-rpi.gpio 45 | 46 | # Enable systemd 47 | ENV INITSYSTEM on 48 | 49 | # versions 50 | ENV NODE_EXPORTER_VERSION 0.16.0 51 | ENV DIST_ARCH linux-armv6 52 | 53 | WORKDIR /etc 54 | COPY --from=buildstep /etc/node_exporter-${NODE_EXPORTER_VERSION}.${DIST_ARCH} . 55 | 56 | WORKDIR /opt/gwexporter 57 | COPY --from=buildstep /opt/gwexporter . 58 | 59 | WORKDIR /opt/ttn-gateway 60 | COPY --from=buildstep /opt/ttn-gateway/mp_pkt_fwd . 61 | COPY --from=buildstep /usr/local/lib/libpaho-embed-* /usr/lib/ 62 | COPY --from=buildstep /usr/lib/libttn* /usr/lib/ 63 | COPY run.py run.py 64 | 65 | WORKDIR / 66 | 67 | COPY start.sh.metering start.sh 68 | 69 | # run when container lands on device 70 | CMD ["bash", "start.sh"] 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This [balenaCloud](https://www.balena.io/cloud/) (previously resin.io) setup is based on the [Multi-protocol Packet Forwarder by Jac Kersing](https://github.com/kersing/packet_forwarder/tree/master/mp_pkt_fwd). 3 | 4 | An alternative guide to use this balenaCloud setup can be found in the official TTN documentation at: https://www.thethingsnetwork.org/docs/gateways/rak831/ 5 | 6 | ## Difference between Poly-packet-forwarder and Multi-protocol-packet-forwarder 7 | mp-pkt-fwd uses the new protocolbuffers-over-mqtt-over-tcp protocol for gateways, as defined by TTN and used by the TTN kickstarter gateway. Using this protcol the gateway is authenticated, which means it is registered under a specific user and can thus be trusted. Because it uses TCP, the chance of packet loss is much lower than with the previous protocol that used UDP. Protocolbuffers packs the data in a compact binary mode into packets, using much less space than the plaintext json that was previously used. It should therefore consume less bandwidth. 8 | 9 | When you use this repository, the settings you set on the TTN console are taken as the primary settings. The settings from the console are read and applied at gateway startup. If you for example change the location of the gateway on the console, that setting will only be applied when the gateway restarts. 10 | 11 | # balenaCloud TTN Gateway Connector for Raspberry Pi 12 | 13 | balenaCloud Dockerfile & scripts for [The Things Network](http://thethingsnetwork.org/) gateways based on the Raspberry Pi. This updated version uses the gateway connector protocol, not the old packet forwarder. See the [TTN documentation on Gateway Registration](https://www.thethingsnetwork.org/docs/gateways/registration.html). 14 | 15 | Currently any Raspberry Pi with one of the following gateway boards, communicating over SPI, are supported, but not limited to these: 16 | * [IMST iC880A-SPI](http://webshop.imst.de/ic880a-spi-lorawan-concentrator-868mhz.html). Preferable configured as described [by TTN-ZH](https://github.com/ttn-zh/ic880a-gateway/wiki). You **do not** need to follow the **Setting up the software** step, as the setup scripts in this repository does it for you. 17 | * [LinkLabs Raspberry Pi "Hat"](http://link-labs.myshopify.com/products/lorawan-raspberry-pi-board) 18 | * [RisingHF IoT Dicovery](http://www.risinghf.com/product/risinghf-iot-dicovery/?lang=en) 19 | * [RAK831](http://www.rakwireless.com/en/WisKeyOSH/RAK831) 20 | 21 | ## Prerequisites 22 | 23 | 1. Build your hardware. 24 | 2. Create a new gateway that uses `gateway connector` on the [TTN Console](https://console.thethingsnetwork.org/gateways). Also set the location and altitude of your gateway. We will come back to this console later to obtain the gateway ID and access key. 25 | 3. Create and sign into an account at https://www.balena.io/cloud/, which is the central "device dashboard". 26 | 27 | ## Create a balenaCloud application 28 | 29 | 1. On balenaCloud, create an "Application" for managing your TTN gateway devices. I'd suggest that you give it the name "ttngw", select the appropriate device type (i.e. Raspberry Pi 2 or Raspberry Pi 3), and click "Create New Application". You only need to do this once, after which you'll be able to manage one or many gateways of that type. 30 | 2. You'll then be brought to the Device Management dashboard for that Application. Follow the instructions to "Add device" and create a boot SD-card for your Raspberry Pi. (Pro Tip: Use a fast microSD card and a USB 3 adapter if you can, because it can take a while to copy all that data. Either that, or be prepared to be very patient.) 31 | 3. When the (long) process of writing the image to the SD card completes, insert it into your Raspberry Pi, connect it to the network with Ethernet, and power it up. 32 | 4. After several minutes, on the balenaCloud Devices dashboard you'll now see your device - first in a "Configuring" state, then "Idle". Click it to open the Devices control panel. 33 | 5. If you like, enter any new Device Name that you'd like, such as "my-gateway-amsterdam". 34 | 35 | ## Configure the gateway device 36 | 37 | Click the "Environment Variables" section at the left side of the screen. This will allow you to configure this and only this device. These variables will be used to pull information about this gateway from TTN, and will be used to create a "global_conf.json" and "local_conf.json" file for this gateway. 38 | 39 | For a more complete list of possible environment variables, see [CONFIGURATION](CONFIGURATION.md). 40 | 41 | ### Device environment variables - no GPS 42 | 43 | For example, for an IMST iC880A or RAK831 with no GPS, the MINIMUM environment variables that you should configure at this screen should look something like this: 44 | 45 | Name | Value 46 | ------------------|-------------------------- 47 | GW_ID | The gateway ID from the TTN console 48 | GW_KEY | The gateway KEY from the TTN console 49 | GW_RESET_PIN | 22 (optional) 50 | 51 | GW_RESET_PIN can be left out if you are using Gonzalo Casas' backplane board, or any other setup using pin 22 as reset pin. This is because pin 22 is the default reset pin used by this balenaCloud setup. 52 | 53 | 54 | ### Device environment variables - with GPS 55 | 56 | For example a LinkLabs gateway, which has a built-in GPS, you need: 57 | 58 | Name | Value 59 | ------------------|-------------------------- 60 | GW_ID | The gateway ID from the TTN console 61 | GW_KEY | The gateway KEY from the TTN console 62 | GW_GPS | true 63 | GW_RESET_PIN | 29 64 | 65 | 66 | ## Reset pin values 67 | 68 | Depending on the way you connect the concentrator board to the Raspberry Pi, the reset pin of the concentrator might be on a different GPIO pin of the Raspberry Pi. Here follows a table of the most common backplane boards used, and the reset pin number you should use in the `GW_RESET_PIN` environment variable. 69 | 70 | Note that the reset pin you should define is the physical pin number on the Raspberry Pi. To translate between different numbering schemes you can use [pinout.xyz](https://pinout.xyz/). 71 | 72 | Backplane | Reset pin 73 | ------------------|----------- 74 | Gonzalo Casas backplane
https://github.com/gonzalocasas/ic880a-backplane
https://www.tindie.com/stores/gnz/ | 22 75 | ch2i
https://github.com/ch2i/iC880A-Raspberry-PI | 11 76 | Linklabs Rasberry Pi Hat
https://www.amazon.co.uk/868-MHz-LoRaWAN-RPi-Shield/dp/B01G7G54O2 | 29 77 | Rising HF Board
http://www.risinghf.com/product/risinghf-iot-dicovery/?lang=en | 26 78 | IMST backplane or Lite gateway
https://wireless-solutions.de/products/long-range-radio/lora_lite_gateway.html | 29 (untested) 79 | Coredump backplane
https://github.com/dbrgn/ic880a-backplane/
https://shop.coredump.ch/product/ic880a-lorawan-gateway-backplane/ | 22 80 | RAK backplane
| 11 81 | Pi Supply IoT LoRa Gateway HAT for Raspberry Pi
https://uk.pi-supply.com/products/iot-lora-gateway-hat-for-raspberry-pi | 15 82 | 83 | 84 | If you get the message 85 | `ERROR: [main] failed to start the concentrator` 86 | after balenaCLoud is finished downloading the application, or when restarting the gateway, it most likely means the `GW_RESET_PIN` you defined is incorrect. Alternatively the problem can be caused by the hardware, typically for the `IMST iC880A-SPI` board with insufficient voltage, try another power supply or slightly increase the voltage. 87 | 88 | 89 | ## Special note for using a Raspberry Pi 3 90 | 91 | There is a backward incomatibility between the Raspberry Pi 1 and 2 hardware, and Raspberry Pi 3. For Raspberry Pi 3, it is necessary to make a small additional configuration change. 92 | 93 | Click <- to go back to the Device List, and note that on the left there is an option called "Fleet Configuration". Click it. 94 | 95 | Add a New config variable as follows: 96 | 97 | ### Application config variables 98 | 99 | Name | Value 100 | ------------------------------|-------------------------- 101 | RESIN_HOST_CONFIG_core_freq | 250 102 | RESIN_HOST_CONFIG_dtoverlay | pi3-miniuart-bt 103 | 104 | ## TRANSFERRING TTN GATEWAY SOFTWARE TO BALENACLOUD SO THAT IT MAY BE DOWNLOADED ON YOUR DEVICES 105 | 106 | 1. On your computer, clone this git repo. For example in a terminal on Mac or Linux type: 107 | 108 | ```bash 109 | git clone https://github.com/jpmeijers/ttn-resin-gateway-rpi.git 110 | cd ttn-resin-gateway-rpi/ 111 | ``` 112 | 2. Now, type the command that you'll see displayed in the edit control in the upper-right corner of the balenaCloud devices dashboard for your device. This command "connects" your local directory to the balenaCloud GIT service, which uses GIT to "receive" the gateway software from TTN, and it looks something like this: 113 | 114 | ```bash 115 | git remote add balena youraccount@git.balena-cloud.com:youraccount/yourapplication.git 116 | ``` 117 | 118 | 3. Add your SSH public key to the list at https://dashboard.balena-cloud.com/preferences/sshkeys. You may need to search the internet how to create a SSH key on your operating system, where to find it afterwards, copy the content, and paste the content to the balenaCloud console. 119 | 120 | 4. Type the following commands into your terminal to "push" the TTN files up to balenaCloud: 121 | 122 | ```bash 123 | git add . 124 | git commit -m "first upload of ttn files to balenaCloud" 125 | git push -f balena master 126 | ``` 127 | 128 | 5. What you'll now see happening in terminal is that this "git push" does an incredible amount of work: 129 | 1. It will upload a Dockerfile, a "build script", and a "run script" to balenaCloud 130 | 2. It will start to do a "docker build" using that Dockerfile, running it within a QEMU ARM virtual machine on the balenaCloud service. 131 | 3. In processing this docker build, it will run a "build.sh" script that downloads and builds the packet forwarder executable from source code, for RPi+iC880A-SPI. 132 | 4. When the build is completed, you'll see a unicorn 🦄 ASCII graphic displayed in your terminal. 133 | 134 | 6. Now, switch back to your device dashboard, you'll see that your Raspberry Pi is now "updating" by pulling the Docker container from the balenaCloud service. Then, after "updating", you'll see the gateway's log file in the window at the lower right corner. You'll see it initializing, and will also see log output each time a packet is forwarded to TTN. You're done! 135 | 136 | 137 | 138 | ## Troubleshooting ## 139 | If you get the error below please check if your ssh public key has been added to you balenaCloud account. In addition verify whether your private key has the correct permissions (i.e. chmod 400 ~/.ssh/id_rsa). 140 | 141 | ```bash 142 | $ git push -f balena master 143 | Connection closed by xxx.xxx.xxx.xxx port 22 144 | fatal: Could not read from remote repository. 145 | 146 | Please make sure you have the correct access rights 147 | and the repository exists. 148 | $ 149 | ``` 150 | 151 | # Pro Tips 152 | 153 | - At some point if you would like to add a second gateway, third gateway, or a hundred gateways, all you need to do is to add a new device to your existing Application. You needn't upload any new software to balenaCloud, because balenaCloud already knows what software belongs on the gateway. So long as the environment variables are configured correctly for that new device, it'll be up and running immediately after you burn an SD card and boot it. 154 | 155 | - balenaCloud will automatically restart the gateway software any time you change the environment variables. You'll see this in the log. Also, note that balenaCloud restarts the gateway properly after power failures. If the packet forwarder fails because of an error, it will also automatically attempt to restart. 156 | 157 | - If you'd like to update the software across all the gateways in your device fleet, simply do the following: 158 | ``` 159 | git add . 160 | git commit -m "Updated gateway version" 161 | git push -f balena master 162 | ``` 163 | 164 | - For devices without a GPS, the location that is configured on the TTN console is used. This location is only read at startup of the gateway. Therefore, after you set or changed the location, restart the application from the balenaCloud console. 165 | 166 | # Device statistics 167 | If you want to show nice looking statistics for your gateway(s) there are a couple of additional steps to take. First, copy `Dockerfile.template.metering` to `Dockerfile.template`. Next copy `start.sh.metering` to `start.sh`. Now use the instructions above to update the balenaCloud image. 168 | 169 | Once the new image is deployed, go to the balenaCloud dashboard for your devices and select 'Enable Public device URL' in the drop down menu (the one to the right of the light bulb). That is all that is required to provide metrics. Now you will need to install a metrics collector on a seperate system as outlined in [Fleet-wide Machine Metrics Monitoring in 20mins](https://www.balena.io/blog/prometheusv2/). 170 | 171 | (To show packet forwarder graphs you need to add your own graphs to the provided templates) 172 | 173 | # Credits 174 | 175 | * [Gonzalo Casas](https://github.com/gonzalocasas) on the [iC880a-based gateway](https://github.com/ttn-zh/ic880a-gateway/tree/spi) 176 | * [Ruud Vlaming](https://github.com/devlaam) on the [Lorank8 installer](https://github.com/Ideetron/Lorank) 177 | * [Jac Kersing](https://github.com/kersing) on the [Multi-protocol packet forwarder](https://github.com/kersing/packet_forwarder/tree/master/mp_pkt_fwd) 178 | * [Ray Ozzie](https://github.com/rayozzie/ttn-resin-gateway-rpi) on the original ResinIO setup 179 | * [The Team](https://www.balena.io/team/) at balena 180 | -------------------------------------------------------------------------------- /dev/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | INSTALL_DIR="/opt/ttn-gateway" 4 | 5 | mkdir -p $INSTALL_DIR/dev 6 | cd $INSTALL_DIR/dev 7 | 8 | if [ ! -d lora_gateway ]; then 9 | git clone https://github.com/kersing/lora_gateway.git || { echo 'Cloning lora_gateway failed.' ; exit 1; } 10 | else 11 | cd lora_gateway 12 | git reset --hard 13 | git pull 14 | cd .. 15 | fi 16 | 17 | if [ ! -d paho.mqtt.embedded-c ]; then 18 | git clone https://github.com/kersing/paho.mqtt.embedded-c.git || { echo 'Cloning paho mqtt failed.' ; exit 1; } 19 | else 20 | cd paho.mqtt.embedded-c 21 | git reset --hard 22 | git pull 23 | cd .. 24 | fi 25 | 26 | if [ ! -d ttn-gateway-connector ]; then 27 | git clone https://github.com/kersing/ttn-gateway-connector.git || { echo 'Cloning gateway connector failed.' ; exit 1; } 28 | else 29 | cd ttn-gateway-connector 30 | git reset --hard 31 | git pull 32 | cd .. 33 | fi 34 | 35 | if [ ! -d protobuf-c ]; then 36 | git clone https://github.com/kersing/protobuf-c.git || { echo 'Cloning protobuf-c failed.' ; exit 1; } 37 | else 38 | cd protobuf-c 39 | git reset --hard 40 | git pull 41 | cd .. 42 | fi 43 | 44 | if [ ! -d packet_forwarder ]; then 45 | git clone https://github.com/kersing/packet_forwarder.git || { echo 'Cloning packet forwarder failed.' ; exit 1; } 46 | else 47 | cd packet_forwarder 48 | git reset --hard 49 | git pull 50 | cd .. 51 | fi 52 | 53 | if [ ! -d protobuf ]; then 54 | git clone https://github.com/google/protobuf.git || { echo 'Cloning protobuf failed.' ; exit 1; } 55 | else 56 | cd protobuf 57 | git reset --hard 58 | git pull 59 | cd .. 60 | fi 61 | 62 | cd $INSTALL_DIR/dev/lora_gateway/libloragw 63 | sed -i -e 's/PLATFORM= .*$/PLATFORM= imst_rpi/g' library.cfg 64 | sed -i -e 's/CFG_SPI= .*$/CFG_SPI= native/g' library.cfg 65 | make -j$(nproc) 66 | 67 | cd $INSTALL_DIR/dev/protobuf-c 68 | ./autogen.sh 69 | ./configure 70 | make protobuf-c/libprotobuf-c.la 71 | mkdir bin 72 | ./libtool install /usr/bin/install -c protobuf-c/libprotobuf-c.la `pwd`/bin 73 | rm -f `pwd`/bin/*so* 74 | 75 | cd $INSTALL_DIR/dev/paho.mqtt.embedded-c/ 76 | make 77 | make install 78 | 79 | cd $INSTALL_DIR/dev/ttn-gateway-connector 80 | cp config.mk.in config.mk 81 | make -j$(nproc) 82 | cp bin/libttn-gateway-connector.so /usr/lib/ 83 | 84 | # Some custom changes 85 | cd $INSTALL_DIR/dev/packet_forwarder/mp_pkt_fwd/inc/ 86 | 87 | sed -i -e 's/#define DEBUG_PKT_FWD .*$/#define DEBUG_PKT_FWD 1/g' trace.h 88 | sed -i -e 's/#define DEBUG_JIT .*$/#define DEBUG_JIT 1/g' trace.h 89 | sed -i -e 's/#define DEBUG_JIT_ERROR .*$/#define DEBUG_JIT_ERROR 1/g' trace.h 90 | # Timersync prints out way too much, so disable 91 | sed -i -e 's/#define DEBUG_TIMERSYNC .*$/#define DEBUG_TIMERSYNC 0/g' trace.h 92 | sed -i -e 's/#define DEBUG_BEACON .*$/#define DEBUG_BEACON 1/g' trace.h 93 | sed -i -e 's/#define DEBUG_LOG .*$/#define DEBUG_LOG 1/g' trace.h 94 | sed -i -e 's/#define DEBUG_FOLLOW .*$/#define DEBUG_FOLLOW 1/g' trace.h 95 | 96 | 97 | 98 | cd $INSTALL_DIR/dev/packet_forwarder/mp_pkt_fwd/ 99 | make -j$(nproc) 100 | 101 | # Copy things needed at runtime to where they'll be expected 102 | cp $INSTALL_DIR/dev/packet_forwarder/mp_pkt_fwd/mp_pkt_fwd $INSTALL_DIR/mp_pkt_fwd 103 | 104 | echo "Build & Installation Completed." 105 | -------------------------------------------------------------------------------- /gwexporter.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jpmeijers/ttn-resin-gateway-rpi/69e3019c803560a5850af25258fd7a9687695f35/gwexporter.tgz -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Author: JP Meijers 4 | Date: 2017-02-26 5 | Based on: https://github.com/rayozzie/ttn-resin-gateway-rpi/blob/master/run.sh 6 | 7 | 2019-11-08: Modified by pe1mew to add GW_LOGGER and GW_AUTOQUIT_THRESHOLD 8 | """ 9 | import os 10 | import os.path 11 | import sys 12 | import urllib2 13 | import time 14 | import uuid 15 | import json 16 | import subprocess 17 | try: 18 | import RPi.GPIO as GPIO 19 | except RuntimeError: 20 | print("Error importing RPi.GPIO! This is probably because you need superuser privileges. You can achieve this by using 'sudo' to run your script") 21 | 22 | GWID_PREFIX="FFFE" 23 | 24 | if not os.path.exists("/opt/ttn-gateway/mp_pkt_fwd"): 25 | print ("ERROR: gateway executable not found. Is it built yet?") 26 | sys.exit(0) 27 | 28 | if os.environ.get('HALT') != None: 29 | print ("*** HALT asserted - exiting ***") 30 | sys.exit(0) 31 | 32 | # Show info about the machine we're running on 33 | print ("*** Resin Machine Info:") 34 | print ("*** Type: "+str(os.environ.get('RESIN_MACHINE_NAME'))) 35 | print ("*** Arch: "+str(os.environ.get('RESIN_ARCH'))) 36 | 37 | if os.environ.get("RESIN_HOST_CONFIG_core_freq")!=None: 38 | print ("*** Core freq: "+str(os.environ.get('RESIN_HOST_CONFIG_core_freq'))) 39 | 40 | if os.environ.get("RESIN_HOST_CONFIG_dtoverlay")!=None: 41 | print ("*** UART mode: "+str(os.environ.get('RESIN_HOST_CONFIG_dtoverlay'))) 42 | 43 | 44 | # Check if the correct environment variables are set 45 | 46 | print ("*******************") 47 | print ("*** Configuration:") 48 | print ("*******************") 49 | 50 | if os.environ.get("GW_EUI")==None: 51 | # The FFFE should be inserted in the middle (so xxxxxxFFFExxxxxx) 52 | my_eui = format(uuid.getnode(), '012x') 53 | my_eui = my_eui[:6]+GWID_PREFIX+my_eui[6:] 54 | my_eui = my_eui.upper() 55 | else: 56 | my_eui = os.environ.get("GW_EUI") 57 | 58 | print ("GW_EUI:\t"+my_eui) 59 | 60 | if os.environ.get("ACCOUNT_SERVER_DOMAIN")==None: 61 | account_server_domain="account.thethingsnetwork.org" 62 | else: 63 | account_server_domain=os.environ.get("ACCOUNT_SERVER_DOMAIN") 64 | 65 | # Define default configs 66 | description = os.getenv('GW_DESCRIPTION', "") 67 | placement = "" 68 | latitude = os.getenv('GW_REF_LATITUDE', 0) 69 | longitude = os.getenv('GW_REF_LONGITUDE', 0) 70 | altitude = os.getenv('GW_REF_ALTITUDE', 0) 71 | frequency_plan_url = os.getenv('FREQ_PLAN_URL', "https://%s/api/v2/frequency-plans/EU_863_870" % account_server_domain) 72 | 73 | """ 74 | Takes a router address as input, and returns it in the format expected for the packet forwarder configuration 75 | """ 76 | def sanitize_router_address(address): 77 | address_no_proto = "" 78 | splitted_by_protocol = address.split("://") 79 | if len(splitted_by_protocol) == 1: 80 | address_no_proto = address 81 | else: 82 | address_no_proto = splitted_by_protocol[1] 83 | 84 | # Workaround as the account server returns mqtts ports which we can't connect to 85 | address_no_proto = address_no_proto.replace(":8883", ":1883") 86 | address_no_proto = address_no_proto.replace(":8882", ":1882") 87 | 88 | return address_no_proto 89 | 90 | # Fetch config from TTN if TTN is enabled 91 | if(os.getenv('SERVER_TTN', "true")=="true"): 92 | print ("Enabling TTN gateway connector") 93 | 94 | if os.environ.get("GW_ID")==None: 95 | print ("WARNING: No GW_ID defined. Falling back to EUI.") 96 | my_gw_id = "eui-"+my_eui.lower() 97 | print ("GW_ID:\t"+my_gw_id) 98 | else: 99 | my_gw_id = os.environ.get("GW_ID") 100 | 101 | if os.environ.get("GW_KEY")==None: 102 | print ("ERROR: GW_KEY required") 103 | print ("See https://www.thethingsnetwork.org/docs/gateways/registration.html#via-gateway-connector") 104 | sys.exit(0) 105 | 106 | print ("*******************") 107 | print ("*** Fetching config from TTN account server") 108 | print ("*******************") 109 | 110 | # Fetch the URL, if it fails try 30 seconds later again. 111 | config_response = "" 112 | while True: 113 | try: 114 | req = urllib2.Request('https://%s/api/v2/gateways/%s' % (account_server_domain, my_gw_id)) 115 | req.add_header('Authorization', 'Key '+os.environ.get("GW_KEY")) 116 | response = urllib2.urlopen(req, timeout=30) 117 | config_response = response.read() 118 | except urllib2.URLError as err: 119 | print (err) 120 | print ("Unable to fetch configuration from TTN. Is the TTN API reachable from gateway? Are your GW_ID and GW_KEY correct? Retry in 30s") 121 | time.sleep(30) 122 | continue 123 | break 124 | 125 | # Parse config 126 | ttn_config = {} 127 | try: 128 | ttn_config = json.loads(config_response) 129 | except: 130 | print ("Unable to parse configuration from TTN") 131 | sys.exit(0) 132 | 133 | frequency_plan = ttn_config.get('frequency_plan', "EU_863_870") 134 | frequency_plan_url = ttn_config.get('frequency_plan_url', "https://%s/api/v2/frequency-plans/EU_863_870" % account_server_domain) 135 | 136 | if os.environ.get("ROUTER_MQTT_ADDRESS"): 137 | router = sanitize_router_address(os.environ.get("ROUTER_MQTT_ADDRESS")) 138 | elif "router" in ttn_config: 139 | fetched_router_address = ttn_config['router'].get('mqtt_address', "mqtt://router.dev.thethings.network:1883") 140 | router = sanitize_router_address(fetched_router_address) 141 | else: 142 | router = "router.dev.thethings.network" 143 | 144 | if "attributes" in ttn_config: 145 | description = ttn_config['attributes'].get('description', "") 146 | placement = ttn_config['attributes'].get('placement', "unknown") 147 | 148 | if "antenna_location" in ttn_config: 149 | latitude = ttn_config['antenna_location'].get('latitude', 0) 150 | longitude = ttn_config['antenna_location'].get('longitude', 0) 151 | altitude = ttn_config['antenna_location'].get('altitude', 0) 152 | 153 | fallback_routers = [] 154 | if "fallback_routers" in ttn_config: 155 | for fb_router in ttn_config["fallback_routers"]: 156 | if "mqtt_address" in fb_router: 157 | fallback_routers.append(fb_router["mqtt_address"]) 158 | 159 | 160 | print ("Gateway ID:\t"+my_gw_id) 161 | print ("Gateway Key:\t"+os.environ.get("GW_KEY")) 162 | print ("Frequency plan:\t\t"+frequency_plan) 163 | print ("Frequency plan url:\t"+frequency_plan_url) 164 | print ("Gateway description:\t"+description) 165 | print ("Gateway placement:\t"+placement) 166 | print ("Router:\t\t\t"+router) 167 | print ("") 168 | print ("Fallback routers:") 169 | for fb_router in fallback_routers: 170 | print ("\t"+fb_router) 171 | # Done fetching config from TTN 172 | else: 173 | print ("TTN gateway connector disabled. Not fetching config from account server.") 174 | 175 | print ("Latitude:\t\t"+str(latitude)) 176 | print ("Longitude:\t\t"+str(longitude)) 177 | print ("Altitude:\t\t"+str(altitude)) 178 | print ("Gateway EUI:\t"+my_eui) 179 | print ("Has hardware GPS:\t"+str(os.getenv('GW_GPS', False))) 180 | print ("Hardware GPS port:\t"+os.getenv('GW_GPS_PORT', "/dev/ttyAMA0")) 181 | 182 | 183 | 184 | # Retrieve global_conf 185 | sx1301_conf = {} 186 | try: 187 | response = urllib2.urlopen(frequency_plan_url, timeout=30) 188 | global_conf = response.read() 189 | global_conf_object = json.loads(global_conf) 190 | if('SX1301_conf' in global_conf_object): 191 | sx1301_conf = global_conf_object['SX1301_conf'] 192 | except urllib2.URLError as err: 193 | print ("Unable to fetch global conf from Github") 194 | sys.exit(0) 195 | 196 | sx1301_conf['antenna_gain'] = float(os.getenv('GW_ANTENNA_GAIN', 0)) 197 | 198 | 199 | # Build local_conf 200 | gateway_conf = {} 201 | gateway_conf['gateway_ID'] = my_eui 202 | gateway_conf['contact_email'] = os.getenv('GW_CONTACT_EMAIL', "") 203 | gateway_conf['description'] = description 204 | gateway_conf['stat_file'] = 'loragwstat.json' 205 | gateway_conf['push_timeout_ms'] = int(os.getenv("GW_PUSH_TIMEOUT", 100)) # Default in code is 100 206 | 207 | if(os.getenv('GW_LOGGER', "false")=="true"): 208 | gateway_conf['logger'] = True 209 | else: 210 | gateway_conf['logger'] = False 211 | 212 | if(os.getenv('GW_FWD_CRC_ERR', "false")=="true"): 213 | #default is False 214 | gateway_conf['forward_crc_error'] = True 215 | 216 | if(os.getenv('GW_FWD_CRC_VAL', "true")=="false"): 217 | #default is True 218 | gateway_conf['forward_crc_valid'] = False 219 | 220 | if(os.getenv('GW_DOWNSTREAM', "true")=="false"): 221 | #default is True 222 | gateway_conf['downstream'] = False 223 | 224 | # Parse GW_GPS env var. It is a string, we need a boolean. 225 | if(os.getenv('GW_GPS', "false")=="true"): 226 | gw_gps = True 227 | else: 228 | gw_gps = False 229 | 230 | # Use hardware GPS 231 | if(gw_gps): 232 | print ("Using real GPS") 233 | gateway_conf['gps'] = True 234 | gateway_conf['fake_gps'] = False 235 | gateway_conf['gps_tty_path'] = os.getenv('GW_GPS_PORT', "/dev/ttyAMA0") 236 | # Use fake GPS with coordinates from TTN 237 | elif(gw_gps==False and latitude!=0 and longitude!=0): 238 | print ("Using fake GPS") 239 | gateway_conf['gps'] = True 240 | gateway_conf['fake_gps'] = True 241 | gateway_conf['ref_latitude'] = float(latitude) 242 | gateway_conf['ref_longitude'] = float(longitude) 243 | gateway_conf['ref_altitude'] = float(altitude) 244 | # No GPS coordinates 245 | else: 246 | print ("Not sending coordinates") 247 | gateway_conf['gps'] = False 248 | gateway_conf['fake_gps'] = False 249 | 250 | # Log all LoRaWAN packets to console 251 | if(os.getenv('GW_LOGGER', "false")=="true"): 252 | gateway_conf['logger'] = True 253 | print ("Packet logging enabled") 254 | 255 | # Autoquit when a number of PULL_ACKs have been missed 256 | autoquit_threshold = int(os.getenv('GW_AUTOQUIT_THRESHOLD', 0)) 257 | if(autoquit_threshold > 0): 258 | gateway_conf['autoquit_threshold'] = int(os.getenv('GW_AUTOQUIT_THRESHOLD', 5)) 259 | print ("Autoquit after", gateway_conf['autoquit_threshold'], "missed PULL_ACKs") 260 | 261 | # Add server configuration 262 | gateway_conf['servers'] = [] 263 | 264 | # Add TTN server 265 | if(os.getenv('SERVER_TTN', "true")=="true"): 266 | server = {} 267 | server['serv_type'] = "ttn" 268 | server['server_address'] = router 269 | server['server_fallbacks'] = fallback_routers 270 | server['serv_gw_id'] = my_gw_id 271 | server['serv_gw_key'] = os.environ.get("GW_KEY") 272 | server['serv_enabled'] = True 273 | if(os.getenv('SERVER_TTN_DOWNLINK', "true")=="false"): 274 | server['serv_down_enabled'] = False 275 | else: 276 | server['serv_down_enabled'] = True 277 | gateway_conf['servers'].append(server) 278 | else: 279 | if(os.getenv('SERVER_0_ENABLED', "false")=="true"): 280 | server = {} 281 | if(os.getenv('SERVER_0_TYPE', "semtech")=="ttn"): 282 | server['serv_type'] = "ttn" 283 | server['serv_gw_id'] = os.environ.get("SERVER_0_GWID") 284 | server['serv_gw_key'] = os.environ.get("SERVER_0_GWKEY") 285 | server['server_address'] = os.environ.get("SERVER_0_ADDRESS") 286 | server['serv_port_up'] = int(os.getenv("SERVER_0_PORTUP", 1700)) 287 | server['serv_port_down'] = int(os.getenv("SERVER_0_PORTDOWN", 1700)) 288 | server['serv_enabled'] = True 289 | if(os.getenv('SERVER_0_DOWNLINK', "false")=="true"): 290 | server['serv_down_enabled'] = True 291 | else: 292 | server['serv_down_enabled'] = False 293 | gateway_conf['servers'].append(server) 294 | 295 | # Add up to 3 additional servers 296 | if(os.getenv('SERVER_1_ENABLED', "false")=="true"): 297 | server = {} 298 | if(os.getenv('SERVER_1_TYPE', "semtech")=="ttn"): 299 | server['serv_type'] = "ttn" 300 | server['serv_gw_id'] = os.environ.get("SERVER_1_GWID") 301 | server['serv_gw_key'] = os.environ.get("SERVER_1_GWKEY") 302 | server['server_address'] = os.environ.get("SERVER_1_ADDRESS") 303 | server['serv_port_up'] = int(os.getenv("SERVER_1_PORTUP", 1700)) 304 | server['serv_port_down'] = int(os.getenv("SERVER_1_PORTDOWN", 1700)) 305 | server['serv_enabled'] = True 306 | if(os.getenv('SERVER_1_DOWNLINK', "false")=="true"): 307 | server['serv_down_enabled'] = True 308 | else: 309 | server['serv_down_enabled'] = False 310 | gateway_conf['servers'].append(server) 311 | 312 | if(os.getenv('SERVER_2_ENABLED', "false")=="true"): 313 | server = {} 314 | if(os.getenv('SERVER_2_TYPE', "semtech")=="ttn"): 315 | server['serv_type'] = "ttn" 316 | server['serv_gw_id'] = os.environ.get("SERVER_2_GWID") 317 | server['serv_gw_key'] = os.environ.get("SERVER_2_GWKEY") 318 | server['server_address'] = os.environ.get("SERVER_2_ADDRESS") 319 | server['serv_port_up'] = int(os.getenv("SERVER_2_PORTUP", 1700)) 320 | server['serv_port_down'] = int(os.getenv("SERVER_2_PORTDOWN", 1700)) 321 | server['serv_enabled'] = True 322 | if(os.getenv('SERVER_2_DOWNLINK', "false")=="true"): 323 | server['serv_down_enabled'] = True 324 | else: 325 | server['serv_down_enabled'] = False 326 | gateway_conf['servers'].append(server) 327 | 328 | if(os.getenv('SERVER_3_ENABLED', "false")=="true"): 329 | server = {} 330 | if(os.getenv('SERVER_3_TYPE', "semtech")=="ttn"): 331 | server['serv_type'] = "ttn" 332 | server['serv_gw_id'] = os.environ.get("SERVER_3_GWID") 333 | server['serv_gw_key'] = os.environ.get("SERVER_3_GWKEY") 334 | server['server_address'] = os.environ.get("SERVER_3_ADDRESS") 335 | server['serv_port_up'] = int(os.getenv("SERVER_3_PORTUP", 1700)) 336 | server['serv_port_down'] = int(os.getenv("SERVER_3_PORTDOWN", 1700)) 337 | server['serv_enabled'] = True 338 | if(os.getenv('SERVER_3_DOWNLINK', "false")=="true"): 339 | server['serv_down_enabled'] = True 340 | else: 341 | server['serv_down_enabled'] = False 342 | gateway_conf['servers'].append(server) 343 | 344 | 345 | # We merge the json objects from the global_conf and local_conf and save it to the global_conf. 346 | # Therefore there will not be a local_conf.json file. 347 | local_conf = {'SX1301_conf': sx1301_conf, 'gateway_conf': gateway_conf} 348 | with open('/opt/ttn-gateway/global_conf.json', 'w') as the_file: 349 | the_file.write(json.dumps(local_conf, indent=4)) 350 | 351 | 352 | 353 | # Endless loop to reset and restart packet forwarder 354 | while True: 355 | # Reset the gateway board - this only works for the Raspberry Pi. 356 | GPIO.setmode(GPIO.BOARD) # hardware pin numbers, just like gpio -1 357 | 358 | if (os.environ.get("GW_RESET_PIN")!=None): 359 | try: 360 | pin_number = int(os.environ.get("GW_RESET_PIN")) 361 | print ("[TTN Gateway]: Resetting concentrator on pin "+str(os.environ.get("GW_RESET_PIN"))) 362 | GPIO.setup(pin_number, GPIO.OUT, initial=GPIO.LOW) 363 | GPIO.output(pin_number, 0) 364 | time.sleep(0.1) 365 | GPIO.output(pin_number, 1) 366 | time.sleep(0.1) 367 | GPIO.output(pin_number, 0) 368 | time.sleep(0.1) 369 | GPIO.input(pin_number) 370 | GPIO.cleanup(pin_number) 371 | time.sleep(0.1) 372 | except ValueError: 373 | print ("Can't interpret "+os.environ.get("GW_RESET_PIN")+" as a valid pin number.") 374 | 375 | else: 376 | print ("[TTN Gateway]: Resetting concentrator on default pin 22.") 377 | GPIO.setup(22, GPIO.OUT, initial=GPIO.LOW) 378 | GPIO.output(22, 0) 379 | time.sleep(0.1) 380 | GPIO.output(22, 1) 381 | time.sleep(0.1) 382 | GPIO.output(22, 0) 383 | time.sleep(0.1) 384 | GPIO.input(22) 385 | GPIO.cleanup(22) 386 | time.sleep(0.1) 387 | 388 | # Start forwarder 389 | subprocess.call(['/opt/ttn-gateway/mp_pkt_fwd', '-c', '/opt/ttn-gateway/', '-s', os.getenv('SPI_SPEED', '8000000')]) 390 | time.sleep(15) 391 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python /opt/ttn-gateway/run.py 4 | -------------------------------------------------------------------------------- /start.sh.metering: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start the node exporter 4 | mkdir /mnt/ramdisk && mount -t tmpfs -o size=8m tmpfs /mnt/ramdisk 5 | cd /mnt/ramdisk 6 | /opt/ttn-gateway/run.py & 7 | 8 | # WORKAROUND: Add symlink, as gwexporter otherwise currently don't seem to find this file 9 | ln -s /mnt/ramdisk/loragwstat.json /opt/gwexporter/loragwstat.json 10 | 11 | export PATH=$PATH:/opt/gwexporter/bin 12 | node /opt/gwexporter/gwexporter.js & 13 | 14 | cd /etc && ./node_exporter --web.listen-address ":81" 15 | --------------------------------------------------------------------------------