IoT Browser Control

This post is as an extension to my earlier post

The here presented code can generally be used to remotely control and configure any IoT (internet-of-things) device or even appliance, which is in the range of your home WiFi.

And the advantage of this method (to others…)…: You can just use your standard browser on any of your devices, to control and configure your IoT devices — no app installation needed – no special hardware needed…

Browser Graphical User Interface (GUI) for Sprinkler System


Here I will be applying this to the sprinkler system:

The goal is to have a remote control for the sprinkler that you can call with any browser for example on your smartphone.

The hardware you need is described in my post Nothing changed there.

The changes are all on software side. The following picture is a sketch for the software inter-dependencies:

The Software

The central program is the Python Flask application ‘webForm’. This application calls the html template in the file ‘template/guiPage.html’, which is the website you can call in your browser – the control hub for your sprinkler system.

In detail:

    1. The file webForm contains following python code
      from flask import Flask, render_template, flash, request
      from wtforms import Form, TextField, validators
      import json
      import RPi.GPIO as GPIO
      import sprinklerMain as valvesMain
      import os
      # App config.
      DEBUG = True
      app = Flask(__name__)
      app.config['SECRET_KEY'] = '17d441f27d441f27567d441f2b6176a'
      with open('/home/pi/Blog_flaskSprinkler/settings.json') as data_file:
      	sprinkler = json.load(data_file)
      class ReusableForm(Form):
      	Z0 = TextField('Z0:', validators=[validators.required(), validators.Length(min=1, max=35)])
      	Z1 = TextField('Z1:', validators=[validators.required(), validators.Length(min=1, max=35)])
      	Z2 = TextField('Z2:', validators=[validators.required(), validators.Length(min=1, max=35)])
      	Z3 = TextField('Z3:', validators=[validators.required(), validators.Length(min=1, max=35)])
      @app.route("/", methods=['GET', 'POST'])
      def guiPage():
      	form = ReusableForm(request.form)
      	if request.method == 'POST':
      		if request.form['submit'] == "submitAll":
      			if Z0 and Z0.isnumeric():
      				valve = sprinkler['0']
      				sprinkler['0'] = [valve[0], Z0]
      			if Z1 and Z1.isnumeric():
      				valve = sprinkler['1']
      				sprinkler['1'] = [valve[0], Z1]
      			if Z2 and Z2.isnumeric():
      				valve = sprinkler['2']
      				sprinkler['2'] = [valve[0], Z2]
      			if Z3 and Z3.isnumeric():
      				valve = sprinkler['3']
      				sprinkler['3'] = [valve[0], Z3]
      			flash('Zone0 : ' + Z0)
      			flash('Zone1 : ' + Z1)
      			flash('Zone2 : ' + Z2)
      			flash('Zone3 : ' + Z3)
      			# Save new values back to the json file...:
      			with open('/home/pi/Blog_flaskSprinkler/settings.json','w+') as fp:
      				json.dump(sprinkler, fp, sort_keys=True, indent=4)
      		elif request.form['submit'] == "startAll":
      			print("Starting all valves...")
      			os.system('/home/pi/flaskSprinkler/ &')		# Start sprinklerMain in background (new thread)...
      		elif request.form['submit'] == "stopAll":
      			print("Stopping all valves...") 
      			valvesMain.closeAllValves()	# stop all valves...
      		elif request.form['submit'] == "start0":
      			print("Starting 0 : Front left, at walkway...")
      			valve = sprinkler['0']
      		elif request.form['submit'] == "stop0":
      			print("Stop 0")
      			valve = sprinkler['0']
      		elif request.form['submit'] == "start1":
      			print("Starting 1 : Front left, at garage wall...")
      			valve = sprinkler['1']
      		elif request.form['submit'] == "stop1":
      			print("Stop 1")
      			valve = sprinkler['1']
      		elif request.form['submit'] == "start2":
      			print("Starting 2 : Right backyard, behind shed...")
      			valve = sprinkler['2']
      		elif request.form['submit'] == "stop2":
      			print("Stop 2")
      			valve = sprinkler['2']
      		elif request.form['submit'] == "start3":
      			print("Starting 3 : Backyard cherry trees...")
      			valve = sprinkler['3']
      		elif request.form['submit'] == "stop3":
      			print("Stop 3")
      			valve = sprinkler['3']
      	for i in range(0,len(sprinkler)):
      		valve = sprinkler[str(i)]
      	return render_template('guiPage.html', form=form, defname = dynamicvalue)
      if __name__ == "__main__":, host='')'')

      In above code in line 14 the secret key is necessary for the communication between the flask app webForm and the jinja code in the html template /template/guiPage.html.

      In the lines 16 and 17 the json file settings.json which contains a Python dictionary with the pin addresses and the watering times for the different sprinkler valves. Note: If you are planning to call this routine from a timer set in crontab, make sure you use absolute paths in these scripts (e.g. here ‘/home/pi/flaskSprinkler/settings.json’).
      This dictionary file is saved in the variable sprinkler.

      The class definition in line 19 to 23 defines the input fields where the user can input new watering times (in seconds).

      Then from line 25 to 102 comes the actual application to the html page in /template/guiPage.html:

      Line 28 checks if one of the buttons in web page have been pressed. In this case it is first checked if a “submitAll” has been sent from there. In that case it collects the new watering times the user put into the webform. If a field (e.g. Z0) is not empty then the old value in the Python dictionary sprinkler gets overwritten with this new value. The other values stay unchanged.

      Additionally e.g. Z0.isnumeric() makes sure that the new value is really numeric and not a typo containing other characters.

      In the lines 46 to 49 the sprinkler times are sent to the html file, for display in the web-browser. Again this only works if the SECRET_KEY was provided.

      In line 51 and 52 the modified set of the watering times in the dictionary sprinkler is saved back into the settings.json file (overwriting he old values).
      If “startAll” was pressed in the browser GUI (line 54), then the Python script /home/pi/flaskSprinkler/ is called. This call is performed using a Linux system call and sending the routine in the “background” with the & symbol at the end of the call. This has the advantage that can run its opening and shutting of valves independent of the webForm script. Now the user can terminate (or kill) the routine by pressing  “stopAll” in the webForm (line 57). This calls in the subroutine closeAllValves(), which first closes all valves and then kills itself (

      Line 61 calls openValve() with the valve number in the script to activate this valve.

      Line 65 calls closeValve() with the valve number in the script to deactivate this valve.
      Similar it can be done with any number of valves.
      In lines 99 to 102 the collected sprinkler values are transferred to the guiPage.html page in the template/ folder using the render_template() method.
      Line 105 to 107 contain the main function.

      This script should be made executable using following call:

      sudo chmod ug+x

      With this you actually don’t need the .py ending anymore and can call the script just webForm. The header #!/usr/bin/python links this script to python and you also don’t need to explicitely call python to run this. Now you can call this just with

      > ./webForm
    2. The json file settings.json contains the GPIO addresses of the Raspberry Pi that are connected to vales and the running times each valve is supposed to be opened for. The file is organized as a Python dictionary and each set of GPIO address/running times has a consecutive number (here “0” to “3”):
          "0": [
          "1": [
          "2": [
          "3": [
    3. The python script contains the functions to control the GPIO ports of the Raspberry Pi, which in turn control the valves of the sprinkler system.
      import RPi.GPIO as GPIO
      import time
      import json
      import os
      from collections import OrderedDict 
      def openCloseValves(valve, zeit):
      	print("Opening Valve " + str(valve) + " for " + str(zeit) + " seconds..."),
      	GPIO.setup(valve, GPIO.OUT)
      	GPIO.output(valve, GPIO.LOW)	# Open valve
      	GPIO.output(valve, GPIO.HIGH)	# Close valve
      def openValve(valve):
      	print("Opening Valve " + str(valve) + " ..."),
      	GPIO.setup(valve, GPIO.OUT)
      	GPIO.output(valve, GPIO.LOW)	# Open valve
      def closeValve(valve):
      	print("Closing Valve " + str(valve) + " ..."),
      	GPIO.setup(valve, GPIO.OUT)
      	GPIO.output(valve, GPIO.HIGH)	# Close valve
      def run():
      	value =[]
      	with open('/home/pi/flaskSprinkler/settings.json') as data_file:
      		sprinkler = json.load(data_file)
      		for i in range(0,len(sprinkler)):
      			value = sprinkler[str(i)]
      			valve = int(value[0])
      			zeit = int(value[1])
      			print("sprinkler new " + str(valve) + " value : " + str(zeit))
      			openCloseValves(valve, zeit)
      	# Perform a bit of cleanup...
      def closeAllValves():
      	for value in range(2,28):
      			GPIO.setup(int(value), GPIO.OUT)
      			GPIO.output(int(value), GPIO.HIGH)
      			continue	# continue with the next GPIO pin...
              # Kill routine sprinkler script in case that is running...
                      os.system("sudo pkill -9 sprinklerMain.p")
      if __name__=="__main__":
      		print("An Error occured OR user stopped routine...!!!")

      The subroutine run() (line 44) activates in a loop one valve after the other, taking its information from the settings.json file. The actual GPIO control is formulated in the function openCloseValves().
      The function openValve() (line 27) allows just the opening of one valve.
      The function closeValve() (line 36) allows just the closing of one valve.
      The function closeAllValves() (line 56) first closes all valves (all GPIO ports of the Raspbery Pi), then kills its own script (

      The actual GUI (graphical user interface) that is displayed in your browser is “designed” in the file guiPage.html. This file needs to be in the folder templates/.

      Sprinkler Setting Web Form

      Values Set:


      Line 5 to 10 define the background colors.

      Lines 19 to 67 define and design the text boxes and buttons of the GUI interface in the website.

      In lines 70 to 88 is an interactive jinja code snippet that combined with the flash() function used in displays the new times the user typed in, combined with the non-changed values.

    Below picture shows the result of this html code when opened in a web-browser. The address you will have to use is the IP address of your Raspberry Pi in your home-network (something you an find out by typing e.g. ifconfig) followed by :5000. So the address you have to type could be something like (yours might have a different IP address).

    Keeping it Running

    This code worked fine so far. However I wanted to make sure that the sprinkler stays activated and does its job regularly. For this I added three lines to crontab:

    @reboot /home/pi/flaskSprinkler/
    15 22 * * * nohup /home/pi/flaskSprinkler/ &
    */5 * * * * nohup /home/pi/flaskSprinkler/ &

    The first line calls a function that does basically the same as closeAllValves() in the script besides that it does not “kill itself”. It just makes shure that at start all valves are closed.

    The second line calls every day at 10:15PM the script which executes all sprinklers one after the other. is sent also here in the background and can be “killed” by the web-interface if the user wishes to.

    The third line calls a simple shell script that every five minutes checks if the web interface is active, i.e. is is still running. This shell script looks like this:

    if ! ps -e | grep "$service" ; then

    I hope this post gives you some ideas how to build and design an IoT system that you can control from any computer or cellphone using a simple browser.
    As usual please don’t hesitate to leave me a note below if you have comments or questions.