The first two chapters of this book introduce basic ROS concepts and its package management system in order to refresh your memory about concepts you should already know. In this first chapter, we will go through ROS concepts such as the ROS Master, the ROS nodes, the ROS parameter server, ROS messages and services discussing what we need to install ROS and how to get started with the ROS master.
In this chapter, we will cover the following topics:
Robot Operating System (ROS) is a flexible framework, providing various tools and libraries to write robotic software. It offers several powerful features to help developers in such tasks as message passing, distributing computing, code reusing, and implementation of state-of-the-art algorithms for robotic applications.
The ROS project was started in 2007, with the name Switchyard, by Morgan Quigley (http://wiki.osrfoundation.org/morgan), as part of the Stanford STAIR robot project. The main development of ROS happened at Willow Garage (https://www.willowgarage.com/).
The ROS community is growing very fast, and there are many users and developers worldwide. Most of the high-end robotics companies are now porting their software to ROS. This trend is also visible in industrial robotics, in which companies are switching from proprietary robotic applications to ROS.
The ROS industrial movement has gained momentum in the past few years, owing to the large amount of research done in that field. ROS Industrial can extend the advanced capabilities of ROS to manufacturing. The increasing applications of ROS can generate a lot of job opportunities in this field. So, after some years, a knowledge of ROS will be an essential requirement for a robotics engineer.
Imagine that we are going to build an autonomous mobile robot. Here are some of the reasons why people choose ROS over other robotic platforms, such as Player, YARP, Orocos, MRPT, and so on:
MoveIt
package can be used for motion planning of robot manipulators. These capabilities can directly be used in our robot software without any hassle. These capabilities are its best form of implementation, so writing new code for existing capabilities is like reinventing the wheel. Also, these capabilities are highly configurable; we can fine-tune each capability using various parameters.There are many reasons to choose ROS other than the preceding points.
Next, we can check the various reasons why people don't use ROS. Here are some of the existing reasons.
Here are some of the reasons why some people do not prefer ROS for their robotic projects:
We now know where we have to use ROS and where we do not. If ROS is really required for your robot, let's start discussing ROS in more detail. First, we can see the underlying core concepts of ROS. There are mainly three levels in ROS: the filesystem level, computation graph level, and community level. We will briefly have a look at each level.
ROS is more than a development framework. We can refer to ROS as a meta-operating system, since it offers not only tools and libraries but even OS-like functions, such as hardware abstraction, package management, and a developer toolchain. Like a real operating system, ROS files are organized on the hard disk in a particular manner, as depicted in the following figure:
Figure 1: ROS filesystem level
Here are the explanations for each block in the filesystem:
package.xml
file inside the ROS package is the manifest file of that package.
export
tag..msg
): The ROS messages are a type of information that is sent from one ROS process to the other. We can define a custom message inside the msg
folder inside a package (my_package/msg/MyMessageType.msg
). The extension of the message file is .msg
..srv
): The ROS service is a kind of request/reply interaction between processes. The reply and request data types can be defined inside the srv
folder inside the package (my_package/srv/MyServiceType.srv
).bloom
.The following screenshot gives you an idea of the files and folders of a package that we are going to create in the upcoming sections:
Figure 2: List of files inside the exercise package
A typical structure of an ROS package is shown here:
Figure 3: Structure of a typical C++ ROS package
We can discuss the use of each folder as follows:
config
: All configuration files that are used in this ROS package are kept in this folder. This folder is created by the user and it is a common practice to name the folder config
to keep the configuration files in it.include/package_name
: This folder consists of headers and libraries that we need to use inside the package.script
: This folder keeps executable Python scripts. In the block diagram, we can see two example scripts.src
: This folder stores the C++ source codes.launch
: This folder keeps the launch files that are used to launch one or more ROS nodes.msg
: This folder contains custom message definitions.srv
: This folder contains the services definitions.action
: This folder contains the action files. We will see more about these kind of files in the next chapter.package.xml
: This is the package manifest file of this package.CMakeLists.txt
: This files contains the directives to compile the package.We need to know some commands to create, modify, and work with the ROS packages. Here are some of the commands used to work with ROS packages:
catkin_create_pkg
: This command is used to create a new packagerospack
: This command is used to get information about the package in the filesystemcatkin_make
: This command is used to build the packages in the workspacerosdep
: This command will install the system dependencies required for this packageTo work with packages, ROS provides a bash-like command called rosbash
(http://wiki.ros.org/rosbash), which can be used to navigate and manipulate the ROS package. Here are some of the rosbash
commands:
roscd
: This command is used to change the current directory using a package name, stack name, or a special location. If we give the argument a package name, it will switch to that package folder.roscp
: This command is used to copy a file from a package.rosed
: This command is used to edit a file using the vim editor.rosrun
: This command is used to run an executable inside a package.The definition of package.xml
of a typical package is shown in the following screenshot:
Figure 4: Structure of package.xml
The package.xml
file consists of the package name, version of the package, the package description, author details, package build dependencies, and runtime dependencies. The <build_depend></build_depend>
tag includes the packages that are necessary to build the source code of the package. The packages inside the <run_depend></run_depend>
tags are necessary during runtime of the package node.
Metapackages are specialized packages in ROS that only contain one file, that is, a package.xml
file. They don't contain folders and files like a normal package.
Metapackages simply group a set of multiple packages as a single logical package. In the package.xml
file, the metapackage contains an export
tag, as shown here:
<export> <metapackage/> </export>
Also, in metapackages, there are no <buildtool_depend>
dependencies for catkin
; there are only <run_depend>
dependencies, which are the packages grouped in the metapackage.
The ROS navigation stack is a good example of metapackages. If ROS and its navigation package are installed, we can try the following command, by switching to the navigation metapackage folder:
$ roscd navigation
Open package.xml
using your favorite text editor (gedit
in the following case):
$ gedit package.xml
This is a lengthy file; here is a stripped-down version of it:
Figure 5: Structure of meta-package package.xml
The ROS nodes can write or read data that has a different type. The types of data are described using a simplified message description language, also called ROS messages. These datatype descriptions can be used to generate source code for the appropriate message type in different target languages.
The data type description of ROS messages is stored in .msg
files in the msg
subdirectory of a ROS package. Even though the ROS framework provides a large set of robotic-specific messages already implemented, developers can define their own message type inside their nodes.
The message definition can consist of two types: fields
and constants
. The field is split into field types and field names. The field type is the data type of the transmitting message and field name is the name of it. The constants define a constant value in the message
file.
Here is an example of message definitions:
int32 number string name float32 speed
Here, the first part is the field type and the second is the field name. The field type is the data type and the field name can be used to access the value from the message. For example, we can use msg.number
for accessing the value of the number from the message.
Here is a table showing some of the built-in field types that we can use in our message:
Primitive type | Serialization | C++ | Python |
| Unsigned 8-bit int |
|
|
| Signed 8-bit int |
|
|
| Unsigned 8-bit int |
|
|
| Signed 16-bit int |
|
|
| Unsigned 16-bit int |
|
|
| Signed 32-bit int |
|
|
| Unsigned 32-bit int |
|
|
| Signed 64-bit int |
|
|
| Unsigned 64-bit int |
|
|
| 32-bit IEEE float |
|
|
| 64-bit IEEE float |
|
|
| ascii string(4) |
|
|
| secs/nsecs unsigned 32-bit ints |
|
|
| secs/nsecs signed 32-bit ints |
|
|
Other kinds of messages are designed to cover a specific application necessity, such as exchanging common geometrical (geometry_msgs
) or sensor (sensor_msgs
) information. A special type of ROS message is called a message header. Headers can carry information, such as time, frame of reference or frame_id
, and sequence number. Using headers, we will get numbered messages and more clarity in who is sending the current message. The header information is mainly used to send data such as robot joint transforms (TF). Here is an example of the message header:
uint32 seq time stamp string frame_id
The rosmsg
command tool can be used to inspect the message header and the field types. The following command helps to view the message header of a particular message:
$ rosmsg show std_msgs/Header
This will give you an output like the preceding example message header. We will look at the rosmsg
command and how to work with custom message definitions further in the upcoming sections.
The ROS services are a type request/response communication between ROS nodes. One node will send a request and wait until it gets a response from the other. The request/response communication is also using the ROS message description.
Similar to the message definitions using the ".msg"
file, we have to define the service definition in another file called ".srv"
, which has to be kept inside the srv
subdirectory of the package. Similar to the message definition, a service description language is used to define the ROS service types.
An example service description format is as follows:
#Request message type string str --- #Response message type string str
The first section is the message type of the request that is separated by ---
and in the next section is the message type of the response. In these examples, both Request
and Response
are strings.
In the upcoming sections, we will look at how to work with ROS services.
The computation in ROS is done using a network of a process called ROS nodes. This computation network can be called the computation graph. The main concepts in the computation graph are ROS Nodes, Master, Parameter server, Messages, Topics, Services, and Bags. Each concept in the graph is contributed to this graph in different ways.
The ROS communication-related packages including core client libraries, such as roscpp
and rospython
, and the implementation of concepts, such as topics, nodes, parameters, and services are included in a stack called ros_comm
(http://wiki.ros.org/ros_comm).
This stack also consists of tools such as rostopic
, rosparam
, rosservice
, and rosnode
to introspect the preceding concepts.
The ros_comm
stack contains the ROS communication middleware packages and these packages are collectively called the ROS Graph layer:
Figure 6: Structure of the ROS Graph layer
The following are abstracts of each graph's concepts:
The following graph shows how the nodes communicate with each other using topics. The topics are mentioned in a rectangle and the nodes are represented in ellipses. The messages and parameters are not included in this graph. These kinds of graphs can be generated using a tool called rqt_graph
(http://wiki.ros.org/rqt_graph):
Figure 7: Graph of communication between nodes using topics
ROS nodes are a process that perform computation using ROS client libraries such as roscpp
and rospy
. One node can communicate with other nodes using ROS Topics, Services, and Parameters.
A robot might contain many nodes; for example, one node processes camera images, one node handles serial data from the robot, one node can be used to compute odometry, and so on.
Using nodes can make the system fault tolerant. Even if a node crashes, an entire robot system can still work. Nodes also reduce the complexity and increase debug-ability compared to monolithic code because each node is handling only a single function.
All running nodes should have a name assigned to identify them from the rest of the system. For example, /camera_node
could be a name of a node that is broadcasting camera images.
There is a rosbash
tool to introspect ROS nodes. The rosnode
command can be used to get information about a ROS node. Here are the usages of rosnode
:
$ rosnode info [node_name]
: This will print the information about the node$ rosnode kill [node_name]
: This will kill a running node$ rosnode list
: This will list the running nodes$ rosnode machine [machine_name]
: This will list the nodes running on a particular machine or a list of machines$ rosnode ping
: This will check the connectivity of a node$ rosnode cleanup
: This will purge the registration of unreachable nodesWe will look at example nodes using the roscpp
client and will discuss the working of ROS nodes that use functionalities such ROS Topics, Service, Messages, and actionlib.
ROS nodes communicate with each other by publishing messages to a topic. As we discussed earlier, messages are a simple data structure containing field types. The ROS message supports standard primitive datatypes and arrays of primitive types.
Nodes can also exchange information using service calls. Services are also messages. The service message definitions are defined inside the srv
file.
We can access the message definition using the following method. For example, to access std_msgs/msg/String.msg
, we can use std_msgs/String
. If we are using the roscpp
client, we have to include std_msgs/String.h
for the string message definition.
In addition to message data type, ROS uses an MD5 checksum comparison to confirm whether the publisher and subscriber exchange the same message data types.
ROS has inbuilt tools called rosmsg
to get information about ROS messages. Here are some parameters used along with rosmsg
:
$ rosmsg show [message]
: This shows the message description$ rosmsg list
: This lists all messages$ rosmsg md5 [message]
: This displays md5sum
of a message$ rosmsg package [package_name]
: This lists messages in a package$ rosmsg packages [package_1] [package_2]
: This lists packages that contain messagesROS topics are named buses in which ROS nodes exchange messages. Topics can anonymously publish and subscribe, which means that the production of messages is decoupled from the consumption. The ROS nodes are not interested in knowing which node is publishing the topic or subscribing topics; they only look for the topic name and whether the message types of the publisher and subscriber are matching.
The communication using topics are unidirectional. If we want to implement a request/response, such as communication, we have to switch to ROS services.
The ROS nodes communicate with topics using TCP/IP-based transport known as TCPROS. This method is the default transport method used in ROS. Another type of communication is UDPROS, which has low-latency, loose transport, and is only suited for teleoperations.
The ROS topic tool can be used to get information about ROS topics. Here is the syntax of this command:
$ rostopic bw /topic
: This command will display the bandwidth used by the given topic.$ rostopic echo /topic
: This command will print the content of the given topic in a human readable format. Users can use the "-p" option to print data in a csv format.$ rostopic find /message_type
: This command will find topics using the given message type.$ rostopic hz /topic
: This command will display the publishing rate of the given topic.
$ rostopic info /topic
: This command will print information about an active topic.$ rostopic list
: This command will list all active topics in the ROS system.$ rostopic pub /topic message_type args
: This command can be used to publish a value to a topic with a message type.$ rostopic type /topic
: This will display the message type of the given topic.When we need a request/response kind of communication in ROS, we have to use the ROS services. ROS topics can't implement natively such kind of communication because it is unidirectional. The ROS services are mainly used in a distributed system.
The ROS services are defined using a pair of messages. We have to define a request datatype and a response datatype in a srv
file. The srv
files are kept in a srv
folder inside a package.
In ROS services, one node acts as a ROS server in which the service client can request the service from the server. If the server completes the service routine, it will send the results to the service client. For example, consider a node able to provide the sum of two numbers received in input, implementing this functionality through a ROS service. The other nodes of our system might request the sum of two numbers via this service. Differently, topics are used to stream continuous data flow.
The ROS service definition can be accessed by the following method; for example, if my_package/srv/Image.srv
can be accessed by my_package/Image
.
In ROS services also, there is an MD5 checksum
that checks in the nodes. If the sum is equal, then only the server responds to the client.
There are two ROS tools to get information about the ROS service. The first tool is rossrv
, which is similar to rosmsg
, and is used to get information about service types. The next command is rosservice
, which is used to list and query about the running ROS services.
The following explain how to use the rosservice
tool to get information about the running services:
$ rosservice call /service args
: This tool will call the service using the given arguments$ rosservice find service_type
: This command will find services in the given service type$ rosservice info /services
: This will print information about the given service$ rosservice list
: This command will list the active services running on the system$ rosservice type /service
: This command will print the service type of a given service$ rosservice uri /service
: This tool will print the service ROSRPC URIA bag file in ROS is for storing ROS message data from topics and services. The .bag
extension is used to represent a bag file.
Bag files are created using the rosbag
command, which will subscribe one or more topics and store the message's data in a file as it's received. This file can play the same topics as they are recorded from or it can remap the existing topics too.
The main application of rosbag
is data logging. The robot data can be logged and can visualize and process offline.
The rosbag
command is used to work with rosbag
files. Here are the commands to record and playback a bag file:
$ rosbag record [topic_1] [topic_2] -o [bag_name]
: This command will record the given topics into a bag file that is given in the command. We can also record all topics using the -a
argument.$ rosbag play [bag_name]
: This will playback the existing bag file.Further details about this command can be found at: http://wiki.ros.org/rosbag/Commandline
There is a GUI tool to handle the record and playback of bag files called rqt_bag
. To learn more about rqt_bag
, go to: http://wiki.ros.org/rqt_bag.
The ROS Master is much like a DNS server, associating unique names and IDs to ROS elements active in our system. When any node starts in the ROS system, it will start looking for the ROS Master and register the name of the node in it. So, the ROS Master has the details of all the nodes currently running on the ROS system. When any details of the nodes change, it will generate a callback and update with the latest details. These node details are useful for connecting with each node.
When a node starts publishing a topic, the node will give the details of the topic, such as name and data type, to the ROS Master. The ROS Master will check whether any other nodes are subscribed to the same topic. If any nodes are subscribed to the same topic, the ROS Master will share the node details of the publisher to the subscriber node. After getting the node details, these two nodes will interconnect using the TCPROS protocol, which is based on TCP/IP sockets. After connecting to the two nodes, the ROS Master has no role in controlling them. We might be able to stop either the publisher node or the subscriber node according to our requirement. If we stop any nodes, it will check with the ROS Master once again. This same method is used for the ROS services.
The nodes are written using the ROS client libraries, such as roscpp
and rospy
. These clients interact with the ROS Master using XML Remote Procedure Call (XMLRPC)-based APIs, which act as the backend of the ROS system APIs.
The ROS_MASTER_URI
environment variable contains the IP and port of the ROS Master. Using this variable, ROS nodes can locate the ROS Master. If this variable is wrong, the communication between nodes will not take place. When we use ROS in a single system, we can use the IP of a localhost or the name localhost
itself. But in a distributed network, in which computation is on different physical computers, we should define ROS_MASTER_URI
properly; only then will the remote nodes be able find each other and communicate with each other. We need only one Master in a distributed system, and it should run on a computer in which all other computers can ping it properly to ensure that remote ROS nodes can access the Master.
The following diagram shows an illustration of how the ROS Master interacts with a publishing and subscribing node, with the publisher node publishing a string type topic with a Hello World
message and the subscriber node subscribing to this topic:
Figure 8: Communication between the ROS Master and Hello World publisher and subscriber
When the publisher node starts publishing the Hello World
message in a particular topic, the ROS Master gets the details of the topic and details of the node. It will search whether any node is subscribing to the same topic. If there are no nodes subscribing to the same topic at that time, both nodes remain unconnected. If the publisher and subscriber nodes run at the same time, the ROS Master exchanges the details of the publisher to the subscriber and they will connect and can exchange data through ROS messages.
When programming a robot, we might have to define robot parameters, such as robot controller gains P, I, and D. When the number of parameters increases, we might need to store them as files. In some situations, these parameters have to share between two or more programs too. In this case, ROS provides a parameter server, which is a shared server in which all ROS nodes can access parameters from this server. A node can read, write, modify, and delete parameter values from the parameter server.
We can store these parameters in a file and load them into the server. The server can store a wide variety of data types and can even store dictionaries. The programmer can also set the scope of the parameter, that is, whether it can be accessed by only this node or all the nodes.
The parameter server supports the following XMLRPC datatypes:
We can also store dictionaries on the parameter server. If the number of parameters is high, we can use a YAML file to save them. Here is an example of the YAML file parameter definitions:
/camera/name : 'nikon' #string type /camera/fps : 30 #integer /camera/exposure : 1.2 #float /camera/active : true #boolean
The rosparam
tool is used to get and set the ROS parameter from the command line. The following are the commands to work with ROS parameters:
$ rosparam set [parameter_name] [value]
: This command will set a value in the given parameter$ rosparam get [parameter_name]
: This command will retrieve a value from the given parameter
$ rosparam load [YAML file]
: The ROS parameters can be saved into a YAML file and it can load to the parameter server using this command$ rosparam dump [YAML file]
: This command will dump the existing ROS parameters to a YAML file$ rosparam delete [parameter_name]
: This command will delete the given parameter$ rosparam list
: This command will list existing parameter namesThe parameters can be changed dynamically during the execution of the node that uses these parameters, using the dyamic_reconfigure
package (http://wiki.ros.org/dynamic_reconfigure).
These are ROS resources that enable a new community for ROS to exchange software and knowledge. The various resources in these communities are as follows:
Before getting started with ROS and trying the code in this book, the following prerequisites should be met:
ros-kinetic-desktop-full
package from the repository list.Before running any ROS nodes, we should start the ROS Master and the ROS parameter server. We can start the ROS Master and the ROS parameter server by using a single command called roscore
, which will start the following programs:
rosout
logging nodesThe rosout
node will collect log messages from other ROS nodes and store them in a log file, and will also re-broadcast the collected log message to another topic. The /rosout
topic is published by ROS nodes by using ROS client libraries such as roscpp
and rospy
, and this topic is subscribed by the rosout
node which rebroadcasts the message in another topic called /rosout_agg
. This topic has an aggregate stream of log messages. The roscore
command is a prerequisite before running any ROS node. The following screenshot shows the messages printing when we run the roscore
command in a Terminal.
The following is a command to run roscore
on a Linux Terminal:
$ roscore
Figure 9: Terminal messages while running the roscore
command
The following are explanations of each section when executing roscore
on the Terminal:
~/.ros/log
folder for collecting logs from ROS nodes. This file can be used for debugging purposes.roscore.xml
. When a launch file starts, it automatically starts the rosmaster
and the ROS parameter server. The roslaunch
command is a Python script, which can start rosmaster
and the ROS parameter server whenever it tries to execute a launch file. This section shows the address of the ROS parameter server within the port.rosdistro
and rosversion
displayed on the Terminal. These parameters are displayed when it executes roscore.xml
. We look at roscore.xml
and its details further in the next section.rosmaster
node is started using ROS_MASTER_URI
, which we defined earlier as an environment variable.rosout
node is started, which will start subscribing the /rosout
topic and rebroadcasting into /rosout_agg
.
The following is the content of roscore.xml
:
<launch> <group ns="/"> <param name="rosversion" command="rosversion roslaunch" /> <param name="rosdistro" command="rosversion -d" /> <node pkg="rosout" type="rosout" name="rosout" respawn="true"/> </group> </launch>
When the roscore
command is executed, initially, the command checks the command-line argument for a new port number for the rosmaster
. If it gets the port number, it will start listening to the new port number; otherwise, it will use the default port. This port number and the roscore.xml
launch file will pass to the roslaunch
system. The roslaunch
system is implemented in a Python module; it will parse the port number and launch the roscore.xml
file.
In the roscore.xml
file, we can see the ROS parameters and nodes are encapsulated in a group XML tag with a /
namespace. The group XML tag indicates that all the nodes inside this tag have the same settings.
The two parameters called rosversion
and rosdistro
store the output of the rosversion
roslaunch
and rosversion
-d
commands using the command
tag, which is a part of the ROS param
tag. The command
tag will execute the command mentioned on it and store the output of the command in these two parameters.
The rosmaster
and parameter server are executed inside roslaunch
modules by using the ROS_MASTER_URI
address. This is happening inside the roslaunch
Python module. The ROS_MASTER_URI
is a combination of the IP address and port in which rosmaster
is going to listen. The port number can be changed according to the given port number in the roscore
command.
Let's check the ROS topics and ROS parameters created after running roscore
. The following command will list the active topics on the Terminal:
$ rostopic list
The list of topics is as follows, as per our discussion on the rosout
node subscribe /rosout
topic. This has all the log messages from the ROS nodes and /rosout_agg
will rebroadcast the log messages:
/rosout/rosout_agg
The following command lists the parameters available when running roscore
. The following is the command to list the active ROS parameter:
$ rosparam list
The parameters are mentioned here; they have the ROS distribution name, version, address of the roslaunch
server and run_id
, where run_id
is a unique ID associated with a particular run of roscore
:
/rosdistro/roslaunch/uris/host_robot_virtualbox__51189/rosversion/run_id
The list of the ROS service generated during the running roscore
can be checked using the following command:
$ rosservice list
The list of services running is as follows:
/rosout/get_loggers/rosout/set_logger_level
These ROS services are generated for each ROS node for setting the logging levels.
ROS is now a trending software framework among roboticists. Gaining knowledge in ROS is essential in the upcoming years if you are planning to build your career as a robotics engineer. In this chapter, we have gone through the basics of ROS, mainly to refresh the concepts if you have already learned ROS. We discussed the necessity of learning ROS and how it excels among the current robotics software platforms. We went through the basic concepts, such as the ROS Master, the parameter server, and roscore
, and looked at the explanation of the working of roscore
. In the next chapter, we will introduce the ROS package management, discussing some practical examples of the ROS communication system.
Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.
If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.
Please Note: Packt eBooks are non-returnable and non-refundable.
Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:
If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:
Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.
You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.
Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.
When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.
For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.