Tutorial: Accessing Querx data with Python
The data saved by Querx can be exported in XML- and CSV-files since the release of firmware version 2. Version 2.1.2 further introduced the possibility of accessing the current values directly in the XML-format, making it possible to, for instance, integrate them into custom projects via scripting languages.
As an example, this tutorial will illustrate how to read out and export the current values in Python 2.7.
Querx Variants
The script will be written in such a way that it can support all variants of Querx (TH, THP, PT - with and without WiFi). More on this in the section on the main() function.
Preparation
First we need to determine the IP-address of the Querx sensor that we want to communicate with.
You can, for example, determine the IP-address of your Querx, using our own Querx Hub application.
If you now enter the following command into your browser
http://[IP-Adresse]/tpl/document.cgi?tpl/j/live.tpl&msub=xml
you should receive a reply in this format (in this example coming from a Querx TH):
Tutorial-Querx TH 192.168.192.206 80 06.01.2017 16:02:20 06.01.2017 15:02:20 1483714940 Temperature C 24.9 Humidity %RH 17 Dew point C -1.9
This block of text contains the current values measured by the device, as well as some useful meta data, such as the time at which the measurement was taken.
XML is a format that makes is possible to transform data structured in such a hierarchical way into a machine-readable form and exchange it between devices.
The objects that are reminiscent of HTML-tags are called XML-nodes. As exemplified by the live-node, these nodes can include subnodes, creating hierarchical structures.
Further information can be found at: https://wikipedia.org/wiki/Extensible_Markup_Language
Since this XML-data was provided by a Querx TH device, it includes the value-nodes temperature and humidity, which in turn each include subnodes for the sensor name, unit and the actual measurement. Sensor name and unit can, by the way, be customized to suit your needs in the configuration area of your Querx sensor.
In the following, this tutorial will provide detailed instructions on how to automatically retrieve and interpret such XML-data.
Complete Script
import sys import httplib # Python 3.x: httplib => http.client import xml.etree.ElementTree as ET querx_address = "192.168.192.206" querx_http_port = 80 xml_url = "/tpl/document.cgi?tpl/j/live.tpl&msub=xml" def get_xml(address, port, url): try: conn = httplib.HTTPConnection(address, port) # Python 3.x: httplib => http.client conn.request("GET", url) res = conn.getresponse() xml = res.read() return(xml) except Exception as e: print ("Error: " + str(e)) sys.exit(1) def get_value_unit(node): #value in node 'value' value = node.findtext('value') #unit in node 'unit' unit = node.findtext('unit') return (value, unit) def get_temperature(live): #temperature node: 'temperature' temp = live.find('temperature') return get_value_unit(temp) def get_humidity(live): #humidity node: 'humidity' humidity = live.find('humidity') return get_value_unit(humidity) def get_pressure(live): #pressure node: 'pressure' pressure = live.find('pressure') return get_value_unit(pressure) def main(): xml = get_xml(querx_address, querx_http_port, xml_url) print (xml) #root node: 'querx' root = ET.fromstring(xml) #sub node 'live' live = root.find('live') if live.find('temperature') != None: tvalue, tunit = get_temperature(live) print ("Temperature: " + tvalue + " " + tunit) if live.find('humidity') != None: hvalue, hunit = get_humidity(live) print ("Humidity: " + hvalue + " " + hunit) if live.find('pressure') != None: pvalue, punit = get_pressure(live) print ("Pressure: " + pvalue + " " + punit) main()
Hint: This example code is written for Python 2.x. In order to make it run in 3.x, the term httplib in lines 2 and 11 needs to be replaced by http-client.
Source Code - Step by Step
Imports
First off, we will import some modules that we will be using from the Python Standard Database.
import sys
The module sys grants access to functions close to the interpreter. It is required to explicitly terminate the program in case of any errors.
import httplib
httplib offers a simple interface that can access HTTP-resources. We will use this module to query and receive the data from the HTTP-server provided by Querx. (Python 3.x: httplib => http.client, see above)
import xml.etree.ElementTree as ET
The module ElementTree is an XML-parser that we will use to convert the XML-string that is retrieved from the Querx HTTP-server into a hierarchic structure, from which we can easily read out the individual data fields. By adding as ET to the command, we can quickly access the module by the abbreviation ET in the following operations.
Configuration Variables
querx_address = "192.168.192.206" querx_http_port = 80 xml_url = "/tpl/document.cgi?tpl/j/live.tpl&msub=xml"
Here we will set the IP-address, the TCP-port (the default setting is 80) and the URL-path of the HTTP-resource that provides the current measurements in the XML-format.
A number of auxiliary functions follow, each of which encapsulates a certain function that are finally merged in the main()-function.
get_xml
def get_xml(address, port, url): try: conn = httplib.HTTPConnection(address, port) conn.request("GET", url) res = conn.getresponse() xml = res.read() return(xml) except Exception as e: print ("Error: " + str(e)) sys.exit(1)
This sets the function get_xml, which receives the configuration variables set in the section above.
The body of this function consists of a try/except block that ensures that any errors defined in the section try that may occur while the script is running will not lead to a crash but to the operation defined in the block except.
The first step is returning an HTTP-connection object by using the function HTTPConnection of the httplib module and loading it into the variable conn.
We then send an HTTP-query to Querx via this connection, using the command .request. The parameter GET determines the HTTP-method and url transmits the URL-path.
In the case of an error (except block), the responsible error will be returned and the program will be terminated via the exit method of the sys-module.
get_value_unit
def get_value_unit(node): #value in node 'value' value = node.findtext('value') #unit in node 'unit' unit = node.findtext('unit') return (value, unit)
Now we need to configure a function get_value_unit. It is used to generate a return values (value, unit) from the input parameter node. In this case, node stands for a single XML-node, being a certain object of the hierarchical structure generated by the XML-parser.
The method findtext(“value”), provided by the node-object searches the node for subnodes with the identifier value and returns the text content of the first subnode it finds.
This enables the function to extract variable pairs consisting of the value and unit from a node, no matter whether node refers to temperature, humidity or air pressure.
Example:
Temperature °C 20.3
If this XML-segment is parsed into a hierarchical structure and transmitted to the function get_value_unit(node) as an argument, the command node.findtext("value") will return the string “20.3”.
The values returned by the findtext function are entered into the two variables value and unit.
The value returned by get_value_unit() is the tuple (value, unit), a structure consisting of both variables. Both variables for value and unit can thus be retrieved from a single return value by using this function.
get_temperature
def get_temperature(live): #temperature node: 'temperature' temp = live.find('temperature') return get_value_unit(temp)
As the name suggests, this function is used to return the value and the unit (as tuple) for the temperature measurement. This makes this function one step more specific than get_value_unit().
As the input parameter (live) is once again an XML-node parsed from the module xml.etree.ElementTree, it supplies a function find(), which returns a subnode it is forwarded.
A quick reminder: findtext() returns the text content of a node forwarded as a parameter, not the node itself.
Example:
Temperature °C 20.3
If this XML-segment is forwarded to get_temperature() as an argument, after having been converted into a hierarchical structure, it will be searched for a subnode called temperature, which, if found, will be loaded into the variable temp.
temp is then forwarded to get_value_unit(), in order to extract the tuple ‘(value, unit)’ from the temperature node.
This is directly returned to the requesting function as a return value.
get_humidity, get_pressure
These functions work just like get_temperature(). The only difference is that they search for subnodes called humidity and pressure, respectively, rather than temperature.
main()-Function
We’re almost done!
The final step is configuring the main() function, which combines all the auxiliary functions into one.
main()
def main(): xml = get_xml(querx_address, querx_http_port, xml_url) print (xml) #root node: 'querx' root = ET.fromstring(xml) #sub node 'live' live = root.find('live') if live.find('temperature') != None: tvalue, tunit = get_temperature(live) print ("Temperature: " + tvalue + " " + tunit) if live.find('humidity') != None: hvalue, hunit = get_humidity(live) print ("Humidity: " + hvalue + " " + hunit) if live.find('pressure') != None: pvalue, punit = get_pressure(live) print ("Pressure: " + pvalue + " " + punit)
First we need to call up get_xml() with the configuration variables as parameters. We receive a string with the values in the XML-format from Querx, which we then save in the variable xml.
The command print(xml) lets us output the string in the console, which can be helpful when debugging.
We then need to execute the function fromstring() with the XML-string as a parameter. This method is provided by the ET-module and is the actual parser:
We can now transfer an XML-string to the module and receive a hierarchical structure, or more specifically the root node as an element-object that contains all existing subnodes, as well as the methods that enable us to search the structure.
Further information can be found at
docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.Element
The top node-element was set as the variable root.
In order to find the subnode live, which contains the current measurements, we once again run the find() method with the argument live. If a subnode live exists within root, it will then be entered into the variable live.
Querx Variants
egnite offers various Querx models for different applications.
- Querx TH and Querx WLAN TH include integrated temperature- and humidity-sensors.
- Querx THP and Querx WLAN THP include integrated temperature-, humidity- and air pressure-sensors.
- Querx PT and Querx WLAN PT measure temperatures.
Since our program works with all of these models, it is necessary to check which values that we have received from a particular model the XML-file actually contains.
This can be done via three if control-structures:
if live.find('temperature') != None: tvalue, tunit = get_temperature(live) print ("Temperature: " + tvalue + " " + tunit)
The live node is searched for a subnode called temperature.
If the result of the find() function is not None, the indented code-segment will be run:
For the temperature values, the live node is forwarded to the function get_temperature() and the tuple from the return value is saved in two variables tvalue (temperature value) and tunit (temperature unit).
The variables are then transmitted to the console using the print command.
If the result of the find() function, on the other hand, is None, the indented section is skipped. It obviously makes no sense to read out a part of the hierarchical structure that does not exist for want of a corresponding sensor.
The corresponding code-segments for the humidity and air pressure values react in the same way.
Congratulations. You have completed the tutorial!
The only thing that is left to do now is to start the program by running our main function:
main();
If you now start the script with the command
python script.py
you should see a readout of the complete XML-document as well as the values for the sensors of your Querx device listed in the form sensorname: value unit.