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

Community Tip - Learn all about PTC Community Badges. Engage with PTC and see how many you can earn! X

IoT Tips

Sort by:
1. Create a network and added all Entities that implement from a specific ThingShape in the network 2. Create a ThingShape mashup as below Note: Bind the Entity parameter to DynamicThingShapes_TracotrShape's service GetProperties input EntityName. Laso bind mashup RefreshRequested event to that service 3. Create a mashup named ContentShape, add Tree widget and ContainedMashp in it 4. Bind Service GetNetworkConnection's Selected Row(s) result and Selected RowsChanged event to ContainedMashup widget Note: Master can total replace ThingShape mashup. Suggest to use Master after ThingWorx 6.0
View full tip
This Blog presents a simple Java utility to validate the deployment of ThingWatcher. It is important to note that the utility used is not a real life situation, the intent was to keep it as simple as possible in order to achieve its aim: validation of the deployment. An understanding of Java IDE (such as Eclipse) is necessary in order to run the utility with relevant dependency and classpath setup. Those are beyond the scope of this posting. We will cover the following points: Pre-Requisites Using the sample utility Code walk through Validate training job creation Validate model job creation Update for ThingWorx Analytics 8.0 Pre-requisites A strict adherence to the ThingWatcher deployment guide is recommended in order to first deploy training and model microservices as well as to familiarize yourself with ThingWatcher APIs. Prior to testing ThingWatcher, both the training and model microservices should be up and running The media for ThingWatcher (including model and training micro-service) should be downloaded from PTC Software Download page . The commands to deploy the micro-services will vary depending on the platform used and are presented in the ThingWatcher deployment guide. As a reference example, on Windows the command will be similar to the following: Start Docker: Start > Program > Docker > Docker Quick Start Terminal Load model micro service tar $ docker load < "D:\PTC\MED-61147-CD-522_F000_ThingWorx-Analytics-ThingWatcher-52-2\components\ModelService\ModelService\model-service.tar"     3. Install model service: $ docker run -d -p 8080:8080 -v '/d/TWatcherStorage/model:/data/models' -v '/d/TWatcherStorage/db:/tmp/' twxml/model-service:1.0 -Dfile.storage.path=/data/models -jar maven/model-1.0.jar server maven/standalone-evaluator.yml     4. Load training micro service tar file                         $ docker load < "D:\PTC\MED-61147-CD-522_F000_ThingWorx-Analytics-ThingWatcher-52-2\components\TrainingService\TrainingService\training-service.tar"     5. Install training service                         $ docker run -d -p 8090:8080  twxml/training-service:1.0.0  -Dmodel.destination.uri=model://192.168.99.100:8080/models -jar maven/training-standalone-1.0.0-bin.jar server /maven/training-standalone-single.yml Note: the -Dmodel.destination.uri points here to the model micro-service host. To find the ip address, enter docker-machine ip on the model micro-service docker machine.     6. Validate micro-services deployment: Execute docker ps  and confirmed that both services are up, as in the following example: CONTAINER ID        IMAGE                          COMMAND                      CREATED            STATUS              PORTS NAMES 5b6a29b95611        twxml/training-service:1.0.0  "java -Dmodel.destina"  13 days ago        Up 44 minutes      8081/tcp, 0.0.0.0:8090->8080/tcp  modest_albattani 8c13c0bc910e        twxml/model-service:1.0        "java -Dfile.storage."      2 weeks ago        Up 44 minutes      0.0.0.0:8080->8080/tcp, 8081/tcp  thirsty_ptolemy   Using the sample utility Download the attachment Main.java Import Main.java into Eclipse (or IDE of choice) with the ThingWatcher dependencies added in classpath. Update the trainingBaseURI (see below) to points to the training micro-services. The utility should be ready to execute. Code walk through The code declares a thingwatcher in the following snippet: ThingWatcher thingwatcher = new ThingWatcherBuilder() .certainty(90.0) .trainingDataDuration(60) .trainingDataDurationUnit(DurationUnit.SECOND) .trainingBaseURI("http://192.168.99.100:8090/training") .getThingWatcher(); In the above code it is important to update the trainingBaseURI argument with the correct ip address and port for the training micro-service host. The code then loops 10000 times and sends a new value, which simulates the sensor data, at a simulated 100 ms interval. The value is computed as Math.sin(i) for the whole calibrating phase and most of the monitoring phase too. We artificially introduce an anomaly by sending a value of Math.incremetExact(i) between the 9000 th and 9900 th iterations. During the Monitoring phase, the code logs the value, the anomalous status and the thingwatcher state. It is advised to save the output to a file in order to review the logging once the utility has run. In Eclipse this can be done by selecting the Main.java with right mouse button > Run As… > Run Configuration > Common and tick Output File under the Standard Input and Output, and specify a location for the output file. A review of the output log file will shows that somewhere between timestamp 900000 and 990000, the isAnomalousValue is true. Note that this does not starts and ends exactly at 900000 and 990000, as ThingWatcher needs a few occurrences before reporting it as anomaly. Sample output indicating an anomalous state: [main] INFO com.thingworx.analytics.demo.Main - Value = 901700,9017.0,-9016.403802019577 [main] INFO com.thingworx.analytics.demo.Main - isAnomalousValue = true [main] INFO com.thingworx.analytics.demo.Main - ThingWatcherStat = MONITORING As part of validating the successful deployment of ThingWatcher, it is recommended to validate the correct creation of a training and model job. Validate training job creation In order to validate the successful creation of a training job, execute a GET request to the training micro service : http://192.168.99.100:8090/training (update the ip address to the one on your system) This should return a COMPLETED job whose body starts with something similar to: Validate model job creation In order to validate the successful creation of a model job, execute a GET request to http://192.168.99.100:8080/models (update the ip address to the one on your system) to see all the models that have been created. For example: Alternatively, click (or use) the URI reported in the training job output, here http://192.168.99.100:8080/models/6/pmml.xml, to see the complete model definition. The output will be similar to: When this sample test runs correctly, the ThingWatcher deployment has been validated. Update for ThingWorx Analytics 8.0 Deploying the microservices, see Video Link : 1937 Updated Java code: see Does anyone know how to use java api to achieve anomaly detection with Thingwatcher8.0? To Note: The utility provided is for testing purpose only. The code does not represent any kind of best practice and is not meant to be a perfect java coding example. It is provided as is with no guarantee.
View full tip
First of all wishing everyone a blessed 2017 So here is a little something that hopefully can be helpful for all you Thingworx developers! This is a 'Remote Monitoring Application Starter' Mainly this is created around Best Practices for Security and provides a lot of powerful Modeling and Mashup techniques. Also has some cool Dashboard techniques Everything is documented in accompanying documents also in the zip (sorry went through a few steps to get this up properly. Install instructions: Thingworx Remote Monitoring Starter Application – Installation Guide Files All files needed are in a Folder called: RemoteMonitoringStarter, this is an Export to ThingworxStorage Extensions Not included, but the application uses the GoogleWidgetsExtension (Google Map) Steps Import Google Map extension. Place RemoteMonitoringStarter folder in the ThingworxStorage exports folder. From Thingworx do an Import from ThingworxStorage – Include Data, Use Default Persistence Provider, do NOT ignore Subsystems. After the import has finished, go to Organizations and open Everyone. In the Organization remove Users from the Everyone organization unit. Go to DataTables and open PTC.RemoteMonitoring.Simulation.DT Go to Services and execute SetSimulationValues Go to the UserManagementSubsystem In the Configuration section add PTC.RemoteMonitoring.Session.TS to the Session. Note: This step may already be done. Note: Screen shots provided at the end. Account Passwords FullAdmin/FullAdmin All other users have a password of: password. NOTE: You may have to Reset your Administrator password using the FullAdmin account. I also recommend changing the passwords after installing.
View full tip
I had a better example, this is my original example ... If I find the other one I'll upload that as well.
View full tip
You can control the Tracking Indicator that is used to mark the ThingMark position. The Tracking Indicator is a green hexagon, in the screenshot below the red arrow points to it. You can control the display of this tracking indicator via the Display Tracking Indicator property of the ThingMark widget: But you can also get fancier. Here is an exmaple that shows the tracking indicator for 3 seconds when the tracking has started and then hides it automatically. To achieve such a behavior you'll have to use a bit of Javascript. We'll first create a function hideIn3Sec() in the javascript section of our view and then add it to the javascript handler of the Tracking Acquired event of the ThingMark widget. Step 1: Here is the code for copy/paste convenience: $scope.hideIn3Sec=function(){   // The $timeout function has two arguments: the function to execute (defined inline here)   // and the time in msec after which the function is invoked.   $timeout( function hide(){     // you may have to change 'thingMark-1' by the id of the ThingMark in your own experience     $scope.app.view['Home'].wdg['thingMark-1']['trackingIndicator']=false;   },   3000); } Step 2: That's it. Have fun!
View full tip
Hello, There have been some inquires about how can one use AngularJS for developing custom parts that can run in the ThingWorx environment. To address these inquires I have created a document that describes the process of integrating AngularJS with ThingWorx. The document attached comes with the source code for the examples presented throughout the document and an extension for AngularJS 1.5.8 and angular-material components. Feedback is appreciated. Thank you.
View full tip
ThingWorx Analytics Builder - Upload Data   This video walks you through how to upload data and shows the configuration settings. Please be aware that shown configuration settings page is different for version 8.1.   Updated Link for access to this video:  ThingWorx Analytics Builder: Upload Data
View full tip
One of the recurring patterns on the Axeda Platform is making requests from custom objects to other services, to be called either via Scripto, or through Expression Rules that help integrate Axeda data with your custom systems or third parties such as Salesforce.com.  Java developers would normally use a URLConnection to do this, but due to security requirements, access to the URLConnection API is sandboxed, and the HTTPBuilder API is provided instead. Below is a short example of GETting a payload from http://www.mocky.io/v2/57d02c05100000c201208cb5 to your custom object.  One of the requirements of many services is being able to pass in API keys as part of the header request.  While in this example the API key is embedded in the code, the recommended way of storing API keys on the Axeda Platform is to use the External Credential lockbox API.  This allows you to change the API keys securely without needing to change code. import groovyx.net.http.HTTPBuilder import static groovyx.net.http.ContentType.* import static groovyx.net.http.Method.* def http = new HTTPBuilder('https://www.mocky.io') http.request( GET, JSON ) {     uri.path = '/v2/57d02c05100000c201208cb5'     uri.headers.'appKey' = '7661392f-2372-4cba-a921-f1263c938090'     response.success = { resp ->         println "POST response status: ${resp.statusLine}"         logger.info "POST RESPONSE status: ${resp.statusLine}"         assert resp.statusLine.statusCode == 201     } } An example for Salesforce might look like so: import groovyx.net.http.HTTPBuilder import static groovyx.net.http.ContentType.* import static groovyx.net.http.Method.* def xml_body = """<?xml version="1.0" encoding="utf-8" ?> <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">   <env:Body>     <n1:login xmlns:n1="urn:partner.soap.sforce.com">       <n1:username>johndoe@example.com</n1:username>       <n1:password>Password+SECRETKEY</n1:password>     </n1:login>   </env:Body> </env:Envelope> """ def http = new HTTPBuilder('https://login.salesforce.com/') http.request( POST ) {     uri.path = '/services/Soap/u/35.0 '     body = xml_body     response.success = { resp ->         println "POST response status: ${resp.statusLine         logger.info "POST RESPONSE status: ${resp.statusLine}"         assert resp.statusLine.statusCode == 201     } } This request will give you a security token you can use in future calls to Salesforce APIs; you would use Groovy's native XmlSlurper/XmlParser to parse the response and get the session id to use in future requests.  You would then use this session id like in the following example to get the available REST resources: import groovyx.net.http.HTTPBuilder import static groovyx.net.http.ContentType.* import static groovyx.net.http.Method.* def http = new HTTPBuilder('https://na1.salesforce.com/') http.request( POST ) {     uri.path = '/services/data/v29.0'     uri.headers.'Authorization' = 'Bearer SESSIONID'     response.success = { resp ->         println "POST response status: ${resp.statusLine}"         logger.info "POST RESPONSE status: ${resp.statusLine}"         assert resp.statusLine.statusCode == 201     } } Further reading: HttpBuilder Wiki - https://github.com/jgritman/httpbuilder/wiki Groovy Xml Processing - http://groovy-lang.org/processing-xml.html
View full tip
The following is valid  for ThingWorx Analytics (TWA) 52.0.2 till 8.0 For release 8.3.0 and above see How to score new data in ThingWorx Analytics 8.3.x ?   Overview The main steps are as follow: Create a dataset Configure the dataset Upload data to the dataset Optimize the dataset Create filters for training and scoring data Train the model Execute scoring on existing data Upload new data to dataset Execute scoring on new data TWA models are dataset centric, which means a model created with one dataset cannot be reused with a different dataset. In order to be able to score new data, a specific feature, record purpose in the below example, is included in the dataset. This feature needs to be included from the beginning when the data is first uploaded to TWA. A filter on that feature can then be created to allow to isolate desired data. When new data comes in, they are added to the original dataset but with a specific value for the filtered feature (record purpose), which allows to discriminate and score only those new records. Process Create a dataset This example uses the beanpro demo dataset Create dataset is done through a POST on datasets REST API as below 2. Configure dataset This is done through a POST on <dataset>/configuration REST API 3.      Upload data         4.      Optimize the dataset         5.      Create filters The dataset includes a feature named record purpose created especially to differentiate between the rows to be used for training and the rows to be used for scoring. New data to be added will have record purpose set to scoringnew, which will allow to execute a scoring job limited to those filtered new rows Filter for training data: Filter for new scoring data        6.      Train the model This is done through a POST on <dataset>/prediction API        7.      Score the training data This is done through a POST on <dataset>/predictive_scores API. Note the use of the filter TrainingData created earlier. This allow to score only the rows with training as value for record purpose feature. Note: scoring could also be done without filter at this stage, in which case all the data in the dataset will be scored and not just the ones with training fore record purpose   Retrieving the scoring result show all the records in the dataset:   8.      Upload new data The newly uploaded csv file should only contains new record. This will be appended to the existing ones.   Note that the new record (it could be more than one) has a value scoringnew for the record purpose feature: This will allow to use the previously created filter ScoringNewData so that a new scoring job will only take into account this new record.   9.      Scoring new data A POST on API predictive_scores is executed however using the filter ScoringNewData. This results in only the newly added data to be scored and therefore a much quicker execution time too. Retrieving the scoring result shows only the new record:
View full tip
Overview REST stands for representational state transfer and is a software architectural style common in the World Wide Web. Anything with a RESTful interface can be communicated with using standard REST syntax. ThingWorx has such an interface built-in to make viewing and updating Thing properties as well as executing services easy to do independently of the Web UI.   How to Use REST API The ThingWorx REST API is entirely accessible via URL using the following syntax:    (Precision LMS. Getting Started With ThingWorx 5.4 (Part 1 of Introduction to ThingWorx 5.4). PTC University. https://precisionlms.ptc.com/viewer/course/en/21332822/page/21332905.)   The above example shows how to access a service called “GetBlogEntriesWithComments” found on the “ThingWorxTrainingMaintenanceBlog” Thing. Notice that even though this service gets XML formatted data, the method is type “POST” and “GET” will not work in this scenario (Further reading: https://support.ptc.com/appserver/cs/view/solution.jsp?n=CS214689&lang=en_US).   In order to be able to run REST API calls from the browser, one must allow request method switching. This can be enabled by checking the box “Allow Request Method Switch” in PlatformSubsystem (Further reading: https://support.ptc.com/appserver/cs/view/solution.jsp?n=CS224211&lang=en_US).   Access REST API from Postman Postman is a commonly used REST client which can ping servers via REST API in a manner which mimics third party software. It is free and easy-to-use, with a full tutorial located here: https://www.getpostman.com/docs/   In order to make a request, populate the URL field with a properly formatted REST API call (see previous section). Parameters will not automatically be URL-encoded, but right-clicking on a highlighted portion of the URL and selecting EncodeURIComponent encodes the section.   Next click the headers tab. Here is where the content-type, accept, and authorization are set for the REST call. Accept refers to which response format the REST call is expecting while content-type refers to the format of the request being sent to the server. Authhorization is required for accessing ThingWorx, even via REST API (see previous section for examples authenticating using an app key, but in Postman you can also use Basic Auth using a username and password)   In Postman, there is also ample opportunity to modify the request body under the Body tab. There are several options here for setting parameters. Form-data and x-www-form-urlencoded both allow for setting key value pairs easily and cleanly, and in the latter case, encoding occurs automatically (e.g. “Hello World” becomes %22Hello%20World%22). Raw request types can contain anything and Postman will not touch anything entered except to replace environment variables. Whatever is placed in the text area under raw will get sent with the request (normally XML or JSON, as specified by content-type). Finally, binary allows for sending things which cannot normally be entered into Postman, e.g. image, text, or audio files.     REST API Examples For introductory level examples, see the previous Blog document found here: https://community.thingworx.com/docs/DOC-3315   Retrieving property values from “MyThing” using GET, the default method type (notice how no “method=GET” is required here, though it would still work with that as well): http://localhost/Thingworx/Things/MyThing/Properties/   Updating “MyProperty “with the value “hello” on “MyThing” using PUT: http://localhost/Thingworx/Things/MyThing/Properties/MyProperty?method=PUT&value=hello In Postman, you can send multiple property updates at once via query body (in this case updating all of the properties, the string “Prop1” and the number “Prop2” on MyThing) § Query: http://localhost/Thingworx/Things/MyThing/Properties/* § Query Type: PUT § Query Headers: Content-Type: application/json Authorization: Basic Auth (input username and password on Authorization tab and this will auto-populate) § Body JSON: {"Prop1":"hello world","Prop2":10} Note: you can also specify multiple properties as shown, but only update one at a time in Postman by utilizing the browser syntax given above   Calling “MyService” (a service on “TestThing)” with a String input parameter (“InputString”): http://localhost/Thingworx/Things/TestThing/Services/MyService?method=post&InputString=input   It is easier to pass things like XML and JSON into services using Postman. This query calls “MyJSONService” on “MyThing” with a JSON input parameter § Query: http://localhost/Thingworx/Things/MyThing/Services/MyJSONService § Query Type: § Queries Headers: Accept should match service output (text/html for String) Content-Type: application/json or Authorization: Basic Auth (input username and password on Authorization tab and this will auto-populate) Body JSON: {"InputJSON":"{\"JSONInput\":{\"PropertyName\":\"TestingProp\",\"PropertyValue\":\"Test\"}}"} Body XML:{"xmlInput": "<xml><name>User1</name></xml"}   Viewing “BasicMashup” using AppKey authentication (so no login is required because this Application Key is set-up to login as a user who has permissions to view the Mashup): http://localhost/Thingworx/Mashups/BasicMashup?appKey=b101903d-af6f-43ae-9ad8-0e8c604141af&x-thingworx-session=true Read more here: https://support.ptc.com/appserver/cs/view/solution.jsp?n=CS227935   Downloading Log Information from “ApplicationLog” (or other log types): http://localhost/Thingworx/Logs/ApplicationLog/Services/QueryLogEntries?method=POST   In Postman, more information can be passed into some queries via query body § Query: http://localhost/Thingworx/Logs/ApplicationLog/Services/QueryLogEntries Query Type: POST Query Headers: Accept: application/octet-stream or Content-Type: application/json Authorization: Basic Auth (input username and password on Authorization tab and this will auto-populate) Body: {\"searchExpression\":\"\",\"origin\":\"\",\"instance\":\"\",\"thread\":\"\", \"startDate\":1462457344702,\"endDate\":1462543744702,\"maxItems\":100}   Downloading “MyFile.txt” from “MyRepo” FileRepository (here, “/” refers to the home folder of this FileRepository and the full path would be something like “C:\ThingworxStorage\repository\MyRepo\MyFolder\MyFile.txt”): http://localhost/Thingworx/FileRepositoryDownloader?download-repository=MyRepo&download-path=/MyFolder/MyFile.txt   Uploading files to FileRepository type Things is a bit tricky as anything uploaded must be Base64 encoded prior to making the service call. In Postman, this is the configuration to used to send a file called “HelloWorld.txt”, containing the string “Hello World!”, to a folder called “FolderInRepo” on a FileRepository named “MyRepo”:   Query: http://localhost/Thingworx/Things/MyRepo/Services/SaveBinary Query Type: POST Query Headers: Accept: application/json Content-Type: application/json Authorization: Basic Auth (input username and password on Authorization tab and this will auto-populate) Body: {"path" : "/FolderInRepo/HelloWorld.txt", "content" : "SGVsbG8gV29ybGQh"} Notice here that the content has been encoded to Base64 using a free online service. In most cases, this step can be handled by programming language code more easily and for more challenging file content   Resources and other built-in Things can be accessed in similar fashion to user-created Things. This query searches for Things with the “GenericThing” ThingTemplate implemented: http://localhost/Thingworx/Resources/SearchFunctions/Services/SearchThingsByTemplate?method=POST&thingTemplate=GenericThing   Deleting “MyThing” (try using services for this instead when possible since they are likely safer): http://localhost/Thingworx/Things/MyThing1?method=DELETE&content-type=application/JSON   Exporting all data within ThingWorx using the DataExporter functionality: http://localhost/Thingworx/DataExporter?Accept=application/octet-stream   Exporting all entities which have the Model Tag “Application.TestTerm” within ThingWorx using the Exporter functionality: http://localhost/Thingworx/Exporter?Accept=text/xml&searchTags=Applications:TestTerm
View full tip
This script will get all contacts (optionally limited to a particular organization) and check whether there is a DeviceContact associated with it.  If there is no DeviceContact (meaning it is not associated with a device), it deletes the contact. Note - It is worthwhile to test this script by commenting out the contact.delete() line first and reviewing which contacts will be deleted.  Also, this script works by finding all contacts, therefore it is not recommended to run the script repeatedly within a short period of time. Parameter: organizationName  (OPTIONAL) - Str - the name of the organization import net.sf.json.JSONObject import com.axeda.drm.sdk.device.DeviceFinder import com.axeda.drm.sdk.device.Device import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.data.CurrentDataFinder import com.axeda.drm.sdk.data.DataValue import net.sf.json.groovy.JsonSlurper import com.axeda.drm.sdk.contact.Contact import com.axeda.drm.sdk.contact.ContactFinder import com.axeda.drm.sdk.contact.Location import com.axeda.drm.sdk.contact.LocationFinder import com.axeda.drm.sdk.contact.OrganizationFinder import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.contact.Organization import com.axeda.drm.sdk.contact.DeviceContact import com.axeda.drm.sdk.contact.ContactMethodType import com.axeda.drm.sdk.contact.DeviceContactFinder import groovy.json.* import com.axeda.drm.sdk.scripto.Request import com.axeda.common.sdk.id.Identifier /** * ContactDelete.groovy * ----------------------- * * Finds all contacts, then finds the device contact for each contact. If null, deletes the contact. * * @params * organizationName (OPTIONAL) Str - limit the contact deletion to an organization * * * @author Sara Streeter <sstreeter@axeda.com> */ /** * initialize our global variables * json = the contents of our response * infoString = a stringBuilder used to collect debug information during the script * contentType = the content type we will return * scriptname = The name of this Script, used in multiple places */ def json = new groovy.json.JsonBuilder() def infoString = new StringBuilder() def contentType = "application/json" def scriptName = "ContactDelete.groovy" def root = ["result":["deleted":[]]] def timings = [:] timings.contactFinding = 0 timings.contactIterating = 0 wholestart = System.currentTimeMillis() final Context CONTEXT = Context.getSDKContext() try {       def params = Request?.parameters?.size() > 0 ? Request?.parameters : parameters       ContactFinder cfinder = new ContactFinder(CONTEXT)       def start = System.currentTimeMillis()     def organization       if (params.organizationName != null && params.organizationName != ""){         OrganizationFinder oFinder = new OrganizationFinder(CONTEXT)         oFinder.setName(params.organizationName)         organization = oFinder.find()               if (organization){             cfinder.setOrganization(organization)         }     }       List<Contact> contacts = cfinder.findAll()     timings.contactFinding += System.currentTimeMillis()-start       root.result.contactSize = contacts.size()       start = System.currentTimeMillis()     contacts.each{  contact ->           DeviceContactFinder dcfinder = new DeviceContactFinder(CONTEXT)         dcfinder.setContactId(contact.id)         def dc = dcfinder.findAll()         if (dc.size() == 0){             root.result.deleted << [                 id: contact.id.value,                 firstName: contact.firstName,                 lastName: contact.lastName,                 organization: contact.organization?.name             ]             contact.delete()  // comment out this line to check which contacts will be deleted first.         }     }     timings.contactIterating += System.currentTimeMillis()-start     } catch (Exception e) {     processException(scriptName,json,e) } finally {     timings.wholescript = System.currentTimeMillis() - wholestart     root += [timings: timings] } return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(root).toString(2)] /*     Processes the contents of an Exception and add it to the Errors collection     @param json The markup builder */ private def processException(String scriptName, JsonBuilder json, Exception e) {     // catch the exception output     def logStringWriter = new StringWriter()     e.printStackTrace(new PrintWriter(logStringWriter))     logger.error("Exception occurred in ${scriptName}: ${logStringWriter.toString()}")     /*         Construct the error response         - errorCode Will be an element from an agreed upon enum         - errorMessage The text of the exception      */     json.errors  {         error {             message     "[${scriptName}]: " + e.getMessage()             timestamp   "${System.currentTimeMillis()}"         }     }     return json }
View full tip
This is an example to show returning an uploaded file as a binary from a Groovy Script. Parameters: model_name serial_number import java.io.StringWriter import java.io.PrintWriter import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.data.* import com.axeda.drm.sdk.device.* import javax.activation.MimetypesFileTypeMap try {     Context ctx = Context.getUserContext()     ModelFinder modelFinder = new ModelFinder(ctx)     modelFinder.setName(parameters.model_name)     Model model = modelFinder.find()     DeviceFinder dfinder = new DeviceFinder(ctx)     dfinder.setModel(model)     dfinder.setSerialNumber(parameters.serial_number)     Device d = dfinder.find()     UploadedFileFinder uff = new UploadedFileFinder(ctx)     uff.device = d     def ufiles = uff.findAll()     UploadedFile ufile     if (ufiles.size() > 0) {         ufile = ufiles[0]         File f = ufile.extractFile()     def type = getType(f)     return ['Content-Type': type, 'Content': new FileInputStream(f)]      } else {     return ['Content-Type': 'text/plain', 'Content': 'No files have been uploaded'] } } catch (Exception e) {     logger.info(e.message)     StringWriter logStringWriter = new StringWriter();     PrintWriter logPrintWriter = new PrintWriter(logStringWriter)     e.printStackTrace(logPrintWriter)     logger.info(logStringWriter.toString()) } static String getType(File f) {   MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap()   return mimeTypesMap.getContentType(f); }
View full tip
Update properties on a model. Parameter: modelName (REQUIRED) - String - the name of the model to have its properties updated. import com.axeda.drm.sdk.device.DeviceProperty import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.device.DevicePropertyFinder import com.axeda.drm.sdk.device.Property import com.axeda.drm.sdk.device.PropertyType import com.axeda.common.sdk.id.Identifier Set<String> REQUIRED_PROPERTIES = [     "TestProperty0","TestProperty1","TestProperty2" ] try {       final def Context CONTEXT = Context.getSDKContext()       ModelFinder modelFinder = new ModelFinder(CONTEXT);     modelFinder.setName(parameters.modelName)    Model model = modelFinder.find();    if (model == null){ throw new Exception("No model found") }    modelProperties = findModelProperties(CONTEXT, model.id)     updateProperties(CONTEXT, model.id, modelProperties, REQUIRED_PROPERTIES)     modelProperties.properties.each{ logger.info("$it.name :$it.value") } } catch (Exception e){     logger.info e.localizedMessage } return true private DeviceProperty findModelProperties(Context context, Identifier modelID) {   def finder = new DevicePropertyFinder(context)   finder.id = modelID   finder.type = PropertyType.MODEL_TYPE   return finder.findOne() as DeviceProperty } private void updateProperties(Context context, Identifier modelID, DeviceProperty modelProperties, Set<String> requiredProperties) {   if (!modelProperties) {     modelProperties = new DeviceProperty(context)     modelProperties.id = modelID     modelProperties.type = PropertyType.MODEL_TYPE     modelProperties.properties = []   }   (requiredProperties - new HashSet<String>(modelProperties.properties.collect { it.name })).inject(modelProperties.properties) { list, propertyName -> list << new Property(0, propertyName, "") }   modelProperties.store() }
View full tip
Sends an email with an alarm name passed in by an Expression Rule. Parameters (passed in as arguments to ExecuteCustomObject in the Expression Rule): fromaddress toaddress import com.axeda.drm.sdk.contact.Email /* * ExprRuleAlarmToEmail.groovy * * Sends an email with an alarm name passed in by an Expression Rule. * * @param fromaddress - (REQ):Str email address of sender. * @param toaddress - (REQ): Str email address of recipient * * * @note Should be executed from an Expression Rule like the following: * * Type: Alarm * If: Alarm.severity > 490 && Alarm.severity < 700 * Then: ExecuteCustomObject("ExprRuleAlarmToEmail", "fake_sender@axeda.com","fake_recipient@axeda.com") * * @author Sara Streeter <sstreeter@axeda.com> */ try {   String fromaddress = parameters.fromaddress   String toaddress = parameters.toaddress   String subject = "Axeda Alarm - ${alarm.name}"   String body = "You are receiving this alarm ${alarm.name} because you are subscribed to its updates."   sendEmail(fromaddress, toaddress, subject, body) } catch (Exception e) { logger.error(e.message) }     public void sendEmail(String fromAddress,String toAddress,String subject, String body) {         try {             Email.send(fromAddress, toAddress, subject, body);         } catch (AddressException ae) {             logger.error(ae);         }     }
View full tip
This example shows how a file can be retrieved via Scripto and then displayed on a Web page. Precondition is that an asset has an uploaded file. This script assumes the file is there and that it is not extremely large (under 1 megabyte). This example uses base64 encoding to convert the file into a string. Future versions of Scripto will support other data streams so that base64 encoding will not be necessary. import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.data.UploadedFile import com.axeda.drm.sdk.data.UploadedFileFinder import com.axeda.drm.sdk.device.Device import com.axeda.drm.sdk.device.DeviceFinder // This script requires parameter "id" Context ctx = Context.create(parameters.username); def response = '' try {     DeviceFinder deviceFinder = new DeviceFinder(ctx, new Identifier(parameters.id as Integer));     Device device = deviceFinder.find();     UploadedFileFinder uff = new UploadedFileFinder(ctx)     uff.device = device     uff.hint = 'photo'     def ufiles = uff.findAll()     UploadedFile ufile     if (ufiles.size() > 0) {         ufile = ufiles[0]         File f = ufile.extractFile()         response = getBytes(f).encodeBase64(false).toString()     } } catch (Exception e) {     logger.info(e.message);     response = [             faultcode: 'Groovy Exception',             faultstring: e.message     ]; } return ['Content-Type': 'data:image/png;base64', 'Content': response]; static byte[] getBytes(File file) throws IOException {     return getBytes(new FileInputStream(file)); } static byte[] getBytes(InputStream is) throws IOException {     ByteArrayOutputStream answer = new ByteArrayOutputStream(); // reading the content of the file within a byte buffer     byte[] byteBuffer = new byte[8192];     int nbByteRead /* = 0*/;     try {         while ((nbByteRead = is.read(byteBuffer)) != -1) { // appends buffer             answer.write(byteBuffer, 0, nbByteRead);         }     } finally {         is.close()     }     return answer.toByteArray(); }
View full tip
These code snippets illustrate parsing CSV files and populating the Axeda Enterprise with data, locations and organizations.  These files are incoming to the Axeda Platform. Note:  These snippets do NOT handle null values in the CSV due to the lack of a CSV parsing library.  Workaround is to populate empty values with an empty or null signifier (such as a blank space) and test for these on the Groovy side. Code Snippets: CSV file to Data Items CSV file to Location Organization Script Name: CSV file to Data Items Description: Executed from an expression rule with file hint "datainsert", takes a CSV file and adds data based on values. Parameters: OPTIONAL - only needed for debugging modelName - (OPTIONAL) Str - name of the model serialNumber - (OPTIONAL) Str - name of the serial number import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.device.DeviceFinder import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.device.DataItemFinder import com.axeda.drm.sdk.device.DataItem import com.axeda.drm.sdk.data.DataValueEntry import java.util.regex.Pattern import groovy.json.* import com.axeda.drm.services.device.DataItemType import net.sf.json.JSONObject /** * CSVToData.groovy * ----------------------- * * Executed from an expression rule with file hint "datainsert", takes a CSV file and adds data based on values. * * @note  There must be a column with "model" and one with "serial".  The rest of the columns should be data item names with values * in the rows. DOES NOT handle null values in CSV.  Workaround is to insert blank spaces in null values and test for those on the Groovy side. * Solution would be to add a library for CSV parsing such as open csv. * * @params - only needed if NOT executed from expression rule - primarily for debugging * modelName - (OPTIONAL) Str - name of the model * serialNumber - (OPTIONAL) Str - name of the serial number * * */ /** * initialize our global variables * json = the contents of our response * infoString = a stringBuilder used to collect debug information during the script * contentType = the content type we will return * scriptname = The name of this Script, used in multiple places */ def json = new groovy.json.JsonBuilder() def infoString = new StringBuilder() def contentType = "application/json" def scriptName = "CSVToData.groovy" def root = ["result":["items":[]]] def columns = [] try {   Context CONTEXT = Context.getSDKContext()   def modelIndex   def serialIndex   // initialize Model and Device Finders   ModelFinder modelFinder = new ModelFinder(CONTEXT)   DeviceFinder deviceFinder = new DeviceFinder(CONTEXT)   // implicit object compressedFile   File file = compressedFile.getFiles()[0].extractFile() /* //begin non-expression rule code, useful for debugging     File file     modelFinder.setName(Request.parameters.modelname)               def model1 = modelFinder.find()     deviceFinder.setSerialNumber(Request.parameters.serialNumber)     deviceFinder.setModel(model1)     def d = deviceFinder.find()      UploadedFileFinder uff = new UploadedFileFinder(CONTEXT)     uff.device = d     def ufiles = uff.findAll()     UploadedFile ufile     if (ufiles.size() > 0) {         ufile = ufiles[0]         file = ufile.extractFile()     }          */ //end non-expression rule code   file.eachLine {line ->       def row = line.tokenize(',')          // set the column headings       if (columns.size() == 0){         columns = row              // find model and serial index, assumes there's a column that has "model" and "serial", otherwise take columns 0 and 1         def modelpatt = Pattern.compile(/[A-Za-z_\-]{0,}model[A-Za-z_\-]{0,}/, Pattern.CASE_INSENSITIVE)         def serialpatt = Pattern.compile(/[A-Za-z_\-]{0,}serial[A-Za-z_\-]{0,}/, Pattern.CASE_INSENSITIVE)         modelIndex = columns.findIndexOf{ it ==~ modelpatt } > -1 ? columns.findIndexOf{ it ==~ modelpatt } : 0         serialIndex = columns.findIndexOf{ it ==~ serialpatt } > -1 ? columns.findIndexOf{ it ==~ serialpatt } : 1            }       // otherwise populate data       else {                  modelFinder.setName(row.get(modelIndex))           def model = modelFinder.find()                  deviceFinder.setModel(model)           deviceFinder.setSerialNumber(row.get(serialIndex))                  def device = deviceFinder.find()                  def assetInfo = [                     "model": model.name,                     "serial": device.serialNumber,                     "data":[]                     ]                  row.eachWithIndex{ item, index ->               if (index != modelIndex && index != serialIndex){                 def dataItemName = columns[index].replace(" ","")                 DataItemFinder dif = new DataItemFinder(CONTEXT);                 dif.setDataItemName(dataItemName);                 dif.setModel(model);                 DataItem dataItem = dif.find();                              if (dataItem){                     if (item.isNumber()){                        item = Double.valueOf(item)                     }                     DataValueEntry dve = new DataValueEntry(CONTEXT, device, dataItem, item)                     dve.store()                 }                 else {                     DataItem newDataItem                     if (item.isNumber()){                         newDataItem = new DataItem(CONTEXT, model,DataItemType.ANALOG, dataItemName)                         item = Double.valueOf(item)                     }                     else {                        newDataItem = new DataItem(CONTEXT, model,DataItemType.STRING, dataItemName)                     }                    newDataItem.store()                    DataValueEntry dve = new DataValueEntry(CONTEXT, device, newDataItem, item)                     dve.store()                 }                 assetInfo.data << [                         "name": dataItemName,                         "value": item                     ]                            }               root.result.items << assetInfo           }              }   }   logger.info(JSONObject.fromObject(root).toString(2)) } catch (Exception e) {     processException(scriptName,json,e) } //return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(root).toString(2)] /*     Processes the contents of an Exception and add it to the Errors collection     @param json The markup builder */ private def processException(String scriptName, JsonBuilder json, Exception e) {     // catch the exception output     def logStringWriter = new StringWriter()     e.printStackTrace(new PrintWriter(logStringWriter))     logger.error("Exception occurred in ${scriptName}: ${logStringWriter.toString()}")     /*         Construct the error response         - errorCode Will be an element from an agreed upon enum         - errorMessage The text of the exception      */     json.errors  {         error {             message     "[${scriptName}]: " + e.getMessage()             timestamp   "${System.currentTimeMillis()}"         }     }     return json } Script Name: CSV file to Location Organization Description: Executed from an expression rule with file hint "locorginsert", takes a CSV file and adds orgs and locations based on values. Parameters: OPTIONAL - only needed for debugging modelName - (OPTIONAL) Str - name of the model serialNumber - (OPTIONAL) Str - name of the serial number import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.device.DeviceFinder import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.device.DataItemFinder import com.axeda.drm.sdk.device.DataItem import com.axeda.drm.sdk.data.DataValueEntry import java.util.regex.Pattern import groovy.json.* import com.axeda.drm.services.device.DataItemType import net.sf.json.JSONObject import com.axeda.drm.sdk.contact.Organization import com.axeda.drm.sdk.contact.Location import com.axeda.drm.sdk.contact.OrganizationFinder import com.axeda.drm.sdk.contact.LocationFinder import com.axeda.drm.sdk.data.UploadedFile import com.axeda.drm.sdk.data.UploadedFileFinder /** * CSVToLocOrg.groovy * ----------------------- * * Executed from an expression rule with file hint "locorginsert", takes a CSV file and adds orgs and locations based on values. * * @note  There must be a column with "model" and one with "serial".  The rest of the columns should be either parts of a * location or an organization.  The location parts columns should be prefixed with the org# that they correspond to. * DOES NOT handle null values in CSV.  Workaround is to insert blank spaces in null values and test for those on the Groovy side. * Solution would be to add a library for CSV parsing such as open csv. * * @params - only needed if NOT executed from expression rule - primarily for debugging * modelName - (OPTIONAL) Str - name of the model * serialNumber - (OPTIONAL) Str - name of the serial number * * * */ /** * initialize our global variables * json = the contents of our response * infoString = a stringBuilder used to collect debug information during the script * contentType = the content type we will return * scriptname = The name of this Script, used in multiple places */ def json = new groovy.json.JsonBuilder() def infoString = new StringBuilder() def contentType = "application/json" def scriptName = "CSVToLocOrg.groovy" def root = ["result":["items":[]]] def columns = [] try {   Context CONTEXT = Context.getSDKContext()   def modelIndex   def serialIndex   def locIndices = [:]   def locKeys = ["line1","line2", "address1", "address2", "city","state","zip","country", "org"]   // initialize Finders   ModelFinder modelFinder = new ModelFinder(CONTEXT)   DeviceFinder deviceFinder = new DeviceFinder(CONTEXT)   LocationFinder locationFinder = new LocationFinder(CONTEXT)   OrganizationFinder organizationFinder = new OrganizationFinder(CONTEXT)   // implicit object compressedFile   File file = compressedFile.getFiles()[0].extractFile()   /* //begin non-expression rule code, useful for debugging     File file     modelFinder.setName(Request.parameters.modelname)               def model1 = modelFinder.find()     deviceFinder.setSerialNumber(Request.parameters.serialNumber)     deviceFinder.setModel(model1)     def d = deviceFinder.find()      UploadedFileFinder uff = new UploadedFileFinder(CONTEXT)     uff.device = d     def ufiles = uff.findAll()     UploadedFile ufile     if (ufiles.size() > 0) {         ufile = ufiles[0]         file = ufile.extractFile()     }           */ //end non-expression rule code   file.eachLine {line ->       def row = line.tokenize(',')       // set the column headings       if (columns.size() == 0){         columns = row         // find model and serial index, assumes there's a column that has "model" and "serial", otherwise take columns 0 and 1         def modelpatt = Pattern.compile(/[A-Za-z_\-]{0,}model[A-Za-z_\-]{0,}/, Pattern.CASE_INSENSITIVE)         def serialpatt = Pattern.compile(/[A-Za-z_\-]{0,}serial[A-Za-z_\-]{0,}/, Pattern.CASE_INSENSITIVE)         modelIndex = columns.findIndexOf{ it ==~ modelpatt } > -1 ? columns.findIndexOf{ it ==~ modelpatt } : 0         serialIndex = columns.findIndexOf{ it ==~ serialpatt } > -1 ? columns.findIndexOf{ it ==~ serialpatt } : 1               locKeys.each{ key ->             // construct a regex for each key and create a map for finding/creating             def locPatt = Pattern.compile(/[A-Za-z0-9_\-]{0,}${key}[A-Za-z0-9_\-]{0,}/, Pattern.CASE_INSENSITIVE)             def colIndex = columns.findIndexOf{                     def match = it =~ locPatt                     if (match){                         return match?.getAt(0)                     }                 }                       if (colIndex > -1){                 locIndices[colIndex] = key             }         }       }       // otherwise populate data       else {           modelFinder.setName(row.get(modelIndex))           def model = modelFinder.find()           deviceFinder.setModel(model)           deviceFinder.setSerialNumber(row.get(serialIndex))           def device = deviceFinder.find()           def assetInfo = [                     "model": model.name,                     "serial": device.serialNumber,                     "locs":[]                     ]                   def locMap = [:]           def orgName           def locKey           def locBool = false // make sure we get some criteria           row.eachWithIndex{ item, index ->                            if (index != modelIndex && index != serialIndex && item && item != ""){                   locKey = locIndices[index]                                   if (locKey){                       locBool = true                       if (locKey == "address1"){                         locKey = "line1"                       }                       if (locKey == "address2"){                         locKey = "line2"                       }                       if (locKey == "org"){                             orgName = item                       }                       // don't execute if we've got an organization key                       else {                           // for finding                           locationFinder[locKey] = item                           // for creating (if needed)                           locMap[locKey] = item                       }                   }                               }           }                   assetInfo.org           Organization org                   if (orgName){               organizationFinder.setName(orgName)               org = organizationFinder.find()                           if (!org){                 org = new Organization(CONTEXT, orgName)                 org.store()                  }                       }                  Location loc           if (locBool){               logger.info("with bool")             loc = locationFinder.find()             logger.info(loc?.name)           }                   if (!loc){                          def line1 = locMap["line1"]                           def name = line1?.replace(" ","")?.replace(/\./,"")?.replace("_","") + "_Loc"                           def line2 = locMap["line2"]               def city = locMap["city"]               def state = locMap["state"]               def zip = locMap["zip"]               def country = locMap["country"]                           if (line1 && city){                loc = new Location(CONTEXT,name,line1,line2,city,state,zip,country)                loc.store()                                         }                           if (loc && org){                   org.addLocation(loc)                   org.store()               }                       }                   assetInfo.locs << [                    "name": loc.name,                     "line1": loc.line1,                     "line2": loc.line2,                     "city": loc.city,                     "state": loc.state,                     "zip": loc.zip,                     "country": loc.country                                      ]                    assetInfo.org = [                         "name": org.name                                       ]           root.result.items << assetInfo       }   }   logger.info(JSONObject.fromObject(root).toString(2)) } catch (Exception e) {     processException(scriptName,json,e) } //return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(root).toString(2)] /*     Processes the contents of an Exception and add it to the Errors collection     @param json The markup builder */ private def processException(String scriptName, JsonBuilder json, Exception e) {     // catch the exception output     def logStringWriter = new StringWriter()     e.printStackTrace(new PrintWriter(logStringWriter))     logger.error("Exception occurred in ${scriptName}: ${logStringWriter.toString()}")     /*         Construct the error response         - errorCode Will be an element from an agreed upon enum         - errorMessage The text of the exception      */     json.errors  {         error {             message     "[${scriptName}]: " + e.getMessage()             timestamp   "${System.currentTimeMillis()}"         }     }     return json }
View full tip
This article explains how to monitor concurrent user logins on the Axeda Platform. Its going to do this by creating an asset to monitor the solution. This asset will have a dataitem that tracks the users logged in. You can use this dataitem to trend usage during the day, or calculate the max per day. Or you could alarm if the number of users goes over some limit. The following needs to be created on your Platform : A model named “Monitor”, containing an analog DataItem named “userlogins” and an asset named “Metrics” This asset will receive the values. Expression Rule: Create two expression rules that update the number of users, triggered on user login or logout: Name : UserLoginMonitor Type: Userlogin and Userlogout (two rules required) IF:     true THEN: ExecuteCustomObject(“getUserLogins”, User.total) This calls a script and passes in User.total  = The total number of users that are logged into the system (Concurrently). Groovy Script (Custom Object) Now, the next step is to write a Groovy Script (Custom Object) You can copy and paste the following code into your Groovy script. Give the script a name : getUserLogins This script also has a parameter named logins import com.axeda.drm.sdk.device.DataItem; import com.axeda.drm.sdk.device.Device; import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.device.DataItemFinder; import com.axeda.drm.sdk.device.ModelFinder; import com.axeda.drm.sdk.device.DeviceFinder; import com.axeda.drm.sdk.data.DataValueEntry import com.axeda.drm.sdk.device.Model; import com.axeda.drm.sdk.data.CurrentDataFinder; import com.axeda.drm.sdk.data.DataValue; def logins= parameters.logins def ctx = Context.create() def mod = loadModel("Monitor",ctx) def dev = loadDevice("Metrics",mod,ctx) DataItemFinder dif = new DataItemFinder(ctx) dif.setModel(mod) dif.setDataItemName("userlogins") DataItem di = dif.find() DataValueEntry dve = new DataValueEntry(ctx, dev, di, logins) dve.store()     public void setDataItem(String dataItemName, Integer dataItemValue, Device device, Context context)     {         DataItemFinder dif = new DataItemFinder(ctx);         dif.setDataItemName(dataItemName);         dif.setModel(device.getModel());         DataItem di = dif.find();         DataValueEntry dve = new DataValueEntry(ctx, dev, di, newValue)         dve.store()     }     public DataValue findCurrentDataItemValue(Device device, String dataItemName,Context ctx)     {         CurrentDataFinder cdFinder =  new CurrentDataFinder(ctx,device);         DataValue dv = cdFinder.find(dataItemName);         return dv;     }     public DataItem loadDataItem(Model model,String dataItemName, Context ctx)      {         DataItemFinder iFinder = new DataItemFinder(ctx);         iFinder.setDataItemName(dataItemName);         iFinder.setModel(model);         return iFinder.find();     }     public Model loadModel(String modelNumber, Context ctx)     {         ModelFinder mf = new ModelFinder(ctx);         mf.setName("Monitor");         return mf.find();     }     public Device loadDevice(String serialNumber,Model model, Context context ) {         DeviceFinder df = new DeviceFinder(context);         df.setSerialNumber(serialNumber);         df.setModel(model);         return df.find();     } Whenever a user logs into the Axeda Platform, the Metrics asset will show the concurrent number of logged in users in the Platform. This dataitem can be graphed to see the pattern of usage. If you wanted to take action based on the number of users, create an expression rule to alarm when a threshold is reached. This rule should be associated with the model "Monitor". Name : LoginsCheck Type: Data IF:     userlogins > 40 THEN: CreateAlarm("Login limit", 100, str(userlogins)+" users") Now an alarm is created each time too many users are logged in. An alarm can be used for notifications or viewed on the Monitor asset.
View full tip
This is a collection of methods for working with ExtendedObjects based on the functions used in Fleetster. import com.axeda.drm.sdk.device.Device import com.axeda.platform.sdk.v1.services.ServiceFactory import com.axeda.platform.sdk.v1.services.extobject.ExtendedObjectSearchCriteria import com.axeda.platform.sdk.v1.services.extobject.PropertySearchCriteria import com.axeda.platform.sdk.v1.services.extobject.expression.PropertyExpressionFactory import com.axeda.drm.sdk.data.CurrentDataFinder import com.axeda.platform.sdk.v1.services.extobject.ExtendedObject import com.axeda.platform.sdk.v1.services.extobject.Property import java.text.DecimalFormat import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.device.DeviceDataFinder import com.axeda.drm.sdk.user.User import com.axeda.platform.sdk.v1.services.extobject.ExtendedObjectService import com.axeda.platform.sdk.v1.services.extobject.PropertyType import com.axeda.platform.sdk.v1.services.extobject.PropertyDataType import com.axeda.platform.sdk.v1.services.extobject.ExtendedObjectType import com.axeda.common.sdk.id.Identifier import groovy.time.* /* ************************************* */ /* HelperFunctionsExtendedObjects.groovy * Extended Object retrieval/manipulation functions. * * A collection of methods for working with ExtendedObjects. * * author: sara streeter <sstreeter@axeda.com> ************************************* */     def eoSvc = new ServiceFactory().getExtendedObjectService()     def fetchFirstExtendedObject(DataItem dataItem, Map searchcriteria) {         def criteria = new ExtendedObjectSearchCriteria()         criteria.extendedObjectClassName = 'com.axeda.drm.sdk.device.DataItem'         criteria.internalObjectId = dataItem.id.value         criteria.extendedClientKey = "ExtendedDataItem!${dataItem.name}"         def eo       if (searchcriteria != null){             criteria.propertySearchCriteria = advancedPropertySearch(searchcriteria)         }        def queryResult = eoSvc.findExtendedObjects(criteria,  -1, -1, null)         if (queryResult.size() > 0 ){         eo = queryResult.first()         }        return eo     }    def addOrFetchExtendedDataItem(DataItem dataItem, Map searchcriteria) {         def criteria = new ExtendedObjectSearchCriteria()         criteria.extendedObjectClassName = 'com.axeda.drm.sdk.device.DataItem'         criteria.internalObjectId = dataItem.id.value         criteria.extendedClientKey = "ExtendedDataItem!${dataItem.name}"         def eo       if (searchcriteria != null){             criteria.propertySearchCriteria = advancedPropertySearch(searchcriteria)         }        def queryResult = eoSvc.findExtendedObjects(criteria,  -1, -1, null)        if (queryResult.size() == 0 || queryResult == null){               eo = new ExtendedObject()               eo.internalObjectId = dataItem.id.value               eo.extendedObjectType = eoSvc.findExtendedObjectTypeByClassname('com.axeda.drm.sdk.device.DataItem')               eo.externalClientKey = "ExtendedDataItem!${dataItem.name}"               eo = eoSvc.createExtendedObject(eo)             searchcriteria += [eotype: "ExtendedObject"]             createProperties(eoSvc, eo, searchcriteria)          }         else eo = queryResult.first()        return eo     }    def fetchExtendedDataItem(DataItem dataItem) {         def criteria = new ExtendedObjectSearchCriteria()         criteria.extendedObjectClassName = "com.axeda.drm.sdk.device.DataItem"         criteria.extendedClientKey = "ExtendedDataItem!${dataItem.name}"         criteria.internalObjectId = dataItem.id.value        def queryResult = eoSvc.findExtendedObjects(criteria,  -1, -1, null)         return queryResult     }    def fetchExtendedObjectsAdvancedCriteria(DataItem dataItem, Map searchcriteria, String classname, String uniqueKey) {         def criteria = new ExtendedObjectSearchCriteria()         criteria.extendedObjectClassName = "com.axeda.drm.sdk.device.DataItem"         criteria.extendedClientKey = "ExtendedDataItem!${dataItem.name}"         criteria.internalObjectId = dataItem.id.value        if (searchcriteria != null){             criteria.propertySearchCriteria = advancedPropertySearch(searchcriteria)         }        def queryResult = eoSvc.findExtendedObjects(criteria,  -1, -1, null)         return queryResult     }    def fetchExtendedObject(User user, Map searchcriteria) {         def criteria = new ExtendedObjectSearchCriteria()         criteria.extendedObjectClassName = "com.axeda.drm.sdk.user.User"         criteria.extendedClientKey = "ExtendedObject!${user.username}"         criteria.internalObjectId = user.id.value         criteria.propertySearchCriteria = exactdatePropertySearch(searchcriteria)         def queryResult = eoSvc.findExtendedObjects(criteria, -1, -1, null)         return queryResult     }    def addExtendedObject(String eoTypeName, Long referenceId, String referenceName, Map objectProperties) {       def eo = new ExtendedObject()       eo.internalObjectId = referenceId       eo.extendedObjectType = eoSvc.findExtendedObjectTypeByClassname(eoTypeName)       eo.externalClientKey = referenceName       eo = eoSvc.createExtendedObject(eo)       eo = createProperties(eoSvc, eo, objectProperties)        return eo     }    def updateExtendedObject(ExtendedObject ExtendedObject, ExtendedObject ExtendedObject) {                 def ExtendedObjectprop = getProperties(ExtendedObject)                 def ExtendedObjectprop = getProperties(ExtendedObject)                 def newproperties = [:]                 if (ExtendedObjectprop.timestamp != null){                 def ExtendedObjecttype = ExtendedObjectprop.lasttype                 def ExtendedObjecttime = ExtendedObjectprop.lasttime                 if (ExtendedObjecttype == null){                     newproperties["lasttype"] = ExtendedObjectprop.type                     newproperties["lasttime"] = ExtendedObjectprop.timestamp                 }                 else {                     def oldtime = Long.valueOf(ExtendedObjecttime)                     def total = (Long.valueOf(ExtendedObjectprop.timestamp) - oldtime)/1000                     // illustrating getPropertyByName /                     def lasttype = ExtendedObject.getPropertyByName("lasttype")                     lasttype.setValue(ExtendedObjectprop.type)                     def lasttime = ExtendedObject.getPropertyByName("lasttime")                     lasttime.setValue(ExtendedObjectprop.timestamp)                     updateProperty(lasttype)                     updateProperty(lasttime)                     if (ExtendedObjectprop.containsKey(ExtendedObjecttype + "_total")==false)                         {                             newproperties[ExtendedObjecttype + "_total"] = total                         }                     else {                         def totalprop = ExtendedObject.getPropertyByName(ExtendedObjecttype + "_total")                         def lasttotal = Double.valueOf(totalprop.value)                         totalprop.setValue(String.valueOf(Double.valueOf(total) + lasttotal))                         updateProperty(totalprop)                     }                 }                 if (newproperties.size() > 0){                     ExtendedObject = createProperties(eoSvc, ExtendedObject, newproperties)                 }         }         return ExtendedObject    }     def getProperties(ExtendedObject object){         def result = object.properties.inject([:]) { target, property ->               target += [(property.propertyType.name): castPropertyValueToDefinedType(property)]             }         return result     }     def formatProperties(Map properties){         def result = properties.collect { property, value ->                [type: property, time: value]             }         return result     } def updateProperty(Property property){         eoSvc.updateProperty(property)         return property }     //default string version     def createProperties (ExtendedObjectService eoSvc, ExtendedObject object, Map properties) {         return createProperties(eoSvc, object, properties, PropertyDataType.String)     }     //WARNING: PDT may not work if it's a Date; it hasn't been tested.     def createProperties (ExtendedObjectService eoSvc, ExtendedObject object, Map properties, PropertyDataType PDT) {        // http://groovy.codehaus.org/Operators#Operators-ElvisOperator         PDT = PDT?:PropertyDataType.String         properties.each { k , v ->             def property = new com.axeda.platform.sdk.v1.services.extobject.Property()             def propertytype = object.extendedObjectType.getPropertyTypeByName(k.toString())             if (propertytype == null){                 def newPropertyType = new PropertyType()                 newPropertyType.name = k                 newPropertyType.dataType = PDT                 newPropertyType.extendedObjectType = object.extendedObjectType                 eoSvc.createPropertyType(newPropertyType)                 property.propertyType = newPropertyType             } else { property.propertyType = propertytype }             property.value = (PDT == PropertyDataType.Date && v instanceof Date?v.format("yyyy-MM-ddTHH:mm:ssZ"):v.toString())             eoSvc.createPropertyOnExtendedObject(object, property)         }         object = findExtendedObjectById(object.id)         return object     }     def findExtendedObjectById(Long id){         def criteria = new ExtendedObjectSearchCriteria()         criteria.id = id         def queryResult = eoSvc.findExtendedObjects(criteria,  -1, -1, null)         return queryResult.first() }     def findObjectByNextPropertyValue (ExtendedObject object, Map criteria, PropertySearchCriteria psCriteria){        Property prop = object.getPropertyByName(criteria.incrementName)        def incrementedProp = prop.value.toInteger() + criteria.incrementValue.toInteger()            psCriteria.setPropertyExpression(            PropertyExpressionFactory.and(                     psCriteria.propertyExpression,                     PropertyExpressionFactory.eq(criteria.incrementName, incrementedProp.toString())                    )             )        def eoCriteria = new ExtendedObjectSearchCriteria()         eoCriteria.extendedObjectClassName = "com.axeda.drm.sdk.user.User"         eoCriteria.internalObjectId = object.internalObjectId         eoCriteria.propertySearchCriteria = psCriteria        def queryResult = eoSvc.findExtendedObjects(eoCriteria,  -1, -1, null)        return queryResult.first()     }     def advancedPropertySearch (Map searchcriteria){           def propCriteria = new PropertySearchCriteria()        propCriteria.setPropertyExpression(            PropertyExpressionFactory.and(                PropertyExpressionFactory.eq("year", searchcriteria.year),                     PropertyExpressionFactory.and(                         PropertyExpressionFactory.eq("month", searchcriteria.month),                                     PropertyExpressionFactory.eq("date", searchcriteria.date)                             )                    )             )         return propCriteria     }   static def extendedObjectToMap(ExtendedObject object) {     Map result = [:]     result.extendedObjectType = object?.extendedObjectType?.className     result.extendedObjectTypeId = object?.extendedObjectTypeId     result.id = object?.id     result.externalClientKey = object?.externalClientKey     result.internalObjectId = object?.internalObjectId     // build up the properties of the ExtendedObject.     result.properties = object?.properties.inject([:]) { target, property ->       target += [(property?.propertyType?.name): castPropertyValueToDefinedType(property)]     }     return result   }       static def extendedObjectTypeToMap(ExtendedObjectType objectType) {         Map result = [:]         result.className = objectType.className         result.id = objectType.id         result.displayName  = objectType.displayName         result.userDefined = objectType.userDefined         result.description = objectType.description         result.properties = objectType.propertyTypes.inject([]) { List list, PropertyType propertyType ->           list << [                   name: propertyType.name,                   id: propertyType.id,                   description: propertyType.description,                   dataType: propertyType.dataType.toString(),                   extendedObjectType: propertyType.extendedObjectType.className           ]         }         return result       }       private static def castPropertyValueToDefinedType(Property property) {         switch(property.propertyType.dataType) {           case PropertyDataType.Boolean:             return property.value as Boolean           case PropertyDataType.Date:             Calendar calendar = javax.xml.bind.DatatypeConverter.parseDateTime(property.value)             return calendar.getTime()           case PropertyDataType.Double:             return property.value as Double           case PropertyDataType.Integer:             return property.value as Long           case PropertyDataType.String:           default:             return property.value         }       }       static def removeExtendedObject(Long itemId) {         def eoSvc = new ServiceFactory().getExtendedObjectService()         eoSvc.deleteExtendedObject(itemId)       }       def removeUserExtendedObjects(User user) {         def queryresult = fetchUserExtendedObjects(user)         queryresult.each{                 eoSvc.deleteExtendedObject(it.id)         }       }      ExtendedObjectType findExtendedObjectType(String typeName) {     ExtendedObjectType userObjType = eoSvc.findExtendedObjectTypeByClassname(typeName)     return userObjType   }   ExtendedObjectType findOrCreateExtendedObjectType(String typeName) {     ExtendedObjectType type = findExtendedObjectType(typeName)     if (type == null) {       type = new ExtendedObjectType()       type.className = typeName       type.displayName = typeName       type.description = "Autocreated type for $typeName"       type = eoSvc.createExtendedObjectType(type)     }     return type   }
View full tip
This script will return, in XML format, all models for a particular user. It is designed to be called as a web service, in which case the username parameter will be supplied by the platform from the user authentication information passed in the web service call. You should define this script as a Custom Object of type Action. You can test this script in the Groovy development environment on the platform by explicitly supplying the username parameter (i.e., your email address). import com.axeda.drm.sdk.Context; import com.axeda.drm.sdk.user.User; import com.axeda.drm.sdk.device.*; import com.axeda.drm.sdk.model.*; import com.axeda.common.sdk.jdbc.StringQuery; import java.util.*; import groovy.xml.MarkupBuilder import org.custommonkey.xmlunit.* import com.axeda.common.sdk.id.Identifier; def writer def xml try {   String username = parameters.username   Context ctx = Context.create(username);   ModelFinder mf = new ModelFinder(ctx);   List dList = mf.findAll();   Context.create();   writer = new StringWriter()   xml = new MarkupBuilder(writer)   xml.Response() {     for (d in dList) Model('name': d.getName());   } } catch (Exception ex) {   writer = new StringWriter()   xml = new MarkupBuilder(writer)   xml.Response() {     Fault {       Code('Groovy Exception')       Message(ex.getMessage())       StringWriter sw = new StringWriter();       PrintWriter pw = new PrintWriter(sw);       ex.printStackTrace(pw);       Detail(sw.toString())     }   } } //logger.info(writer.toString()); return ['Content-Type': 'text/xml', 'Content': writer.toString()]
View full tip
This script will return, in XML format, the alarms, location information and some data for a particular asset, identified by serial number. It is designed to be called as a web service, in which case the username parameter will be supplied by the platform from the user authentication information passed in the web service call, and the devicename parameter will be passed as an argument to the call. You can test this script in the Groovy development environment on the platform by explicitly supplying the username and devicename parameters (i.e., your email address and "asset1"). The code below will filter data items and return values for the data item "value1". import com.axeda.drm.sdk.Context; import com.axeda.drm.sdk.device.DeviceFinder; import com.axeda.drm.sdk.device.Device; import com.axeda.drm.sdk.data.AlarmFinder; import com.axeda.drm.sdk.data.Alarm; import com.axeda.drm.sdk.mobilelocation.MobileLocation; import com.axeda.common.sdk.jdbc.StringQuery; import com.axeda.common.sdk.id.Identifier; import groovy.xml.MarkupBuilder; try {   logger.info "parameters: " + parameters   if (!parameters.id) {     throw new Exception("parameters.id required");   }   // operate in the context of the user calling the service   Context ctx = Context.create(parameters.username);   // setup the finders   DeviceFinder df = new DeviceFinder(ctx);   df.id = new Identifier(Long.parseLong(parameters.id));   // find the device and its data   logger.info "Finding asset"   Device device = df.find();   if (!device) {     throw new Exception("Unable to find asset with id "+ parameters.id);   }   AlarmFinder af = new AlarmFinder(ctx);   af.device = device;   // generate the XML   writer = new StringWriter();   xml = new MarkupBuilder(writer);   xml.Alarms() {     af.findAll().each() { Alarm alarm ->       xml.Alarm('id':alarm.id) {         xml.DataItemName(alarm.dataItemName);         xml.DataItemValue(alarm.dataItemValue);         xml.Date(alarm.date.time);         xml.Description(alarm.description);         xml.MobileLocation() {           MobileLocation ml = alarm.mobileLocation;           if (ml) {             xml.BeginTimeStamp(ml.beginTimeStamp);             xml.EndTimeStamp(ml.endTimeStamp);             xml.Lat(ml.lat);             xml.Lng(ml.lng);             xml.Alt(ml.alt);             xml.IsCurrentLocation(ml.isCurrentLocation());           }         }         xml.Name(alarm.name);         xml.Note(alarm.note);         xml.Severity(alarm.severity);         xml.State(alarm.state);       }     }   }   // return the results   return ['Content-Type': 'text/xml', 'Content': writer.toString()] } catch (Exception ex) {   // return the exception   writer = new StringWriter();   xml = new MarkupBuilder(writer);   xml.error() {     faultcode("ErrorType.Exception");     faultstring(ex.message);   }   return ['Content-Type': 'text/xml', 'Content': writer.toString()] }
View full tip
This script will return, in XML format, details of all alarms for a particular asset, identified by serial number. It is designed to be called as a web service, in which case the username parameter will be supplied by the platform from the user authentication information passed in the web service call, and the id parameter will be supplied as an argument to the call. You should define this script as a Custom Object of type Action. You can test this script in the Groovy development environment on the platform by explicitly supplying the username and id parameters (i.e., your email address and "asset1"). import com.axeda.drm.sdk.Context; import com.axeda.drm.sdk.device.DeviceFinder; import com.axeda.drm.sdk.device.Device; import com.axeda.drm.sdk.data.AlarmFinder; import com.axeda.drm.sdk.data.Alarm; import com.axeda.drm.sdk.mobilelocation.MobileLocation; import com.axeda.common.sdk.jdbc.StringQuery; import com.axeda.common.sdk.id.Identifier; import groovy.xml.MarkupBuilder; try {   logger.info "parameters: " + parameters   if (!parameters.id) {     throw new Exception("parameters.id required");   }   // operate in the context of the user calling the service   Context ctx = Context.create(parameters.username);   // setup the finders   DeviceFinder df = new DeviceFinder(ctx);   df.id = new Identifier(Long.parseLong(parameters.id));   // find the device and its data   logger.info "Finding asset"   Device device = df.find();   if (!device) {     throw new Exception("Unable to find asset with id "+ parameters.id);   }   AlarmFinder af = new AlarmFinder(ctx);   af.device = device;   // generate the XML   writer = new StringWriter();   xml = new MarkupBuilder(writer);   xml.Alarms() {     af.findAll().each() { Alarm alarm ->       xml.Alarm('id':alarm.id) {         xml.DataItemName(alarm.dataItemName);         xml.DataItemValue(alarm.dataItemValue);         xml.Date(alarm.date.time);         xml.Description(alarm.description);         xml.MobileLocation() {           MobileLocation ml = alarm.mobileLocation;           if (ml) {             xml.BeginTimeStamp(ml.beginTimeStamp);             xml.EndTimeStamp(ml.endTimeStamp);             xml.Lat(ml.lat);             xml.Lng(ml.lng);             xml.Alt(ml.alt);             xml.IsCurrentLocation(ml.isCurrentLocation());           }         }         xml.Name(alarm.name);         xml.Note(alarm.note);         xml.Severity(alarm.severity);         xml.State(alarm.state);       }     }   }   // return the results   return ['Content-Type': 'text/xml', 'Content': writer.toString()] } catch (Exception ex) {   // return the exception   writer = new StringWriter();   xml = new MarkupBuilder(writer);   xml.error() {     faultcode("ErrorType.Exception");     faultstring(ex.message);   }   return ['Content-Type': 'text/xml', 'Content': writer.toString()] }
View full tip
Announcements