Personal Universal Controller
Appliance Specification Language Documentation
Documention originally written by Jeffrey Nichols and
Mathilde Pignol.
Language design by the personal universal controller working group:
Michael Higgins, Joe Hughes, Peter Lucas, Brad A. Myers, Jeffrey
Nichols, and Mathilde Pignol. Speech interface contributions by
Thomas K. Harris.
The personal universal controller (PUC) project's goal is to build a software framework running on top of a personal digital assistant (PDA) that is capable of providing an interface that will allow users to control any appliance within their environment. In essence, the PUC is a device-independent, appliance-independent, user-dependent remote control device.
Because of the universal nature of the PUC, it is not reasonable to pre-program the controller device with an interface for every appliance that might be encountered. Similarly, it is not reasonable to require each appliance to provide hard-coded interfaces for several device form factors, and expect the controller devices to interpolate to their actual form factor. This approach ignores user interface conventions that may differ among controller platforms, which cannot easily be interpolated between. For example, certain platforms may not have support for certain types of widgets, and it is possible to imagine a future device that would differ significantly from the devices of today (e.g. a watch with a circular display that uses several physical dials for input).
We have chosen to automatically generate user interfaces on the controller device from an interface-independent specification language. This specification language describes the functions of the appliance and provides knowledge about how these functions relate to each other. We hope that many different kinds of interfaces can be generated from this specification, including interfaces for handhelds, WAP cellular phones, and future devices with new and different interaction styles. We also plan to explore the use of our specification with the automatic generation of speech interfaces.
This document describes the specification language that we have designed for supporting automatic generation of user interfaces. It begins with a description of the conceptual basis for the language, and then defines the language in concrete terms. The document concludes with a section detailing planned additions to the specification language. An example specification is also included in the first appendix.
Users of this document may also be interested in documentation of the network protocol that is used by PUCs to communicate with appliances.
| Date | Name | Comments |
|---|---|---|
| 02/22/2002 | jwn | Fixed typos and added information about new tags for supporting the creation of speech interfaces from the specification language. See <phonetic> tag and the label dictionary explanation. |
| 02/23/2002 | jwn | Made priority a parameter of <group>, <state>, <command>, and <explanation> instead of having its own tag. |
| 02/27/2002 | jwn | Made changes to support the DTD and its formatting requirements. Added the DTD section. Updated the example spec to reflect all of the recent language revisions. Added the <apply-type> and <object-ref> tags to support the reuse of type declarations and multiple occurences of the same object within the group tree. This was previously included as a feature that was incompatible with the restrictions of the DTD. Added the <number> tag to make numeric values symmetrical with the use of <refvalue>, again for DTD conformance. Also removed the recording parameter from the <map> and <labels> elements. Instead, each of these elements may now contain multiple <text-to-speech> elements that may specify similar information. |
| 03/11/2002 | jwn | Added a reference to the new protocol documentation. |
The design of the specification language was based upon prototyping interfaces for a phone and a stereo appliance on a standard handheld form factor (we tried both Palm and PocketPC). These prototypes taught us several things about the functions of appliances:
Each of these items are described in more detail below.
Three types of appliance objects are supported in the specification language.
Although there are differences between states, commands and explanations, they also share a common property of being enabled. When an object is enabled (or active), the user interface widgets that correspond to that object can be manipulated by the user. Knowing the circumstances in which an object will be enabled or disabled can provide a helpful hint for structuring the interface, because items that are active in similar situations can be grouped, and items can be placed on panels such that the widgets are not visible when the object would not be active. Specifying the prior knowledge of the enabled property is discussed in more detail in the Dependency Information sub-section.
Another common property of appliance objects is the need to specify rich labeling information for flexibility when generating interfaces in different form factors.
To support specifying labeling information, we use the concept of a label dictionary. At any place in the specification where a label can be entered, more than one label may be provided. It is expected that these labels would all contain the same general information, but vary in terms of length and detail. The interface generator would choose the longest label that fit within the space allocated on the screen.
Labels can be specified for any appliance object, and also be linked with particular values of an appliance state's type.
This language also supports the specification of label information for speech interfaces. This support is for both recognition and text-to-speech. Label dictionaries can store phonetic information to help recognizers interpret label names. Multiple pronunciations can be specified for robustness, just as multiple labels can be specified. To assist text-to-speech engines, each label dictionary can be associated with a URL that points to an audio recording of the label.
Every appliance state has a type object associated with it. The type information is used to determine what kinds of widgets can be used to manipulate the state and may be used in the future as one method of determining whether a standard widget configuration should be used in certain circumstances.
There are currently eight different kinds of types that can be used in the specification:
Italicized types have not yet been implemented.
Each of these types has a different set of parameters that can be specified for it. For a complete list of those parameters, see the tag reference.
The custom type is used for defining domain-specific types that can be used by the interface generator in situations where domain-specific information or widgets are available. This is a first cut at providing a means of referring to domain-specific information that is shared a priori by the controller and the appliance. In the future, we anticipate providing a way for the controller to download this information from the appliance when it is not understood. For example, if a controller does not understand a particular custom type, it would be able to request the appliance to send it the basic type components of the custom type.
The specification language contains a provision for re-using type declarations in subsequently defined state variables via the <apply-type> element. This allows two or more state variables to have the same type without specifying that information multiple times in a specification. No data is shared between those state variables.
Dependency information is specified for each appliance object as a boolean equation. This information gives the interface generator some approximate a priori knowledge of when the object will be enabled (see the Appliance Objects section for more information).
Three kinds of dependencies can be specified. Each of these dependencies specifies a state that is depended upon, and a value of that state.
These dependencies can be composed into boolean formulas using AND and OR.
Dependency information is used for determining the structure of the generated user interface. We believe that it is possible to determine all of the important structural information from the dependency information alone.
It is not always sufficient to use only dependency information for determining the structure of the user interface however, because the resolution of the structure found from the dependency information may not be fine enough for the form factor of the user interface. To improve the resolution of the structure choices, the specification also includes a human-designed group tree, which tells the user interface generator how to split widgets across screens when dependency information does not exist to make the decision otherwise.
In some cases, it may be useful to place an appliance object at multiple locations in the group tree. This is allowed is the specification via the <object-ref> element.
Our specification language is based on the XML standard from the Worldwide Web Consortium. Most XML document formats have a DTD or a Schema, which define their structure. We have created a DTD for our language and we may specify a Schema in the future. For specific formatting information about our language, please refer to the DTD. The tag reference and the example specification in Appendix A may also be helpful for understanding the particulars of our specification language.
All appliance specification should contain the following line as a part of the XML prolog.
<!DOCTYPE spec SYSTEM "http://www.cs.cmu.edu/~jeffreyn/controller/pucspec.dtd">
<!-- This is version 0.1 of the Personal Universal Controller -->
<!-- Appliance Specification Document Type Definition -->
<!ENTITY % PRIORITY '(1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10)'>
<!ELEMENT spec (groupings)>
<!ATTLIST spec name CDATA #REQUIRED>
<!ELEMENT groupings (group+)>
<!ELEMENT group (active-if?, labels?, (command | explanation | group | state | object-ref)+)>
<!ATTLIST group priority %PRIORITY; #IMPLIED>
<!ELEMENT active-if (and | or | (equals | greaterthan | lessthan)+)>
<!ATTLIST active-if ignore (parent | all) #IMPLIED>
<!ELEMENT command (labels?, active-if?)>
<!ATTLIST command name ID #REQUIRED
priority %PRIORITY; #IMPLIED>
<!ELEMENT explanation (labels, active-if?)>
<!ATTLIST explanation name CDATA #REQUIRED priority %PRIORITY; #IMPLIED>
<!ELEMENT labels ((label | refstring | phonetic | text-to-speech)+)>
<!ATTLIST labels recording CDATA #IMPLIED>
<!ELEMENT state ((type | apply-type), labels?, active-if?)>
<!ATTLIST state name ID #REQUIRED
access (ReadOnly | WriteOnly) #IMPLIED
priority %PRIORITY; #IMPLIED>
<!ELEMENT object-ref EMPTY>
<!ATTLIST object-ref name IDREF #REQUIRED>
<!ELEMENT and ((equals | greaterthan | lessthan)+)>
<!ELEMENT equals (#PCDATA)>
<!ATTLIST equals state IDREF #REQUIRED>
<!ELEMENT greaterthan (#PCDATA)>
<!ATTLIST greaterthan state IDREF #REQUIRED>
<!ELEMENT lessthan (#PCDATA)>
<!ATTLIST lessthan state IDREF #REQUIRED>
<!ELEMENT or ((equals | greaterthan | lessthan)+)>
<!ELEMENT label (#PCDATA)>
<!ELEMENT phonetic (#PCDATA)>
<!ELEMENT text-to-speech EMPTY>
<!ATTLIST text-to-speech text CDATA #REQUIRED
recording CDATA #IMPLIED>
<!ELEMENT type (valueSpace, expectedValues?, valueLabels?)>
<!ATTLIST type name ID #IMPLIED>
<!ELEMENT apply-type EMPTY>
<!ATTLIST apply-type name IDREF #REQUIRED>
<!ELEMENT refstring EMPTY>
<!ATTLIST refstring state IDREF #REQUIRED>
<!ELEMENT expectedValues (boolean | custom | enumerated | fixedpt | integer | string | floatingpt)>
<!ELEMENT valueLabels (map+)>
<!ELEMENT valueSpace (boolean | custom | enumerated | fixedpt | integer | string | floatingpt)>
<!ELEMENT boolean EMPTY>
<!ELEMENT custom (#PCDATA)>
<!ELEMENT enumerated (items+)>
<!ELEMENT fixedpt (incr?, max?, min?)>
<!ELEMENT integer (incr?, max?, min?)>
<!ELEMENT floatingpt (max?, min?)>
<!ELEMENT string EMPTY>
<!ELEMENT map ((label | phonetic | refstring | text-to-speech)+)>
<!ATTLIST map index CDATA #REQUIRED
enable CDATA #IMPLIED>
<!ELEMENT items (#PCDATA)>
<!ELEMENT incr (number | refvalue)>
<!ELEMENT max (number | refvalue)>
<!ELEMENT min (number | refvalue)>
<!ELEMENT number (#PCDATA)>
<!ELEMENT refvalue EMPTY>
<!ATTLIST refvalue state IDREF #REQUIRED>
This section describes all of the tags available in the specification language and how they are used.
<spec name="Sample Specification"></spec>
<groupings></groupings>
<group priority=10></group>
Group nodes may be assigned a label dictionary with the <labels> tag. Group nodes may also specify dependencies for all their members using the <active-if> tag. These dependencies are applied to the rest of the member's dependencies with an AND logical operation.
<object-ref name="ObjectName"/>
<state name="StateName" access="ReadOnly" | "WriteOnly" priority=10></state>
<command name="CommandName" priority=10></command>
<explanation name="ExplanationName" priority=10></explanation>
<type name="TypeName"></type> <type name="TypeName"/>
<apply-type name="TypeName"/>
<valueSpace></valueSpace>
<expectedValues></expectedValues>
<boolean/>
<custom/>
<enumerated></enumerated>
Enumerated type values are treated as integers that range between 1 and the number of items in the type. Zero is not a valid value for an enumerated type. This is important when you specify an index to the <map> tag;
<items></items>
<fixedpt></fixedpt>
<pointpos></pointpos>
<floatingpt></floatingpt>
<integer></integer>
<incr></incr> <max></max> <min></min>
The increment may not be defined for the floating point type.
<string/>
<valueLabels></valueLabels>
<map index="value" enable="StateName" recording="URL"></map>
Certain values of a variable can also be enabled based on the state of another boolean state variable, as specified in the enable parameter. This parameter may also start with an ! symbol, which indicates that the enable should be based on the inverse of that states value.
<labels recording="URL"></labels>
<text-to-speech text="<whisper>mute</whisper>" recording="mute.au"/>
<label></label>
<phonetic></phonetic>
<active-if ignore="all" | "parent"></active-if>
<equals state="SomeState">value</equals>
<greaterthan state="SomeState">value</greaterthan>
<lessthan state="SomeState">value</lessthan>
<and></and>
<or></or>
<refstring state="StateName"/>
<refvalue state="StateName"/>
<number>12</number>
In the future, we are planning to add several new features to the specification language. This section discusses a few of these features.
Sometimes when defining a particular function of an appliance, it seems useful to give the interface generator a hint of how to appropriately render that function. For example, the balance of the output between the right and left speakers has an inherent horizontal quality that should be maintained in the user interface. Currently, there is no way for the specification designer to provide the interface generator with this kind of meta-information that may be critical to generating a good interface. Hints are a generic solution to this problem.
Unfortunately, the idea of a hint is a little too generic to support within the spec. While it is useful to have this information, the language should avoid allowing the specification designer to over-specify a device. This keeps the size of a device specification from getting excessively large, and also relieves the programmers of an interface generator from writing code to interpret a generic hint. A happy medium will need to be achieved.
One type of hint that is particularly important are hints about standard arrangements of widgets. For example, the control buttons for a CD or Tape player are typically arranged in two rows, with a large play button on top, adjacent to smaller pause and stop button. The bottom row usually consists of the fast-forward and rewind buttons, which are usually larger than the stop and pause buttons, but smaller than the play button. It may also be valuable to duplicate control arrangements from the appliance, if the transfer of interface knowledge from the appliance to the controller would be useful.
In the future, we will work on methods of communicating this information and understanding the trade-offs between when available layout information should be used and when it should not.
Some commands have an implicit relationship with state variables on the device. For example, the seek button on a radio must be represented as a command, because its affect on the radio station state variable can not be known a priori. However, it does affect a state variable in some way and this information could useful to the user interface. This will be included in an upcoming revision of the specification language.
Lists are important part of many user interfaces. MP3 players have a list of songs that they play from, and radios may have a list of preset stations that can be jumped to. Earlier in this document, we specified lists as one of the types that would be supported by the PUC in the future. It turns out that supporting lists is more difficult than supporting the other basic types, because there are many different types of list operations, and not every list operation makes sense in every instance of a list in a user interface. How do we specify which list operations are available?
One method that might be used for supporting lists in a more general way is a task model. To date, many user interface generation researchers have used task models to aid in the development of interface generators. We haven't used a task model so far, because the typical tasks that we have encountered are typically one or two steps deep and multiple tasks may have operations that interleave with each other. This seems less likely to occur with a list, with the added benefit that the description of the operations on the list would be reduced to the tasks that the user will want to perform.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE spec SYSTEM "http://www.cs.cmu.edu/~jeffreyn/controller/pucspec.dtd">
<!-- Note: This file is best viewed in Internet Explorer. -->
<!-- This is specification of the Audiophase home stereo that was used
for the PDG annual meeting on 02/15/2002. -->
<spec name="Audiophase 5 CD Stereo">
<groupings>
<group>
<state name="PowerState" priority="10">
<type name="OnOffType">
<valueSpace>
<boolean/>
</valueSpace>
<valueLabels>
<map index="false">
<label>Off</label>
</map>
<map index="true">
<label>On</label>
</map>
</valueLabels>
</type>
<labels>
<label>Stereo Power</label>
<label>Power</label>
<label>Powr</label>
<label>Pwr</label>
</labels>
</state>
<group>
<active-if>
<equals state="PowerState">true</equals>
</active-if>
<labels>
<label>Volume</label>
<label>Vol</label>
</labels>
<command name="VolumeUp" priority="10">
<labels>
<label>Volume Up</label>
<label>Vol. Up</label>
<label>^</label>
</labels>
</command>
<command name="VolumeDn" priority="10">
<labels>
<label>Volume Down</label>
<label>Vol. Down</label>
<label>Down</label>
<label>v</label>
</labels>
</command>
</group>
<state name="XBassState" priority="5">
<type>
<valueSpace>
<boolean/>
</valueSpace>
</type>
<labels>
<label>X-Bass</label>
</labels>
<active-if>
<equals state="PowerState">true</equals>
</active-if>
</state>
<group>
<active-if>
<equals state="PowerState">true</equals>
</active-if>
<state name="ModeState">
<type>
<valueSpace>
<enumerated>
<items>4</items>
</enumerated>
</valueSpace>
<valueLabels>
<map index="1">
<label>Tape</label>
</map>
<map index="2">
<label>CD</label>
</map>
<map index="3">
<label>AUX</label>
</map>
<map index="4">
<label>Tuner</label>
</map>
</valueLabels>
</type>
<labels>
<label>Output Mode</label>
<label>Mode</label>
</labels>
</state>
<group>
<active-if>
<equals state="ModeState">1</equals>
</active-if>
<explanation name="TapeExpl">
<labels>
<label>Tape not controllable.</label>
</labels>
</explanation>
</group>
<group>
<active-if>
<equals state="ModeState">3</equals>
</active-if>
<explanation name="AUXExpl">
<labels>
<label>AUX not controllable.</label>
</labels>
</explanation>
</group>
<group>
<active-if>
<equals state="ModeState">4</equals>
</active-if>
<labels>
<label>Tuner</label>
</labels>
<state name="RadioBandState">
<type>
<valueSpace>
<boolean/>
</valueSpace>
<valueLabels>
<map index="true">
<label>FM</label>
</map>
<map index="false">
<label>AM</label>
</map>
</valueLabels>
</type>
<labels>
<label>Radio Band</label>
<label>Band</label>
</labels>
</state>
<group>
<command name="SeekForward">
<labels>
<label>Seek Forward</label>
<label>Seek -></label>
<label>>></label>
</labels>
</command>
<command name="SeekReverse">
<labels>
<label>Seek Reverse</label>
<label><-Seek</label>
<label><<</label>
</labels>
</command>
</group>
<group>
<active-if>
<equals state="RadioBandState">true</equals>
</active-if>
<state name="FMStation">
<type name="FMType">
<valueSpace>
<custom>radio/station-fm</custom>
</valueSpace>
</type>
<labels>
<label>Radio Station</label>
<label>Station</label>
</labels>
</state>
<state name="FMPresetNumber">
<type name="FMPresetType">
<valueSpace>
<enumerated>
<items>5</items>
</enumerated>
</valueSpace>
<valueLabels>
<map index="1">
<refstring state="FMPresetValue1"/>
</map>
<map index="2">
<refstring state="FMPresetValue2"/>
</map>
<map index="3">
<refstring state="FMPresetValue3"/>
</map>
<map index="4">
<refstring state="FMPresetValue4"/>
</map>
<map index="5">
<refstring state="FMPresetValue5"/>
</map>
</valueLabels>
</type>
<labels>
<label>Presets</label>
</labels>
</state>
<state name="FMPresetValue1" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="FMPresetValue2" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="FMPresetValue3" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="FMPresetValue4" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="FMPresetValue5" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
</group>
<group>
<active-if>
<equals state="RadioBandState">false</equals>
</active-if>
<state name="AMStation">
<type name="AMType">
<valueSpace>
<custom>radio/station-am</custom>
</valueSpace>
</type>
<labels>
<label>Radio Station</label>
<label>Station</label>
</labels>
</state>
<state name="AMPresetNumber">
<type name="AMPresetType">
<valueSpace>
<enumerated>
<items>5</items>
</enumerated>
</valueSpace>
<valueLabels>
<map index="1">
<refstring state="AMPresetValue1"/>
</map>
<map index="2">
<refstring state="AMPresetValue2"/>
</map>
<map index="3">
<refstring state="AMPresetValue3"/>
</map>
<map index="4">
<refstring state="AMPresetValue4"/>
</map>
<map index="5">
<refstring state="AMPresetValue5"/>
</map>
</valueLabels>
</type>
<labels>
<label>Presets</label>
</labels>
</state>
<state name="AMPresetValue1" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="AMPresetValue2" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="AMPresetValue3" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="AMPresetValue4" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
<state name="AMPresetValue5" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
</state>
</group>
</group>
<group>
<active-if>
<equals state="ModeState">2</equals>
</active-if>
<labels>
<label>CD</label>
</labels>
<state name="CDPlayMode">
<type>
<valueSpace>
<custom>playcontrol</custom>
</valueSpace>
</type>
<labels>
<label>CD Status</label>
<label>Status</label>
</labels>
</state>
<group>
<labels>
<label>Discs</label>
</labels>
<state name="CDDiscActive">
<type>
<valueSpace>
<enumerated>
<items>5</items>
</enumerated>
</valueSpace>
<valueLabels>
<map index="1" enable="Disc1Avail">
<label>1</label>
</map>
<map index="2" enable="Disc2Avail">
<label>2</label>
</map>
<map index="3" enable="Disc3Avail">
<label>3</label>
</map>
<map index="4" enable="Disc4Avail">
<label>4</label>
</map>
<map index="5" enable="Disc5Avail">
<label>5</label>
</map>
</valueLabels>
</type>
<labels>
<label>Disc</label>
</labels>
</state>
<state name="Disc1Avail" access="ReadOnly">
<type name="DiscType">
<valueSpace>
<boolean/>
</valueSpace>
<valueLabels>
<map index="true">
<label>Active</label>
</map>
<map index="false">
<label>Not Active</label>
</map>
</valueLabels>
</type>
<labels>
<label>Disc 1</label>
<label>1</label>
</labels>
</state>
<state name="Disc2Avail" access="ReadOnly">
<apply-type name="DiscType"/>
<labels>
<label>Disc 2</label>
<label>2</label>
</labels>
</state>
<state name="Disc3Avail" access="ReadOnly">
<apply-type name="DiscType"/>
<labels>
<label>Disc 3</label>
<label>3</label>
</labels>
</state>
<state name="Disc4Avail" access="ReadOnly">
<apply-type name="DiscType"/>
<labels>
<label>Disc 4</label>
<label>4</label>
</labels>
</state>
<state name="Disc5Avail" access="ReadOnly">
<apply-type name="DiscType"/>
<labels>
<label>Disc 5</label>
<label>5</label>
</labels>
</state>
</group>
<group>
<command name="CDPrevTrack">
<labels>
<label>Prev</label>
<label><</label>
</labels>
</command>
<state name="CDTrackState" access="ReadOnly">
<type>
<valueSpace>
<string/>
</valueSpace>
</type>
<labels>
<label>Track</label>
</labels>
</state>
<command name="CDNextTrack">
<labels>
<label>Next</label>
<label>></label>
</labels>
</command>
</group>
<state name="CDRandomState">
<type>
<valueSpace>
<boolean/>
</valueSpace>
</type>
<labels>
<label>Random</label>
</labels>
<active-if>
<equals state="CDPlayMode">1</equals>
</active-if>
</state>
<state name="CDRepeatState">
<type>
<valueSpace>
<enumerated>
<items>5</items>
</enumerated>
</valueSpace>
<valueLabels>
<map index="1">
<label>Off</label>
</map>
<map index="2">
<label>One Track</label>
<label>One</label>
</map>
<map index="3" enable="CDRandomState">
<label>All Tracks</label>
<label>All</label>
</map>
<map index="4" enable="!CDRandomState">
<label>One Disc</label>
<label>1 Disc</label>
</map>
<map index="5" enable="!CDRandomState">
<label>All Discs</label>
<label>All</label>
</map>
</valueLabels>
</type>
<labels>
<label>Repeat</label>
</labels>
</state>
</group>
</group>
</group>
</groupings>
</spec>