Overview of the PPS Service

The services layer of the QNX SDK for Apps and Media is built on the Persistent Publish/Subscribe (PPS) service, a simple filesystem-based facility that provides information persistence across reboots. Small and extensible, PPS allows interfacing from almost any higher-level language that supports open, read, write, and close operations on files.

Note: For a more in-depth description of PPS, see the Persistent Publish/Subscribe Developer's Guide.

Key concepts

Objects
Objects are implemented as files under the /pps directory. Your apps and HMI use objects to communicate with each other. There can be many objects in the system but never more than one instance of the same object.
Apps and HMI services often use a control object for sending commands and a corresponding status object for publishing responses.
Client apps can read the special .all object to get notifications of changes to all the objects in a directory. They can use the special .notify object to get changes for a certain set of objects.
Attributes
Objects contain attributes (or properties) that apps can modify. Each attribute appears on a single line in the object file.
Publishers
As publishers, apps can modify objects and their attributes so that other interested apps can receive updates. Publishing is asynchronous—apps don't have to wait for the publisher.
To publish to an object, the publisher calls open() for that object and then write() to modify it. Multiple publishers can publish to the same object. When a publisher changes an object, the PPS service informs all subscribers of the change.
Subscribers
As subscribers, apps receive updates for objects and attributes that publishers have modified. To get updates for an object, a subscriber calls open() for that object and then read() to query it. Note that reads are nonblocking by default. Multiple subscribers can subscribe to the same object.
Note: The same app can be a publisher, a subscriber, or both.
Full subscription mode
In full mode (the default), the subscriber gets a "snapshot" of the entire object as it exists when the request is made. Note that if a publisher changes the object many times, the subscriber may miss some of the changes. Full mode is useful, for instance, for high-bandwidth objects that have numerous and frequent changes.
Delta subscription mode
In delta mode, the subscriber gets only the changes made to an object. On first read, the subscriber will get all the object's attributes (because the subscriber knows nothing yet about the object's state); subsequent reads will return only the changes since the previous read. Delta mode is useful, for instance, when you want to receive all the warnings or error messages that might be published to an object.
Persistence
PPS maintains objects in memory while it's running and can save them to persistent storage (either at shutdown or on demand) on any reliable filesystem, such as flash or hard disk. Objects can be restored immediately on startup or on first access.
Server objects
PPS supports point-to-point communication between a server and one or more clients. An app can designate itself as the server when creating a PPS object. When a client writes to this object, only the server gets the message. PPS appends a unique identifier to the object name so that the server knows which client app is sending the message.
When the server replies, it must append the same identifier to the object name so that the response is sent only to the client indicated by that identifier. In this communication mode, both the server and the clients read from and write to the object. For more details, see "Server objects" in the Persistent Publish/Subscribe Developer's Guide.

Command-line options for the PPS service

pps [-A file][-b][-C][-d backlog][-l argument][-m mount][-p dir]
    [-P prio][-t period][-T tolerance][-U uid:gid][-v]
-A file
Set the path to the ACL configuration file. For details, see "Access Control List configuration file" in the Persistent Publish/Subscribe Developer's Guide.
-b
Don't run in the background (useful for debugging).
-C
Convert between -U and non-U persistence formats.
-d backlog
Set the default size of the delta backlog, in kilobytes (default is 256 kilobytes).
-l argument
Set the object load behavior:
  • 0 — load directory names and objects on demand (default).
  • 1 — load directories at startup, but objects on demand.
  • 2 — load directories and objects at startup.
-m mount
Specify the mountpoint for PPS (default is /pps).
-p dir
Specify the directory for persistent storage (default is /var/pps).
-P prio
Set the priority of the persistence thread.
-t period
Set the time period (in milliseconds) for writing to persistent storage (default is off).
-T tolerance
Set the tolerance (in milliseconds) for writing to persistent storage (default is off).
-U uid:gid
Downgrade from root to the specified UID and GID.
-v
Run in verbose mode (use multiple v's to increase verbosity).
Note: You can also use SIGUSR1 to increase verbosity.

Pathname options

PPS lets you use various pathname options when opening objects. An option must follow a question mark (?). Use a comma to separate multiple options. For example, opening the playlist object like this:

/pps/media/playlist?wait,delta

will open the object with the wait and delta options.

You can set these options:

backlog=size
Set the total delta size to keep before flushing this client's buffer of deltas. The size is in kilobytes, so 4 means 4 KB. The default is 256 KB, unless you specify the -d option, which overrides the default delta size.
cred
Add client credentials to this object. This option is effective only when server is used, because it tells PPS to pass the client's PID, UID, and GID to the server by including these fields in the object name.
critical
Designate the publisher as critical to the object. For details, see the "Critical option" section in the PPS Developer's Guide.
delta
Open the object in delta mode, which means only the changes made to the object are returned by a read operation.
deltadir
Return the names of all objects listed in the .all object in a directory.
f=attrspec{+attrspec}...
Filter notifications based on changes to the names and/or values of specified attributes, where attrspec can be either an attribute's name or an expression specifying an attribute's value. A value expression consists of an attribute name, followed by an operator, followed by a value.
  • Operators for integers (which must be in the range of a long long type) are: <, <=, >, >=, =, ==, and !=
  • Operators for strings are: =, ==, and != (you can use + if escaped with \)
flow=backlog_size
Deliver purge notifications for this object (similar to a server object). This flag takes an optional argument for the number of kilobytes of backlog (i.e., series of deltas) that the server is permitted. If you don't specify this argument, the backlog size is used; if this other option isn't defined, the default size of 256 KB is used.
When the server falls behind in reading the object and the backlog exceeds the size specified in flow, the object will be purged and the server will receive purge notifications of the form |@objname.
A purge will occur for a client if it doesn't read the reply data at a fast enough rate. In this case, the server will received purge notifications of the form |@objname.clientid.
The flow flag is effective only when delta is used, and is mutually exclusive with backlog and server (because it enables the server mode internally).
hiwater=backlog_percentage
Deliver overflow notifications for this object when the client backlog exceeds a certain limit. This flag takes a mandatory argument in the range of 1 to 99, to indicate the percentage of client backlog at which the server will begin receiving overflow notifications. We refer to this limit as the high watermark for overflow.
As long as the backlog remains above this limit, the server will receive a notification of the form ^@objname.clientid for every write that it performs on the object.
The hiwater flag is valid only with the flow flag and must be explicitly set to enable overflow notifications. The default hiwater value of 100 means the service waits until the client backlog is full before purging the object and hence, no overflow notifications are sent.
nopersist
Make the object nonpersistent.
notify=id:value
Associate the object with the notification group specified by id:value, where:
  • id is the string returned by the first read from the .notify object
  • value is any arbitrary string
opens
Update an _opens::rd,wr attribute when the open count changes.
reflect
Reflect attribute updates made on this object back to the process that wrote them. When this option is set, if a process writes data to the object and then reads the object using the same file descriptor, the process will read the data that it wrote. By default, this option isn't set and a read() operation won't return the data written with the same file descriptor, because this isn't considered a change.
server
Designate the publisher as a server for the object (see "Server Objects" for details).
verbose
Set the verbosity level for this object.
wait
Clear the O_NONBLOCK flag so that read() calls will wait for any object changes, including deltas.

Object format

Objects appear as files in the PPS filesystem. For example, to view the contents of an object called AA:BA:19:B2:AA:70 (in this case, the filename is a device's MAC address) under the /pps/services/bluetooth/remote_devices/ directory, you can simply use cat at the command line:

cat /pps/services/bluetooth/remote_devices/AA:BA:19:B2:AA:70

The object's contents might look like this:

@AA:BA:19:B2:AA:70
[n]cod::0x007a020c
[n]name::My mobile
[n]paired:b:false
[n]rssi::0x00

The first line always begins with an AT sign (@), immediately followed by the object's name. Each line afterwards begins with a qualifier, followed by an attribute name, followed by its encoding, followed by its value. For example, this line:

[n]paired:b:false

means that the nonpersistence qualifier ([n]) has been set and that the attribute paired has the Boolean value of false.

Note: For details on encodings and on qualifiers, see these sections in the Persistent Publish/Subscribe Developer's Guide:

Format for messages to server objects

Messages written to server objects must have this format:

msg::command_string\nid::ID_number\ndat:json:{JSON_data}

where:
command_string
Name of the command being sent to the object.
ID_number
Any ID that identifies this instance of the message. The server always reflects the ID back in the response.
JSON_data
The dat attribute is usually JSON-encoded, because it may contain more than a simple string.

Format for responses

Responses always reflect the command_string and ID_number that were sent in the message, along with any errors:

res::command_string\nid::ID_number\ndat:json:{JSON_data}\nerr::errno_number\nerrstr::error_description

Changing the directory for persistent storage

The root PPS object tree (/pps by default) may look something like this:

# pwd
/pps
# ls -1F
accounts/
applications/
qnx/
services/
system/
#

PPS populates its root object tree from the persistence tree (/var/pps by default), where the objects and attributes that you want to persist are stored.

To specify a different directory for persistent storage:
  1. Create your own persistence directory (e.g., mkdir /myobjects).
  2. Start the PPS service from a different mountpoint (e.g., /fs/pps) and specify your new persistence directory:

    pps -m /fs/pps -p /myobjects

Note: You may want to run PPS with the -t option, which lets you specify the time period (in milliseconds) that the service will use to write to persistent storage. Without the -t, you won't see any changes in your persistence directory until PPS exits.