├── 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 |
--------------------------------------------------------------------------------