Brett is chief architect, device management framework, for Art & Logic Inc. He can be contacted at [email protected]
Developers routinely encounter a variety of interfaces and protocols when designing and building devices. For instance, manufacturers of embedded devices have historically provided simple command-line interfaces to monitor and control their products, while in the networking world, vendors often resort to SNMP interfaces. In other industries, you run into devices with protocols ranging from Sun's ONC-RPC or CORBA to Java RMI or custom homebrewed binary protocols.
More recently, designers of these systems have started integrating web-based management interfaces. In addition to being ideal for occasional users or consumer products, these systems employ a protocol that provides a remote API to devices, allowing the development of applications that are not bound by web browser limitations. The DMF Embedded Web Application Server (developed by Art & Logic, the company I work for; http://www.artlogic.com/dmf/) is one such tool that lets you build traditional web-based device management interfaces and open up devices as web services.
Some applications of web service-enabled devices are:
- Custom standalone applications to manage devices (like the monitor application I present in this article).
- Building one-to-many network management systems. A single application can manage a large number of devices, presenting the state of the network either via a standard desktop UI or creating a web interface to manage the attached devices.
- Device-to-device communications. Adding support for the client end of one of the web service protocols lets you develop networks of devices that monitor each other.
- Adapter interfaces. Legacy device management applications can be connected to new devices via adapter applications that support the legacy protocol on one side and XML-RPC on the other. Alternatively, legacy devices can be coupled with applications that translate between their protocol and XML-RPC.
- Building richer web interfaces (using Macromedia Flash) that run in browsers, both on the desktop and on handheld devices, scaling automatically to the available screen size.
In this article, I present a monitor application (written in Python) that uses XML-RPC to monitor the state of a simulated device. The DMF Embedded Web Application Server implements the server-side functionality required for the client-side Python application and assumes that a device exists that implements the XML-RPC API discussed here. (The complete source code to the application is available electronically; see "Resource Center," page 5.) Some of the advantages for using XML-RPC or SOAP instead of CORBA, SNMP, RMI, or other legacy protocols are:
- Being HTTP-based, XML-RPC or SOAP move easily through firewalls.
- Being text-based and also using self-describing XML messages, the protocols ease the creation of systems that are more flexible than those built with messages defined at compile time.
- The web service protocols are language-agnostic; http://www.xmlrpc.com/ lists almost 70 client library implementations in languages ranging from C/C++, Python, Perl, Java, to Pike, REBOL, and Squeak. SOAP is currently supported by Microsoft's .NET languages, but many other languages have (or are adding) SOAP support.
- The relative simplicity of the protocols lowers the bar to building an application that uses them.
- Tools like my company's Device Management Framework (DMF) lets a single server support a traditional web interface as well as XML-RPC and SOAP via a single piece of back-end code that integrates with the actual device.
While this article focuses on using XML-RPC to monitor and control devices, you could just as easily use SOAP (XML-RPC's sibling protocol). In fact, SOAP may be a preferable protocol in some cases, depending on tool support (for instance, if you plan on writing clients with one of the .NET languages or another tool that speaks SOAP natively).
Central to the design of the monitor application I present here is the decision to view the state of the device as a tree of named parameters. Each of these parameters has a value and separate read/write access controls. While it's not required as part of the design, I usually model the parameter space as a hierarchical tree. One advantage of a hierarchical parameter space is that the DMF's XML-RPC implementation answers a request for a node in the tree by returning the values of all the parameters beneath that node, letting you condense several (or many) HTTP transactions with a single call/response cycle.
For example, you might design a device that has a node at the root of its parameter tree named "sys." Under that you could build a tree like the following:
...and so on.
Requesting the value of sys.info returns the values of the parameters sys.info.sysName, sys.info.uptime, sys.info.description, and sys.info.buildDate as a single XML-RPC structure. Parameters at the leaves of the tree may be indexable. For instance, there might be valid values for the parameters sys.network.ipAddress through sys.network.ipAddress[sys.network.interfaceCount-1].
The XML-RPC API
Since the entire state of the device is represented by a tree of named parameters, an API with get/set semantics is all that's needed to monitor and control the device. Example 1 is pseudocode for the two main functions in the XML-RPC interface.
When an error occurs in XML-RPC, the server notifies the client by sending a special Fault message to notify the client code of the problem. The Fault message contains a structure with two members: an application-defined fault code and a text description of the error.
The Python xmlrpclib module converts fault messages arriving from the server into an exception object that you can catch, keeping the main flow of the client app logic clean; see Example 2.
The Python Device Class
I use two Python classes to abstract the remote device in the client. The main class, Device, provides three major API functions:
- value = Query(parameterName [, index]) performs an XML-RPC call to the device and caches the returned value(s) in a ValueTree object. This cache gives you a mechanism to request a snapshot of some subsection of the parameter tree from the device, then you can pull out the individual parameters one at a time later.
- value = GetValue(parameterName [, index]) retrieves the requested parameter from the ValueTree cache; if the parameter is not found, it raises an exception. No XML-RPC calls are made to the device.
- SetValue(parameterName, value, [index]) sends a new parameter value to the device using an XML-RPC call. If the set() operation succeeds, the new value is cached in the device's ValueTree, and calling GetValue() returns this new value (without performing another XML-RPC call).
The ValueTree class stores data from the device in the same nested dictionary data structure used by the xmlrpclib. Doing this lets you swap in new subtrees in a single step. The main API functions exposed by the ValueTree class are value = GetValue(parameterName [,index]) and SetValue(parameterName, value [, index]), which retrieve or set the requested value in the ValueTree's set of nested dictionaries. The Device class's GetValue() function just delegates to the implementation of ValueTree's GetValue() function.
For this client-side monitor demonstration, I've built a simulated device with a small (but interesting) parameter tree. The pseudodevice contains two subtrees that branch from the top level: system.info and system.values. The system.info branch contains parameters that provide information about the device itself:
- system.info.name. A string of up to 80 characters giving the name of this device.
- system.info.description. A string of up to 80 characters giving a description of the device.
- system.info.uptime. The number of seconds since the device was started.
The system.values branch of the parameter tree contains the actual data values reported by the device, as well as additional information about the data values. I've modeled this application after several real-world projects I've worked onmonitoring satellite telemetry data, broadcasting equipment at FM radio stations, and the like. Each of the data value parameters also has an associated text label and separate highAlarm and lowAlarm alarm values. When the data value is either lower than its lowAlarm value or higher than its highAlarm alarm value, it is considered to be in an alarm state. Each of these parameters is indexable; calling server.get("system.values.value") returns the entire array in a single XML-RPC array, or you may also call server.get("system.values.value", 0) to retrieve just the first data value. The full list of parameters contained in the system.values subtree is:
- system.values.value. An array of 16 integers representing current data values.
- system.values.highAlarm. An array of 16 integers representing the high alarm point for each data value.
- system.values.lowAlarm. An array of 16 integers representing the low alarm point for each data value.
- system.values.label. An array of 16 text strings (each up to 32 characters long) with a descriptive label for each of the data values
The Monitor Application
The monitor application uses the Device class to monitor the simulated pseudodevice. The simulator is written to initialize each of the 16 data values randomly between 1 and 100, then perform a simple random walk around each of those data values every time an update is requested.
The application interface was written using the Tkinter toolkit, standard with Python. The application should build and run cleanly on any platform supported by Python.
As Figure 1 shows, each data value on the device is represented in the UI as a single row within a grid. Each row displays that value's associated label, lower/upper alarms, and current value. The label and alarm values are editable; moving the cursor out of a modified cell causes the app to transmit the new label or alarm value to the device. The current value is displayed as read-only. When the current value is lower than the lower alarm setting for that channel, it is displayed as yellow text on a blue background; if it is higher than the upper alarm, it is displayed as white text on a red background. Every second, the monitor application queries the device for the pertinent section of its parameter tree and updates the display accordingly.
Potential Drawbacks of XML-RPC
While I've shown here the relative ease with which you can build a standalone application to manage a device using XML-RPC, there are aspects of the protocol that you must consider before adopting it for real projects.
- No built-in security model or authentication. Other XML-RPC applications typically handle this problem by sending username/password in the clear on every call made to the server. The next major release of the DMF framework features a better authentication model, implemented by adding a few new functions to the API that perform a cryptographic hash on the authentication credentials as part of a login procedure. The output of that procedure is a unique ID that is then passed to the server with every call.
- Network administrators may not like its ability to pass through firewalls. Many applications for managing devices over XML-RPC will require that only users inside the firewall be allowed to access the device. Selecting an appropriate port number eliminates the problem.
- Call/response design makes asynchronous alerts difficult. Being built on top of HTTP, XML-RPC servers don't do anything until a client makes a request. We get around that limitation by polling, which has proven to be adequate in every situation that we've encountered to date. Common objections to polling include concerns about increasing network loads, increasing the load on the server, and alert latencies that may approach the polling frequency. While there are some situations where one or more of these concerns may prove valid enough to avoid using XML-RPC for device control (it's probably not an appropriate technology to use to monitor audio streams, for example), we have found that in the real-world applications that our customers have built, the load is light enough and the latency small enough to make this kind of monitor and control application more than sufficient.