├── .gitattributes
├── .gitignore
├── 1_Pi_Config.sh
├── 2_Setup.sh
├── 3_WiFi_AP.sh
├── 4_WiFi_Docker.sh
├── 5_GPS.sh
├── README.md
├── build_legacy.sh
├── config
├── 90-wireless.rules
├── dnsmasq.conf
├── hostapd.conf
├── interfaces
└── rc.local
├── doc
├── AccessPoint.md
├── DisplayOled.md
└── Lorawan-Server.md
├── loragw.service
├── monitor-gpio-helium.py
├── monitor-gpio.py
├── monitor-ws2812.py
├── monitor.service
├── oled.py
├── oled.service
├── sensors_js
├── install.sh
├── sensors-js.service
└── sensors.js
├── set_config.py
├── start.sh
├── testled.js
├── testled.py
└── wificfg.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | www/
2 |
3 | # Object files
4 | *.o
5 | *.ko
6 | *.obj
7 | *.elf
8 |
9 | # Precompiled Headers
10 | *.gch
11 | *.pch
12 |
13 | # Libraries
14 | *.lib
15 | *.a
16 | *.la
17 | *.lo
18 |
19 | # Shared objects (inc. Windows DLLs)
20 | *.dll
21 | *.so
22 | *.so.*
23 | *.dylib
24 |
25 | # Executables
26 | *.exe
27 | *.out
28 | *.app
29 | *.i*86
30 | *.x86_64
31 | *.hex
32 |
33 | # Debug files
34 | *.dSYM/
35 |
--------------------------------------------------------------------------------
/1_Pi_Config.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if [ $(id -u) -ne 0 ]; then
4 | echo "Installer must be run as root."
5 | echo "Try 'sudo bash $0'"
6 | exit 1
7 | fi
8 |
9 | MODEL=`cat /proc/device-tree/model`
10 |
11 | echo "This script configure a Raspberry Pi"
12 | echo "Raspian for being a LoRaWAN Gateway"
13 | echo
14 | echo "Device is $MODEL"
15 | echo
16 | echo "Run time ~5 minutes. Reboot required."
17 | echo
18 | echo -n "CONTINUE? [Y/n] "
19 | read
20 | if [[ "$REPLY" =~ ^(no|n|N)$ ]]; then
21 | echo "Canceled."
22 | exit 0
23 | fi
24 |
25 | # These functions have been copied from excellent Adafruit Read only tutorial
26 | # https://github.com/adafruit/Raspberry-Pi-Installer-Scripts/blob/master/read-only-fs.sh
27 | # the one inspired by my original article http://hallard.me/raspberry-pi-read-only/
28 | # That's an excellent demonstration of collaboration and open source sharing
29 | #
30 | # Given a filename, a regex pattern to match and a replacement string:
31 | # Replace string if found, else no change.
32 | # (# $1 = filename, $2 = pattern to match, $3 = replacement)
33 | replace() {
34 | grep $2 $1 >/dev/null
35 | if [ $? -eq 0 ]; then
36 | # Pattern found; replace in file
37 | sed -i "s/$2/$3/g" $1 >/dev/null
38 | fi
39 | }
40 |
41 | # Given a filename, a regex pattern to match and a replacement string:
42 | # If found, perform replacement, else append file w/replacement on new line.
43 | replaceAppend() {
44 | grep $2 $1 >/dev/null
45 | if [ $? -eq 0 ]; then
46 | # Pattern found; replace in file
47 | sed -i "s/$2/$3/g" $1 >/dev/null
48 | else
49 | # Not found; append on new line (silently)
50 | echo $3 | sudo tee -a $1 >/dev/null
51 | fi
52 | }
53 |
54 | # Given a filename, a regex pattern to match and a string:
55 | # If found, no change, else append file with string on new line.
56 | append1() {
57 | grep $2 $1 >/dev/null
58 | if [ $? -ne 0 ]; then
59 | # Not found; append on new line (silently)
60 | echo $3 | sudo tee -a $1 >/dev/null
61 | fi
62 | }
63 |
64 | # Given a list of strings representing options, display each option
65 | # preceded by a number (1 to N), display a prompt, check input until
66 | # a valid number within the selection range is entered.
67 | selectN() {
68 | for ((i=1; i<=$#; i++)); do
69 | echo $i. ${!i}
70 | done
71 | echo
72 | REPLY=""
73 | while :
74 | do
75 | echo -n "SELECT 1-$#: "
76 | read
77 | if [[ $REPLY -ge 1 ]] && [[ $REPLY -le $# ]]; then
78 | return $REPLY
79 | fi
80 | done
81 | }
82 |
83 | echo "Updating dependencies"
84 | apt-get update && apt-get upgrade -y --force-yes && apt-get update
85 | apt-get install -y --force-yes git-core build-essential ntp scons i2c-tools
86 |
87 | echo "Updating python dependencies"
88 | apt-get install -y --force-yes python-dev swig python-psutil python-rpi.gpio python-pip
89 | python -m pip install --upgrade pip setuptools wheel
90 |
91 | if [[ ! -d /home/loragw ]]; then
92 | echo "Adding new user loragw, enter it password"
93 | useradd -m loragw -s /bin/bash
94 | passwd loragw
95 | usermod -a -G sudo loragw
96 | cp /etc/sudoers.d/010_pi-nopasswd /etc/sudoers.d/010_loragw-nopasswd
97 | sed -i -- 's/pi/loragw/g' /etc/sudoers.d/010_loragw-nopasswd
98 | cp /home/pi/.profile /home/loragw/
99 | cp /home/pi/.bashrc /home/loragw/
100 | chown loragw:loragw /home/loragw/.*
101 | usermod -a -G i2c,spi,gpio loragw
102 | fi
103 |
104 | echo "Enabling Uart, I2C, SPI, Video Memory to 16MB"
105 | replaceAppend /boot/config.txt "^.*enable_uart.*$" "enable_uart=1"
106 | replaceAppend /boot/config.txt "^.*dtparam=i2c_arm=.*$" "dtparam=i2c_arm=on"
107 | replaceAppend /boot/config.txt "^.*dtparam=spi=.*$" "dtparam=spi=on"
108 | replaceAppend /boot/config.txt "^.*gpu_mem=.*$" "gpu_mem=16"
109 | replaceAppend /etc/modules "^.*i2c-dev.*$" "i2c-dev"
110 |
111 | echo -n "Do you want to configure timezone [y/N] "
112 | read
113 | if [[ "$REPLY" =~ ^(yes|y|Y)$ ]]; then
114 | echo "Reconfiguring Time Zone."
115 | dpkg-reconfigure tzdata
116 | fi
117 |
118 | if [[ ! -f /usr/local/bin/log2ram ]]; then
119 | echo -n "Do you want to enable log2ram [y/N] "
120 | read
121 | if [[ "$REPLY" =~ ^(yes|y|Y)$ ]]; then
122 | echo "Setting up log2ram."
123 | git clone https://github.com/azlux/log2ram.git
124 | cd log2ram
125 | chmod +x install.sh uninstall.sh
126 | ./install.sh
127 | ln -s /usr/local/bin/log2ram /etc/cron.hourly/
128 | echo "cleaning up log rotation"
129 | replace /etc/logrotage.d/rsyslog "^.*daily.*$" " hourly"
130 | replace /etc/logrotage.d/rsyslog "^.*monthly.*$" " daily"
131 | replace /etc/logrotage.d/rsyslog "^.*delaycompress.*$" " "
132 |
133 | echo "forcing one log rotation"
134 | logrotate /etc/logrotate.conf
135 | echo "Please don't forget to adjust the logrotate"
136 | echo "paratemeters in /etc/logrotage.d/* to avoid"
137 | echo "filling up the ramdisk, see README in"
138 | echo "https://github.com/ch2i/LoraGW-Setup/"
139 | echo ""
140 | fi
141 | fi
142 |
143 | # set hostname to loragw-xxyy with xxyy last MAC Address digits
144 | set -- `cat /sys/class/net/wlan0/address`
145 | IFS=":"; declare -a Array=($*)
146 | NEWHOST=loragw-${Array[4]}${Array[5]}
147 |
148 | echo ""
149 | echo "Please select new device name (hostname)"
150 | selectN "Leave as $HOSTNAME" "loragw" "$NEWHOST"
151 | SEL=$?
152 | if [[ $SEL -gt 1 ]]; then
153 | if [[ $SEL == 2 ]]; then
154 | NEWHOST=loragw
155 | fi
156 | sudo bash -c "echo $NEWHOST" > /etc/hostname
157 | replace /etc/hosts "^127.0.1.1.*$HOSTNAME.*$" "127.0.1.1\t$NEWHOST"
158 | echo "New hostname set to $NEWHOST"
159 | else
160 | echo "hostname unchanged"
161 | fi
162 |
163 | echo "Done."
164 | echo
165 | echo "Settings take effect on next boot."
166 | echo "after reboot, login back here with"
167 | echo "ssh loragw@$NEWHOST.local"
168 | echo
169 | echo -n "REBOOT NOW? [y/N] "
170 | read
171 | if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then
172 | echo "Exiting without reboot."
173 | exit 0
174 | fi
175 | echo "Reboot started..."
176 | reboot
177 | exit 0
178 |
179 |
--------------------------------------------------------------------------------
/2_Setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | INSTALL_DIR="/opt/loragw"
4 | MODEL=`cat /proc/device-tree/model`
5 |
6 | if [ $(id -u) -ne 0 ]; then
7 | echo "Installer must be run as root."
8 | echo "Try 'sudo bash $0'"
9 | exit 1
10 | fi
11 |
12 | echo "This script configures a Raspberry Pi"
13 | echo "as a LoRaWAN Gateway connected to TTN,"
14 | echo
15 | echo "It will install the following dependencies"
16 | echo "nodejs, git, pyhton, ntp, scons, ws2812"
17 | echo "compile packet forwarder and install the"
18 | echo "needed services (Monitor, Oled, GW) at boot"
19 | echo
20 | echo "Device is $MODEL"
21 | echo
22 | echo "Run time ~5 to 15 minutes depending on features."
23 | echo
24 | echo -n "CONTINUE? [Y/n] "
25 | read
26 | if [[ "$REPLY" =~ ^(no|n|N)$ ]]; then
27 | echo "Canceled."
28 | exit 0
29 | fi
30 |
31 | # Given a filename, a regex pattern to match and a replacement string:
32 | # Replace string if found, else no change.
33 | # (# $1 = filename, $2 = pattern to match, $3 = replacement)
34 | replace() {
35 | grep $2 $1 >/dev/null
36 | if [ $? -eq 0 ]; then
37 | # Pattern found; replace in file
38 | sed -i "s/$2/$3/g" $1 >/dev/null
39 | fi
40 | }
41 |
42 | # Given a filename, a regex pattern to match and a replacement string:
43 | # If found, perform replacement, else append file w/replacement on new line.
44 | replaceAppend() {
45 | grep $2 $1 >/dev/null
46 | if [ $? -eq 0 ]; then
47 | # Pattern found; replace in file
48 | sed -i "s/$2/$3/g" $1 >/dev/null
49 | else
50 | # Not found; append on new line (silently)
51 | echo $3 | sudo tee -a $1 >/dev/null
52 | fi
53 | }
54 |
55 | # Given a filename, a regex pattern to match and a string:
56 | # If found, no change, else append file with string on new line.
57 | append1() {
58 | grep $2 $1 >/dev/null
59 | if [ $? -ne 0 ]; then
60 | # Not found; append on new line (silently)
61 | echo $3 | sudo tee -a $1 >/dev/null
62 | fi
63 | }
64 |
65 | # These functions have been copied from excellent Adafruit Read only tutorial
66 | # https://github.com/adafruit/Raspberry-Pi-Installer-Scripts/blob/master/read-only-fs.sh
67 | # the one inspired by my original article http://hallard.me/raspberry-pi-read-only/
68 | # That's an excellent demonstration of collaboration and open source sharing
69 | #
70 | # Given a list of strings representing options, display each option
71 | # preceded by a number (1 to N), display a prompt, check input until
72 | # a valid number within the selection range is entered.
73 | selectN() {
74 | for ((i=1; i<=$#; i++)); do
75 | echo $i. ${!i}
76 | done
77 | echo
78 | REPLY=""
79 | while :
80 | do
81 | echo -n "SELECT 1-$#: "
82 | read
83 | if [[ $REPLY -ge 1 ]] && [[ $REPLY -le $# ]]; then
84 | return $REPLY
85 | fi
86 | done
87 | }
88 |
89 | echo ""
90 | echo "Target board/shield for this $MODEL:"
91 | selectN "CH2i RAK831 Minimal" "CH2i RAK831 with WS2812B Led" "CH2i ic880a" "IMST Lora Lite (ic880a)" "RAK831 official shield" "All other models"
92 | BOARD_TARGET=$?
93 | if [[ $BOARD_TARGET == 1 ]]; then
94 | GW_RESET_PIN=25
95 | MONITOR_SCRIPT=monitor-ws2812.py
96 | export GW_RESET_PIN
97 | fi
98 | if [[ $BOARD_TARGET == 2 ]]; then
99 | GW_RESET_PIN=25
100 | MONITOR_SCRIPT=monitor-ws2812.py
101 | export GW_RESET_PIN
102 | fi
103 | if [[ $BOARD_TARGET == 4 ]]; then
104 | GW_RESET_PIN=5
105 | export GW_RESET_PIN
106 | fi
107 | if [[ $BOARD_TARGET == 3 ]]; then
108 | GW_RESET_PIN=17
109 | MONITOR_SCRIPT=monitor-gpio.py
110 | export GW_RESET_PIN
111 | fi
112 | if [[ $BOARD_TARGET == 5 ]]; then
113 | GW_RESET_PIN=17
114 | export GW_RESET_PIN
115 | fi
116 |
117 | if [[ $MONITOR_SCRIPT == "" ]]; then
118 | echo "No monitoring script"
119 | else
120 | echo "Monitor script is $MONITOR_SCRIPT"
121 | fi
122 | echo "GW Reset pin is GPIO$GW_RESET_PIN"
123 |
124 | echo ""
125 | echo -n "Do you want to build Kersing packet forwarder [Y/n] "
126 | read BUILD_GW
127 |
128 | echo ""
129 | echo -n "Do you want to build legacy packet forwarder [y/N] "
130 | read BUILD_LEGACY
131 |
132 | echo ""
133 | echo "You can enable monitor service that manage blinking led to"
134 | echo "display status and also add button management to shutdown PI"
135 | echo -n "Would you like to enable this [Y/n] "
136 | read EN_MONITOR
137 |
138 | echo ""
139 | echo "If you a OLED display, You can enable OLED service that"
140 | echo "display some system information and LoRaWAN packet info"
141 | echo -n "Do you want to install I2C OLED [y/N] "
142 | read EN_OLED
143 |
144 | echo ""
145 | echo -n "Do you want to setup TTN [Y/n] "
146 | read EN_TTN
147 | if [[ ! "$EN_TTN" =~ ^(no|n|N)$ ]]; then
148 |
149 | echo "It's now time to create and configure your gateway on TTN"
150 | echo "See https://www.thethingsnetwork.org/docs/gateways/registration.html#via-gateway-connector"
151 | echo "once done, grab your gateway id (GW_ID) and key (GW_KEY) and paste them here"
152 |
153 | if [[ $GW_ID == "" ]]; then
154 | echo "No environement for GW_ID"
155 | echo ""
156 | echo -n "Please enter GW_ID: "
157 | read GW_ID
158 | export GW_ID
159 | fi
160 |
161 | if [[ $GW_KEY == "" ]]; then
162 | echo "No environement for GW_KEY"
163 | echo "See https://www.thethingsnetwork.org/docs/gateways/registration.html#via-gateway-connector"
164 | echo ""
165 | echo -n "Please enter GW_KEY: "
166 | read GW_KEY
167 | export GW_KEY
168 | fi
169 |
170 | if [[ $GW_RESET_PIN == "" ]]; then
171 | GW_RESET_PIN=25
172 | echo "No environement for GW_RESET_PIN"
173 | echo "Please select your reset pin"
174 | echo "see https://github.com/jpmeijers/ttn-resin-gateway-rpi/blob/master/README.md"
175 | echo "under section Reset Pin Values"
176 | echo "enter 25 (GPIO25) for this RAK831 shield or classic Gonzalo Casas"
177 | echo "enter 17 (GPIO17) for classic ic880A by CH2i"
178 | echo ""
179 | echo -n "Please enter GW_RESET_PIN [$GW_RESET_PIN]:"
180 | read GW_RESET_PIN
181 | export GW_RESET_PIN
182 | fi
183 | fi
184 |
185 | # Set the reset în in startup shell
186 | replace ./start.sh "^.*RESET_BCM_PIN=.*$" "SX1301_RESET_BCM_PIN=$GW_RESET_PIN"
187 |
188 | grep "Pi\ 3" /proc/device-tree/model >/dev/null
189 | if [ $? -eq 0 ]; then
190 | echo "Installing nodejs v10 for Raspberry PI 3"
191 | curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
192 | apt-get install nodejs
193 |
194 | # iC880a and RPI 3 Setup Activity LED and Power OFF Led
195 | if [[ $BOARD_TARGET == 3 ]]; then
196 | replaceAppend /boot/config.txt "^dtoverlay=gpio-poweroff.*$" "dtoverlay=gpio-poweroff,gpiopin=24"
197 | replaceAppend /boot/config.txt "^dtoverlay=pi3-act-led.*$" "dtoverlay=pi3-act-led,gpio=23"
198 | fi
199 |
200 | fi
201 |
202 | grep "Pi\ Zero" /proc/device-tree/model >/dev/null
203 | if [ $? -eq 0 ]; then
204 | echo "Installing nodejs lts for Raspberry PI Zero"
205 | wget -O - https://raw.githubusercontent.com/sdesalas/node-pi-zero/master/install-node-v.lts.sh | bash
206 | append1 /home/loragw/.profile "^.*PATH:/opt/nodejs/bin.*$" "export PATH=$PATH:/opt/nodejs/bin"
207 | append1 /home/loragw/.profile "^.*NODE_PATH=.*$" "NODE_PATH=/opt/nodejs/lib/node_modules"
208 | fi
209 |
210 | apt-get -y install protobuf-compiler libprotobuf-dev libprotoc-dev automake libtool autoconf
211 |
212 | # Board has WS1812B LED
213 | if [[ $BOARD_TARGET == 2 ]]; then
214 | echo "Installing WS2812B LED driver"
215 | cd /home/loragw/
216 |
217 | echo "Blacklisting snd_bcm2835 module due to WS2812b LED PWM"
218 | touch /etc/modprobe.d/snd-blacklist.conf
219 | append1 /etc/modprobe.d/snd-blacklist.conf "^.*snd_bcm2835.*$" "blacklist snd_bcm2835"
220 |
221 | echo "Installing WS2812B drivers and libraries"
222 | git clone https://github.com/jgarff/rpi_ws281x
223 | cd rpi_ws281x/
224 | scons
225 | scons deb
226 | dpkg -i libws2811*.deb
227 | cp ws2811.h /usr/local/include/
228 | cp rpihw.h /usr/local/include/
229 | cp pwm.h /usr/local/include/
230 | cd python
231 | python ./setup.py build
232 | python setup.py install
233 | cd /home/loragw/
234 | npm install -g --unsafe-perm rpi-ws281x-native
235 | npm link rpi-ws281x-native
236 | # We're sudo reset owner
237 | chown -R loragw:loragw /home/loragw/rpi_ws281x
238 | chown -R loragw:loragw /home/loragw/node_modules
239 | fi
240 |
241 | if [[ "$EN_OLED" =~ ^(yes|y|Y)$ ]]; then
242 | echo "Configuring and installing OLED driver"
243 | cd /home/loragw/
244 | replaceAppend /boot/config.txt "^.*dtparam=i2c_arm=.*$" "dtparam=i2c_arm=on,i2c_baudrate=400000"
245 | apt-get install -y --force-yes libfreetype6-dev libjpeg-dev
246 |
247 | echo "Install luma OLED core"
248 | sudo -H pip install --upgrade luma.oled
249 |
250 | echo "Get examples files (and font)"
251 | mkdir -p /usr/share/fonts/truetype/luma
252 | git clone https://github.com/rm-hull/luma.examples.git
253 | # We're sudo reset owner
254 | chown -R loragw:loragw /home/loragw/luma.examples
255 | cp luma.examples/examples/fonts/*.ttf /usr/share/fonts/truetype/luma/
256 |
257 | #echo "Build examples files"
258 | #sudo apt install libsdl-dev libportmidi-dev libsdl-ttf2.0-dev libsdl-mixer1.2-dev libsdl-image1.2-dev
259 | #sudo pip install --upgrade setuptools
260 | #cd luma.examples
261 | #udo -H pip install -e .
262 | fi
263 |
264 | if [[ ! "$BUILD_GW" =~ ^(no|n|N)$ ]]; then
265 | echo "Building LoraGW and kersing packet Forwarder"
266 | mkdir -p $INSTALL_DIR/dev
267 | cd $INSTALL_DIR/dev
268 |
269 | if [ ! -d lora_gateway ]; then
270 | git clone https://github.com/kersing/lora_gateway.git || { echo 'Cloning lora_gateway failed.' ; exit 1; }
271 | else
272 | cd lora_gateway
273 | git reset --hard
274 | git pull
275 | cd ..
276 | fi
277 |
278 | if [ ! -d paho.mqtt.embedded-c ]; then
279 | git clone https://github.com/kersing/paho.mqtt.embedded-c.git || { echo 'Cloning paho mqtt failed.' ; exit 1; }
280 | else
281 | cd paho.mqtt.embedded-c
282 | git reset --hard
283 | git pull
284 | cd ..
285 | fi
286 |
287 | if [ ! -d ttn-gateway-connector ]; then
288 | git clone https://github.com/kersing/ttn-gateway-connector.git || { echo 'Cloning gateway connector failed.' ; exit 1; }
289 | else
290 | cd ttn-gateway-connector
291 | git reset --hard
292 | git pull
293 | cd ..
294 | fi
295 |
296 | if [ ! -d protobuf-c ]; then
297 | git clone https://github.com/kersing/protobuf-c.git || { echo 'Cloning protobuf-c failed.' ; exit 1; }
298 | else
299 | cd protobuf-c
300 | git reset --hard
301 | git pull
302 | cd ..
303 | fi
304 |
305 | if [ ! -d packet_forwarder ]; then
306 | git clone https://github.com/kersing/packet_forwarder.git || { echo 'Cloning packet forwarder failed.' ; exit 1; }
307 | else
308 | cd packet_forwarder
309 | git reset --hard
310 | git pull
311 | cd ..
312 | fi
313 |
314 | if [ ! -d protobuf ]; then
315 | git clone https://github.com/google/protobuf.git || { echo 'Cloning protobuf failed.' ; exit 1; }
316 | else
317 | cd protobuf
318 | git reset --hard
319 | git pull
320 | cd ..
321 | fi
322 |
323 | cd $INSTALL_DIR/dev/lora_gateway/libloragw
324 | sed -i -e 's/PLATFORM= .*$/PLATFORM= imst_rpi/g' library.cfg
325 | sed -i -e 's/CFG_SPI= .*$/CFG_SPI= native/g' library.cfg
326 | make
327 |
328 | cd $INSTALL_DIR/dev/protobuf-c
329 | ./autogen.sh
330 | ./configure
331 | make protobuf-c/libprotobuf-c.la
332 | mkdir bin
333 | ./libtool install /usr/bin/install -c protobuf-c/libprotobuf-c.la `pwd`/bin
334 | rm -f `pwd`/bin/*so*
335 |
336 | cd $INSTALL_DIR/dev/paho.mqtt.embedded-c/
337 | make
338 | make install
339 |
340 | cd $INSTALL_DIR/dev/ttn-gateway-connector
341 | cp config.mk.in config.mk
342 | make
343 | cp bin/libttn-gateway-connector.so /usr/lib/
344 |
345 | cd $INSTALL_DIR/dev/packet_forwarder/mp_pkt_fwd/
346 | make
347 |
348 | # Copy things needed at runtime to where they'll be expected
349 | cp $INSTALL_DIR/dev/packet_forwarder/mp_pkt_fwd/mp_pkt_fwd $INSTALL_DIR/mp_pkt_fwd
350 |
351 | if [ ! -f $INSTALL_DIR/mp_pkt_fwd ]; then
352 | echo "Oup's, something went wrong, kersing forwarder not found"
353 | echo "please check for any build error"
354 | else
355 | echo "Build & Installation Completed."
356 | echo "kersing forwarder is located at $INSTALL_DIR/mp_pkt_fwd"
357 | echo ""
358 | fi
359 | fi
360 |
361 |
362 | if [[ "$BUILD_LEGACY" =~ ^(yes|y|Y)$ ]]; then
363 | echo "Building legacy packet Forwarder"
364 |
365 | mkdir -p $INSTALL_DIR/dev
366 | mkdir -p $INSTALL_DIR/dev/legacy
367 | cd $INSTALL_DIR/dev/legacy
368 |
369 | # Build legacy loragw library
370 | if [ ! -d lora_gateway ]; then
371 | git clone https://github.com/TheThingsNetwork/lora_gateway.git
372 | pushd lora_gateway
373 | else
374 | pushd lora_gateway
375 | git reset --hard
376 | git pull
377 | fi
378 | sed -i -e 's/PLATFORM= .*$/PLATFORM= imst_rpi/g' ./libloragw/library.cfg
379 | sed -i -e 's/CFG_SPI= .*$/CFG_SPI= native/g' ./libloragw/library.cfg
380 | make
381 |
382 | popd
383 | # Build legacy packet forwarder
384 | if [ ! -d packet_forwarder ]; then
385 | git clone https://github.com/ch2i/packet_forwarder
386 | pushd packet_forwarder
387 | else
388 | pushd packet_forwarder
389 | git pull
390 | git reset --hard
391 | fi
392 | make
393 |
394 | # Copy legacy forwarder where it'll be expected
395 | cp ./poly_pkt_fwd/poly_pkt_fwd $INSTALL_DIR/poly_pkt_fwd
396 |
397 | popd
398 |
399 | if [ ! -f $INSTALL_DIR/poly_pkt_fwd ]; then
400 | echo "Oup's, something went wrong, legacy forwarder not found"
401 | echo "please check for any build error"
402 | else
403 | # Copy things needed at runtime to where they'll be expected
404 | cp $INSTALL_DIR/dev/legacy/packet_forwarder/poly_pkt_fwd/poly_pkt_fwd $INSTALL_DIR/poly_pkt_fwd
405 | echo "Build & Installation Completed."
406 | echo "forwarder is located at $INSTALL_DIR/poly_pkt_fwd"
407 | echo ""
408 | echo "Do you want this forwarder to be run instead"
409 | echo -n "kersing mp_pkt_fwd? [y/N] "
410 | read
411 | if [[ "$REPLY" =~ ^(yes|y|Y)$ ]]; then
412 | replace ./start.sh "^.*mp_pkt_fwd.*$" "./poly_pkt_fwd"
413 | fi
414 |
415 | fi
416 |
417 | fi
418 |
419 | # Copying all needed script and system
420 | cd /home/loragw/LoraGW-Setup
421 | cp ./oled.py $INSTALL_DIR/
422 | cp ./monitor-ws2812.py $INSTALL_DIR/
423 | cp ./monitor-gpio.py $INSTALL_DIR/
424 | if [[ $MONITOR_SCRIPT != "" ]]; then
425 | ln -s $INSTALL_DIR/$MONITOR_SCRIPT $INSTALL_DIR/monitor.py
426 | fi
427 | cp ./monitor.service /lib/systemd/system/
428 | cp ./oled.service /lib/systemd/system/
429 | cp start.sh $INSTALL_DIR/
430 |
431 | if [[ ! "$EN_TTN" =~ ^(no|n|N)$ ]]; then
432 | # script to get config from TTN server
433 | python set_config.py
434 |
435 | # Copy config to running folder
436 | sudo mv global_conf.json $INSTALL_DIR/
437 |
438 | # Prepare start forwarder as systemd script
439 | sudo cp ./loragw.service /lib/systemd/system/
440 | sudo systemctl enable loragw.service
441 | sudo systemctl start loragw.service
442 |
443 | echo ""
444 | echo "all done, please check service running log with"
445 | echo "sudo journalctl -f -u loragw.service"
446 |
447 | fi
448 |
449 | if [[ ! "$EN_MONITOR" =~ ^(no|n|N)$ ]]; then
450 | echo "monitor service enabled!"
451 | sudo systemctl enable monitor.service
452 | sudo systemctl start monitor.service
453 | echo ""
454 | fi
455 |
456 | if [[ "$EN_OLED" =~ ^(yes|y|Y)$ ]]; then
457 | echo "Oled service enabled!"
458 | sudo systemctl enable oled.service
459 | sudo systemctl start oled.service
460 | echo "Please follow the procedure located here to finish setup"
461 | echo "https://github.com/ch2i/LoraGW-Setup/doc/DisplayOled.md"
462 | echo "Also don't forget to set the OLED wiring and type by editing file"
463 | echo "/opt/loragw/oled.py (as described into procedure)"
464 | echo ""
465 | fi
466 |
--------------------------------------------------------------------------------
/3_WiFi_AP.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | INSTALL_DIR="/opt/loragw"
4 | MODEL=`cat /proc/device-tree/model`
5 |
6 | if [ $(id -u) -ne 0 ]; then
7 | echo "Installer must be run as root."
8 | echo "Try 'sudo bash $0'"
9 | exit 1
10 | fi
11 |
12 | echo "This script configures a Raspberry Pi"
13 | echo "as a wifi access point and client"
14 | echo
15 | echo "It will install the following dependencies"
16 | echo "hostapd, dnsmasq and configure SSID / Password"
17 | echo
18 | echo "Device is $MODEL"
19 | echo
20 |
21 | if echo "$MODEL" | grep -q "Zero"; then
22 | echo "Device Match configuration OK";
23 | else
24 | echo "This model is not supported, this"
25 | echo "script runs only on Pi Zero else"
26 | echo "it will break your network config"
27 | echo "You'll loose network on reboot and"
28 | echo "potentially access to your device"
29 | echo ""
30 | echo "!!! you've been warned !!!"
31 | echo "exiting immediatly to avoid this"
32 | echo "for this PI model use docker mode"
33 | echo "and follow procedure located here"
34 | echo "https://github.com/cjimti/iotwifi"
35 | echo "to install docker then run script"
36 | echo "4_WiFi_Docker.sh of this folder"
37 |
38 |
39 | exit 0
40 | fi
41 |
42 |
43 | echo "Run time ~1 minute. Reboot required."
44 | echo
45 | echo -n "CONTINUE? [Y/n] "
46 | read
47 | if [[ "$REPLY" =~ ^(no|n|N)$ ]]; then
48 | echo "Canceled."
49 | exit 0
50 | fi
51 |
52 | # These functions have been copied from excellent Adafruit Read only tutorial
53 | # https://github.com/adafruit/Raspberry-Pi-Installer-Scripts/blob/master/read-only-fs.sh
54 | # the one inspired by my original article http://hallard.me/raspberry-pi-read-only/
55 | # That's an excellent demonstration of collaboration and open source sharing
56 | #
57 | # Given a filename, a regex pattern to match and a replacement string:
58 | # Replace string if found, else no change.
59 | # (# $1 = filename, $2 = pattern to match, $3 = replacement)
60 | replace() {
61 | grep $2 $1 >/dev/null
62 | if [ $? -eq 0 ]; then
63 | # Pattern found; replace in file
64 | sed -i "s/$2/$3/g" $1 >/dev/null
65 | fi
66 | }
67 |
68 | # Given a filename, a regex pattern to match and a replacement string:
69 | # If found, perform replacement, else append file w/replacement on new line.
70 | replaceAppend() {
71 | grep $2 $1 >/dev/null
72 | if [ $? -eq 0 ]; then
73 | # Pattern found; replace in file
74 | sed -i "s/$2/$3/g" $1 >/dev/null
75 | else
76 | # Not found; append on new line (silently)
77 | echo $3 | sudo tee -a $1 >/dev/null
78 | fi
79 | }
80 |
81 | # Given a filename, a regex pattern to match and a string:
82 | # If found, no change, else append file with string on new line.
83 | append1() {
84 | grep $2 $1 >/dev/null
85 | if [ $? -ne 0 ]; then
86 | # Not found; append on new line (silently)
87 | echo $3 | sudo tee -a $1 >/dev/null
88 | fi
89 | }
90 |
91 |
92 | echo ""
93 | echo "Enter SSID you want to see for your WiFi AP"
94 | echo -n "default is set to $HOSTNAME : "
95 | read SSID
96 | if [[ $SSID == "" ]]; then
97 | SSID=$HOSTNAME
98 | fi
99 |
100 | echo ""
101 | echo "Enter password you want for you access point"
102 | echo -n "default is set $SSID : "
103 | read PSK
104 | if [[ $PSK == "" ]]; then
105 | PSK=$SSID
106 | fi
107 |
108 | echo ""
109 | echo "Please enter WiFi country code"
110 | echo -n "default is set to FR : "
111 | read CCODE
112 |
113 | echo ""
114 | echo "Setting up WiFi access point with"
115 | echo "SSID : $SSID"
116 | echo "PSK : $PSK"
117 |
118 | sudo apt-get install -y hostapd dnsmasq
119 |
120 | # Set the SSID/PSK
121 | echo "Replacing SSID / PASK in hostapd.conf"
122 | replace ./config/hostapd.conf "^.*_AP_SSID_.*$" "ssid=$SSID"
123 | replace ./config/hostapd.conf "^.*_AP_PASSWORD_.*$" "wpa_passphrase=$PSK"
124 | if [[ $CCODE != "" ]]; then
125 | echo "Setting default country code to $CCCODE in hostapd.conf"
126 | replace ./config/hostapd.conf "^.*country_code=.*$" "country_code=$CCODE"
127 | fi
128 |
129 | # Copy configuration files (save orgininal files)
130 | cp ./config/90-wireless.rules /etc/udev/rules.d/
131 | cp ./config/hostapd.conf /etc/hostapd/
132 | cp ./config/dnsmasq.conf /etc/
133 | cp /etc/network/interfaces /etc/network/interfaces.old
134 | cp ./config/interfaces /etc/network/
135 |
136 | echo "Setting default hostapd config file"
137 | append1 /etc/default/hostapd "^.*DAEMON_CONF=.*$" "DAEMON_CONF=\"/etc/hostapd/hostapd.conf\""
138 |
139 | # disable dhcpcd service
140 | update-rc.d dhcpcd disable
141 |
142 | # Fix bootup (save orgininal file)
143 | cp /etc/rc.local /etc/rc.local.old
144 | cp ./config/rc.local /etc/
145 |
146 | echo "Done."
147 | echo
148 | echo "Settings take effect on next boot."
149 | echo "after reboot, login back here with"
150 | echo "ssh loragw@$HOSTNAME.local"
151 | echo
152 | echo -n "REBOOT NOW? [y/N] "
153 | read
154 | if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then
155 | echo "Exiting without reboot."
156 | exit 0
157 | fi
158 | echo "Reboot started..."
159 | reboot
160 | exit 0
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/4_WiFi_Docker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | INSTALL_DIR="/opt/loragw"
4 | MODEL=`cat /proc/device-tree/model`
5 |
6 | if [ $(id -u) -ne 0 ]; then
7 | echo "Installer must be run as root."
8 | echo "Try 'sudo bash $0'"
9 | exit 1
10 | fi
11 |
12 | echo "This script configures a Raspberry Pi"
13 | echo "as a wifi access point and client"
14 | echo
15 | echo "It will install a docker container doing"
16 | echo "all the necessary staff, please see"
17 | echo "https://github.com/cjimti/iotwifi"
18 | echo
19 | echo "Device is $MODEL"
20 | echo
21 |
22 | echo "Run time ~3 minutes. Reboot required."
23 | echo
24 | echo -n "CONTINUE? [Y/n] "
25 | read
26 | if [[ "$REPLY" =~ ^(no|n|N)$ ]]; then
27 | echo "Canceled."
28 | exit 0
29 | fi
30 |
31 | # These functions have been copied from excellent Adafruit Read only tutorial
32 | # https://github.com/adafruit/Raspberry-Pi-Installer-Scripts/blob/master/read-only-fs.sh
33 | # the one inspired by my original article http://hallard.me/raspberry-pi-read-only/
34 | # That's an excellent demonstration of collaboration and open source sharing
35 | #
36 | # Given a filename, a regex pattern to match and a replacement string:
37 | # Replace string if found, else no change.
38 | # (# $1 = filename, $2 = pattern to match, $3 = replacement)
39 | replace() {
40 | grep $2 $1 >/dev/null
41 | if [ $? -eq 0 ]; then
42 | # Pattern found; replace in file
43 | sed -i "s/$2/$3/g" $1 >/dev/null
44 | fi
45 | }
46 |
47 | echo ""
48 | echo "Enter SSID you want to see for your WiFi AP"
49 | echo -n "default is set to $HOSTNAME : "
50 | read SSID
51 | if [[ $SSID == "" ]]; then
52 | SSID=$HOSTNAME
53 | fi
54 |
55 | echo ""
56 | echo "Enter password you want for you access point"
57 | echo -n "default is set $SSID : "
58 | read PSK
59 | if [[ $PSK == "" ]]; then
60 | PSK=$SSID
61 | fi
62 |
63 | echo ""
64 | echo "Please enter WiFi country code"
65 | echo -n "default is set to FR : "
66 | read CCODE
67 |
68 | echo ""
69 | echo "Setting up WiFi access point with"
70 | echo "SSID : $SSID"
71 | echo "PSK : $PSK"
72 |
73 | echo "installing dpendencies"
74 | echo "Docker install script"
75 | curl -sSL https://get.docker.com | sh
76 | sudo usermod -aG docker loragw
77 |
78 | echo "ull the IOT Wifi Docker Image"
79 | docker pull cjimti/iotwifi
80 |
81 | # Set the SSID/PSK
82 | echo "Replacing SSID / PASK in hostapd.conf"
83 | replace ./wificfg.json "^.*_AP_SSID_.*$" "$SSID"
84 | replace ./wificfg.json "^.*_AP_PASSWORD_.*$" "$PSK"
85 |
86 | # Copy config file to install folder
87 | sudo mv ./wificfg.json $INSTALL_DIR/
88 | sudo ln -s $INSTALL_DIR/wificfg.json ./
89 |
90 | if [[ $CCODE != "" ]]; then
91 | echo "Setting default country code to $CCCODE in hostapd.conf"
92 | replace /etc/wpa_supplicant/wpa_supplicant.conf "^.*country=.*$" "country=$CCODE"
93 | fi
94 |
95 |
96 | # prevent wpa_supplicant from starting on boot it will be used by docker
97 | sudo systemctl mask wpa_supplicant.service
98 | # rename wpa_supplicant on the host to ensure that it is not used.
99 | sudo mv /sbin/wpa_supplicant /sbin/wpa_supplicant.old
100 | # kill any running processes named wpa_supplicant
101 | sudo pkill wpa_supplicant
102 |
103 | echo "starting docker as a service"
104 | docker run --restart unless-stopped -d --privileged --net host \
105 | -v /opt/loragw//wificfg.json:/cfg/wificfg.json \
106 | -v /etc/wpa_supplicant/wpa_supplicant.conf:/etc/wpa_supplicant/wpa_supplicant.conf \
107 | cjimti/iotwifi
108 |
109 | echo "Done."
110 | echo
111 | echo "Settings take effect on next boot."
112 | echo "after reboot, login back here with"
113 | echo "ssh loragw@$HOSTNAME.local"
114 | echo
115 | echo -n "REBOOT NOW? [y/N] "
116 | read
117 | if [[ ! "$REPLY" =~ ^(yes|y|Y)$ ]]; then
118 | echo "Exiting without reboot."
119 | exit 0
120 | fi
121 | echo "Reboot started..."
122 | reboot
123 | exit 0
124 |
125 |
--------------------------------------------------------------------------------
/5_GPS.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | INSTALL_DIR="/opt/loragw"
4 | MODEL=`cat /proc/device-tree/model`
5 |
6 | if [ $(id -u) -ne 0 ]; then
7 | echo "Installer must be run as root."
8 | echo "Try 'sudo bash $0'"
9 | exit 1
10 | fi
11 |
12 | echo "This script configures a Raspberry Pi"
13 | echo "GPS for TTN gateway"
14 | echo
15 | echo "Device is $MODEL"
16 | echo
17 |
18 | echo "Run time ~1 minute. Reboot required."
19 | echo
20 | echo -n "CONTINUE? [Y/n] "
21 | read
22 | if [[ "$REPLY" =~ ^(no|n|N)$ ]]; then
23 | echo "Canceled."
24 | exit 0
25 | fi
26 |
27 | echo ""
28 | echo "Enter GPS device to use"
29 | echo -n "default is set to /dev/ttyS0 : "
30 | read DEVGPS
31 | if [[ $DEVGPS == "" ]]; then
32 | DEVGPS="/dev/ttyS0"
33 | fi
34 |
35 | sudo systemctl stop serial-getty@ttyS0.service
36 | sudo systemctl disable serial-getty@ttyS0.service
37 |
38 | echo
39 | echo "now change the file $INSTALL_DIR/local_conf.json"
40 | echo "and check that the following lines exist"
41 | echo
42 | echo '"fake_gps": false,'
43 | echo '"gps": true,'
44 | echo '"gps_tty_path": "'$DEVGPS'"'
45 | echo
46 | echo "you may also need to add theese lines"
47 | echo "to /boot/config.txt (example for PI 3)"
48 | echo
49 | echo "dtoverlay = pi3-miniuart-bt"
50 | echo "enable_uart=1"
51 | echo
52 | echo "then reboot the Raspberry Pi"
53 |
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lora Gateway base setup for SX1301 based concentrators
2 |
3 | This setup is used for some LoraWAN concentrators based on small computers such as Raspberry PI or others.
4 | For example it works fine with the RAK831 PI Zero [shield](https://github.com/hallard/RAK831-Zero)
5 |
6 |
7 |
8 | And for the [iC880a](https://github.com/ch2i/iC880A-Raspberry-PI) sield for Raspberry PI V2 or V3.
9 |
10 |
11 |
12 | # Installation
13 |
14 | Download [Raspbian lite image](https://downloads.raspberrypi.org/raspbian_lite_latest) and [flash](https://www.raspberrypi.org/documentation/installation/installing-images/README.md) it to your SD card using [etcher](http://etcher.io/).
15 |
16 | ## Prepare SD to your environement
17 |
18 | Once flashed, you need to do some changes on boot partition (windows users, remove and then replug SD card)
19 |
20 | ### Enable SSH
21 |
22 | Create a dummy `ssh` file on this partition. By default SSH is now disabled so this is required to enable it. Windows users, make sure your file doesn't have an extension like .txt etc.
23 |
24 | ### Enable USB OTG (Pi Zero Only)
25 |
26 | If you need to be able to use OTG (Console access for any computer by conecting the PI to computer USB port)
27 | Open up the file `cmdline.txt`. Be careful with this file, it is very picky with its formatting! Each parameter is seperated by a single space (it does not use newlines). Insert `modules-load=dwc2,g_ether` after `rootwait quiet`.
28 |
29 | The new file `cmdline.txt` should looks like this
30 | ```
31 | dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=37665771-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet modules-load=dwc2,g_ether init=/usr/lib/raspi-config/init_resize.sh
32 | ```
33 |
34 | For OTG, add also the bottom of the `config.txt` file, on a new line
35 | ```
36 | dtoverlay=dwc2
37 | ```
38 |
39 | ### Optionnal, disable Auto Resize of SD Card
40 |
41 | And since I don't like the Auto Resize SD function (I prefer do do it manually from `raspi-config`), remove also from the file `cmdline.txt` auto resize by deleting the following
42 | ```
43 | init=/usr/lib/raspi-config/init_resize.sh
44 | ```
45 |
46 | The new file `cmdline.txt` should looks like this
47 | ```
48 | dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=37665771-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet modules-load=dwc2,g_ether
49 | ```
50 |
51 | ### Pre Connect to your WiFi AP
52 |
53 | Finally, on same partition (boot), to allow your PI to connect to your WiFi after first boot, create a file named `wpa_supplicant.conf` to allow the PI to be connected on your WiFi network.
54 |
55 | ```
56 | country=FR
57 | ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
58 | update_config=1
59 | network={
60 | ssid="YOUR-WIFI-SSID"
61 | psk="YOUR-WIFI-PASSWORD"
62 | }
63 | ```
64 | Of course change country, ssid and psk with your own WiFi settings.
65 |
66 |
67 | That's it, eject the SD card from your computer, put it in your Raspberry Pi Zero . It will take up to 90s to boot up (shorter on subsequent boots). You then can SSH into it using `raspberrypi.local` as the address.
68 | If WiFi does not work, connect it via USB to your computer It should then appear as a USB Ethernet device.
69 |
70 | ```shell
71 | ssh pi@raspberrypi.local
72 | ```
73 |
74 | ## Now connect to raspberry PI with ssh or via USB otg
75 |
76 | Remember default login/paswword (ssh or serial console) is pi/raspberry.
77 |
78 | So please **for security reasons, you should change this default password**
79 | ```shell
80 | passwd
81 | ```
82 |
83 | ### Launch PI configuration script
84 |
85 | This [1_Pi_Config.sh](https://github.com/ch2i/LoraGW-Setup/blob/master/1_Pi_Config.sh) script will prepare your Pi environnment, create and configure a loragw user, add access to SPI, I2C, Uart. It will reduce video memory to 16MB to allow max memory for the stuff need to be done.
86 | It also enable excellent [log2ram](https://github.com/azlux/log2ram) SD card preservation.
87 |
88 | ```shell
89 | wget https://raw.githubusercontent.com/ch2i/LoraGW-Setup/master/1_Pi_Config.sh
90 | chmod ug+x 1_Pi_Config.sh
91 | sudo ./1_Pi_Config.sh
92 | ```
93 |
94 |
95 | ## Reconnect after reboot
96 |
97 | Log back with `loragw` user and if you changed hostname to loragw-xxyy, use this command
98 | ```shell
99 | ssh loragw@loragw-xxyy.local
100 | ```
101 |
102 |
103 | ### Get CH2i Gateway Install repository
104 | ```
105 | git clone https://github.com/ch2i/LoraGW-Setup
106 | cd LoraGW-Setup
107 | ```
108 |
109 | ## Configure Gateway on TTN console
110 |
111 | Now you need to register your new GW on TTN before next step, see [gateway registration](https://www.thethingsnetwork.org/docs/gateways/registration.html#via-gateway-connector), the GW_ID and GW_KEY will be asked by the script
112 |
113 | ### Then launch script to install all stuff
114 |
115 | ```shell
116 | sudo ./2_Setup.sh
117 | ```
118 |
119 |
120 | That's it, If you are using PI Zero [shield](https://github.com/hallard/RAK831-Zero), the 2 LED should be blinking green and you should be able to see your brand new gateway on TTN
121 |
122 | # Usefull information
123 |
124 | ## Startup
125 | Check all is fine also at startup, reboot your gateway.
126 | ```
127 | sudo reboot
128 | ```
129 |
130 | ## LED Blinking colors (RAK831 Shied with 2 WS2812B Leds)
131 |
132 | WS2812B driver use DMA channel, and with new Raspbian version, using DMA 5 will corrupt your SD card. see this [issue](https://github.com/jgarff/rpi_ws281x/issues/224). It's now solved but if you have old GW with old scripts, be sure to update the line of script `/opt/loragw/monitor_ws2812.py` from
133 | ```python
134 | strip = Adafruit_NeoPixel(2, gpio_led, 800000, 5, False, 64, 0, ws.WS2811_STRIP_GRB)
135 | ```
136 |
137 | to (using DMA channel 10 instead of 5)
138 | ```python
139 | strip = Adafruit_NeoPixel(2, gpio_led, 800000, 10, False, 64, 0, ws.WS2811_STRIP_GRB)
140 | ```
141 |
142 | ### LED 1
143 |
144 | - green => connected to Internet
145 | - blue => No Internet connexion but gateway [WiFi AP](https://github.com/ch2i/LoraGW-Setup/blob/master/doc/AccessPoint.md) is up
146 | - red => No Internet, no WiFi Access Point
147 |
148 | ### LED 2
149 |
150 | - green => packet forwarder is started and running
151 | - blue => no packed forwarder but local [LoRaWAN server](https://github.com/ch2i/LoraGW-Setup/blob/master/doc/Lorawan-Server.md) is started
152 | - red => No packet forwarder nor LoRaWAN server
153 |
154 | ## LED Blinking colors (iC880a with 4 GPIO Leds)
155 |
156 | - GPIO 4 (Blue) Blink => Internet access OK
157 | - GPIO 18 (Yellow) Blink => local web server up & running
158 | - GPIO 24 (Green)
159 | - Blink => packet forwarder is running
160 | - Fixed => Shutdown OK, can remove power
161 | - GPIO 23 (Red)
162 | - Blink every second, one of the previous service down (local web, internet, )
163 | - Middle bink on every bad LoRaWAN packet received
164 | - Lot of short blink => Activity on SD Card (seen a boot for example)
165 |
166 |
167 | ### Change behaviour
168 |
169 | You can change LED code behaviour at the end of script `/opt/loragw/monitor.py`
170 |
171 |
172 | ## Shutdown
173 | You can press (and let it pressed) the switch push button, leds well become RED and after 2s start blinking in blue. If you release button when they blink blue, the Pi will initiate a shutdown. So let it 30s before removing power.
174 |
175 | ### Shutdown LED display (for RPI 3 and iC880a only)
176 |
177 | If you have a raspberry PI 3 with this [iC880A shield](https://github.com/ch2i/iC880A-Raspberry-PI), then the `/boot/config.txt` file has been enhanced with the following lines:
178 |
179 | ```
180 | # When system if Halted/OFF Light Green LED
181 | dtoverlay=gpio-poweroff,gpiopin=24
182 | ```
183 | The Green LED (gpio24) will stay on when you can remove the power of the gateway. It's really a great indicator.
184 |
185 | You can also select which GPIO LED is used to replace activity LED if you need it.
186 | ```
187 | # Activity LED
188 | dtoverlay=pi3-act-led,gpio=23
189 | ```
190 | The Red LED (gpio23) will blink on activity.
191 |
192 | ### Shutdown or Activity LED (for RPI Zero Shield V1.5+)
193 |
194 | If you have a raspberry PI Zero with this RAK831, then you can change the `/boot/config.txt` file to choose one of the two following features:
195 |
196 | ```
197 | # When system is Halted/OFF Light Green LED
198 | dtoverlay=gpio-poweroff,gpiopin=26
199 | ```
200 | The Green LED (gpio26) will stay on when you can remove the power of the gateway. It's really a great indicator.
201 |
202 | You can also choose select this LED to replace activity LED if you need it.
203 | ```
204 | # Activity LED
205 | dtparam=act_led_gpio=26
206 | ```
207 | The Greend LED (gpio26) will blink on activity.
208 |
209 | You can select only one of the 2 options, not both at the same time.
210 |
211 | ## Detailled information
212 |
213 | The installed sofware is located on `/opt/loragw`, I changed this name (original was ttn-gateway) just because not all my gateways are connected to TTN so I wanted to have a more generic setup.
214 |
215 | ```shell
216 | ls -al /opt/loragw/
217 | total 344
218 | drwxr-xr-x 3 root root 4096 Jan 21 03:15 .
219 | drwxr-xr-x 5 root root 4096 Jan 21 01:01 ..
220 | drwxr-xr-x 9 root root 4096 Jan 21 01:03 dev
221 | -rw-r--r-- 1 root root 6568 Jan 21 01:15 global_conf.json
222 | -rwxr-xr-- 1 root root 3974 Jan 21 01:15 monitor-gpio.py
223 | -rwxr-xr-- 1 root root 3508 Jan 21 03:15 monitor.py
224 | -rwxr-xr-- 1 root root 4327 Jan 21 01:15 monitor-ws2812.py
225 | -rwxr-xr-x 1 root root 307680 Jan 21 01:14 mp_pkt_fwd
226 | -rwxr-xr-- 1 root root 642 Jan 21 01:36 start.sh
227 | ```
228 |
229 | LED blinking and push button functions are done with the monitor.py service (launched by systemd at startup).
230 | There are 2 versions of this service (with symlink), one with WS2812B led and another for classic GPIO LED such as the one on this [IC880A shield](https://github.com/ch2i/iC880A-Raspberry-PI). So if you want to change you can do it like that
231 |
232 | ### stop the service
233 | ```shell
234 | sudo systemctl stop monitor
235 | ```
236 |
237 | ### If you have ic880a shield, change monitor service
238 |
239 | In this case you do not have WS2812B RGB LED on the shield, but GPIO classic one. The push button GPIO to power off the PI is also not on the same GPIO, so you need to setup the correct monitor service.
240 |
241 | ```shell
242 | sudo rm /opt/loragw/monitor.py
243 | sudo ln -s /opt/loragw/monitor-gpio.py /opt/loragw/monitor.py
244 | ```
245 |
246 | ### start the service
247 | ```shell
248 | sudo systemctl start monitor
249 | ```
250 |
251 | ### Check packed forwarder log
252 | ```shell
253 | sudo journalctl -f -u loragw
254 | ```
255 |
256 |
257 | ```
258 | -- Logs begin at Sun 2018-01-21 14:57:08 CET. --
259 | Jan 22 01:00:41 loragw loragw[240]: ### GPS IS DISABLED!
260 | Jan 22 01:00:41 loragw loragw[240]: ### [PERFORMANCE] ###
261 | Jan 22 01:00:41 loragw loragw[240]: # Upstream radio packet quality: 100.00%.
262 | Jan 22 01:00:41 loragw loragw[240]: # Semtech status report send.
263 | Jan 22 01:00:41 loragw loragw[240]: ##### END #####
264 | Jan 22 01:00:41 loragw loragw[240]: 01:00:41 INFO: [TTN] bridge.eu.thethings.network RTT 52
265 | Jan 22 01:00:41 loragw loragw[240]: 01:00:41 INFO: [TTN] send status success for bridge.eu.thethings.network
266 | Jan 22 01:00:53 loragw loragw[240]: 01:00:53 INFO: Disabling GPS mode for concentrator's counter...
267 | Jan 22 01:00:53 loragw loragw[240]: 01:00:53 INFO: host/sx1301 time offset=(1516578208s:159048µs) - drift=-55µs
268 | Jan 22 01:00:53 loragw loragw[240]: 01:00:53 INFO: Enabling GPS mode for concentrator's counter.
269 | Jan 22 01:01:11 loragw loragw[240]: ##### 2018-01-22 00:01:11 GMT #####
270 | Jan 22 01:01:11 loragw loragw[240]: ### [UPSTREAM] ###
271 | Jan 22 01:01:11 loragw loragw[240]: # RF packets received by concentrator: 0
272 | Jan 22 01:01:11 loragw loragw[240]: # CRC_OK: 0.00%, CRC_FAIL: 0.00%, NO_CRC: 0.00%
273 | Jan 22 01:01:11 loragw loragw[240]: # RF packets forwarded: 0 (0 bytes)
274 | Jan 22 01:01:11 loragw loragw[240]: # PUSH_DATA datagrams sent: 0 (0 bytes)
275 | Jan 22 01:01:11 loragw loragw[240]: # PUSH_DATA acknowledged: 0.00%
276 | Jan 22 01:01:11 loragw loragw[240]: ### [DOWNSTREAM] ###
277 | Jan 22 01:01:11 loragw loragw[240]: # PULL_DATA sent: 0 (0.00% acknowledged)
278 | Jan 22 01:01:11 loragw loragw[240]: # PULL_RESP(onse) datagrams received: 0 (0 bytes)
279 | Jan 22 01:01:11 loragw loragw[240]: # RF packets sent to concentrator: 0 (0 bytes)
280 | Jan 22 01:01:11 loragw loragw[240]: # TX errors: 0
281 | Jan 22 01:01:11 loragw loragw[240]: ### BEACON IS DISABLED!
282 | Jan 22 01:01:11 loragw loragw[240]: ### [JIT] ###
283 | Jan 22 01:01:11 loragw loragw[240]: # INFO: JIT queue contains 0 packets.
284 | Jan 22 01:01:11 loragw loragw[240]: # INFO: JIT queue contains 0 beacons.
285 | Jan 22 01:01:11 loragw loragw[240]: ### GPS IS DISABLED!
286 | Jan 22 01:01:11 loragw loragw[240]: ### [PERFORMANCE] ###
287 | Jan 22 01:01:11 loragw loragw[240]: # Upstream radio packet quality: 0.00%.
288 | Jan 22 01:01:11 loragw loragw[240]: # Semtech status report send.
289 | Jan 22 01:01:11 loragw loragw[240]: ##### END #####
290 | Jan 22 01:01:11 loragw loragw[240]: 01:01:11 INFO: [TTN] bridge.eu.thethings.network RTT 53
291 | Jan 22 01:01:11 loragw loragw[240]: 01:01:11 INFO: [TTN] send status success for bridge.eu.thethings.network
292 | ```
293 |
294 |
295 | ### Use legacy Packet Forwarder (not needed)
296 |
297 | First build it
298 | ```
299 | ./build_legacy.sh
300 | ```
301 |
302 | If you want to use the legacy packet forwarder, you'll need to change file `/opt/loragw/start.sh` to replace the last line
303 |
304 | ```
305 | ./mp_pkt_fwd.sh
306 | ```
307 | by
308 | ```
309 | ./poly_pkt_fwd.sh
310 | ```
311 |
312 | ```shell
313 | sudo systemctl stop loragw
314 | sudo systemctl start loragw
315 | ```
316 |
317 | ## Adjust log2ram
318 |
319 | if you chose log2ram to reduce SD card write, you need to change some log file rotation to avoid RAM Disk to be full.
320 |
321 | For this you need to edit each file in `/etc/logrotate.d/`, and on each file:
322 |
323 | - remove line(s) containing `delaycompress` (this avoid uncompressed old log)
324 | - change line(s) containing `rotate n` by rotate 12 (this this the max log file history)
325 | - change line(s) containing `daily` by `hourly` (rotate log each hour)
326 | - change line(s) containing `monthly` by `daily` (rotate log each day)
327 |
328 | In this case we got last 12H with 1 file per hour. Of course, you can adjust these paramaters to feet you need, it's just an example,
329 |
330 | file `/etc/logrotate.d/rsyslog`
331 |
332 | ```
333 | /var/log/syslog
334 | {
335 | rotate 12
336 | hourly
337 | missingok
338 | notifempty
339 | compress
340 | postrotate
341 | invoke-rc.d rsyslog rotate > /dev/null
342 | endscript
343 | }
344 |
345 | /var/log/mail.info
346 | /var/log/mail.warn
347 | /var/log/mail.err
348 | /var/log/mail.log
349 | /var/log/daemon.log
350 | /var/log/kern.log
351 | /var/log/auth.log
352 | /var/log/user.log
353 | /var/log/lpr.log
354 | /var/log/cron.log
355 | /var/log/debug
356 | /var/log/messages
357 | {
358 | rotate 12
359 | hourly
360 | missingok
361 | notifempty
362 | compress
363 | sharedscripts
364 | postrotate
365 | invoke-rc.d rsyslog rotate > /dev/null
366 | endscript
367 | }
368 |
369 | ```
370 |
371 | # And here is the final result
372 |
373 | Click on image to see the video
374 |
375 | [](https://www.youtube.com/watch?v=AZTomPGSOBY "CH2i RAK831 GW")
376 |
377 | # Add some other features
378 |
379 | Here are other feature I use sometime on my gateways:
380 |
381 | - Put the whole filesystem in [ReadOnly](https://hallard.me/raspberry-pi-read-only/)
382 | - Setup PI as a WiFi [access point](https://github.com/ch2i/LoraGW-Setup/blob/master/doc/AccessPoint.md)
383 | - Install a nice local [LoraWAN Server](https://github.com/ch2i/LoraGW-Setup/blob/master/doc/Lorawan-Server.md)
384 | - Use a OLED [display](https://github.com/ch2i/LoraGW-Setup/blob/master/doc/DisplayOled.md)
385 |
--------------------------------------------------------------------------------
/build_legacy.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | INSTALL_DIR="/opt/loragw"
4 |
5 | mkdir -p $INSTALL_DIR/dev
6 | mkdir -p $INSTALL_DIR/dev/legacy
7 | cd $INSTALL_DIR/dev/legacy
8 |
9 | # Build legacy loragw library
10 | if [ ! -d lora_gateway ]; then
11 | git clone https://github.com/TheThingsNetwork/lora_gateway.git || { echo 'Cloning lora_gateway failed.' ; exit 1; }
12 | pushd lora_gateway
13 | else
14 | pushd lora_gateway
15 | git reset --hard
16 | git pull
17 | fi
18 | sed -i -e 's/PLATFORM= .*$/PLATFORM= imst_rpi/g' ./libloragw/library.cfg
19 | sed -i -e 's/CFG_SPI= .*$/CFG_SPI= native/g' ./libloragw/library.cfg
20 | make
21 |
22 | popd
23 | # Build legacy packet forwarder
24 | if [ ! -d packet_forwarder ]; then
25 | git clone https://github.com/ch2i/packet_forwarder
26 | pushd packet_forwarder
27 | else
28 | pushd packet_forwarder
29 | git pull
30 | git reset --hard
31 | fi
32 | make
33 | popd
34 |
35 |
36 | # Copy things needed at runtime to where they'll be expected
37 | cp $INSTALL_DIR/dev/legacy/packet_forwarder/poly_pkt_fwd/poly_pkt_fwd $INSTALL_DIR/poly_pkt_fwd
38 |
39 | if [ ! -f $INSTALL_DIR/poly_pkt_fwd ]; then
40 | echo "Oup's, something went wrong, forwarder not found"
41 | echo "please check for any build error"
42 | else
43 | echo "Build & Installation Completed."
44 | echo "forwarder is located at $INSTALL_DIR/poly_pkt_fwd"
45 | echo ""
46 | echo "you can now run the setup script with sudo ./setup.sh"
47 | fi
48 |
--------------------------------------------------------------------------------
/config/90-wireless.rules:
--------------------------------------------------------------------------------
1 | ACTION=="add|change", SUBSYSTEM=="ieee80211", KERNEL=="phy0", RUN+="/sbin/iw phy %k interface add ap0 type __ap"
2 |
--------------------------------------------------------------------------------
/config/dnsmasq.conf:
--------------------------------------------------------------------------------
1 | interface=lo,ap0
2 | no-dhcp-interface=lo,wlan0
3 | bind-interfaces
4 | server=8.8.8.8
5 | domain-needed
6 | bogus-priv
7 |
8 | # You can change this to another network
9 | dhcp-range=192.168.50.50,192.168.50.100,12h
10 |
11 | # if you have read only FS best to put in tmp area
12 | #dhcp-leasefile=/tmp/dnsmasq.leases
13 |
14 | # if you want to to some rediection
15 | #address=/*.apple.com/192.168.50.1
16 | #address=/*.icloud.com/192.168.50.1
17 | #address=/#/192.168.50.1
18 |
19 | ###### logging ############
20 | # own logfile
21 | #log-facility=/var/log/dnsmasq.log
22 | # log-async
23 | # log dhcp infos
24 | #log-dhcp
25 | # debugging dns
--------------------------------------------------------------------------------
/config/hostapd.conf:
--------------------------------------------------------------------------------
1 | ctrl_interface=/var/run/hostapd
2 | ctrl_interface_group=0
3 | interface=ap0
4 | driver=nl80211
5 | ssid=_AP_SSID_
6 | hw_mode=g
7 | channel=9
8 | wmm_enabled=0
9 | macaddr_acl=0
10 | auth_algs=1
11 | wpa=2
12 | wpa_passphrase=_AP_PASSWORD_
13 | wpa_key_mgmt=WPA-PSK
14 | wpa_pairwise=TKIP CCMP
15 | rsn_pairwise=CCMP
16 | country_code=FR
17 |
--------------------------------------------------------------------------------
/config/interfaces:
--------------------------------------------------------------------------------
1 | # Include files from /etc/network/interfaces.d:
2 | source-directory /etc/network/interfaces.d
3 |
4 | auto lo
5 | auto ap0
6 | auto wlan0
7 | iface lo inet loopback
8 |
9 | allow-hotplug wlan0
10 | iface wlan0 inet manual
11 | wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
12 | iface wlan0 inet dhcp
13 |
14 | allow-hotplug ap0
15 | iface ap0 inet static
16 | address 192.168.50.1
17 | netmask 255.255.255.0
18 | hostapd /etc/hostapd/hostapd.conf
19 |
--------------------------------------------------------------------------------
/config/rc.local:
--------------------------------------------------------------------------------
1 | #!/bin/sh -e
2 | #
3 | # rc.local
4 | #
5 | # This script is executed at the end of each multiuser runlevel.
6 | # Make sure that the script will "exit 0" on success or any other
7 | # value on error.
8 | #
9 | # In order to enable or disable this script just change the execution
10 | # bits.
11 | #
12 | # By default this script does nothing.
13 |
14 | ifdown --force wlan0 && ifdown --force ap0 && ifup ap0 && ifup wlan0
15 | sysctl -w net.ipv4.ip_forward=1
16 | iptables -t nat -A POSTROUTING -s 192.168.50.0/24 ! -d 192.168.50.0/24 -j MASQUERADE
17 | systemctl restart dnsmasq
18 | iwconfig wlan0 power off
19 |
20 | # Print the IP address
21 | _IP=$(hostname -I) || true
22 | if [ "$_IP" ]; then
23 | printf "My IP address is %s\n" "$_IP"
24 | fi
25 |
26 | exit 0
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/doc/AccessPoint.md:
--------------------------------------------------------------------------------
1 | # Promote Raspberry PI based gateway as a WiFi access point
2 |
3 | I found several method to got this working but despite my best efforts, I was unable to get any of those tutorials to work reliably on their own until I found this article. The procedure here works with Raspberry **PI Zero W and Debian Stretch**.
4 |
5 | It may work (or not) with other PI model or OS version.
6 |
7 | I copied the procedure from [original article](https://albeec13.github.io/2017/09/26/raspberry-pi-zero-w-simultaneous-ap-and-managed-mode-wifi/) to have a backup in case and also to adapt to my needs.
8 |
9 | Other solutions I tried with mitigated success on PI Zero is excellent [RaspAP-webgui](https://github.com/billz/raspap-webgui) to make access point. It may works perfecttly for RPI 3 but still issues with PI Zero.
10 |
11 | # Pi Zero W with Raspbian Stretch Procedure
12 |
13 | ## Create new network interface for Access Point (ap0)
14 |
15 | Create/edit the file `/etc/udev/rules.d/90-wireless.rules`, and add the following line.
16 |
17 | ```
18 | ACTION=="add|change", SUBSYSTEM=="ieee80211", KERNEL=="phy0", RUN+="/sbin/iw phy %k interface add ap0 type __ap"
19 | ```
20 |
21 |
22 | ## Install the packages you need for DNS, Access Point and Firewall rules.
23 | ```
24 | sudo apt-get install -y hostapd dnsmasq iptables-persistent
25 | ```
26 |
27 | ## Configure the DNS server
28 |
29 | Edit the file `/etc/dnsmasq.conf` that should be like that, I kept the comments has a reminder to activate some features
30 |
31 | ```
32 | interface=lo,ap0
33 | no-dhcp-interface=lo,wlan0
34 | bind-interfaces
35 | server=8.8.8.8
36 | domain-needed
37 | bogus-priv
38 | dhcp-range=192.168.50.50,192.168.50.100,12h
39 |
40 | #dhcp-leasefile=/tmp/dnsmasq.leases
41 | #address=/*.apple.com/192.168.50.1
42 | #address=/*.icloud.com/192.168.50.1
43 | #address=/#/192.168.50.1
44 |
45 | ###### logging ############
46 | # own logfile
47 | #log-facility=/var/log/dnsmasq.log
48 | # log-async
49 | # log dhcp infos
50 | #log-dhcp
51 | # debugging dns
52 | ```
53 |
54 | After that my AP is distributing address from `192.168.50.50` to `192.168.50.100`
55 |
56 | ## Setup the Access Point configuration
57 |
58 | ### Edit the file `/etc/hostapd/hostapd.conf` that should be like that
59 |
60 | ```
61 | ctrl_interface=/var/run/hostapd
62 | ctrl_interface_group=0
63 | interface=ap0
64 | driver=nl80211
65 | ssid=_AP_SSID_
66 | hw_mode=g
67 | channel=9
68 | wmm_enabled=0
69 | macaddr_acl=0
70 | auth_algs=1
71 | wpa=2
72 | wpa_passphrase=_AP_PASSWORD_
73 | wpa_key_mgmt=WPA-PSK
74 | wpa_pairwise=TKIP CCMP
75 | rsn_pairwise=CCMP
76 | country_code=FR
77 | ```
78 |
79 | Replace `_AP_SSID_` with the SSID you want for your access point. Replace `_AP_PASSWORD_` with the password for your access point. Make sure it has enough characters to be a legal password! (8 characters minimum) else hostapd won't start.
80 |
81 | Change also `country_code` to your own country
82 |
83 | ### Edit the file `/etc/default/hostapd` to enable service to start correctly
84 |
85 | ```
86 | #DAEMON_OPTS=" -f /tmp/hostapd.log"
87 | DAEMON_CONF="/etc/hostapd/hostapd.conf"
88 | ```
89 |
90 | ## Set up the client wifi (station) on wlan0.
91 |
92 | Create or edit `/etc/wpa_supplicant/wpa_supplicant.conf`. The contents depend on whether your home network is open, WEP or WPA/2. It is
93 | probably WPA2, and so should look like:
94 |
95 | ```
96 | country=FR
97 | ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
98 | update_config=1
99 |
100 | network={
101 | ssid="_ST_SSID_"
102 | psk="_ST_PASSWORD_"
103 | id_str="_MYAP_"
104 | }
105 |
106 | ```
107 |
108 | Replace `_ST_SSID_` with your router SSID and `_ST_PASSWORD_` with your wifi password (in clear text).
109 | id_str is a name you'll need later, replace `_MYAP_` by the logical name you want.
110 |
111 | Change also `country` to your own country
112 |
113 |
114 | ## Set up the network inferfaces
115 |
116 | Edit the file `/etc/network/interfaces`. Mine looks like that:
117 |
118 | ```
119 | # Include files from /etc/network/interfaces.d:
120 | source-directory /etc/network/interfaces.d
121 |
122 | auto lo
123 | auto ap0
124 | auto wlan0
125 | iface lo inet loopback
126 |
127 | allow-hotplug wlan0
128 | iface wlan0 inet manual
129 | wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
130 | iface _MYAP_ inet dhcp
131 |
132 | allow-hotplug ap0
133 | iface ap0 inet static
134 | address 192.168.50.1
135 | netmask 255.255.255.0
136 | hostapd /etc/hostapd/hostapd.conf
137 | ```
138 |
139 | Replace `_MYAP_` with the logical name you put in the file `wpa_supplicant.conf`
140 |
141 | ## Set up boot order and/or workaround
142 |
143 | Make sure do disable dhcpcd with
144 | ```shell
145 | sudo update-rc.d dhcpcd disable
146 | ```
147 |
148 | Edit `/etc/rc.local` and add the following lines just before `# Print the IP Address`
149 |
150 | ```shell
151 | ifdown --force wlan0 && ifdown --force ap0 && ifup ap0 && ifup wlan0
152 | sysctl -w net.ipv4.ip_forward=1
153 | iptables -t nat -A POSTROUTING -s 192.168.50.0/24 ! -d 192.168.50.0/24 -j MASQUERADE
154 | systemctl restart dnsmasq
155 | iwconfig wlan0 power off
156 | ```
157 |
158 | ## Optional, add AP display on OLED software
159 |
160 | If you have enabled OLED, you may need to change the following line in `/opt/loragw/oled.py` according to the connected network interface you have to add the ap0 interface
161 |
162 | Mine is wlan0 for WiFi and ap0 for WiFi [access point](https://github.com/ch2i/LoraGW-Setup/blob/master/doc/AccessPoint.md)
163 | ```python
164 | draw.text((col1, line1),"Host :%s" % socket.gethostname(), font=font10, fill=255)
165 | draw.text((col1, line2), lan_ip("wlan0"), font=font10, fill=255)
166 | draw.text((col1, line3), network("wlan0"), font=font10, fill=255)
167 | draw.text((col1, line4), lan_ip("uap0"), font=font10, fill=255)
168 | draw.text((col1, line5), network("uap0"), font=font10, fill=255)
169 | ```
170 |
171 | if for example it's running from RPI 3 with eth0 code could be
172 | ```python
173 | draw.text((col1, line1),"Host :%s" % socket.gethostname(), font=font10, fill=255)
174 | draw.text((col1, line2), lan_ip("eth0"), font=font10, fill=255)
175 | draw.text((col1, line3), network("eth0"), font=font10, fill=255)
176 | draw.text((col1, line4), lan_ip("ap0"), font=font10, fill=255)
177 | draw.text((col1, line5), network("ap0"), font=font10, fill=255)
178 | ```
179 |
--------------------------------------------------------------------------------
/doc/DisplayOled.md:
--------------------------------------------------------------------------------
1 | # Use nice I2C OLED display
2 |
3 | ## Install the packages you need
4 | ```
5 | sudo apt-get update && sudo apt-get upgrade
6 | sudo apt-get install i2c-tools
7 | ```
8 |
9 | ## Increase the I2C baudrate from the default of 100KHz to 400KHz
10 |
11 | ```
12 | sudo nano /boot/config.txt
13 | ```
14 |
15 | add this line at the end of the file
16 | ```
17 | dtparam=i2c_arm=on,i2c_baudrate=400000
18 | ```
19 |
20 |
21 |
22 | ## Add your user to I2C group
23 |
24 | ```
25 | sudo usermod -a -G i2c loragw
26 | ```
27 |
28 | then *logout (CTRL-d) and log back so that the group membership permissions take effect*
29 | ```shell
30 | sudo reboot
31 | ```
32 |
33 | ## Check you can see I2C OLED
34 |
35 | ```shell
36 | i2cdetect -y 1
37 | ```
38 |
39 | you should see something in your OLED I2C address, here it's 3C (Hex)
40 | ```
41 | 0 1 2 3 4 5 6 7 8 9 a b c d e f
42 | 00: -- -- -- -- -- -- -- -- -- -- -- -- --
43 | 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
44 | 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
45 | 30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
46 | 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
47 | 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
48 | 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
49 | 70: -- -- -- -- -- -- -- --
50 | ```
51 |
52 | ## Install OLED Python library
53 |
54 | ### Install dependencies
55 | ```
56 | sudo apt install python-dev python-pip libfreetype6-dev libjpeg-dev build-essential
57 | ```
58 |
59 | ### Install luma OLED core
60 | You can go for a coffe after this one, takes some time
61 | ```
62 | sudo -H pip install --upgrade luma.oled
63 | ```
64 |
65 | ### Get examples files (and font)
66 | ```
67 | sudo mkdir -p /usr/share/fonts/truetype/luma
68 | git clone https://github.com/rm-hull/luma.examples.git
69 | sudo cp luma.examples/examples/fonts/*.ttf /usr/share/fonts/truetype/luma/
70 | ```
71 |
72 | ### Build examples (recommend to skip this section)
73 | But if you want to run all examples, which I do not recommand because it takes SD space and very long time to install, type the following
74 | ```
75 | sudo apt install libsdl-dev libportmidi-dev libsdl-ttf2.0-dev libsdl-mixer1.2-dev libsdl-image1.2-dev
76 | sudo pip install --upgrade setuptools
77 | cd luma.examples
78 | sudo -H pip install -e .
79 | ```
80 |
81 | ## Configure OLED
82 |
83 | ### Configure OLED Hardware
84 |
85 | You may need to change the following line in `oled.py` according to the connected OLED type and it I2C address.
86 |
87 | The i2c adress can be seen with `i2cdetect -y 1`
88 |
89 | ```
90 | 0 1 2 3 4 5 6 7 8 9 a b c d e f
91 | 00: -- -- -- -- -- -- -- -- -- -- -- -- --
92 | 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
93 | 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
94 | 30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
95 | 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
96 | 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
97 | 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
98 | 70: -- -- -- -- -- -- -- --
99 | ```
100 |
101 | If you already installed all the Gateway software it's located in `/opt/loragw/oled.py` else in `~/LoraGW-Setup/oled.py`
102 |
103 | Mine is 1.3" SH1106 with 0x3c address so my config is
104 | ```python
105 | serial = i2c(port=1, address=0x3c)
106 | device = sh1106(serial)
107 | ```
108 |
109 | But as an example, for SSD1306 oled with address 0x3d
110 | ```python
111 | serial = i2c(port=1, address=0x3d)
112 | device = ssd1306(serial)
113 | ```
114 |
115 | Then you can check OLED display with, shold display something
116 | ```
117 | ./oled.py
118 | ```
119 |
120 |
121 | ### Configure OLED software
122 |
123 | You may need to change the following line in `/opt/loragw/oled.py` according to the connected network interface you have
124 |
125 | Mine is wlan0 for WiFi and uap0 for WiFi [access point](https://github.com/ch2i/LoraGW-Setup/blob/master/doc/AccessPoint.md)
126 | ```python
127 | draw.text((col1, line1),"Host :%s" % socket.gethostname(), font=font10, fill=255)
128 | draw.text((col1, line2), lan_ip("wlan0"), font=font10, fill=255)
129 | draw.text((col1, line3), network("wlan0"), font=font10, fill=255)
130 | draw.text((col1, line4), lan_ip("uap0"), font=font10, fill=255)
131 | draw.text((col1, line5), network("uap0"), font=font10, fill=255)
132 | ```
133 |
134 | if for example it's running from RPI 3 with eth0 code could be
135 | ```python
136 | draw.text((col1, line1),"Host :%s" % socket.gethostname(), font=font10, fill=255)
137 | draw.text((col1, line2), lan_ip("eth0"), font=font10, fill=255)
138 | draw.text((col1, line3), network("eth0"), font=font10, fill=255)
139 | draw.text((col1, line4), lan_ip("wlan0"), font=font10, fill=255)
140 | draw.text((col1, line5), network("wlan0"), font=font10, fill=255)
141 | ```
142 |
143 | Once all is setup and running you can enable disbale OLED service with
144 | ```
145 | sudo systemctl enable oled
146 | sudo systemctl disable oled
147 | ```
148 |
149 | And start or stop the OLED service with
150 | ```
151 | sudo systemctl start oled
152 | sudo systemctl stop oled
153 | ```
154 |
--------------------------------------------------------------------------------
/doc/Lorawan-Server.md:
--------------------------------------------------------------------------------
1 | # Use LoRaWAN Server
2 |
3 | ## Description
4 | For this we will use a freee open source lorawan-server written by Peter Gotthard. Thanks to him for sharing. The server is very light and efficient and always safisfied my need.
5 |
6 | ## Installation
7 | You can follow the installation procedure [here](https://github.com/gotthardp/lorawan-server/blob/master/doc/Installation.md)
8 |
9 | I generally use to download the Debian package install it with `dpkg -i`
10 |
11 | ```
12 | wget https://github.com/gotthardp/lorawan-server/releases/download/v0.5.3/lorawan-server_0.5.3_all.deb
13 | dpkg -i lorawan-server_0.5.3_all.deb
14 | ```
15 |
16 | ## Post Installation
17 | Then to improove log rotation, you can add this lines to
18 | `/etc/logrotate.d/rsyslog`
19 | ```
20 | /var/log/lorawan-server/crash.log
21 | /var/log/lorawan-server/error.log
22 | /var/log/lorawan-server/debug.log
23 | {
24 | rotate 1
25 | daily
26 | missingok
27 | notifempty
28 | compress
29 | postrotate
30 | invoke-rc.d rsyslog rotate > /dev/null
31 | endscript
32 | }
33 | ```
34 |
35 | ### Forward packets to local Server
36 |
37 | If you installed all your gateway stuff from this current [repository](https://github.com/ch2i/LoraGW-Setup), you just need to enable the forwarder to send packed to the Local LoRaWAN server.
38 | For this set the line `serv_enabled` to `true` for the server `127.0.0.1`
39 | Take care, the one that has both port to 1680, not the one with 1688/1689 (this one is for OLED)
40 |
41 | `sudo nano /opt/loragw/global_conf.json`
42 |
43 | ```json
44 | {
45 | "server_address": "127.0.0.1",
46 | "serv_enabled": true, // <== set this one to true
47 | "serv_port_up": 1680,
48 | "serv_port_down": 1680
49 | }
50 | ```
51 |
52 | And restart loragw service to take into account the new local server with
53 | ```
54 | sudo systemctl stop loragw
55 | sudo systemctl start loragw
56 | ```
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/loragw.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Lora Gateway
3 | Requires=network-online.target
4 | After=network-online.target
5 |
6 | [Service]
7 | WorkingDirectory=/opt/loragw/
8 | ExecStart=/opt/loragw/start.sh
9 | SyslogIdentifier=loragw
10 | Restart=on-failure
11 | RestartSec=5
12 |
13 | [Install]
14 | WantedBy=multi-user.target
15 |
--------------------------------------------------------------------------------
/monitor-gpio-helium.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # **********************************************************************************
3 | # monitor-gpio-helium.py
4 | # **********************************************************************************
5 | # Script for monitoring LoraWAN Gateways based on small Linux computers
6 | # it's lighing some LEDs depending on status and process
7 | # it's also monitoring a push button to do a clean shutdown
8 | # this one monitor also status of helium gateway service
9 | #
10 | # Written by Charles-Henri Hallard http://ch2i.eu
11 | #
12 | # History : V1.00 2017-12-22 - Creation
13 | #
14 | # All text above must be included in any redistribution.
15 | #
16 | # **********************************************************************************
17 |
18 | import RPi.GPIO as GPIO
19 | import thread
20 | import time
21 | import os
22 | import urllib
23 | import sys
24 | import signal
25 | import subprocess
26 | import select
27 | from systemd import journal
28 |
29 | gpio_blu = 4
30 | gpio_yel = 18
31 | gpio_red = 23
32 | gpio_grn = 24
33 |
34 | internet = False # True if internet connected
35 | pktfwd = False # True if packet forwarder is started
36 | helium = False # True if helium_gateway is running
37 |
38 | def signal_handler(signal, frame):
39 | GPIO.output(gpio_blu, GPIO.LOW)
40 | GPIO.output(gpio_yel, GPIO.LOW)
41 | GPIO.output(gpio_red, GPIO.LOW)
42 | GPIO.output(gpio_grn, GPIO.LOW)
43 | sys.exit(0)
44 |
45 |
46 | def check_process(process):
47 | proc = subprocess.Popen(["if pgrep " + process + " >/dev/null 2>&1; then echo '1'; else echo '0'; fi"], stdout=subprocess.PIPE, shell=True)
48 | (ret, err) = proc.communicate()
49 | ret = int(ret)
50 | # print ret
51 | if ret==1:
52 | return True
53 | else:
54 | return False
55 |
56 | def check_inet(delay):
57 | global internet
58 | global pktfwd
59 | global helium
60 |
61 | while True:
62 | #print "check Internet"
63 | try:
64 | url = "https://www.google.com"
65 | urllib.urlopen(url)
66 | internet = True
67 | except:
68 | internet = False
69 |
70 | # Check packet forwarder
71 | pktfwd = check_process("mp_pkt_fwd") or check_process("poly_pkt_fwd") or check_process("lora_pkt_fwd")
72 | # Check helium gateway
73 | helium = check_process("helium_gateway")
74 |
75 | time.sleep(delay)
76 |
77 | def check_packet(pool_delay):
78 |
79 | # Create a systemd.journal.Reader instance
80 | j = journal.Reader()
81 | # Set the reader's default log level
82 | j.log_level(journal.LOG_INFO)
83 | # Filter log entries
84 | j.add_match(_SYSTEMD_UNIT="helium_gateway.service", SYSLOG_IDENTIFIER="helium_gateway")
85 | j.seek_tail()
86 | j.get_previous()
87 | p = select.poll()
88 | # Register the journal's file descriptor with the polling object.
89 | journal_fd = j.fileno()
90 | poll_event_mask = j.get_events()
91 | p.register(journal_fd, poll_event_mask)
92 |
93 | # Poll for new journal entries every 250ms
94 | while True:
95 | if p.poll(pool_delay):
96 | if j.process() == journal.APPEND:
97 | for entry in j:
98 | try:
99 | # message starts with what we call packet
100 | msg = str(entry['MESSAGE'])
101 | if msg.startswith(('uplink','downlink','join')):
102 | GPIO.output(gpio_grn, GPIO.HIGH)
103 | time.sleep(.3)
104 | except:
105 | pass
106 | GPIO.output(gpio_grn, GPIO.LOW)
107 |
108 | # Use the Broadcom SOC Pin numbers
109 | # Setup the Pin with Internal pullups enabled and PIN in reading mode.
110 | GPIO.setwarnings(False)
111 | GPIO.setmode(GPIO.BCM)
112 | GPIO.setup(gpio_blu, GPIO.OUT)
113 | GPIO.setup(gpio_yel, GPIO.OUT)
114 | GPIO.setup(gpio_red, GPIO.OUT)
115 | GPIO.setup(gpio_grn, GPIO.OUT)
116 |
117 | signal.signal(signal.SIGINT, signal_handler)
118 |
119 | try:
120 | thread.start_new_thread( check_inet, (5, ) )
121 | except:
122 | print "Error: unable to start check_inet thread"
123 |
124 | try:
125 | thread.start_new_thread( check_packet, (250, ) )
126 | except:
127 | print "Error: unable to start check_packet thread"
128 |
129 | # Now wait!
130 | while True:
131 | # Light off all LEDS
132 | led_blu = GPIO.LOW
133 | led_yel = GPIO.LOW
134 | led_red = GPIO.LOW
135 |
136 | # Blue Internet OK else light RED
137 | if internet == True:
138 | led_blu = GPIO.HIGH
139 | else:
140 | led_red = GPIO.HIGH
141 |
142 | # Yellow forwarders OK else light RED
143 | if pktfwd == True and helium == True:
144 | led_yel = GPIO.HIGH
145 | else:
146 | led_red = GPIO.HIGH
147 |
148 | # Blink Blue (Internet)
149 | GPIO.output(gpio_blu, led_blu)
150 | time.sleep(.2)
151 | GPIO.output(gpio_blu, GPIO.LOW)
152 | time.sleep(.8)
153 |
154 | # Blink Yellow (Forwarders)
155 | GPIO.output(gpio_yel, led_yel)
156 | time.sleep(.2)
157 | GPIO.output(gpio_yel, GPIO.LOW)
158 | time.sleep(.8)
159 |
160 |
161 |
--------------------------------------------------------------------------------
/monitor-gpio.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # **********************************************************************************
3 | # monitor-gpio.py
4 | # **********************************************************************************
5 | # Script for monitoring LoraWAN Gateways based on small Linux computers
6 | # it's lighing some LEDs depending on status and process
7 | # it's also monitoring a push button to do a clean shutdown
8 | #
9 | # Written by Charles-Henri Hallard http://ch2i.eu
10 | #
11 | # History : V1.00 2017-12-22 - Creation
12 | #
13 | # All text above must be included in any redistribution.
14 | #
15 | # **********************************************************************************
16 |
17 | import RPi.GPIO as GPIO
18 | import thread
19 | import time
20 | import os
21 | import urllib
22 | import sys
23 | import signal
24 | import subprocess
25 |
26 | # Switch push button
27 | gpio_pin = 19
28 |
29 | gpio_blu = 4
30 | gpio_yel = 18
31 | gpio_red = 23
32 | gpio_grn = 24
33 |
34 | internet = False # True if internet connected
35 | lorawan = False # True if local LoraWan server is running
36 | web = False # True if local Web Server is running
37 | hostapd = False # True if wifi access point is started
38 | pktfwd = False # True if packet forwarder is started
39 |
40 |
41 | def signal_handler(signal, frame):
42 | GPIO.output(gpio_blu, GPIO.LOW)
43 | GPIO.output(gpio_yel, GPIO.LOW)
44 | GPIO.output(gpio_red, GPIO.LOW)
45 | GPIO.output(gpio_grn, GPIO.LOW)
46 | sys.exit(0)
47 |
48 |
49 | def check_process(process):
50 | proc = subprocess.Popen(["if pgrep " + process + " >/dev/null 2>&1; then echo '1'; else echo '0'; fi"], stdout=subprocess.PIPE, shell=True)
51 | (ret, err) = proc.communicate()
52 | ret = int(ret)
53 | # print ret
54 | if ret==1:
55 | return True
56 | else:
57 | return False
58 |
59 | def check_inet(delay):
60 | global internet
61 | global lorawan
62 | global web
63 | global hostapd
64 | global pktfwd
65 |
66 | while True:
67 | #print "check Internet"
68 | try:
69 | url = "https://www.google.com"
70 | urllib.urlopen(url)
71 | internet = True
72 | except:
73 | internet = False
74 |
75 | try:
76 | url = "http://127.0.0.1"
77 | urllib.urlopen(url)
78 | web = True
79 | except:
80 | web = False
81 |
82 | try:
83 | url = "http://127.0.0.1:8080"
84 | urllib.urlopen(url)
85 | lorawan = True
86 | except:
87 | lorawan = False
88 |
89 | # Check WiFi AP mode and packet forwarder
90 | #hostapd = check_process("hostapd")
91 | pktfwd = check_process("mp_pkt_fwd") or check_process("poly_pkt_fwd")
92 |
93 | time.sleep(delay)
94 |
95 | # Use the Broadcom SOC Pin numbers
96 | # Setup the Pin with Internal pullups enabled and PIN in reading mode.
97 | GPIO.setwarnings(False)
98 | GPIO.setmode(GPIO.BCM)
99 | GPIO.setup(gpio_pin, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
100 | GPIO.setup(gpio_blu, GPIO.OUT)
101 | GPIO.setup(gpio_yel, GPIO.OUT)
102 | GPIO.setup(gpio_red, GPIO.OUT)
103 | GPIO.setup(gpio_grn, GPIO.OUT)
104 |
105 |
106 | # Our function on what to do when the button is pressed
107 | def checkShutdown():
108 | if GPIO.input(gpio_pin) == 1:
109 |
110 | GPIO.output(gpio_blu, GPIO.LOW)
111 | GPIO.output(gpio_yel, GPIO.LOW)
112 | GPIO.output(gpio_red, GPIO.LOW)
113 | GPIO.output(gpio_grn, GPIO.LOW)
114 |
115 | time.sleep(.9)
116 | if GPIO.input(gpio_pin) == 1:
117 | GPIO.output(gpio_blu, GPIO.HIGH)
118 | time.sleep(.9)
119 | if GPIO.input(gpio_pin) == 1:
120 | GPIO.output(gpio_yel, GPIO.HIGH)
121 | time.sleep(.9)
122 | if GPIO.input(gpio_pin) == 1:
123 | GPIO.output(gpio_red, GPIO.HIGH)
124 | time.sleep(.9)
125 | if GPIO.input(gpio_pin) == 1:
126 | for x in range(0, 10):
127 | GPIO.output(gpio_blu, GPIO.HIGH)
128 | GPIO.output(gpio_yel, GPIO.HIGH)
129 | GPIO.output(gpio_red, GPIO.HIGH)
130 | GPIO.output(gpio_grn, GPIO.HIGH)
131 | time.sleep(.2)
132 | GPIO.output(gpio_blu, GPIO.LOW)
133 | GPIO.output(gpio_yel, GPIO.LOW)
134 | GPIO.output(gpio_red, GPIO.LOW)
135 | GPIO.output(gpio_grn, GPIO.LOW)
136 | time.sleep(.4)
137 | print "shutdown"
138 | os.system("sudo halt &")
139 | time.sleep(30)
140 |
141 | signal.signal(signal.SIGINT, signal_handler)
142 |
143 | try:
144 | thread.start_new_thread( check_inet, (5, ) )
145 | except:
146 | print "Error: unable to start thread"
147 |
148 | # Now wait!
149 | while 1:
150 | led_blu = GPIO.LOW
151 | led_yel = GPIO.LOW
152 | led_red = GPIO.LOW
153 | led_grn = GPIO.LOW
154 |
155 | if internet == True:
156 | led_blu = GPIO.HIGH
157 | else:
158 | led_red = GPIO.HIGH
159 |
160 | if web == True:
161 | led_yel = GPIO.HIGH
162 | else:
163 | led_red = GPIO.HIGH
164 |
165 | if pktfwd == True:
166 | led_grn = GPIO.HIGH
167 | else:
168 | led_red = GPIO.HIGH
169 |
170 | GPIO.output(gpio_blu, led_blu)
171 | time.sleep(.2)
172 | checkShutdown();
173 | GPIO.output(gpio_blu, GPIO.LOW)
174 | time.sleep(.8)
175 | checkShutdown();
176 |
177 | GPIO.output(gpio_yel, led_yel)
178 | time.sleep(.2)
179 | checkShutdown();
180 | GPIO.output(gpio_yel, GPIO.LOW)
181 | time.sleep(.8)
182 | checkShutdown();
183 |
184 | GPIO.output(gpio_grn, led_grn)
185 | time.sleep(.2)
186 | checkShutdown();
187 | GPIO.output(gpio_grn, GPIO.LOW)
188 | time.sleep(.8)
189 | checkShutdown();
190 |
191 |
192 |
--------------------------------------------------------------------------------
/monitor-ws2812.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # **********************************************************************************
4 | # monitor-ws2812.py
5 | # **********************************************************************************
6 | # Script for monitoring LoraWAN Gateways based on small Linux computers
7 | # it's lighing some WS2812 LEDs depending on status and process
8 | # it's also monitoring a push button to do a clean shutdown
9 | #
10 | # Written by Charles-Henri Hallard http://ch2i.eu
11 | #
12 | # History : V1.00 2017-12-22 - Creation
13 | #
14 | # All text above must be included in any redistribution.
15 | #
16 | # Poor code, written in a hurry, need optimization and rewrite for sure
17 | #
18 | # **********************************************************************************
19 |
20 | import RPi.GPIO as GPIO
21 | import thread
22 | import time
23 | import os
24 | import urllib
25 | from neopixel import *
26 | import sys
27 | import signal
28 | import subprocess
29 |
30 | gpio_pin = 17 # Switch push button pin
31 | gpio_led = 18 # WS2812 led pin
32 |
33 | internet = False # True if internet connected
34 | lorawan = False # True if local LoraWan server is running
35 | web = False # True if local Web Server is running
36 |
37 | hostapd = False # True if wifi access point is started
38 | pktfwd = False # True if packet forwarder is started
39 |
40 | # LED color definition
41 | color_off = Color(0,0,0)
42 | color_red = Color(128,0,0)
43 | color_grn = Color(0,128,0)
44 | color_blu = Color(0,0,128)
45 | color_yel = Color(128,128,0)
46 |
47 | def signal_handler(signal, frame):
48 | colorSet(strip, 0, color_off )
49 | colorSet(strip, 1, color_off )
50 | sys.exit(0)
51 |
52 |
53 | # Check if a process is running
54 | def check_process(process):
55 | proc = subprocess.Popen(["if pgrep " + process + " >/dev/null 2>&1; then echo '1'; else echo '0'; fi"], stdout=subprocess.PIPE, shell=True)
56 | (ret, err) = proc.communicate()
57 | ret = int(ret)
58 | #print ret
59 | if ret==1:
60 | return True
61 | else:
62 | return False
63 |
64 | # Check internet connected
65 | def check_inet(delay):
66 | global internet
67 | global lorawan
68 | global web
69 | global hostapd
70 | global pktfwd
71 |
72 | while True:
73 | #print "check Internet"
74 | try:
75 | url = "https://www.google.com"
76 | urllib.urlopen(url)
77 | internet = True
78 | except:
79 | internet = False
80 |
81 | # Check local Web Server (if any)
82 | try:
83 | url = "http://127.0.0.1"
84 | urllib.urlopen(url)
85 | web = True
86 | except:
87 | web = False
88 |
89 | # Check local LoRaWAN server (if any)
90 | try:
91 | url = "http://127.0.0.1:8080"
92 | urllib.urlopen(url)
93 | lorawan = True
94 | except:
95 | lorawan = False
96 |
97 | # Check WiFi AP mode and packet forwarder
98 | hostapd = check_process("hostapd")
99 | pktfwd = check_process("mp_pkt_fwd") or check_process("poly_pkt_fwd")
100 |
101 | time.sleep(delay)
102 |
103 |
104 | # Use the Broadcom SOC Pin numbers
105 | # Setup the Switch pin with pulldown enabled and PIN in input
106 | GPIO.setwarnings(False)
107 | GPIO.setmode(GPIO.BCM)
108 | GPIO.setup(gpio_pin, GPIO.IN, pull_up_down = GPIO.PUD_DOWN)
109 |
110 |
111 | # Define functions which animate LEDs in various ways.
112 | def colorSet(strip, led, color):
113 | strip.setPixelColor(led, color)
114 | strip.show()
115 |
116 | # Our function on what to do when the button is pressed
117 | def checkShutdown():
118 | # Button pressed light LED1 in RED
119 | if GPIO.input(gpio_pin) == 1:
120 | colorSet(strip, 0, color_red)
121 | colorSet(strip, 1, color_off)
122 | time.sleep(.9)
123 | # Still pressed after 900ms light LED2 in RED
124 | if GPIO.input(gpio_pin) == 1:
125 | colorSet(strip, 1, color_red)
126 | time.sleep(.9)
127 | # Still pressed after 900ms more, blink blue 10 times
128 | if GPIO.input(gpio_pin) == 1:
129 | for x in range(0, 10):
130 | colorSet(strip, 0, color_blu)
131 | colorSet(strip, 1, color_blu)
132 | time.sleep(.2)
133 | colorSet(strip, 0, color_off)
134 | colorSet(strip, 1, color_off)
135 | time.sleep(.4)
136 | print "shutdown"
137 | # start the poweroff process in background
138 | os.system("sudo poweroff &")
139 | # prevent this script to continue
140 | time.sleep(30)
141 |
142 | signal.signal(signal.SIGINT, signal_handler)
143 |
144 | # Create NeoPixel object 2 LEDs, 64 Brighness GRB leds
145 | strip = Adafruit_NeoPixel(2, gpio_led, 800000, 10, False, 64, 0, ws.WS2811_STRIP_GRB)
146 |
147 | # Intialize the library (must be called once before other functions).
148 | strip.begin()
149 |
150 | # Check connection/process every 5 seconds
151 | try:
152 | thread.start_new_thread( check_inet, (5, ) )
153 | except:
154 | print "Error: unable to start thread"
155 |
156 | # Now wait in infinite loop
157 | while 1:
158 | led0 = color_red
159 | led1 = color_red
160 |
161 | if internet == True:
162 | led0 = color_grn
163 | else:
164 | if hostapd == True:
165 | led0 = color_blu
166 |
167 | # if pktfwd == True and web == True and lorawan == True:
168 | if pktfwd == True :
169 | led1 = color_grn
170 | else:
171 | if lorawan == True:
172 | led0 = color_blu
173 |
174 | colorSet(strip, 0, led0)
175 | time.sleep(.2)
176 | checkShutdown();
177 | colorSet(strip, 0, color_off)
178 | time.sleep(.8)
179 | checkShutdown();
180 |
181 | colorSet(strip, 1, led1)
182 | time.sleep(.2)
183 | checkShutdown();
184 | colorSet(strip, 1, color_off)
185 | time.sleep(.8)
186 | checkShutdown();
187 |
--------------------------------------------------------------------------------
/monitor.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=LoraGW monitoring service
3 |
4 | [Service]
5 | WorkingDirectory=/opt/loragw/
6 | ExecStart=/opt/loragw/monitor.py
7 | SyslogIdentifier=monitor
8 | Restart=on-failure
9 | RestartSec=5
10 |
11 | [Install]
12 | WantedBy=multi-user.target
13 |
--------------------------------------------------------------------------------
/oled.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | import psutil
5 | import platform
6 | import thread
7 | import socket
8 | import SocketServer
9 | import json
10 | from luma.core.interface.serial import i2c
11 | from luma.core.render import canvas
12 | from luma.oled.device import ssd1306
13 | from luma.oled.device import sh1106
14 | from PIL import ImageFont
15 | import time
16 | from datetime import datetime
17 |
18 | width=128
19 | height=64
20 |
21 | font_pixel=12
22 | line1=0
23 | line2=line1+12
24 | line3=line2+12
25 | line4=line3+12
26 | line5=line4+12
27 | col1=0
28 | lora_rssi = None
29 | lora_chan = None
30 | lora_freq = None
31 | lora_datr = None
32 | lora_mote = None
33 |
34 | class LeaseEntry:
35 | def __init__(self, leasetime, macAddress, ipAddress, name):
36 | if (leasetime == '0'):
37 | self.staticIP = True
38 | else:
39 | self.staticIP = False
40 | self.leasetime = datetime.fromtimestamp(int(leasetime)).strftime('%Y-%m-%d %H:%M:%S')
41 | self.macAddress = macAddress.upper()
42 | self.ipAddress = ipAddress
43 | self.name = name
44 |
45 | def serialize(self):
46 | return {
47 | 'staticIP': self.staticIP,
48 | 'leasetime': self.leasetime,
49 | 'macAddress': self.macAddress,
50 | 'ipAddress': self.ipAddress,
51 | 'name': self.name
52 | }
53 |
54 |
55 | class MyUDPHandler(SocketServer.BaseRequestHandler):
56 |
57 | def is_json(self, myjson):
58 | try:
59 | json_object = json.loads(myjson)
60 | except ValueError, e:
61 | return False
62 | return True
63 |
64 | def handle(self):
65 | json_data
66 | global lora_rssi
67 | global lora_chan
68 | global lora_freq
69 | global lora_datr
70 | global lora_mote
71 |
72 | data = self.request[0]
73 | # for poly_pkt_fwd remove 12 first bytes
74 | # if mp_pkt_fwd then packet are already fine
75 | if data.find("{\"rxpk\":[") > 0:
76 | data = data[12::]
77 |
78 | if self.is_json(data):
79 | #print("\r\n")
80 | #print(data)
81 | js_data = json.loads(data)
82 |
83 | # for mp_pkt_fwd
84 | if js_data.get('type')=='uplink':
85 | lora_mote = js_data["mote"]
86 | lora_rssi = js_data["rssi"]
87 | lora_chan = js_data["chan"]
88 | lora_freq = js_data["freq"]
89 | lora_datr = js_data["datr"]
90 | # for poly_pkt_fwd
91 | elif js_data.get('rxpk'):
92 | lora_mote = "legacy_fwd"
93 | lora_rssi = js_data["rxpk"][0]["rssi"]
94 | lora_chan = js_data["rxpk"][0]["chan"]
95 | lora_freq = js_data["rxpk"][0]["freq"]
96 | lora_datr = js_data["rxpk"][0]["datr"]
97 |
98 | def leaseSort(arg):
99 | # Fixed IPs first
100 | if arg.staticIP == True:
101 | return 0
102 | else:
103 | return arg.name.lower()
104 |
105 | def getLeases():
106 | leases = list()
107 | try:
108 | with open("/var/lib/misc/dnsmasq.leases") as f:
109 | for line in f:
110 | elements = line.split()
111 | if len(elements) == 5:
112 | entry = LeaseEntry(elements[0],elements[1],elements[2],elements[3])
113 | leases.append(entry)
114 |
115 | leases.sort(key = leaseSort)
116 | return [lease.serialize() for lease in leases]
117 |
118 | except Exception as e:
119 | print "error in getLeases"
120 | print(e)
121 | print(type(e))
122 | return None
123 |
124 |
125 | def do_nothing(obj):
126 | pass
127 |
128 | def make_font(name, size):
129 | font_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', name))
130 | return ImageFont.truetype(font_path, size)
131 |
132 | # rev.1 users set port=0
133 | # substitute spi(device=0, port=0) below if using that interface
134 | serial = i2c(port=1, address=0x3c)
135 | device = sh1106(serial)
136 | #device = ssd1306(serial)
137 | device.cleanup = do_nothing
138 |
139 | font10 = make_font("/usr/share/fonts/truetype/luma/ProggyTiny.ttf", 16)
140 | byteunits = ('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB')
141 |
142 | def udp_receive(delay):
143 | server = SocketServer.UDPServer(("127.0.0.1", 1688), MyUDPHandler)
144 | server.serve_forever()
145 | #server.handle_request()
146 |
147 | def filesizeformat(value):
148 | exponent = int(log(value, 1024))
149 | return "%.1f %s" % (float(value) / pow(1024, exponent), byteunits[exponent])
150 |
151 | def bytes2human(n):
152 | symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
153 | prefix = {}
154 | for i, s in enumerate(symbols):
155 | prefix[s] = 1 << (i + 1) * 10
156 | for s in reversed(symbols):
157 | if n >= prefix[s]:
158 | value = int(float(n) / prefix[s])
159 | return '%s%s' % (value, s)
160 | return "%sB" % n
161 |
162 | def if_stat(iface):
163 | try:
164 | stat = psutil.net_io_counters(pernic=True)[iface]
165 | return "Tx %s Rx %s" % (bytes2human(stat.bytes_sent), bytes2human(stat.bytes_recv))
166 |
167 | except KeyError as e:
168 | return "Tx %s Rx %s" % (bytes2human(0), bytes2human(0))
169 |
170 | def lan_info(iface):
171 | ip = "No Interface"
172 | stat = ""
173 | try:
174 | for snic in psutil.net_if_addrs()[iface]:
175 | if snic.family == socket.AF_INET:
176 | ip = snic.address
177 | stat = if_stat(iface)
178 | break
179 | else:
180 | ip ="No IP"
181 |
182 | return "%-5s: %s" % (iface, ip), stat
183 |
184 | except KeyError as e:
185 | return ip, stat
186 |
187 |
188 | def uptime():
189 | try:
190 | f = open( "/proc/uptime" )
191 | contents = f.read().split()
192 | f.close()
193 | except:
194 | return "no /proc/uptime"
195 |
196 | total_seconds = float(contents[0])
197 |
198 | # Helper vars:
199 | MINUTE = 60
200 | HOUR = MINUTE * 60
201 | DAY = HOUR * 24
202 |
203 | # Get the days, hours, etc:
204 | days = int( total_seconds / DAY )
205 | hours = int( ( total_seconds % DAY ) / HOUR )
206 | minutes = int( ( total_seconds % HOUR ) / MINUTE )
207 | seconds = int( total_seconds % MINUTE )
208 |
209 | # Build up the pretty string (like this: "N days, N hours, N minutes, N seconds")
210 | string = ""
211 | if days > 0:
212 | string += str(days) + "d "
213 | if len(string) > 0 or hours > 0:
214 | string += str(hours) + "h "
215 | if len(string) > 0 or minutes > 0:
216 | string += str(minutes) + "m "
217 | if hours == 0 and seconds >0:
218 | string += str(seconds) + "s"
219 |
220 | return string;
221 |
222 | def ip_client(thislease):
223 | ips = thislease["ipAddress"].split('.')
224 | return "%s.%s: %s" % (ips[2],ips[3], thislease["name"])
225 |
226 | def stats():
227 | global looper
228 | lease = None
229 | with canvas(device) as draw:
230 | #draw.rectangle((0,0,127,63), outline="white", fill="black")
231 | if looper==0:
232 | looper=1
233 | if lora_mote != None:
234 | draw.text((col1, line1),"Mote %s" % lora_mote, font=font10, fill=255)
235 | draw.text((col1, line2),"RSSI %sdBm" % lora_rssi, font=font10, fill=255)
236 | draw.text((col1, line3),"Chan %s" % lora_chan, font=font10, fill=255)
237 | draw.text((col1, line4),"Freq %.2f MHz" % lora_freq, font=font10, fill=255)
238 | draw.text((col1, line5),"Rate %s" % lora_datr, font=font10, fill=255)
239 | else:
240 | draw.text((col1, line1),"No LoRaWAN Data yet", font=font10, fill=255)
241 |
242 | elif looper==1:
243 | looper = 2
244 | draw.text((col1, line1),"Host :%s" % socket.gethostname(), font=font10, fill=255)
245 | # Try to get wlan0 if not then eth0
246 | ip, stats = lan_info("eth0")
247 | if ip == "No Interface":
248 | ip, stats = lan_info("wlan0")
249 |
250 | draw.text((col1, line2), ip, font=font10, fill=255)
251 | draw.text((col1, line3), stats, font=font10, fill=255)
252 |
253 | ip, stats = lan_info("ap0")
254 | if ip != "No IP" and ip != "No Interface":
255 | draw.text((col1, line4), ip, font=font10, fill=255)
256 | draw.text((col1, line5), stats, font=font10, fill=255)
257 | lease = getLeases()
258 | else:
259 | draw.text((col1, line4), "ap0", font=font10, fill=255)
260 | draw.text((col1, line5), "No Access Point", font=font10, fill=255)
261 |
262 | if lease == None:
263 | looper = 3
264 | elif looper==2:
265 | looper = 3
266 | lease = getLeases()
267 | lg = len(lease)
268 |
269 | draw.text((col1, line1), "Wifi Clients => %d" % lg, font=font10, fill=255)
270 | if lg>=1: draw.text((col1, line2), ip_client(lease[0]), font=font10, fill=255)
271 | if lg>=2: draw.text((col1, line3), ip_client(lease[1]), font=font10, fill=255)
272 | if lg>=3: draw.text((col1, line4), ip_client(lease[2]), font=font10, fill=255)
273 | if lg>=4: draw.text((col1, line5), ip_client(lease[3]), font=font10, fill=255)
274 |
275 | elif looper==3:
276 | looper = 0
277 | tempC = int(open('/sys/class/thermal/thermal_zone0/temp').read())
278 | av1, av2, av3 = os.getloadavg()
279 | mem = psutil.virtual_memory()
280 | dsk = psutil.disk_usage("/")
281 |
282 | draw.text((col1, line1), "CPU LOAD: %.1f %.1f" % (av1, av3), font=font10, fill=255)
283 | draw.text((col1, line2), "MEM FREE: %s/%s" % (bytes2human(mem.available), bytes2human(mem.total)), font=font10, fill=255)
284 | draw.text((col1, line3), "DSK FREE: %s/%s" % (bytes2human(dsk.total-dsk.used), bytes2human(dsk.total)),font=font10, fill=255)
285 | draw.text((col1, line4), "CPU TEMP: %sc" % (str(tempC/1000)), font=font10, fill=255)
286 | draw.text((col1, line5), "UP : %s" % uptime(), font=font10, fill=255)
287 |
288 | else:
289 | #draw.text((col1, line1),"%s %s" % (platform.system(),platform.release()), font=font10, fill=255)
290 | #uptime = datetime.now() - datetime.fromtimestamp(psutil.boot_time())
291 | #draw.text((col1, line2),str(datetime.now().strftime('%a %b %d %H:%M:%S')), font=font10, fill=255)
292 | #draw.text((col1, line3),"Uptime %s" % str(uptime).split('.')[0], font=font10, fill=255)
293 | looper=0
294 |
295 | def main():
296 | global looper
297 | global json_data
298 | looper = 1
299 | json_data = None
300 |
301 | try:
302 | thread.start_new_thread( udp_receive, (5, ) )
303 | except:
304 | print "Error: unable to start thread"
305 |
306 | while True:
307 | stats()
308 | if looper==0:
309 | time.sleep(2)
310 | else:
311 | time.sleep(5)
312 |
313 | if __name__ == "__main__":
314 | try:
315 | main()
316 | except KeyboardInterrupt:
317 | pass
318 |
--------------------------------------------------------------------------------
/oled.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=LoraGW OLED service
3 |
4 | [Service]
5 | WorkingDirectory=/opt/loragw/
6 | ExecStart=/opt/loragw/oled.py
7 | SyslogIdentifier=monitor
8 | Restart=on-failure
9 | RestartSec=5
10 |
11 | [Install]
12 | WantedBy=multi-user.target
13 |
--------------------------------------------------------------------------------
/sensors_js/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # ******************************************************************************
4 | # installer script to send sensors data to cayenne IoT dashboard
5 | # you can have a BMP280/BME280 and SI7021/HTU21D conencted to I2C bus
6 | # This sample has been written by Charles-Henri Hallard (ch2i.eu)
7 | # *******************************************************************************
8 |
9 | function prog_installed {
10 | local ret=1
11 | type $1 >/dev/null 2>&1 || { local ret=0; }
12 | echo "$ret"
13 | }
14 |
15 | # Stop on the first sign of trouble
16 | set -e
17 |
18 | if [ $UID != 0 ]; then
19 | echo "ERROR: Operation not permitted. Forgot sudo?"
20 | exit 1
21 | fi
22 |
23 | # Install repositories
24 | INSTALL_DIR="/opt/loragw"
25 | if [ ! -d "$INSTALL_DIR" ]; then mkdir $INSTALL_DIR; fi
26 |
27 |
28 | # Check if nodejs is installed
29 | if [ $(prog_installed node) == 0 ]; then
30 | echo "nodejs not found, please install following lastest procedure"
31 | exit 1;
32 | else
33 | echo "nodejs found, "`node -v`
34 | fi
35 |
36 | # nodejs module dependencies
37 | pushd $INSTALL_DIR
38 | if [ ! -d node_modules ]; then mkdir node_modules; fi
39 | modules=( bme280-sensor si7021-sensor cayennejs )
40 | for mod in "${modules[@]}"
41 | do
42 | echo "Installing NodeJS $mod"
43 | npm install -g --unsafe-perm $mod
44 | npm link $mod
45 | done
46 | popd
47 |
48 | printf "Please enter your cayenne credentials and your\n"
49 | printf "device client ID (see https://cayenne.mydevices.com/)\n"
50 | printf " Cayenne username : "
51 |
52 | read CAYENNE_USER
53 | if [[ $CAYENNE_USER != "" ]]; then sed -i -- 's/__CAYENNE_USER__/'$CAYENNE_USER'/g' sensors.js; fi
54 | printf " Cayenne password : "
55 | read CAYENNE_PASS
56 | if [[ $CAYENNE_PASS != "" ]]; then sed -i -- 's/__CAYENNE_PASS__/'$CAYENNE_PASS'/g' sensors.js; fi
57 | printf " Cayenne clientID : "
58 | read CAYENNE_CLID
59 | if [[ $CAYENNE_CLID != "" ]]; then sed -i -- 's/__CAYENNE_CLID__/'$CAYENNE_CLID'/g' sensors.js; fi
60 |
61 | # cp nodejs sensor script
62 | cp -f ./sensors.js $INSTALL_DIR/
63 |
64 | # cp startup script as a service
65 | cp ./sensors-js.service /lib/systemd/system/
66 |
67 | # Enable the service
68 | systemctl enable sensors-js.service
69 | systemctl start sensors-js.service
70 |
71 | # wait service to read values
72 | echo "Waiting service to start and connect..."
73 | sleep 3
74 |
75 | echo
76 | echo "Installation completed.\n"
77 | echo "use sudo systemctl status sensors-js to see service status"
78 | echo "use sudo journalctl -f -u sensors-js to see service log"
79 | echo -n
80 | systemctl status sensors-js.service
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/sensors_js/sensors-js.service:
--------------------------------------------------------------------------------
1 | # Enable the service
2 | # systemctl enable sensors-js.service
3 | #
4 | # Start the service
5 | # systemctl start sensors-js.service
6 | #
7 | # Verify it is running
8 | # systemctl status sensors-js.service
9 |
10 |
11 | [Unit]
12 | Description=CH2i Lora Gateway Sensors service
13 |
14 | [Service]
15 | ExecStart=/usr/bin/node /opt/loragw/sensors.js
16 | Restart=always
17 | RestartSec=30
18 | StandardOutput=syslog
19 | StandardError=syslog
20 | SyslogIdentifier=sensors-js
21 |
22 | [Install]
23 | WantedBy=multi-user.target
24 |
--------------------------------------------------------------------------------
/sensors_js/sensors.js:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * NodeJS script to send sensors data to cayenne IoT dashboard
3 | * you can have a BMP280/BME280 and SI7021/HTU21D conencted to I2C bus
4 | * This sample has been written by Charles-Henri Hallard (ch2i.eu)
5 | *
6 | * Requires nodejs to be already installed
7 | * https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
8 | *
9 | * Requires CayenneJS bme280 and si7021 libraries
10 | * npm install bme280-sensor si7021-sensor cayennejs
11 | *
12 | * Don't forget to put your Cayenne credential in this script, all of this
13 | * stuff can be done with the script installer install.sh of this folder
14 | * installer will also make the as daemon to start/stop with the system
15 | *
16 | * sudo node sensors.js
17 | *
18 | *******************************************************************************/
19 |
20 | const BME280 = require('bme280-sensor');
21 | const Si7021 = require('si7021-sensor');
22 | var Cayenne = require('cayennejs');
23 |
24 | const bme280 = new BME280({ i2cBusNo : 1, i2cAddress : 0x76 });
25 | const si7021 = new Si7021({ i2cBusNo : 1, i2cAddress : 0x40 });
26 |
27 | // Update every x seconds
28 | const updateInterval = 60;
29 |
30 | // Initiate Cayenne MQTT API
31 | const cayenneClient = new Cayenne.MQTT({
32 | username: "a1ced9e0-b24e-11e6-bb76-1157597ded22",
33 | password: "0858f39268653283bf68bb08b165c07cd6cb1959",
34 | clientId: "63bf8990-bd27-11e6-ae5a-dfc2c3108b24"
35 | });
36 |
37 | // Read BME280 sensor data, repeat
38 | const readBME280SensorData = () => {
39 | bme280.readSensorData()
40 | .then((data) => {
41 |
42 | console.log(`BME280 data = ${JSON.stringify(data, null, 2)}`);
43 |
44 | // dashboard widget automatically detects datatype & unit
45 | cayenneClient.celsiusWrite (0, data.temperature_C.toFixed(1));
46 | cayenneClient.rawWrite(1, data.humidity.toFixed(0), "rel_hum" , "p" );
47 | cayenneClient.hectoPascalWrite (2, data.pressure_hPa.toFixed(0));
48 |
49 | setTimeout(readBME280SensorData, updateInterval*1000);
50 | })
51 | .catch((err) => {
52 | console.log(`BME280 read error: ${err}`);
53 | setTimeout(readBME280SensorData, updateInterval*1000);
54 | });
55 | };
56 |
57 | const readSI7021SensorData = () => {
58 | si7021.readSensorData()
59 | .then((data) => {
60 |
61 | console.log(`SI7021 data = ${JSON.stringify(data, null, 2)}`);
62 |
63 | cayenneClient.celsiusWrite (4, data.temperature_C.toFixed(1));
64 | cayenneClient.rawWrite(5, data.humidity.toFixed(0), "rel_hum" , "p" );
65 |
66 | setTimeout(readSI7021SensorData, updateInterval*1000);
67 | })
68 | .catch((err) => {
69 | console.log(`Si7021 read error: ${err}`);
70 | setTimeout(readSI7021SensorData, updateInterval*1000);
71 | });
72 | };
73 |
74 | cayenneClient.connect((err , mqttClient) => {
75 | console.log('Cayenne connected')
76 |
77 | // Initialize the BME280 sensor
78 | bme280.init()
79 | .then(() => {
80 | console.log('BME280 initialization succeeded');
81 | readBME280SensorData();
82 | })
83 | .catch((err) => console.error(`BME280 initialization failed: ${err} `));
84 |
85 | // Initialize the si7021 sensor
86 | si7021.reset()
87 | .then(() => {
88 | console.log('SI7021 initialization succeeded');
89 | readSI7021SensorData();
90 | })
91 | .catch((err) => console.error(`Si7021 reset failed: ${err} `));
92 | })
93 |
94 |
--------------------------------------------------------------------------------
/set_config.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 | import os
8 | import os.path
9 | import sys
10 | import urllib2
11 | import time
12 | import uuid
13 | import json
14 | import subprocess
15 | try:
16 | import urlparse
17 | except:
18 | import urllib.parse as urlparse
19 | try:
20 | import RPi.GPIO as GPIO
21 | except RuntimeError:
22 | print("Error importing RPi.GPIO! This is probably because you need superuser privileges. You can achieve this by using 'sudo' to run your script")
23 |
24 | GWID_PREFIX="FFFE"
25 |
26 | if os.environ.get('HALT') != None:
27 | print ("*** HALT asserted - exiting ***")
28 | sys.exit(0)
29 |
30 | # Check if the correct environment variables are set
31 |
32 | print ("*******************")
33 | print ("*** Configuration:")
34 | print ("*******************")
35 |
36 | if os.environ.get("GW_EUI")==None:
37 | # The FFFE should be inserted in the middle (so xxxxxxFFFExxxxxx)
38 | my_eui = format(uuid.getnode(), '012x')
39 | my_eui = my_eui[:6]+GWID_PREFIX+my_eui[6:]
40 | my_eui = my_eui.upper()
41 | else:
42 | my_eui = os.environ.get("GW_EUI")
43 |
44 |
45 | # Define default configs
46 | description = os.getenv('GW_DESCRIPTION', "")
47 | placement = ""
48 | latitude = os.getenv('GW_REF_LATITUDE', 0)
49 | longitude = os.getenv('GW_REF_LONGITUDE', 0)
50 | altitude = os.getenv('GW_REF_ALTITUDE', 0)
51 | frequency_plan_url = os.getenv('FREQ_PLAN_URL', "https://account.thethingsnetwork.org/api/v2/frequency-plans/EU_863_870")
52 |
53 | # Fetch config from TTN if TTN is enabled
54 | if(os.getenv('SERVER_TTN', "true")=="true"):
55 |
56 | if os.environ.get("GW_ID")==None:
57 | print ("ERROR: GW_ID required")
58 | print ("See https://www.thethingsnetwork.org/docs/gateways/registration.html#via-gateway-connector")
59 | sys.exit(0)
60 |
61 | if os.environ.get("GW_KEY")==None:
62 | print ("ERROR: GW_KEY required")
63 | print ("See https://www.thethingsnetwork.org/docs/gateways/registration.html#via-gateway-connector")
64 | sys.exit(0)
65 |
66 | print ("*******************")
67 | print ("*** Fetching config from TTN account server")
68 | print ("*******************")
69 |
70 | # Fetch the URL, if it fails try 30 seconds later again.
71 | config_response = ""
72 | try:
73 | req = urllib2.Request('https://account.thethingsnetwork.org/gateways/'+os.environ.get("GW_ID"))
74 | req.add_header('Authorization', 'Key '+os.environ.get("GW_KEY"))
75 | response = urllib2.urlopen(req, timeout=30)
76 | config_response = response.read()
77 | except urllib2.URLError as err:
78 | print ("Unable to fetch configuration from TTN. Are your GW_ID and GW_KEY correct?")
79 | sys.exit(0)
80 |
81 | # Parse config
82 | ttn_config = {}
83 | try:
84 | ttn_config = json.loads(config_response)
85 | except:
86 | print ("Unable to parse configuration from TTN")
87 | sys.exit(0)
88 |
89 | frequency_plan = ttn_config.get('frequency_plan', "EU_863_870")
90 | frequency_plan_url = ttn_config.get('frequency_plan_url', "https://account.thethingsnetwork.org/api/v2/frequency-plans/EU_863_870")
91 |
92 | if "router" in ttn_config:
93 | router = ttn_config['router'].get('mqtt_address', "mqtt://router.dev.thethings.network:1883")
94 | router = urlparse.urlparse(router)
95 | #router = router.hostname # mp_pkt_fwd only wants the hostname, not the protocol and port
96 | router = router.path # mp_pkt_fwd only wants the hostname, not the protocol and port
97 | else:
98 | router = "router.dev.thethings.network"
99 |
100 | if "attributes" in ttn_config:
101 | description = ttn_config['attributes'].get('description', "")
102 | placement = ttn_config['attributes'].get('placement', "unknown")
103 |
104 | if "antenna_location" in ttn_config:
105 | latitude = ttn_config['antenna_location'].get('latitude', 0)
106 | longitude = ttn_config['antenna_location'].get('longitude', 0)
107 | altitude = ttn_config['antenna_location'].get('altitude', 0)
108 |
109 | fallback_routers = []
110 | if "fallback_routers" in ttn_config:
111 | for fb_router in ttn_config["fallback_routers"]:
112 | if "mqtt_address" in fb_router:
113 | fallback_routers.append(fb_router["mqtt_address"])
114 |
115 |
116 | print ("Gateway ID:\t"+os.environ.get("GW_ID"))
117 | print ("Gateway Key:\t"+os.environ.get("GW_KEY"))
118 | print ("Frequency plan:\t\t"+frequency_plan)
119 | print ("Frequency plan url:\t"+frequency_plan_url)
120 | print ("Gateway description:\t"+description)
121 | print ("Gateway placement:\t"+placement)
122 | print ("Router:\t\t\t"+router)
123 | print ("")
124 | print ("Fallback routers:")
125 | for fb_router in fallback_routers:
126 | print ("\t"+fb_router)
127 | # Done fetching config from TTN
128 | else:
129 | print ("TTN gateway connector disabled. Not fetching config from account server.")
130 |
131 | print ("Latitude:\t\t"+str(latitude))
132 | print ("Longitude:\t\t"+str(longitude))
133 | print ("Altitude:\t\t"+str(altitude))
134 | print ("Gateway EUI:\t"+my_eui)
135 | print ("Has hardware GPS:\t"+str(os.getenv('GW_GPS', False)))
136 | print ("Hardware GPS port:\t"+os.getenv('GW_GPS_PORT', "/dev/ttyAMA0"))
137 |
138 |
139 |
140 | # Retrieve global_conf
141 | sx1301_conf = {}
142 | try:
143 | response = urllib2.urlopen(frequency_plan_url, timeout=30)
144 | global_conf = response.read()
145 | global_conf_object = json.loads(global_conf)
146 | if('SX1301_conf' in global_conf_object):
147 | sx1301_conf = global_conf_object['SX1301_conf']
148 | except urllib2.URLError as err:
149 | print ("Unable to fetch global conf from Github")
150 | sys.exit(0)
151 |
152 | sx1301_conf['antenna_gain'] = float(os.getenv('GW_ANTENNA_GAIN', 0))
153 |
154 |
155 | # Build local_conf
156 | gateway_conf = {}
157 | gateway_conf['gateway_ID'] = my_eui
158 | gateway_conf['contact_email'] = os.getenv('GW_CONTACT_EMAIL', "")
159 | gateway_conf['description'] = description
160 |
161 | if(os.getenv('GW_FWD_CRC_ERR', "false")=="true"):
162 | #default is False
163 | gateway_conf['forward_crc_error'] = True
164 |
165 | if(os.getenv('GW_FWD_CRC_VAL', "true")=="false"):
166 | #default is True
167 | gateway_conf['forward_crc_valid'] = False
168 |
169 | # Parse GW_GPS env var. It is a string, we need a boolean.
170 | if(os.getenv('GW_GPS', "false")=="true"):
171 | gw_gps = True
172 | else:
173 | gw_gps = False
174 |
175 | # Use hardware GPS
176 | if(gw_gps):
177 | print ("Using real GPS")
178 | gateway_conf['gps'] = True
179 | gateway_conf['fake_gps'] = False
180 | gateway_conf['gps_tty_path'] = os.getenv('GW_GPS_PORT', "/dev/ttyAMA0")
181 | # Use fake GPS with coordinates from TTN
182 | elif(gw_gps==False and latitude!=0 and longitude!=0):
183 | print ("Using fake GPS")
184 | gateway_conf['gps'] = True
185 | gateway_conf['fake_gps'] = True
186 | gateway_conf['ref_latitude'] = float(latitude)
187 | gateway_conf['ref_longitude'] = float(longitude)
188 | gateway_conf['ref_altitude'] = float(altitude)
189 | # No GPS coordinates
190 | else:
191 | print ("Not sending coordinates")
192 | gateway_conf['gps'] = False
193 | gateway_conf['fake_gps'] = False
194 |
195 |
196 | # Add server configuration
197 | gateway_conf['servers'] = []
198 |
199 | # Add TTN server
200 | if(os.getenv('SERVER_TTN', "true")=="true"):
201 | server = {}
202 | server['serv_type'] = "ttn"
203 | server['server_address'] = router
204 | server['server_fallbacks'] = fallback_routers
205 | server['serv_gw_id'] = os.environ.get("GW_ID")
206 | server['serv_gw_key'] = os.environ.get("GW_KEY")
207 | server['serv_enabled'] = True
208 | gateway_conf['servers'].append(server)
209 | else:
210 | if(os.getenv('SERVER_0_ENABLED', "false")=="true"):
211 | server = {}
212 | if(os.getenv('SERVER_0_TYPE', "semtech")=="ttn"):
213 | server['serv_type'] = "ttn"
214 | server['serv_gw_id'] = os.environ.get("SERVER_0_GWID")
215 | server['serv_gw_key'] = os.environ.get("SERVER_0_GWKEY")
216 | server['server_address'] = os.environ.get("SERVER_0_ADDRESS")
217 | server['serv_port_up'] = int(os.getenv("SERVER_0_PORTUP", 1700))
218 | server['serv_port_down'] = int(os.getenv("SERVER_0_PORTDOWN", 1700))
219 | server['serv_enabled'] = True
220 | if(os.getenv('SERVER_0_DOWNLINK', "false")=="true"):
221 | server['serv_down_enabled'] = True
222 | else:
223 | server['serv_down_enabled'] = False
224 | gateway_conf['servers'].append(server)
225 |
226 | # Add up to 3 additional servers
227 | if(os.getenv('SERVER_1_ENABLED', "false")=="true"):
228 | server = {}
229 | if(os.getenv('SERVER_1_TYPE', "semtech")=="ttn"):
230 | server['serv_type'] = "ttn"
231 | server['serv_gw_id'] = os.environ.get("SERVER_1_GWID")
232 | server['serv_gw_key'] = os.environ.get("SERVER_1_GWKEY")
233 | server['server_address'] = os.environ.get("SERVER_1_ADDRESS")
234 | server['serv_port_up'] = int(os.getenv("SERVER_1_PORTUP", 1700))
235 | server['serv_port_down'] = int(os.getenv("SERVER_1_PORTDOWN", 1700))
236 | server['serv_enabled'] = True
237 | if(os.getenv('SERVER_1_DOWNLINK', "false")=="true"):
238 | server['serv_down_enabled'] = True
239 | else:
240 | server['serv_down_enabled'] = False
241 | gateway_conf['servers'].append(server)
242 |
243 | if(os.getenv('SERVER_2_ENABLED', "false")=="true"):
244 | server = {}
245 | if(os.getenv('SERVER_2_TYPE', "semtech")=="ttn"):
246 | server['serv_type'] = "ttn"
247 | server['serv_gw_id'] = os.environ.get("SERVER_2_GWID")
248 | server['serv_gw_key'] = os.environ.get("SERVER_2_GWKEY")
249 | server['server_address'] = os.environ.get("SERVER_2_ADDRESS")
250 | server['serv_port_up'] = int(os.getenv("SERVER_2_PORTUP", 1700))
251 | server['serv_port_down'] = int(os.getenv("SERVER_2_PORTDOWN", 1700))
252 | server['serv_enabled'] = True
253 | if(os.getenv('SERVER_2_DOWNLINK', "false")=="true"):
254 | server['serv_down_enabled'] = True
255 | else:
256 | server['serv_down_enabled'] = False
257 | gateway_conf['servers'].append(server)
258 |
259 | if(os.getenv('SERVER_3_ENABLED', "false")=="true"):
260 | server = {}
261 | if(os.getenv('SERVER_3_TYPE', "semtech")=="ttn"):
262 | server['serv_type'] = "ttn"
263 | server['serv_gw_id'] = os.environ.get("SERVER_3_GWID")
264 | server['serv_gw_key'] = os.environ.get("SERVER_3_GWKEY")
265 | server['server_address'] = os.environ.get("SERVER_3_ADDRESS")
266 | server['serv_port_up'] = int(os.getenv("SERVER_3_PORTUP", 1700))
267 | server['serv_port_down'] = int(os.getenv("SERVER_3_PORTDOWN", 1700))
268 | server['serv_enabled'] = True
269 | if(os.getenv('SERVER_3_DOWNLINK', "false")=="true"):
270 | server['serv_down_enabled'] = True
271 | else:
272 | server['serv_down_enabled'] = False
273 | gateway_conf['servers'].append(server)
274 |
275 |
276 | # Add GW Traff server
277 | server = {}
278 | server['serv_type'] = "gwtraf"
279 | server['server_address'] = "127.0.0.1"
280 | server['serv_port_up'] = 1688
281 | server['serv_port_down'] = 1689
282 | server['serv_enabled'] = True
283 | gateway_conf['servers'].append(server)
284 |
285 | # Add Local LoRaWAN server
286 | server = {}
287 | server['server_address'] = "127.0.0.1"
288 | server['serv_port_up'] = 1680
289 | server['serv_port_down'] = 1680
290 | server['serv_enabled'] = False
291 | gateway_conf['servers'].append(server)
292 |
293 |
294 | # We merge the json objects from the global_conf and local_conf and save it to the global_conf.
295 | # Therefore there will not be a local_conf.json file.
296 | local_conf = {'SX1301_conf': sx1301_conf, 'gateway_conf': gateway_conf}
297 | with open('global_conf.json', 'w') as the_file:
298 | the_file.write(json.dumps(local_conf, indent=4))
299 |
300 |
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | # Test the connection, wait if needed.
4 | #while [[ $(ping -c1 google.com 2>&1 | grep " 0% packet loss") == "" ]]; do
5 | # echo "[Lora Gateway]: Waiting for internet connection..."
6 | # sleep 2
7 | # done
8 |
9 | # Reset RAK831 PIN
10 | SX1301_RESET_BCM_PIN=25
11 |
12 | WAIT_GPIO() {
13 | sleep 0.1
14 | }
15 |
16 | # cleanup GPIO
17 | if [ -d /sys/class/gpio/gpio$SX1301_RESET_BCM_PIN ]
18 | then
19 | echo "GPIO$SX1301_RESET_BCM_PIN Already available"
20 | else
21 | echo "$SX1301_RESET_BCM_PIN" > /sys/class/gpio/export; WAIT_GPIO
22 | fi
23 |
24 | # setup GPIO
25 | echo "out" > /sys/class/gpio/gpio$SX1301_RESET_BCM_PIN/direction
26 | WAIT_GPIO
27 | echo "1" > /sys/class/gpio/gpio$SX1301_RESET_BCM_PIN/value
28 | WAIT_GPIO
29 | echo "0" > /sys/class/gpio/gpio$SX1301_RESET_BCM_PIN/value
30 | WAIT_GPIO
31 |
32 | # removed, prevent start on IMST Gateway Lite
33 | #echo "in" > /sys/class/gpio/gpio$SX1301_RESET_BCM_PIN/direction
34 |
35 | # Fire up the forwarder.
36 | ./mp_pkt_fwd
37 |
--------------------------------------------------------------------------------
/testled.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var ws281x = require('rpi-ws281x-native');
4 | var NUM_LEDS = 2
5 | var r = 255
6 | var g = 0
7 | var b = 0
8 | pixelData = new Uint32Array(NUM_LEDS);
9 | ws281x.init(NUM_LEDS);
10 |
11 | // trap the SIGINT and reset before exit
12 | process.on('SIGINT', function () {
13 | ws281x.reset();
14 | process.nextTick(function () { process.exit(0); });
15 | });
16 |
17 |
18 | // animation-loop
19 | var offset = 0;
20 |
21 | setInterval(function () {
22 | if (r==255) console.log('Red');
23 | if (g==255) console.log('Green');
24 | if (b==255) console.log('Blue');
25 |
26 | for (var i = 0; i < NUM_LEDS; i++) {
27 | pixelData[i] = rgb2Int(r,g,b);
28 | }
29 | ws281x.render(pixelData);
30 |
31 | if (r==255) {
32 | g=255; r=0;
33 | } else if (g==255){
34 | b=255; g=0;
35 | } else {
36 | r=255; b=0;
37 | }
38 | }, 2000 );
39 |
40 | function rgb2Int(r, g, b) {
41 | return ((r & 0xff) << 16) + ((g & 0xff) << 8) + (b & 0xff);
42 | }
43 |
44 | console.log('Press +C to exit.');
45 |
46 |
47 |
--------------------------------------------------------------------------------
/testled.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | # NeoPixel library strandtest example
4 | # Author: Tony DiCola (tony@tonydicola.com)
5 | #
6 | # Direct port of the Arduino NeoPixel library strandtest example. Showcases
7 | # various animations on a strip of NeoPixels.
8 | import time
9 |
10 | from neopixel import *
11 |
12 | import argparse
13 | import signal
14 | import sys
15 |
16 | def signal_handler(signal, frame):
17 | colorWipe(strip, Color(0,0,0))
18 | sys.exit(0)
19 |
20 | # LED strip configuration:
21 | LED_COUNT = 2 # Number of LED pixels.
22 | LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!).
23 | #LED_PIN = 10 # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0).
24 | LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
25 | LED_DMA = 5 # DMA channel to use for generating signal (try 5)
26 | LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
27 | LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
28 | LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53
29 | LED_STRIP = ws.WS2811_STRIP_GRB # Strip type and colour ordering
30 |
31 |
32 |
33 | # Define functions which animate LEDs in various ways.
34 | def colorWipe(strip, color, wait_ms=50):
35 | """Wipe color across display a pixel at a time."""
36 | for i in range(strip.numPixels()):
37 | strip.setPixelColor(i, color)
38 | strip.show()
39 | time.sleep(wait_ms/1000.0)
40 |
41 | # Main program logic follows:
42 | if __name__ == '__main__':
43 | signal.signal(signal.SIGINT, signal_handler)
44 |
45 | # Create NeoPixel object with appropriate configuration.
46 | strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP)
47 |
48 | # Intialize the library (must be called once before other functions).
49 | strip.begin()
50 |
51 | print ('Press Ctrl-C to quit.')
52 | while True:
53 | print ('Color Red')
54 | colorWipe(strip, Color(255, 0, 0)) # Red wipe
55 | time.sleep(2)
56 | print ('Color Green')
57 | colorWipe(strip, Color(0, 255, 0)) # Green wipe
58 | time.sleep(2)
59 | print ('Color Blue')
60 | colorWipe(strip, Color(0, 0, 255)) # Blue wipe
61 | time.sleep(2)
62 |
--------------------------------------------------------------------------------
/wificfg.json:
--------------------------------------------------------------------------------
1 | {
2 | "dnsmasq_cfg": {
3 | "address": "/#/192.168.50.1",
4 | "dhcp_range": "192.168.50.50,192.168.50.100,1h",
5 | "vendor_class": "set:device,IoT"
6 | },
7 | "host_apd_cfg": {
8 | "ip": "192.168.50.1",
9 | "ssid": "_AP_SSID_",
10 | "wpa_passphrase":"_AP_PASSWORD_",
11 | "channel": "6"
12 | },
13 | "wpa_supplicant_cfg": {
14 | "cfg_file": "/etc/wpa_supplicant/wpa_supplicant.conf"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------