cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Want the oppurtunity to discuss enhancements to PTC products? Join a working group! X

Java SDK Tutorial Part 4

No ratings

 

Step 7: Delivery Truck Model 

 

In the Delivery Truck application, there are three Delivery Truck Things. Each Thing has a number of Properties based on its location, speed, and its deliveries carried out. In this design, when a delivery is made or the truck is no longer moving, the property values are updated. The DeliveryTruckThing class extends the VirtualThing class and based on the DeliveryTruck Entities in the Composer. After extending VirtualThing, there are a number of steps necessary to get going. For the DeliveryTruckThing and SimpleThing classes, there are a number of methods for creating Properties, Events, Services, and Data Shapes for ease of use.

 

The constructor for the DeliveryTruckThing takes in the name of the Thing, the description of the Thing, and the ConnectedThingClient instance used to make the connection. It then sends these values to the VirtualThing constructor as shown below.

 

public DeliveryTruckThing(String name, String description, ConnectedThingClient client) {
    super(name, description, client);
    ...

 

We use the initializeFromAnnotations method to initialize all of the annotations that we will create in this class. This is done as follows and a necessary call for VirtualThings in the constructor:

 

initializeFromAnnotations();

 

Create Properties

 

You can create Properties in two ways. Using annotations is the recommended method, but there are times in which programmatically creating Properties is the best option. For example, constructing dynamic features or allowing inline functionality would call for the coding style of Property creation. The following shows the Properties that correlate to those in the DeliveryTruck Entities in the Composer. To do this within the code, you would use a PropertyDefinition instance as shown in the SimpleThing.java property1 creation.

 

With Annotation

@SuppressWarnings("serial")
@ThingworxPropertyDefinitions(properties = {    
    @ThingworxPropertyDefinition(name="Driver", description="The name of the driver", baseType="STRING", aspects={"isReadOnly:false"}),
    @ThingworxPropertyDefinition(name="DeliveriesLeft", description="The number of deliveries left", baseType="NUMBER", aspects={"isReadOnly:false"}),
    @ThingworxPropertyDefinition(name="Speed", description="The speed of the truck", baseType="NUMBER", aspects={"isReadOnly:false"}),
    @ThingworxPropertyDefinition(name="Location", description="The location of the truck", baseType="LOCATION", aspects={"isReadOnly:false"}),
    @ThingworxPropertyDefinition(name="TotalDeliveries", description="The number of deliveries", baseType="NUMBER", aspects={"isReadOnly:false"}),
    @ThingworxPropertyDefinition(name="DeliveriesMade", description="The number of deliveries made", baseType="NUMBER", aspects={"isReadOnly:false"}),
})

 

Without Annotation

//Create the property definition with name, description, and baseType
PropertyDefinition property1 = new PropertyDefinition(property, "Description for Property1", BaseTypes.BOOLEAN);
//Create an aspect collection to hold all of the different aspects
AspectCollection aspects = new AspectCollection();
//Add the dataChangeType aspect
aspects.put(Aspects.ASPECT_DATACHANGETYPE, new StringPrimitive(DataChangeType.NEVER.name()));
//Add the dataChangeThreshold aspect
aspects.put(Aspects.ASPECT_DATACHANGETHRESHOLD, new NumberPrimitive(0.0));
//Add the cacheTime aspect
aspects.put(Aspects.ASPECT_CACHETIME, new IntegerPrimitive(0));
//Add the isPersistent aspect
aspects.put(Aspects.ASPECT_ISPERSISTENT, new BooleanPrimitive(false));
//Add the isReadOnly aspect
aspects.put(Aspects.ASPECT_ISREADONLY, new BooleanPrimitive(false));
//Add the pushType aspect
aspects.put("pushType", new StringPrimitive(DataChangeType.NEVER.name()));
//Add the defaultValue aspect
aspects.put(Aspects.ASPECT_DEFAULTVALUE, new BooleanPrimitive(true));
//Set the aspects of the property definition
property1.setAspects(aspects);
//Add the property definition to the Virtual Thing
this.defineProperty(property1);  

 

Property values can either be set with defaults using the aspects setting. Nevertheless, setting a default value will affect the Property in the ThingWorx platform after binding. It will not set a local value in the client application. In this example, we make a request to the ThingWorx Composer for the current values of the delivery truck properties using our getter methods:

 

//Get the current values from the ThingWorx Composer
deliveriesMade = getDeliveriesMade();
deliveriesLeft = getDeliveriesLeft();
totalDeliveries = getTotalDeliveries();
driver = getDriver();
speed = getSpeed();
location = getLocation();

 

Create Event Definitions

 

As with Properties, Events can be created using annotations or code as shown in SimpleThing.java. Here we create the DeliveryStop event that is in the DeliveryTruck instances.

 

With Annotation

@ThingworxEventDefinitions(events = {
    @ThingworxEventDefinition(name="DeliveryStop", description="The event of a delivery truck stopping to deliver a package.", dataShape="DeliveryTruckShape", isInvocable=true, isPropertyEvent=false)
})

 

Without Annotation

//Create the event definition with name and description
EventDefinition event1 = new EventDefinition(event, "Description for Event1");
//Set the event data shape
event1.setDataShapeName("SimpleDataShape");
//Set remote access
event1.setLocalOnly(false);
//Add the event definition to the Virtual Thing
this.defineEvent(event1);  

 

Create Remote Services

 

With remote Services, the implementation is handled by the Java application and can be called either within the application or remotely, by the Composer while a connection is established. The GetTruckReadings Service, a dummy Service used as an example of how to create a remote Service, populates an Info Table and returns that Info Table for whoever would like to use it. You can see how it is possible to define remote Services that can later be bound to Things in the Composer. A Service is defined using @ThingworxServiceDefinition annotation and its result is defined using @ThingworxServiceResult. These annotations take various parameters among including:

 

  • Name
  • Description
  • baseType
  • Aspects

In the second line, you can see the name of the result being set by the CommonPropertyNames field to keep development consistent with creating Things in the Composer.

 

With Annotation

@ThingworxServiceDefinition(name="GetTruckReadings", description="Get Truck Readings")
@ThingworxServiceResult(name=CommonPropertyNames.PROP_RESULT, description="Result", baseType="INFOTABLE", aspects={"dataShape:DeliveryTruckShape"})

 

Without Annotation

//Create the service definition with name and description
ServiceDefinition service1 = new ServiceDefinition(service, "Description for Service1");
//Create the input parameter to string parameter 'name'
FieldDefinitionCollection fields = new FieldDefinitionCollection();
fields.addFieldDefinition(new FieldDefinition("name", BaseTypes.STRING));
service1.setParameters(fields);
//Set remote access
service1.setLocalOnly(false);
//Set return type
service1.setResultType(new FieldDefinition(CommonPropertyNames.PROP_RESULT, BaseTypes.STRING));
//Add the service definition to the Virtual Thing
this.defineService(service1);

//Service1 Definition
public String Service1(String name) throws Exception {
    String result = "Hello " + name;
    return result;
}

 

 

Create Data Shapes

 

Data Shapes must be created using code as seen in DeliveryTruckThing.java as shown below:

 

// Data Shape definition that is used by the delivery stop event
// The event only has one field, the message
FieldDefinitionCollection fields = new FieldDefinitionCollection();
fields.addFieldDefinition(new FieldDefinition(ACTIV_TIME_FIELD, BaseTypes.DATETIME));
fields.addFieldDefinition(new FieldDefinition(DRIVER_NAME_FIELD, BaseTypes.STRING));
fields.addFieldDefinition(new FieldDefinition(TRUCK_NAME_FIELD, BaseTypes.BOOLEAN));
fields.addFieldDefinition(new FieldDefinition(TOTAL_DELIVERIES_FIELD, BaseTypes.NUMBER));
fields.addFieldDefinition(new FieldDefinition(REMAIN_DELIVERIES_FIELD, BaseTypes.NUMBER));
fields.addFieldDefinition(new FieldDefinition(LOCATION_FIELD, BaseTypes.LOCATION));
defineDataShapeDefinition("DeliveryTruckShape", fields);

 

NOTE: It is possible to create a Data Shape, and then use it in a Service definition within your code as StringIndex property, StringMap Data Shape, and StringMapService Service in SimpleThing.java.

 

 

Scan Cycles

 

To complete the implementation of the VirtualThing class, we recommend you provide an override and implementation to the processScanRequest method. This method provides a universal method for all VirtualThing implementations. This method could be used or a new method could be created for this purpose. The processScanRequest method in VirtualThing.java does not have an implementation of its own. An implementation from DeliveryTruckThing.java can be seen below:

 

// The processScanRequest is called by the DeliveryTruckClient every scan cycle
@Override
public void processScanRequest() throws Exception {
    // Execute the code for this simulation every scan
    this.scanDevice();
    this.updateSubscribedProperties(1000);
    this.updateSubscribedEvents(1000);
}

 

Bound Properties in Cycle

 

The scanDevice method in DeliveryTruckThing.java performs a number of tasks from retrieving property values to firing events. To retrieve a property using binding, a request is made to the client using the name of the property. A good programming practice is to handle how these properties are accessed and set. Note that the update method for properties and events must be used after queueing an event or setting a Property value. In the example below, getter and setter methods are used for added control. The getProperty() call is used on the VirtualThing:

 

public Double getSpeed() {
    return (Double) getProperty("Speed").getValue().getValue();
}

public void setSpeed() throws Exception {
    setProperty("Speed", this.speed);
}

public Location getLocation() {
    return (Location) getProperty("Location").getValue().getValue();
}

public void setLocation() throws Exception {
    setProperty("Location", this.location);
}

 

 

Step 8: Services and Events

 

Events and Services can be very useful. Events are a good way to make a Service be asynchronous. You’re able to call a Service, let it return and then your Entity can subscribe to your Event and not keep the original Service function waiting. Events are also a good way to allow the platform to respond to data when it arrives on the edge device without it having to poll the edge device for updates. The DeliveryTruck Entities in the Composer contains a remote Event. You can find a remote Service within the SimpleThing_1 entity.

 

Fire Event

To fire an Event, create a ValueCollection instance, and load it with the necessary fields for the Data Shape of that Event. Then, send the client the request to fire the Event with the collected values, the Event, and information to find the Entity the Event belongs to as shown below in DeliveryTruckThing.java:

 

// Set the event information of the defined data shape for a truck stop event
ValueCollection payload = new ValueCollection();

// Set values to the fields
payload.put(LOCATION_FIELD, new LocationPrimitive(location));
payload.put(REMAIN_DELIVERIES_FIELD, new NumberPrimitive(deliveriesLeft));
payload.put(ACTIV_TIME_FIELD, new DatetimePrimitive(DateTime.now()));
payload.put(TOTAL_DELIVERIES_FIELD, new NumberPrimitive(totalDeliveries));
payload.put(DRIVER_NAME_FIELD, new StringPrimitive(driver));
payload.put(TRUCK_NAME_FIELD, new StringPrimitive(super.getBindingName()));

// This will trigger the 'DeliveryStop' of a remote thing 
// on the platform.
super.queueEvent("DeliveryStop", new DateTime(), payload);

 

Execute Service

 

To execute a Service, you must create a ValueCollection instance, and load it with the necessary parameters of the Service. The ValueCollection is created only when Services and Events are not defined by annotations. Afterwards, you would send the client the request to execute the Service with the parameter values, the Service name, the timeout setting in milliseconds for the Service to finish executing, and information to find the Entity the Service belongs to as shown below in SimpleThingClient.java:

 

public String callService(String name) throws Exception{
    ValueCollection payload = new ValueCollection();
    payload.put("name", new StringPrimitive("Timothy"));
    InfoTable table = handleServiceRequest(service, payload);
    return table.getFirstRow().getStringValue("name");
}
 
TIP: The code for creating the Service and Event should be in the constructor of the extended
VirtualThing (or a method called from the constructor). Also, the Service code examples will work as long as the actual Service is defined. You can see from the examples that the annotation method is much cleaner.
 
 
Click here view Part 5 of this guide.
Version history
Last update:
‎Oct 17, 2022 04:09 PM
Updated by:
Labels (1)
Contributors