All-on-one Rpi Node

This sensor node is made to showcase a use-case of Raspberry Pi for a complete all on one sensor node. For achieving this a DHT-22 sensor along with a digital light sensor was used to measure temperature, humidity, and light. The sensor readings were directly pushed to the FROST Server running on the Pi itself. As a result, the Pi act as an independent sensor system running on the WLAN/WiFi network.

../_images/hardware-setup.jpg

Hardware setup.

Hardware

To realize the objective, following components were used:

Wiring setup

First of all, the grove base shield was connected over the Raspberry Pi board. Then, the sensor connections were made using the connector cables as following:

  • DHT22 Sensor – Digital pin D4
  • Digital Light Sensor – I2C pin-1

Apart from this, there is no need of any other wiring in this case.

Once all these connection were made, the board is remotely accessed with a computer using SSH/VNC mode. Further, steps of software part needs to be followed.

Software

Configuring eduroam

To enable the raspberry pi node to connect with the eduroam WiFi network, first of all download this T-Telesec Global Root Class 2 certificate in .pem format. Copy this certificate file in the /etc/ssl/certs/ folder of the Raspberry Pi.

Now, enter the following command:

sudo nano /etc/network/interfaces

Edit this file and enter the following lines in the file. If the file is already having these lines, keep it unchanged.

allow-hotplug wlan0
iface wlan0 inet manual
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

Save the file with Ctrl+X and then press Y. Now again enter following command:

sudo nano /etc/wpa_supplicant/wpa_supplicant.conf

In the editor mode, enter the following lines in this file. Modify the Identity and Password with the correct eduroam login credentials. Save the file with Ctrl+X and then press Y.

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=DE

network={
  ssid="eduroam"
  proto=RSN
  key_mgmt=WPA-EAP
  eap=PEAP
  ca_cert="/etc/ssl/certs/T-TeleSec_GlobalRoot_Class_2.pem"
  identity="gxxxxxx@eduroam.mwn.de"
  password="XXXXXXXXXX"
  phase1="peaplabel=0"
  phase2="auth=MSCHAPV2"
  subject_match="radius.lrz.de"}

Finally, enter the following command to restart the wifi configuration. Reboot the RaspberryPi and it should have a running WiFi with eduroam.

sudo wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf

Installing docker

To install docker service on the raspberry pi follow the tutorial available here and perform till step 4-i. After this you will have a running docker service on your raspberrypi.

Installing Node-Red

To install a node-red on the raspberyy pi node follow the following tutorial or alternatively follow the commands given below:

sudo apt-get install build-essential
sudo apt-get update
sudo apt-get upgrade
bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)
sudo systemctl start nodered.service      #Autostart node-red at startup
sudo systemctl enable nodered.service      #Autostart node-red at startup

After this, you should have a running node-red service on port 1880 which can be accessed via http://localhost:1880

It is to be noted, that although we have installed a node-red service on our sensor-node, we aren’t using it for this example.

Installing FROST Server

To setup a FROST server follow this detailed guide available on its github repository. Basically there are five major stpes:

Step-by-step commands are also provided below for the reference:

apt-get install postgresql postgis pgadmin3
sudo apt-get update                                       # update package list
sudo apt install openjdk-11-jdk
sudo apt-get install tomcat8 tomcat8-docs tomcat8-admin   # install tomcat
sudo nano /etc/tomcat8/tomcat-users.xml
sudo nano /usr/share/tomcat8-admin/manager/WEB-INF/web.xml
export CATALINA_HOME=/usr/share/tomcat8
sudo service tomcat8 restart

After the above steps are completed, a SensorThings API service should be running at: http://localhost:8080/FROST-Service/v1.0

GrovePi+ and Sensors

To create this sensor node, we used Python for setting up the Raspberry Pi. First, install the Dexter Grove Pi plus library on the board. Now download and run the Python script for All-on-one Raspberry Pi sensor node file in the text editor. This code was created by merging the example code of each of these attached sensor with the python code for transmitting the data to the FROST server. Some required changes were made while merging the example codes, such as changing the pin number for the sensor. The code also requires following dependent libraries to run:

Download these two .pyc files in the same folder with the Python script for All-on-one Raspberry Pi sensor node code file. Create a sub-folder inside this main folder and rename it as “lib”. Move the Adafruit_I2C.pyc into that lib folder. Now, the code can be compiled and run successfully. To post the sensor data to the FROST sever, an http post request needs to be made from the python. For this we use requests library available in the python. The URL needs to be configured in the ‘URL’ variable. Each post request is made separately with the unique datastream id of that particular sensor. Modify the post requests depending on the sensor value and the datastream ids.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import requests, json
URL= "http://tumgispinode.duckdns.org:8080/FROST-Server/v1.0/Observations"
header = {"Content-type" : "application/json"}
				payload = {'result': temp, 'Datastream': {'@iot.id': 1}}
				r = requests.post(URL, data=json.dumps(payload))
				payload = {'result': humidity, 'Datastream': {'@iot.id': 2}}
				r = requests.post(URL, data=json.dumps(payload))
				payload = {'result': readVisibleLux(), 'Datastream': {'@iot.id': 3}}
				r = requests.post(URL, data=json.dumps(payload))
				print(r.text)

To execute the code file run the following command:

python all_on_one_rpi_node.py

The code for sensors need to be modified according to the sensors used. The code below shows the part of the code used here to read, store, and print the sensor values.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
blue = 0    # The Blue colored sensor.
white = 1   # The White colored sensor.

def main():
	init()
	while (True):
		try:
			# This example uses the blue colored sensor. 
			# The first parameter is the port, the second parameter is the type of sensor.
			[temp,humidity] = grovepi.dht(sensor,white)  
			if math.isnan(temp) == False and math.isnan(humidity) == False:
				print("temp = %.02f C humidity =%.02f%%"%(temp, humidity))
				print("Lux: %i [Vis+IR=%i, IR=%i @ Gain=%ix, Timing=%.1fms]" % (readVisibleLux(), channel0, channel1, gain_m, timing_ms))
				sleep(50)		
		except IOError:
			print ("Error")

Services

This node direclty pushes the sensor data to the OGC Sensor Things API configured on the FROST Server using WLAN or WiFi connection. To be able to access the device from the local LAN we use the DNS service from the DuckDNS.

DuckDNS

DuckDNS is a free dynamic DNS hosted on AWS. Anyone can create a free account on this platform and register an ipaddress with a free subdomain as xxxx.duckdns.org. For this example we registered our ipaddress as tumgispinode.duckdns.org. It is required to update the ip address on the website if the ip of the device has changed. This can be automated with a shell script on Raspberry Pi to check the ip and update it on the duckdns website if it has changed. This is the reference Shell script for updating IP address on duckdns platform used in this example. Modify the ECHO URL and the token no with your set-dns address and the authentication token no from the duckdns website.

In addition to that, a cron tab task needs to be setup for running this shell script every few minutes. Enter the crontab edit mode with

 crontab -e

Now add the following lines in the end to automatically run the shell script every five minutes and the always run the python code for sensor readings on the boot. Modify the path of the files according to your file location.

 */5 * * * * /home/pi/duckdns/duck.sh
 @reboot python /home/pi/GrovePi/Node/node.py

Grafana Dashboard

To visualize the collected sensor data we use dashboard service available from Grafana. To install Grafana using docker run:

$ docker run -d -p 3000:3000 grafana/grafana

To enable the Grafana service to be able to read the data from the OGC Sensor things API we need to use linksmart-sensorthings-datasource extension. There is another repository explaining this installation, alternatively you install it with following commands:

docker exec -it -u root grafana /bin/bash
apt-get update
./bin/grafana-cli plugins install linksmart-sensorthings-datasource

Datastreams setup for this sensor node on the FROST server can be seen at: http://tumgispinode.duckdns.org:8080/FROST-Server/v1.0/Datastreams

The GRAFANA dash-board for visualizing the collected data is available at: http://tumgispinode.duckdns.org:3000/d/NAn_6Jmgk/raspberry-pi-node?orgId=1

Code files

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
import grovepi
import math
from time import sleep
import smbus
import requests, json
from Adafruit_I2C import Adafruit_I2C
import RPi.GPIO as GPIO
from smbus import SMBus

URL= "http://tumgispinode.duckdns.org:8080/FROST-Server/v1.0/Observations"
header = {"Content-type" : "application/json"}

TSL2561_Control = 0x80
TSL2561_Timing = 0x81
TSL2561_Interrupt = 0x86
TSL2561_Channel0L = 0x8C
TSL2561_Channel0H = 0x8D
TSL2561_Channel1L = 0x8E
TSL2561_Channel1H = 0x8F

TSL2561_Address = 0x29 #device address

LUX_SCALE = 14 # scale by 2^14
RATIO_SCALE = 9 # scale ratio by 2^9
CH_SCALE = 10 # scale channel values by 2^10
CHSCALE_TINT0 = 0x7517 # 322/11 * 2^CH_SCALE
CHSCALE_TINT1 = 0x0fe7 # 322/81 * 2^CH_SCALE

K1T = 0x0040 # 0.125 * 2^RATIO_SCALE
B1T = 0x01f2 # 0.0304 * 2^LUX_SCALE
M1T = 0x01be # 0.0272 * 2^LUX_SCALE
K2T = 0x0080 # 0.250 * 2^RATIO_SCA
B2T = 0x0214 # 0.0325 * 2^LUX_SCALE
M2T = 0x02d1 # 0.0440 * 2^LUX_SCALE
K3T = 0x00c0 # 0.375 * 2^RATIO_SCALE
B3T = 0x023f # 0.0351 * 2^LUX_SCALE
M3T = 0x037b # 0.0544 * 2^LUX_SCALE
K4T = 0x0100 # 0.50 * 2^RATIO_SCALE
B4T = 0x0270 # 0.0381 * 2^LUX_SCALE
M4T = 0x03fe # 0.0624 * 2^LUX_SCALE
K5T = 0x0138 # 0.61 * 2^RATIO_SCALE
B5T = 0x016f # 0.0224 * 2^LUX_SCALE
M5T = 0x01fc # 0.0310 * 2^LUX_SCALE
K6T = 0x019a # 0.80 * 2^RATIO_SCALE
B6T = 0x00d2 # 0.0128 * 2^LUX_SCALE
M6T = 0x00fb # 0.0153 * 2^LUX_SCALE
K7T = 0x029a # 1.3 * 2^RATIO_SCALE
B7T = 0x0018 # 0.00146 * 2^LUX_SCALE
M7T = 0x0012 # 0.00112 * 2^LUX_SCALE
K8T = 0x029a # 1.3 * 2^RATIO_SCALE
B8T = 0x0000 # 0.000 * 2^LUX_SCALE
M8T = 0x0000 # 0.000 * 2^LUX_SCALE



K1C = 0x0043 # 0.130 * 2^RATIO_SCALE
B1C = 0x0204 # 0.0315 * 2^LUX_SCALE
M1C = 0x01ad # 0.0262 * 2^LUX_SCALE
K2C = 0x0085 # 0.260 * 2^RATIO_SCALE
B2C = 0x0228 # 0.0337 * 2^LUX_SCALE
M2C = 0x02c1 # 0.0430 * 2^LUX_SCALE
K3C = 0x00c8 # 0.390 * 2^RATIO_SCALE
B3C = 0x0253 # 0.0363 * 2^LUX_SCALE
M3C = 0x0363 # 0.0529 * 2^LUX_SCALE
K4C = 0x010a # 0.520 * 2^RATIO_SCALE
B4C = 0x0282 # 0.0392 * 2^LUX_SCALE
M4C = 0x03df # 0.0605 * 2^LUX_SCALE
K5C = 0x014d # 0.65 * 2^RATIO_SCALE
B5C = 0x0177 # 0.0229 * 2^LUX_SCALE
M5C = 0x01dd # 0.0291 * 2^LUX_SCALE
K6C = 0x019a # 0.80 * 2^RATIO_SCALE
B6C = 0x0101 # 0.0157 * 2^LUX_SCALE
M6C = 0x0127 # 0.0180 * 2^LUX_SCALE
K7C = 0x029a # 1.3 * 2^RATIO_SCALE
B7C = 0x0037 # 0.00338 * 2^LUX_SCALE
M7C = 0x002b # 0.00260 * 2^LUX_SCALE
K8C = 0x029a # 1.3 * 2^RATIO_SCALE
B8C = 0x0000 # 0.000 * 2^LUX_SCALE
M8C = 0x0000 # 0.000 * 2^LUX_SCALE

# bus parameters
rev = GPIO.RPI_REVISION
if rev == 2 or rev == 3:
	bus = smbus.SMBus(1)
else:
	bus = smbus.SMBus(0)
i2c = Adafruit_I2C(TSL2561_Address)

debug = False
cooldown_time = 0.005 # measured in seconds
packageType = 0 # 0=T package, 1=CS package
gain = 0        # current gain: 0=1x, 1=16x [dynamically selected]
gain_m = 1      # current gain, as multiplier
timing = 2      # current integration time: 0=13.7ms, 1=101ms, 2=402ms [dynamically selected]
timing_ms = 0   # current integration time, in ms
channel0 = 0    # raw current value of visible+ir sensor
channel1 = 0    # raw current value of ir sensor
schannel0 = 0   # normalized current value of visible+ir sensor
schannel1 = 0   # normalized current value of ir sensor


def readRegister(address):
	try:
		byteval = i2c.readU8(address)

		sleep(cooldown_time)
		if (debug):
			print("TSL2561.readRegister: returned 0x%02X from reg 0x%02X" % (byteval, address))
		return byteval
	except IOError:
		print("TSL2561.readRegister: error reading byte from reg 0x%02X" % address)
		return -1


def writeRegister(address, val):
	try:
		i2c.write8(address, val)

		sleep(cooldown_time)
		if (debug):
			print("TSL2561.writeRegister: wrote 0x%02X to reg 0x%02X" % (val, address))
	except IOError:

		sleep(cooldown_time)
		print("TSL2561.writeRegister: error writing byte to reg 0x%02X" % address)
		return -1

def powerUp():
	writeRegister(TSL2561_Control, 0x03)

def powerDown():
	writeRegister(TSL2561_Control, 0x00)

def setTintAndGain():
	global gain_m, timing_ms

	if gain == 0:
		gain_m = 1
	else:
		gain_m = 16

	if timing == 0:
		timing_ms = 13.7
	elif timing == 1:
		timing_ms = 101
	else:
		timing_ms = 402
	writeRegister(TSL2561_Timing, timing | gain << 4)

def readLux():
	sleep(float(timing_ms + 1) / 1000)

	ch0_low  = readRegister(TSL2561_Channel0L)
	ch0_high = readRegister(TSL2561_Channel0H)
	ch1_low  = readRegister(TSL2561_Channel1L)
	ch1_high = readRegister(TSL2561_Channel1H)

	global channel0, channel1
	channel0 = (ch0_high<<8) | ch0_low
	channel1 = (ch1_high<<8) | ch1_low

	sleep(cooldown_time)
	if debug:
		print("TSL2561.readVisibleLux: channel 0 = %i, channel 1 = %i [gain=%ix, timing=%ims]" % (channel0, channel1, gain_m, timing_ms))

def readVisibleLux():
	global timing, gain

	powerUp()
	readLux()

	if channel0 < 500 and timing == 0:
		timing = 1
		sleep(cooldown_time)
		if debug:
			print("TSL2561.readVisibleLux: too dark. Increasing integration time from 13.7ms to 101ms")
		setTintAndGain()
		readLux()

	if channel0 < 500 and timing == 1:
		timing = 2
		sleep(cooldown_time)
		if debug:
			print("TSL2561.readVisibleLux: too dark. Increasing integration time from 101ms to 402ms")
		setTintAndGain()
		readLux()

	if channel0 < 500 and timing == 2 and gain == 0:
		gain = 1
		sleep(cooldown_time)
		if debug:
			print("TSL2561.readVisibleLux: too dark. Setting high gain")
		setTintAndGain()
		readLux()

	if (channel0 > 20000 or channel1 > 20000) and timing == 2 and gain == 1:
		gain = 0
		sleep(cooldown_time)
		if debug:
			print("TSL2561.readVisibleLux: enough light. Setting low gain")
		setTintAndGain()
		readLux()

	if (channel0 > 20000 or channel1 > 20000) and timing == 2:
		timing = 1
		sleep(cooldown_time)
		if debug:
			print("TSL2561.readVisibleLux: enough light. Reducing integration time from 402ms to 101ms")
		setTintAndGain()
		readLux()

	if (channel0 > 10000 or channel1 > 10000) and timing == 1:
		timing = 0
		sleep(cooldown_time)
		if debug:
			print("TSL2561.readVisibleLux: enough light. Reducing integration time from 101ms to 13.7ms")
		setTintAndGain()
		readLux()

	powerDown()

	if (timing == 0 and (channel0 > 5000 or channel1 > 5000)) or (timing == 1 and (channel0 > 37000 or channel1 > 37000)) or (timing == 2 and (channel0 > 65000 or channel1 > 65000)):
		# overflow
		return -1

	return calculateLux(channel0, channel1)

def calculateLux(ch0, ch1):
	chScale = 0
	if timing == 0:   # 13.7 msec
		chScale = CHSCALE_TINT0
	elif timing == 1: # 101 msec
		chScale = CHSCALE_TINT1;
	else:           # assume no scaling
		chScale = (1 << CH_SCALE)

	if gain == 0:
		chScale = chScale << 4 # scale 1X to 16X

	# scale the channel values
	global schannel0, schannel1
	schannel0 = (ch0 * chScale) >> CH_SCALE
	schannel1 = (ch1 * chScale) >> CH_SCALE

	ratio = 0
	if schannel0 != 0:
		ratio = (schannel1 << (RATIO_SCALE+1)) / schannel0
	ratio = (ratio + 1) >> 1

	if packageType == 0: # T package
		if ((ratio >= 0) and (ratio <= K1T)):
			b=B1T; m=M1T;
		elif (ratio <= K2T):
			b=B2T; m=M2T;
		elif (ratio <= K3T):
			b=B3T; m=M3T;
		elif (ratio <= K4T):
			b=B4T; m=M4T;
		elif (ratio <= K5T):
			b=B5T; m=M5T;
		elif (ratio <= K6T):
			b=B6T; m=M6T;
		elif (ratio <= K7T):
			b=B7T; m=M7T;
		elif (ratio > K8T):
			b=B8T; m=M8T;
	elif packageType == 1: # CS package
		if ((ratio >= 0) and (ratio <= K1C)):
			b=B1C; m=M1C;
		elif (ratio <= K2C):
			b=B2C; m=M2C;
		elif (ratio <= K3C):
			b=B3C; m=M3C;
		elif (ratio <= K4C):
			b=B4C; m=M4C;
		elif (ratio <= K5C):
			b=B5C; m=M5C;
		elif (ratio <= K6C):
			b=B6C; m=M6C;
		elif (ratio <= K7C):
			b=B7C; m=M7C;

	temp = ((schannel0*b)-(schannel1*m))
	if temp < 0:
		temp = 0;
	temp += (1<<(LUX_SCALE-1))
	# strip off fractional portion
	lux = temp>>LUX_SCALE
	sleep(cooldown_time)
	if debug:
		print("TSL2561.calculateLux: %i" % lux)

	return lux

def init():
	powerUp()
	setTintAndGain()
	writeRegister(TSL2561_Interrupt, 0x00)
	powerDown()
# Connect the Grove Temperature & Humidity Sensor Pro to digital port D4
# This example uses the blue colored sensor.
# SIG,NC,VCC,GND
sensor = 4  # The Sensor goes on digital port 4.

# temp_humidity_sensor_type
# Grove Base Kit comes with the blue sensor.
blue = 0    # The Blue colored sensor.
white = 1   # The White colored sensor.

def main():
	init()
	while (True):
		try:
			# This example uses the blue colored sensor. 
			# The first parameter is the port, the second parameter is the type of sensor.
			[temp,humidity] = grovepi.dht(sensor,white)  
			if math.isnan(temp) == False and math.isnan(humidity) == False:
				print("temp = %.02f C humidity =%.02f%%"%(temp, humidity))
				print("Lux: %i [Vis+IR=%i, IR=%i @ Gain=%ix, Timing=%.1fms]" % (readVisibleLux(), channel0, channel1, gain_m, timing_ms))
				payload = {'result': temp, 'Datastream': {'@iot.id': 1}}
				r = requests.post(URL, data=json.dumps(payload))
				payload = {'result': humidity, 'Datastream': {'@iot.id': 2}}
				r = requests.post(URL, data=json.dumps(payload))
				payload = {'result': readVisibleLux(), 'Datastream': {'@iot.id': 3}}
				r = requests.post(URL, data=json.dumps(payload))
				print(r.text)
				sleep(50)		
		except IOError:
			print ("Error")
			
		

if __name__ == "__main__":
        main()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash

getMyIP() {
    local _ip _myip _line _nl=$'\n'
    while IFS=$': \t' read -a _line ;do
        [ -z "${_line%inet}" ] &&
           _ip=${_line[${#_line[1]}>4?1:2]} &&
           [ "${_ip#127.0.0.1}" ] && _myip=$_ip
      done< <(LANG=C /sbin/ifconfig)
    printf ${1+-v} $1 "%s${_nl:0:$[${#1}>0?0:1]}" $_myip
}

echo url="https://www.duckdns.org/update?domains=tumgispinode.duckdns.org&token=XXXXXXXXXXXXXXXXXX&ip=$(getMyIP)&verbose=TRUE" | curl -k -o /home/pi/duckdns/duck.log -K -