Protocol Data Units¶
According to Wikipedia a Protocol Data Unit (PDU) is
Information that is delivered as a unit among peer entities of a network and that may contain control information, address information, or data.
BACpypes uses a slght variation of this definition in that it bundles the address information in with the control information. It considers addressing part of how the data should be delivered, along with other concepts like how important the PDU data is relative to other PDUs.
The basic components of a PDU are the comm.PCI
and
comm.PDUData
classes which are then bundled together to form the
comm.PDU
class.
All of the protocol interpreters that have been written in the course of
developing BACpypes have all had at least some concept of source and
destination. The comm.PCI
defines only two attributes, pduSource
and pduDestination.
Only in the case of pure master/slave networks has only the destination encoded by the master to direct it to a specific slave (so source information is implicit and not encoded) and the response from the slave back to the master (so no addressing is included at all). These special cases are rare.
As a foundation layer, there are no restrictions on the form of the source and
destination, they could be integers, strings or even objects. In general,
the comm.PDU
class is used as a base class for a series of stack
specific components, so UDP traffic will have combinations of IP addresses and
port numbers as source and destination, then that will be inherited by something
that provides more control information, like delivery order or priority.
Beginning with the base class:
>>> from bacpypes.comm import PDU
While source and destination are defined in the PCI, they are optional keyword
parameters. Debugging the contents of the PDU will skip over those attributes
that are None
and strings are assumed to be a sequence of octets and so
are printed as hex encoded strings:
>>> pdu = PDU("hello")
>>> pdu.debug_contents()
pduData = x'68.65.6C.6C.6F'
Now add some source and destination information:
>>> pdu = PDU("hello", source=1, destination=2)
>>> pdu.debug_contents()
pduSource = 1
pduDestination = 2
pduData = x'68.65.6C.6C.6F'
It is customary to allow missing attributes (which is protocol control information or it would be data) to allow the developer to mixed keyword parameters and post-init attribute assignment.
BACnet PDUs¶
The PDU definition in the core is fine for many protocols, but BACnet has two additional protocol parameters, described as attributes of a BACnet PCI information.
The pdu.PCI
class extends the basic PCI with pduExpectingReply and
pduNetworkPriority. The former is only used in MS/TP networks so the
node generating the request will not pass the token before waiting some amount
of time for a response, and the latter is a hint to routers and other deivces
with priority queues for network traffic that a PDU is more or less important.
These two fields are set at the application layer and travel with the PDU content as it travels down the stack.
Encoding and Decoding¶
The encoding and decoding process consists of consuming content from the source PDU and generating content in the destination. BACpypes could have used some kind of “visitor” pattern so the process did not consume the source, but typically when a layer has finished with PDU and will be sending some other PDU upstream or downstream and once that PDU leaves the layer it is not re-visited.
Note
This concept, where an object like a PDU is passed off to some other function and it is no longer “owned” by the builder, is difficult to accomplish in language and runtime environments that do not have automatic garbage collection. It tremendiously simplifies interpreter code.
PDUs nest the control infommation of one level into the data portion of the next level down, and when decoding on the way up a stack it is customary to pass the control information along, even when it isn’t strictly necessary.
The pdu.PCI.update()
function is an example of a method that is used
the way a “copy” operation might be used. The PCI classes, and nested versions
of them, usually have an update function.
Decoding consumes some number of octets from the front of the PDU data:
>>> pdu = PDU("hello!!")
>>> pdu.get()
104
>>> pdu.get_short()
25964
>>> pdu.get_long()
1819222305
And the PDU is now empty:
>>> pdu.debug_contents()
pduData = x''
But the contents can be put back, an implicit append operation:
>>> pdu.put(104)
>>> pdu.put_short(25964)
>>> pdu.put_long(1819222305)
>>> pdu.debug_contents()
pduData = x'68.65.6C.6C.6F.21.21'
Note
There is no distinction between a PDU that is being used as the source to some interpretation process and one that is the destination. Earlier versions of this library made that distinction and the type casting and type conversion code became an impediment to understanding the interpretation, so it was dropped.