Community Tip - When posting, your subject should be specific and summarize your question. Here are some additional tips on asking a great question. X
In the Power Grid application, there are three Generator Things and three Consumers. Each Thing has a number of Properties based on its location, wattage, and its power lines provided with power. In this design, when a generator goes offline, comes back online, or one of the other power grid aspects are updated/created/deleted, the Property values are updated. The GeneratorThing class extends the VirtualThing class and based on the Generator entities in the Composer. After extending VirtualThing, there are a number of steps necessary to get going. For the GeneratorThing 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 GeneratorThing takes in the type of electricity generator, 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 GeneratorThing(String type, 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();
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.
@ThingworxPropertyDefinition(name="GeneratorType", description="Type of generator", baseType="STRING", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="Wattage", description="Wattage produced", baseType="NUMBER", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="Amps", description="Amps", baseType="NUMBER", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="Voltage", description="Voltage", baseType="NUMBER", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="UpTime", description="Number of minutes since the generator connected online", baseType="NUMBER", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="Status", description="Status of the generator", baseType="NUMBER", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="Location", description="Location of the generator", baseType="LOCATION", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="LastSync", description="The last time we performed a sync", baseType="DATETIME", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="ConnectedSince", description="Time where we last performed a successful connection", baseType="DATETIME", aspects={"isReadOnly:false"}),@ThingworxPropertyDefinition(name="TransmissionLines", description="An infotable of power lines", baseType="INFOTABLE", 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 generator properties using our getter methods:
//Get the current values from the ThingWorx Composer
wattage = getGeneratorWattage();
voltage = getGeneratorVoltage();
location = getLocation();
As with Properties, Events can be created using annotations or code as shown in SimpleThing.java. Here we create the GeneratorOffline event that is in the Generator instances.
@ThingworxEventDefinitions(events = { @ThingworxEventDefinition(name="GeneratorOffline", description="The event of a generator going offline", dataShape="GeneratorShape", isInvocable=true, isPropertyEvent=false) })
//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);
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:
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.
@ThingworxServiceDefinition(name="OhmsLawCalculator", description="Get the watts/power (W) based on input voltage (V) and current (I)") @ThingworxServiceResult(name=CommonPropertyNames.PROP_RESULT, description="Result", baseType="NUMBER")
//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; }
Data Shapes must be created using code as seen in GeneratorThing.java as shown below:
// Data Shape definition that is used by the generating going offline event
FieldDefinitionCollection fields = new FieldDefinitionCollection();fields.addFieldDefinition(new FieldDefinition(TYPE_FIELD, BaseTypes.STRING));fields.addFieldDefinition(new FieldDefinition(WATTAGE_FIELD, BaseTypes.STRING));fields.addFieldDefinition(new FieldDefinition(AMPS_FIELD, BaseTypes.NUMBER));fields.addFieldDefinition(new FieldDefinition(VOLTAGE_FIELD, BaseTypes.NUMBER));fields.addFieldDefinition(new FieldDefinition(UP_TIME_FIELD, BaseTypes.NUMBER));fields.addFieldDefinition(new FieldDefinition(STATUS_FIELD, BaseTypes.NUMBER));fields.addFieldDefinition(new FieldDefinition(LOCATION_FIELD, BaseTypes.LOCATION));fields.addFieldDefinition(new FieldDefinition(LAST_SYNC_FIELD, BaseTypes.DATETIME));fields.addFieldDefinition(new FieldDefinition(CONNECTED_SINCE_FIELD, BaseTypes.DATETIME));fields.addFieldDefinition(new FieldDefinition(POWER_LINES_FIELD, BaseTypes.INFOTABLE));defineDataShapeDefinition("GeneratorShape", 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.
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 PowerGrid class 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); }
The scanDevice method in GeneratorThing.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 (used within the Delivery Truck Example), 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); }
Click here to view Part 4 of this guide.