Community Tip - Did you know you can set a signature that will be added to all your posts? Set it here! X
Hi All,
I am attempting to use the Java SDK to create a virtual thing that will create a new remote thing on the TWX server, and eventually have the virtual thing and remote thing automatically bound.
I've created a service on the virtual thing (below) that should allow me to do that, but I am running into problems when I try to initiate that via the remote service browser in TWX.
Here's my java code...
@ThingworxServiceDefinition(name = "Create_Remote_Thing", description = "Creates a remote thing on the server")
@ThingworxServiceResult(name = "result", description = "There is no result", baseType = "NOTHING")
public void Create_Remote_Thing(
@ThingworxServiceParameter(name = "ThingName", description = "The name of the thing that is to be created", baseType = "STRING") String name,
@ThingworxServiceParameter(name = "ThingTemplate", description = "This is the name of the template that is being inherited", baseType = "THINGTEMPLATENAME") String template,
@ThingworxServiceParameter(name = "Description", description = "The description for the thing being created", baseType = "STRING") String description) throws TimeoutException, ConnectionException, Exception {
ValueCollection params = new ValueCollection();
params.put("name", new StringPrimitive(name));
params.put("thingTemplateName", new StringPrimitive(template));
params.put("description", new StringPrimitive(description));
ClientConfigurator config = new ClientConfigurator();
ConnectedThingClient myclient = new ConnectedThingClient(config);
myclient.invokeService(ThingworxEntityTypes.Resources, "EntityServices", "CreateThing", params, 5000);
myclient.invokeService(ThingworxEntityTypes.Things, name, "EnableThing", null, 5000);
myclient.invokeService(ThingworxEntityTypes.Things, name, "RestartThing", null, 5000);
}
This service is now visible on the server, and property values are streaming when I open a connection. That said, I am getting this error when trying to remotely test the service…
Unable to Invoke Service Create_Remote_Thing on Simple1 : No open connections were available on endpoint 17
I’m not quite sure why this would happen while data is streaming, since it seems that a connection is clearly open. My full code can be found here:
https://github.com/PTCMCheli/EdgeConnector/tree/Remote_Services/src/com/ptc/edgeconnector/simulator
Also yes, I have looked extensively at the documentation here: http://support.ptc.com/cs/help/thingworx_hc/thingworx_edge/
Thanks!
Solved! Go to Solution.
While I don't have an example that is written in Java, I do have a service that I have used in the past that will allow you to create things from java.
Below is a service you can add to any Thing that you can then call from Java to create a thing from an edge device if its app key has the privileges to do so.
I usually call this service CreateThing(). Its parameters are ThingName,ThingTemplate, Tag and Description.
---------
var thingName = ThingName;
var template = ThingTemplate;
var tag = Tag;
var description = Description;
//check if thing exists
try {
var exist = Things[thingName].name;
//If Thing exists, no exception will be thrown
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" already exists");
} catch (err) {
// Thing does not exist, does the requested template exist
try {
var exist = ThingTemplates[template].name;
} catch (err){
throw new Error("Error creating " + thingName + ". Error: ThingTemplate \""+template+"\" doesn't exist");
}
//Test is see if thingName meets name rules
// Does it start with a letter
var code = thingName.charCodeAt(0);
if ( !(((code >= 65) && (code <= 90)) //lower case letters
|| ((code >= 97) && (code <= 122)) //uper case letters
|| (code == 95) //underscore
)){
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" is not a valid name for a Thing");
} else {
for(var i = 1; i <thingName.length; i++)
{
var code = thingName.charCodeAt(i);
if ( !( ((code >= 65) && (code <= 90)) // lower case letters
|| ((code >= 97) && (code <= 122)) //upper case letters
|| (code == 95) // underscore
|| (code == 46) // period
|| ((code >= 48) && (code <=57)) //numbers
)){
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" is not a valid name for a Thing");
}
}
}
// Apply a Tag if provided
var applyTag;
if( tag == undefined){
applyTag = false;
} else if( tag.length>0) {
try{
var params = {
name: tag /* STRING */
};
var exist = ModelTags["Applications"].GetVocabularyTerm(params);
} catch(err){
throw new Error("Error creating " + thingName + ". Error: \""+tag+"\" is not a term in Applications");
}
applyTag = true;
} else {
applyTag = false;
}
try {
var params = {
tags: undefined /* TAGS */,
thingTemplateName: template /* THINGTEMPLATENAME */,
description: description /* STRING */,
name: thingName /* STRING */
};
// no return
Resources["EntityServices"].CreateThing(params);
} catch(err) {
throw("Error creating " + thingName + ". Error:" + err.message);
}
Things[thingName].EnableThing();
Things[thingName].RestartThing();
///Apply the tag
if(applyTag)
{
var tags = new Array();
var newTag = {};
newTag.vocabulary = "Applications";
newTag.vocabularyTerm = tag;
tags.push(newTag);
var params = {
tags: tags /* TAGS */
};
// no return
Things[thingName].AddTags(params);
}
}
-------
You would call it from java with a function that looked like this:
/**
* Create a thing on the server if the ThingName does not already exist. Assumes that a thing
* exists on the server called Mobile_Utilities with a service called CreateThing.
* @param client a client with a connection to the server
* @param ThingName the name of the thing to create
* @param ThingTemplate the ThingTemplate to base this thing on
* @param Tag a tag to give this thing like "Applications:myApp"
* @param Description a test description of this thing
* @throws Exception
*/
public void createThing(ConnectedThingClient client, String ThingName,String ThingTemplate, String Tag , String Description) throws Exception {
ValueCollection paramValues = new ValueCollection();
paramValues.put("ThingName", new StringPrimitive(ThingName));
paramValues.put("ThingTemplate", new StringPrimitive(ThingTemplate));
paramValues.put("Tag", new StringPrimitive(Tag));
paramValues.put("Description", new StringPrimitive(Description));
client.invokeService(RelationshipTypes.ThingworxEntityTypes.Things, "Mobile_Utilities", "CreateThing", paramValues, REMOTE_TIMEOUT);
}
I have found this method to be very robust and have used it, generally in mobile applications to register phones but it should work fine in any situation where you need to create a thing to allow your device to auto bind with it.
While I don't have an example that is written in Java, I do have a service that I have used in the past that will allow you to create things from java.
Below is a service you can add to any Thing that you can then call from Java to create a thing from an edge device if its app key has the privileges to do so.
I usually call this service CreateThing(). Its parameters are ThingName,ThingTemplate, Tag and Description.
---------
var thingName = ThingName;
var template = ThingTemplate;
var tag = Tag;
var description = Description;
//check if thing exists
try {
var exist = Things[thingName].name;
//If Thing exists, no exception will be thrown
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" already exists");
} catch (err) {
// Thing does not exist, does the requested template exist
try {
var exist = ThingTemplates[template].name;
} catch (err){
throw new Error("Error creating " + thingName + ". Error: ThingTemplate \""+template+"\" doesn't exist");
}
//Test is see if thingName meets name rules
// Does it start with a letter
var code = thingName.charCodeAt(0);
if ( !(((code >= 65) && (code <= 90)) //lower case letters
|| ((code >= 97) && (code <= 122)) //uper case letters
|| (code == 95) //underscore
)){
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" is not a valid name for a Thing");
} else {
for(var i = 1; i <thingName.length; i++)
{
var code = thingName.charCodeAt(i);
if ( !( ((code >= 65) && (code <= 90)) // lower case letters
|| ((code >= 97) && (code <= 122)) //upper case letters
|| (code == 95) // underscore
|| (code == 46) // period
|| ((code >= 48) && (code <=57)) //numbers
)){
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" is not a valid name for a Thing");
}
}
}
// Apply a Tag if provided
var applyTag;
if( tag == undefined){
applyTag = false;
} else if( tag.length>0) {
try{
var params = {
name: tag /* STRING */
};
var exist = ModelTags["Applications"].GetVocabularyTerm(params);
} catch(err){
throw new Error("Error creating " + thingName + ". Error: \""+tag+"\" is not a term in Applications");
}
applyTag = true;
} else {
applyTag = false;
}
try {
var params = {
tags: undefined /* TAGS */,
thingTemplateName: template /* THINGTEMPLATENAME */,
description: description /* STRING */,
name: thingName /* STRING */
};
// no return
Resources["EntityServices"].CreateThing(params);
} catch(err) {
throw("Error creating " + thingName + ". Error:" + err.message);
}
Things[thingName].EnableThing();
Things[thingName].RestartThing();
///Apply the tag
if(applyTag)
{
var tags = new Array();
var newTag = {};
newTag.vocabulary = "Applications";
newTag.vocabularyTerm = tag;
tags.push(newTag);
var params = {
tags: tags /* TAGS */
};
// no return
Things[thingName].AddTags(params);
}
}
-------
You would call it from java with a function that looked like this:
/**
* Create a thing on the server if the ThingName does not already exist. Assumes that a thing
* exists on the server called Mobile_Utilities with a service called CreateThing.
* @param client a client with a connection to the server
* @param ThingName the name of the thing to create
* @param ThingTemplate the ThingTemplate to base this thing on
* @param Tag a tag to give this thing like "Applications:myApp"
* @param Description a test description of this thing
* @throws Exception
*/
public void createThing(ConnectedThingClient client, String ThingName,String ThingTemplate, String Tag , String Description) throws Exception {
ValueCollection paramValues = new ValueCollection();
paramValues.put("ThingName", new StringPrimitive(ThingName));
paramValues.put("ThingTemplate", new StringPrimitive(ThingTemplate));
paramValues.put("Tag", new StringPrimitive(Tag));
paramValues.put("Description", new StringPrimitive(Description));
client.invokeService(RelationshipTypes.ThingworxEntityTypes.Things, "Mobile_Utilities", "CreateThing", paramValues, REMOTE_TIMEOUT);
}
I have found this method to be very robust and have used it, generally in mobile applications to register phones but it should work fine in any situation where you need to create a thing to allow your device to auto bind with it.
Bill,
That code looks great, i'll give it a try. Thank you!
There is a slight drawback with that since the user will have to import that utility Thing. The end result of this project will be a training tool that a user would be able to connect to their thingworx server and send data to. I'd like to avoid requiring content to be important but if you think that's the only way to have things created automatically then there's no way around it.
Well, the benefits of doing it this way are that the server side code is simpler and the extra error checking you can do on the server also prevents you from creating things with bad names. If you want to automate the thing creation, you could look for the thing that contains the CreateThing() service and if its not there, upload/import an entity file containing it from your remote edge process. Also, keep in mind that the permissions required to create a thing are a potential security risk. You might want to have a one time app key you use for setup, perhaps provided on your command line and then another app key for your day to day use for reporting data.
Hi Bill,
Uploading the utility thing and importing it remote sounds like a great idea. Do you have any kind of documentation about how to do that?
I understand that there are some large security gaps here, but since it being designed to be used in an entirely academic and education setting, I think that is acceptable. I'll also looking into using multiple app-keys as that sounds like a best practice.
Btw just tested the service you put in above, works perfectly. Thanks for all of your help so far!
Hi All,
Just wanted to close this out for anyone who might reference this in the future. I've got this working by creating a Thing on the server side called "EdgeConnectorUtility" which has a service which creates things.
Create_Thing service code:
var thingName = ThingName;
var template = ThingTemplate;
var tag = Tag;
var description = Description;
//check if thing exists
try {
var exist = Things[thingName].name;
//If Thing exists, no exception will be thrown
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" already exists");
} catch (err) {
// Thing does not exist, does the requested template exist
try {
var exist = ThingTemplates[template].name;
} catch (err){
throw new Error("Error creating " + thingName + ". Error: ThingTemplate \""+template+"\" doesn't exist");
}
//Test is see if thingName meets name rules
// Does it start with a letter
var code = thingName.charCodeAt(0);
if ( !(((code >= 65) && (code <= 90)) //lower case letters
|| ((code >= 97) && (code <= 122)) //uper case letters
|| (code == 95) //underscore
)){
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" is not a valid name for a Thing");
} else {
for(var i = 1; i <thingName.length; i++)
{
var code = thingName.charCodeAt(i);
if ( !( ((code >= 65) && (code <= 90)) // lower case letters
|| ((code >= 97) && (code <= 122)) //upper case letters
|| (code == 95) // underscore
|| (code == 46) // period
|| ((code >= 48) && (code <=57)) //numbers
)){
throw new Error("Error creating " + thingName + ". Error: \""+thingName+"\" is not a valid name for a Thing");
}
}
}
// Apply a Tag if provided
var applyTag;
if( tag == undefined){
applyTag = false;
} else if( tag.length>0) {
try{
var params = {
name: tag /* STRING */
};
var exist = ModelTags["Applications"].GetVocabularyTerm(params);
} catch(err){
throw new Error("Error creating " + thingName + ". Error: \""+tag+"\" is not a term in Applications");
}
applyTag = true;
} else {
applyTag = false;
}
try {
var params = {
tags: undefined /* TAGS */,
thingTemplateName: template /* THINGTEMPLATENAME */,
description: description /* STRING */,
name: thingName /* STRING */
};
// no return
Resources["EntityServices"].CreateThing(params);
} catch(err) {
throw("Error creating " + thingName + ". Error:" + err.message);
}
Things[thingName].EnableThing();
Things[thingName].RestartThing();
///Apply the tag
if(applyTag)
{
var tags = new Array();
var newTag = {};
newTag.vocabulary = "Applications";
newTag.vocabularyTerm = tag;
tags.push(newTag);
var params = {
tags: tags /* TAGS */
};
// no return
Things[thingName].AddTags(params);
}
}
This service is then remotely called from my Java application via the SDK. This is the service definition for my virtual thing on the edge...
public void createThing(ConnectedThingClient client, String ThingName,String ThingTemplate, String Tag , String Description) throws Exception {
ValueCollection paramValues = new ValueCollection();
paramValues.put("ThingName", new StringPrimitive(ThingName));
paramValues.put("ThingTemplate", new StringPrimitive(ThingTemplate));
paramValues.put("Tag", new StringPrimitive(Tag));
paramValues.put("Description", new StringPrimitive(Description));
client.invokeService(ThingworxEntityTypes.Things, "EdgeConnectorUtility", "Create_Thing", paramValues, 5000);
}
And finally that service is called when I start my connection in my Thing client code. Here's the call here...
SimpleThing thing = new SimpleThing(thingName, "A basic virtual thing", client);
//Create a new RemoteThing. This is using the server-side service
//called "Create_Thing" in the "UtilityThing" Thing
thing.createThing(client, thingName, "RemoteThing", "", "This is a test using the virtual thing");
// Bind the VirtualThing to the client. This will tell the
// Platform that the RemoteThing 'Simple1' is now connected and
// that it is ready to receive requests.
client.bindThing(thing);
I hope this was helpful for anyone trying to set this up in the future.
Hi,
I tried doing same thing creating thing from edge but am getting exception
"Exception in thread "main" java.lang.Exception: No open connections were available on endpoint 0"
Could you please help me out what i am missing?
i have replicated above code no changes at all.