Kivy logic file

The first thing we will work on is the Python logic for our application. It is demonstrated in the multi-part listing of hmilayout.py, as follows:

# hmilayout.py (part 1)
1 import sys
2 sys.path.extend(["/home/cody/PycharmProjects/VirtualPLC"])
3
4 import Models.FuelFarm.components as components
5 import Models.FuelFarm.functionality as functionality
6
7 from kivy.app import App
8 from kivy.uix.pagelayout import PageLayout
9 from kivy.config import Config
10
11 import kivy
12 kivy.require("1.10.0")

In part 1, we import all of the important modules that we will need. Lines 1 and 2 show the path extension code to ensure the smooth attempt to execute the program. Lines 4 and 5 assign alias names to the components.py and functionality.py files, so we don't have to continually type the entire path every time.

Lines 7-9 import the important pieces from Kivy. There are a number of different layouts available in the uix module, so it is worth looking at them to see which ones might best fit your GUI.

Line 12 is important, as it dictates which Kivy version the program is written for. If a user has an older version of Kivy installed, an exception will be generated and the program won't run; this is because Kivy changes frequently. If a newer version is installed, the exception won't occur.

The following code snippet is part 2 of hmilayout.py:

# hmilayout.py (part 2)
1 # Fix the drawing to a set size and prevent scaling
2 Config.set("graphics", "width", "1112")
3 Config.set("graphics", "height", "849")
4 Config.set("graphics", "resizable", False)
5
6 class HMILayout(PageLayout):
7 # Methods are associated with their class; each class would have its own .kv file
8 @staticmethod
9 def on_state(device): # Get the status of the device
10 if device.state == "down":
11 if device.group not in ["pump1", "pump2", "pump3"]:
12 exec("functionality.{}_open()".format(device.group)) # Dynamically call valve open()
13 else:
14 exec("functionality.{}_on()".format(device.group)) # Dynamically call pump on()

Lines 2-5 are the commands that force the schematic drawing to not be scalable, as well as forcing the displayed image to have the same dimensions as the original file. This way, it is easy to use a program such as GIMP or Photoshop to overlay a grid and determine the rough position of where widgets should be placed.

In normal Python fashion, we create a class in line 6 to contain all the items that will create the GUI. In this case, the class inherits from the PageLayout class, allowing us to simply inherit the layout and functionality of widgets from the parent class. As the comment on line 7 indicates, if multiple classes are used, then each class would have its own .kv layout file associated with it.

We define a static method in lines 8-14 that receives the status state of a particular widget (in this case, a toggle button). A static method is used because we don't care whether a class or instance calls the method, so a static method operates more like a regular function that can be called either from an instance or a class; this is useful in a GUI, as it makes the method more universal in use. If the widget for a particular component is "down", or showing blue on the HMI, the device is either open (if a valve) or on (if a pump).

We use the Python exec() function to call the appropriate action, depending on the component. The exec() command effectively uses the string argument passed into it as a command to the Python interpreter. This choice was made to allow the string command to dynamically determine the name of the component; this is such as having a large if...else block, where each command would be the same but only the component name was different.

The following code snippet is part 3 of hmilayout.py:

# hmilayout.py (part 3)
1 def populate(self):
2 # Make dictionaries to populate table
3 tank_properties1 = {}
4 tank_properties2 = {}
5
6 valve_properties1 = {}
7 valve_properties2 = {}
8 valve_properties3 = {}
9 valve_properties4 = {}
10 valve_properties5 = {}
11 valve_properties6 = {}
12 valve_properties7 = {}
13
14 pump_properties1 = {}
15 pump_properties2 = {}

Part 3 shows a method that will populate the parameter table. This method is linked to one of the buttons on the table slide of the GUI; initially, the table is empty so the button is provided to fill it. This first listing shows all of the empty dictionaries we will use later; there are alternative ways to make dictionaries, such as the dict() method, but it is sometimes easier for clarity to create empty ones and populate as needed.

It should also be noted that the current code does not automatically update the table when data changes. So, if a user clicks one of the toggle buttons on the GUI, the Populate Table button needs to be clicked as well to show the changes.

The following code snippet is part 4 of hmilayout.py:

# hmilayout.py(part4)
1 pump_properties3 = {}
2
3 # Convert instances to dictionaries
4 for key, value in vars(components.tank1).items():
5 tank_properties1[key] = value
6 for key, value in vars(components.tank2).items():
7 tank_properties2[key] = value
8
9 for key, value in vars(components.gate1).items():
10 valve_properties1[key] = value
11 for key, value in vars(components.gate2).items():
12 valve_properties2[key] = value
13 for key, value in vars(components.gate3).items():
14 valve_properties3[key] = value

Line 1 finishes the empty dictionary creation, and then we move into the expressions. The expressions convert all of the component instances we created into key:value pairs that can populate the dictionaries. Because this methodology is the same for different components, we will not cover all of the components as coded. However, the complete hmilayout.py file is available in this book's code repository for review.

The following code snippet is part 5 of hmilayout.py:

# hmilayout.py (part 5)
1 # Populate table
2 self.table.data = [{"value": "Tank"}, {"value": "Level"}, {"value": "Pressure Out"}, {"value": "Flow Out"},
3 {"value": ""}, {"value": ""},
4 # Tank 1
5 {"value": tank_properties1["name"]},
6 {"value": str(tank_properties1["_Tank__level"])},
7 {"value": "{:.2f}".format((tank_properties1["_Tank__tank_press"]))},
8 {"value": "{:.2f}".format((tank_properties1["flow_out"]))},
9 {"value": ""},
10 {"value": ""},
11 # Tank 2
12 {"value": tank_properties2["name"]},
13 {"value": str(tank_properties2["_Tank__level"])},
14 {"value": "{:.2f}".format((tank_properties2["_Tank__tank_press"]))},
15 {"value": "{:.2f}".format((tank_properties2["flow_out"]))},

After we have populated the dictionaries, we need to populate the table itself. This code block takes up nearly 100 lines, so we won't cover each line here. The main thing to get from part 5 is that the table's data is determined by a list of dictionary items. This is a part of how Kivy operates, particularly the RecycleGridLayout class that is used for tabular data display. (We will see that class in the next section when we talk about the Kivy layout.)

The following code snippet is part 6 of hmilayout.py:

# hmilayout.py (part 6)
1 def clear(self):
2 self.table.data = []
3
4 class HMIApp(App):
5 def build(self):
6 return HMILayout()
7
8 if __name__ == "__main__":
9 HMIApp().run()

After we have set up the table to be populated with data from the model, we write the method (line 1) to clear the table when the Clear List button is pressed.

We create a new class in line 4 that has only one method: build(). The whole purpose of this class is to build the GUI we defined in the HMILayout() class.

At the end (line 9), we actually run the GUI when it is called.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.142.2