Creating a Global Positioning Sensor

Table of Contents
  1. Introduction
  2. JAUS Global Pose Sensor Description
  3. GPOS Demo Application
  4. GposClientDemo
  5. Simple Demo Walkthrough

Introduction

The purpose of this tutorial is to demonstrate how to use the OpenJAUS SDK in the development of a JAUS Global Position Sensor (GPOS). Additionally a client of that sensor will be developed to demonstrate the behavior and capabilities of the GPOS service.

This tutorial can also be used as a starting point for implementing other standard senor services (VelocityStateSensor, VisualSensor, etc) or for implementing any services that inherit from the Access Control service.

When combined with the Primitive Driver Tutorial, you should be able to get started with about 95% of all JAUS services.


JAUS Global Position Sensor (GPOS)

The GPOS is a very common service in JAUS systems. Its purpose is to determine the global position and orientation of the platform and provide any clients that needs it, such as other services on the platform or various controllers, like an Operator Control Unit (OCU). The GPOS does this in the global coordinate system using latitude, longitude, and elevation.

The message set of the GlobalPoseSensor service is defined in the JAUS Mobility Service Set (AS6009) and is outlined below:

As can be seen above, the GPOS is a relatively simple service. In terms of its state machine, the GPOS service is also simple. It inherits from the Access Control service which is part of the JAUS Core Service Set (AS5710a).


GposDemo

The GposDemo is a simple implementation of the GPOS Service. It consists of two pieces in a single *.cpp file.

GposDemo.cpp source code


GposComponent Class

Services are implemented through the use of components. A component is a software element in a JAUS system which either provides or uses one or more services. More details on Components can be found in AS5710a. We implement our GposComponent by extending the skeleton component in openjaus::mobility::GlobalPoseSensor.

class GposComponent : public openjaus::mobility::GlobalPoseSensor
{
public:
	GposComponent() :
		GlobalPoseSensor(),
		reportGlobalPose(),
		reportGeomagneticProperty()
	{
		name = "GposDemo";
		this->reportGeomagneticProperty.setMagneticVariation_rad(1.0);

		this->reportGlobalPose.setLatitude_deg(34.5);
		this->reportGlobalPose.setLongitude_deg(-80.04);
		this->reportGlobalPose.setAltitude_m(100);
		this->reportGlobalPose.setRoll_rad(0.5);
		this->reportGlobalPose.setPitch_rad(-0.1);
		this->reportGlobalPose.setYaw_rad(0.1);

		publish(mobility::ReportGlobalPose::ID);
		publish(mobility::ReportGeomagneticProperty::ID);
	}

Most of the code above is very straightforward. First we set the name of the component, and then the fields of the ReportGeomagneticProperty and ReportGlobalPose messages are initialized. In a real-world GPOS component a thread or some other run-time mechanism would be created to populate the message fields with the latest values from the GPS sensor or other positioning system.

Note highlighted lines, #42 and #43, where the bool publish(messageId) function is used. This function registers the given message IDs with the underlying Events service. This allows the Event service to respond properly to Create Event requests for the ReportGlobalPose and ReportGeomagneticProperty messages.

The next part of the GposComponent is implementation of the virtual functions from the GlobalPoseSensor base class. Message callbacks are implemented as get where the query message is passed in as a parameter. This allows the component to respond appropriately.

The JAUS standards define state and guard conditions that are automatically handled by the OpenJAUS library so the message callbacks will not be executed unless it is appropriate to do so. The reader is encouraged to read the JAUS standard documents for a better understanding of state and guard conditions. Here is a description of each implemented method:

  • getReportGlobalPose(mobility::QueryGlobalPose *queryGlobalPose)
    • Executed when a QueryGlobalPose message is received.
    • In a real-world application the values of the ReportGlobalPose message would come from some other thread of run-time process that queries the actual global position of the platform. One example would be a thread that queries a GPS sensor.
  • getReportGeomagneticProperty(mobility::QueryGeomagneticProperty *queryGeomagneticProperty)
    • Executed when the QueryGeomagneticProperty message is received.
    • Again, in a real-world application these values would come from some other thread or run-time process.
  • updateGlobalPose(mobility::SetGlobalPose *setGlobalPose)
    • Executed when a SetGlobalPose message is received. The callback will only be executed if the GPOS is Controlled as defined in the Global Pose Sensor state machine.
    • In a real-world application the values of the SetGlobalPose message would be used to initialize or reset the global position of the platform. This may or may not be possible depending on how your platform is determining its global position and orientation.
  • updateGeomagneticProperty(mobility::SetGeomagneticProperty *setGeomagneticProperty)
    • Executed when a SetGeomagneticProperty message is received. The callback will only be executed if the GPOS is Controlled as defined in the Global Pose Sensor state machine.
    • In a real-world application the values of the SetGeomagneticProperty message would be used to initialize or reset the geomagnetic property of the platform. This may or may not be possible depending on if how your platform is determining this value.
  • getReportGlobalPose()
    • Accessor method so that our demo application can print the values of the message’s field. This may not be necessary in your real-world application.
  • changeGlobalPose(mobility::SetGlobalPose* setGlobalPose)
    • Helper method to update the ReportGlobalPose message from the SetGlobalPose message. This is only necessary for our demo application and wouldn’t be needed in your real-world application

Main Application

The main application is a simple text-based application that allows you to interact with the GposComponent and simulate different behaviors.

We have designed the main application to highlight a few critical or interesting methods which allow you to:

  • Dismiss the controlling client
  • Notify subscribers of on-change events.

Dismissing the Controlling Client

As explained above, the GPOS service must be Controlled before the SetGlobalPose or SetGeomagneticProperty messages will have an effect. Usually, transitions between the Controlled and Not Controlled states are initiated from an Access Control client. However, sometimes it may be necessary for a service to dismiss the controller due to some internal event. A service can do this by calling the dismissController() method. When this is done the service will send a RejectControl message to the controlling client (if necessary) with the CONTROL_RELEASED response code.

// Dismiss the controlling client.
// The controlling client can be dismissed internally if necessary.
std::cout << "Dismissing controller" << std::endl;
component.dismissController();

Notifying On-Change Event Subscribers

If you want your service to support on-change events then you must alert the Event service when a change has occurred. This is done by calling the notifyChanged(unit16 messageId) method. In our example, we support on-change events for the ReportGlobalPose message so we call the notifyChanged method using the ReportGlobalPose message ID. When this method is called the Event service will send out a ReportGlobalPose to all the clients that subscribed to that on-change event.

// Notify the Event service that the ReportGlobalPose message has
// changed. This will cause a ReportGlobalPose message to be sent
// to all the on-change event subscribers.
std::cout << "Trigger On-Changed Event for Report Global Pose" << std::endl;
component.notifyChanged(mobility::ReportGlobalPose::ID);

GposClientDemo.cpp source code

The GposClientDemo is designed to give an example of how to build a component which would be a client to the GPOS component. This simple example shows user how to do common activities within the OpenJAUS framework such as:

  • Finding a GPOS service by its uniform resource identifier (URI) (Discovery service).
  • Creating and stopping periodic and on-change events (Events service).
  • Taking and releasing control of the GPOS service (Access Control service).
  • Querying and setting the global pose (Global Pose Sensor service).

Finding a Service

The Discovery service automatically exchanges system information with other JAUS components and maintains a SystemTree of components and services. Finding a particular service on the System Tree is easily done using the lookupService(string uri) method of the SystemTree class. This returns a vector of JausAddress objects where each JausAddress is for a matching component that implements the GPOS service. This method can be used to find any service by providing the correct URI.

gposList = component.getSystemTree()->lookupService(mobility::GlobalPoseSensor::GlobalPoseSensorUri());
std::cout << "GPOS Services (" << gposList.size() << "):\n";
for(size_t i = 0; i < gposList.size(); i++)
{
	std::cout << "\t" << gposList.at(i).toString() << std::endl;
}

Creating and Stopping Periodic Events

Creating a periodic event is something done very often in the JAUS architecture. This allows a component to create a standing query to another service’s information at a particular update rate. Creation and management of the periodic event is handled by the Event service which is implemented in the OpenJAUS library. Creation is done using the subscribePeriodic(address, message, rateHz) function as shown below. This returns a unique Event ID which is used for further interaction with the Event service such as unsubscribing to an event using the unsubscribe(id) method.

mobility::QueryGlobalPose *query = new mobility::QueryGlobalPose();
query->setQueryPresenceVector(mobility::ReportGlobalPose::PV_ALL_FIELDS);
						
// Magic Number [10]: 10 is the requested rate (in Hz) of the periodic event
subscriptionId = component.subscribePeriodic(gposList.at(0), query, 10.0);
std::cout << "Created Periodic Event: " << subscriptionId << std::endl;
if(component.unsubscribe(subscriptionId))
{
	subscriptionId = 0;
}

Creating and Stopping On-Change Events

Another type of event that can be created are on-change events. Unlike periodic events, on-change events only send updates when there has been a change in the system. This reduces the number of redundant messages received from the client; but care should be taken when using this type of event since update messages may be lost, depending on the communication channel being used (for example, UDP).

If you are using the OpenJAUS SDK to create the server side of the service, ensure that you are correctly notifying subscribers of the changes as described in the Notifying On-Change Event Subscribers section above. If the server side of the service does not correctly notify its subscribers then you will never receive an on-change event update.

Create on-change events by using the subscribeOnChange (address, message) method as shown below. Just like periodic events, you will get a unique Event ID which can be used to manage the created event. Stopping an on-change event is also done using the unsubscribe(id) method.

mobility::QueryGlobalPose *query = new mobility::QueryGlobalPose();
query->setQueryPresenceVector(mobility::ReportGlobalPose::PV_ALL_FIELDS);
subscriptionId3 = component.subscribeOnChange(gposList.at(0), query);
std::cout << "Created On-Change Event: " << subscriptionId3 << std::endl;

Requesting and Releasing Control of a Service

If a service inherits from Access Control then usually you will need to have control of the service before you can do anything useful. In order to control a service you must control the entire component it belongs to.

Requesting control of a component is done through the use of the requestControl method. When you call this method you register a callback function that will be executed when a control response message (ConfirmControl or RejectControl) is received. In your callback, you should ensure that you actually did get control of the component since control may not be available, or the component may already have a controller and reject your request.

component.requestControl(gposList.at(0), processControlResponse);
void processControlResponse(const model::ControlResponse& response)
{
	std::cout << "Received Control Request Response from: " << response.getAddress() << std::endl;
	std::cout << "Response code: " << response.getResponseType() << std::endl;
}

Releasing control of component is done using the releaseControl method. Unlike the requestControl method you do not register a callback; but when a control response message is returned the callback you originally registered will be executed. Again, it is a good idea to check the type of the response to ensure control was actually released.

try
{
	component.releaseControl(gposList.at(0));
	std::cout << "Sent Release Control to " << gposList.at(0) << std::endl;
}
catch(system::Exception &e)
{
	std::cout << e.toString() << std::endl;
}

Sending a Message

Sending a message is very simple. Create the message to be sent, populate it with the required data and send the message using the sendMessage function. Remember to set the destination address and presence vector (if necessary) message fields before you send the message otherwise you may experience problems. The source address will automatically be set for you.

mobility::QueryGlobalPose *query = new mobility::QueryGlobalPose();
query->setQueryPresenceVector(mobility::ReportGlobalPose::PV_ALL_FIELDS);
sendMessage(component, gposList, query);
void sendMessage(core::Base& component, const std::vector<transport::address>& gposList, model::Message* message)
{
	if (gposList.size() > 0)
	{
		message->setDestination(gposList.at(0));
		component.sendMessage(message);
	}
	else
	{
		std::cout << "No known Global Pose Sensors!. You need to find one first." << std::endl;
	}
}

Simple Demo Walkthrough

We provide a simple walkthrough to highlight the sequence of events necessary to set the global pose of the Global Pose Sensor.

  1. Start the GposDemo and GposClientDemo applications.
    • Note that the default networking configuration components allows both demo applications to run on the same machine and they will discover each other. If you wish to run components across a network on separate machines, then you will have to configure them differently using an application configuration (.conf) file.
  2. Switch to the GposClientDemo screen.
  3. Press ‘t’ to show the System Tree.
    • You should see both the GposDemo and GposClientDemo components listed.
  4. Press ‘2’ to query the global pose of the GposDemo component.
    • Notice that you can’t query the GPOS. You need to find it in the System Tree first.
  5. Press ‘1’ to find a GPOS.
    • The component address of the GPOS service is listed.
  6. Press ‘2’ to query the global pose.
    • Take note of the global pose values.
  7. Press ‘8’ to set the global pose.
    • Check the values by querying the global pose again. The values will not have changed. The GPOS needs to be controlled before it will accept SetGlobalPose messages.
  8. Press ‘6’ to take control of the GPOS.
  9. Press ‘8’ to set the global pose.
    • Querying the global pose again shows that this time the values have changed.

Leave a Reply

Your email address will not be published. Required fields are marked *