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 Device Discoverer 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):

<querx ver="1.0">
  <live>
    <hostname>Tutorial-Querx TH</hostname>
    <ip>192.168.192.206</ip>
    <port>80</port>
    <date_local>06.01.2017</date_local>
    <time_local>16:02:20</time_local>
    <date_gmt>06.01.2017</date_gmt>
    <time_gmt>15:02:20</time_gmt>
    <timestamp>1483714940</timestamp>
    <temperature>
      <name>Temperature</name>
      <unit>C</unit>
      <value>24.9</value>
    </temperature>
    <humidity>
      <name>Humidity</name>
      <unit>%RH</unit>
      <value>17</value>
    </humidity>
    <dewpoint>
      <name>Dew point</name>
      <unit>C</unit>
      <value>-1.9</value>
    </dewpoint>
  </live>
</querx>

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: de.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_humidity(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 section 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’ section), 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>
  <name>Temperature</name>
  <unit>°C</unit>
  <value>20.3</value>
</temperature>

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:

<live>
  <temperature>
    <name>Temperature</name>
    <unit>°C</unit>
    <value>20.3</value>
  </temperature>
</live>

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_humidity(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 airpressure-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’.