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 the Community Ranking System, a fun gamification element of the PTC Community. X

IoT Tips

Sort by:
Email an attachment using bytes from a FileInfo Parameters: fileId - the identifier to a FileInfo that has been previously uploaded to the FileStore filename - the name of the attachment toaddress - the email address to send to fromaddress - the email address to send from import com.axeda.drm.util.Emailer; import com.axeda.drm.sdk.contact.Email import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import static com.axeda.sdk.v2.dsl.Bridges.* import com.axeda.services.v2.FileInfoCriteria import org.apache.commons.io.IOUtils import java.security.MessageDigest try {   String fromaddress = parameters.fromaddress   String toaddress = parameters.toaddress   def fileId = parameters.fileId   def filename = parameters.filename   String subject = "Axeda Test Attachment"   String body = "<html><head/><body><p style='background:blue;'>This email has an attachment and a blue background.</p></body></html>"   def thefile = new File(filename)   def inputStream = fileInfoBridge.getFileData(fileId)   byte[] bytes = IOUtils.toByteArray(inputStream);   thefile.setBytes(bytes)   def random_hash = md5('r');   def contentType = "multipart/mixed; boundary=--\"$random_hash\"\r\n"   def htmlType = "text/html" sendEmail(fromaddress, toaddress, subject,  body, contentType, thefile, false, htmlType) } catch (Exception e) { logger.error(e.localizedMessage) } return true def md5(String s) {     MessageDigest digest = MessageDigest.getInstance("MD5")     digest.update(s.bytes);     new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0') } public void sendEmail(String fromAddress, String toAddress,String subject, String body, String encoding, File file, boolean compress, String mimeType) {     try {         Emailer.getInstance().send([new InternetAddress(toAddress)],new InternetAddress(fromAddress), subject,body, encoding, [file] as File[], compress, mimeType);     } catch (Exception ae) {         logger.error(ae.localizedMessage);     } }
View full tip
Hiya,   I recently prepared a short demo which shows how to onboard and use Azure IoT devices in ThingWorx and added some usability tips and tricks to help others who might struggle with some of the things that I did.     The good news... I recorded and posted it to YouTube here.   •Connect Azure IoT Hub with ThingWorx (to be updated soon for 9.0 release) •Using the Azure IoT Dev Kit with ThingWorx •Getting the Azure IoT Hub Connector Up and Running (V3/8.5)   Enjoy, and don't hesitate to comment with your own tips and feedback.   Cheers,   Greg
View full tip
This code snippet shows how to add an existing Device to an existing DeviceGroup using a custom Groovy script executed by the Scripto web service. To call the script create a URL of the following form: http://<HOST>/services/v1/rest/Scripto/execute/addDeviceToDeviceGroup?us... NOTE: Text in angled brackets (< >) indicates a variable. Alternatively, this script can be called by an Expression Rule using the following form: If: Registration.first Then: ExecuteCustomObject("addDeviceToDeviceGroup","<ASSET_ID>","<GROUP_NAME>") It is worth noting that it is important when creating the Groovy script that the parameters be created in the order of the parameter list. import net.sf.json.JSONObject import com.axeda.drm.sdk.device.DeviceGroupFinder import com.axeda.drm.sdk.device.DeviceGroup import com.axeda.drm.sdk.Context import com.axeda.common.sdk.id.Identifier import com.axeda.drm.sdk.device.DeviceFinder def response = [:], status try {   if (parameters.assetId == null) { throw new IllegalArgumentException("parameter 'assetId' was not provided.")}   if (parameters.groupName == null) { throw new IllegalArgumentException("parameter 'groupName was not provided.")}   final def CONTEXT = Context.create(parameters.username)   def dgf = new DeviceGroupFinder(CONTEXT)   dgf.setName(parameters.groupName)   def group = dgf.find()   if (group == null) {     logger.error "could not retrieve group with name of '${parameters.groupName}'"     throw new Exception("could not retrieve group with id of '${parameters.groupName}'")   }   def df = new DeviceFinder(CONTEXT)   df.setId(new Identifier(parameters.assetId))   def device = df.find()   if (device == null) {     logger.error "could not retrieve asset with id of '${parameters.assetId}'"     throw new Exception("could not retrieve asset with id of '${parameters.assetId}'")   }   group.addDevice(device)   group.store()   // do a check to make sure the device is associated with the group.   group = dgf.find()   def devices = group.getDevices()   status = devices.contains(device) ? "success" : "failure"   // prepare the response.   response = [parameters: parameters, status: status] } catch (def e) {   logger.error e.getMessage()   response = [faultcode: e.getCause(), faultstring: e.getMessage()] } return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(response).toString(2)];
View full tip
This script finds an existing Expression Rule and applies it to an asset (via asset includes). Parameters: model - model name serial - serial number exprRuleName - name of the Expression Rule import static com.axeda.sdk.v2.dsl.Bridges.* import net.sf.json.JSONObject import com.axeda.drm.sdk.scripto.Request import com.axeda.services.v2.Asset import com.axeda.services.v2.AssetReference import com.axeda.services.v2.AssetCollection import com.axeda.services.v2.AssetCriteria import com.axeda.services.v2.ExpressionRule import com.axeda.services.v2.ExpressionRuleCriteria /* * ApplyExpRuleToAsset.groovy * * Finds an existing Expression Rule and includes an asset into it. * * @param model        -   (REQ):Str model of the asset. * @param serial        -   (REQ):Str serial number of the asset. * @param exprRuleName        -   (REQ):Str name of the Expression Rule. * * @author Sara Streeter <sstreeter@axeda.com> */ def response = [:] def root = [:] try {    AssetCriteria assetCriteria = new AssetCriteria()    assetCriteria.modelNumber = Request.parameters.model    assetCriteria.serialNumber = Request.parameters.serial    def findAssetResult = assetBridge.find(assetCriteria)    def asset = findAssetResult.assets[0]    ExpressionRuleCriteria expressionRuleCriteria = new ExpressionRuleCriteria()    expressionRuleCriteria.name = Request.parameters.exprRuleName    def expressionRuleFindResult = expressionRuleBridge.find(expressionRuleCriteria)    def expressionRule = expressionRuleFindResult.expressionRules[0]   def expAssets =  expressionRule.includedAssets.add(asset)   expressionRuleBridge.update(expressionRule)   response = [        "expressionRule":expressionRule.name,       "includedAsset": asset.serialNumber        ] } catch (Exception e) {      response = [             faultcode: 'Groovy Exception',             faultstring: e.message     ]; } return ["Content-Type": "application/json","Content":JSONObject.fromObject(response).toString(2)]
View full tip
This code snippet creates then deletes a data item to illustrate CRUD technique. Parameter:  model_number import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.device.DeviceFinder import com.axeda.drm.sdk.data.CurrentDataFinder import com.axeda.drm.sdk.device.Device import com.axeda.drm.sdk.data.HistoricalDataFinder import groovy.xml.MarkupBuilder import com.axeda.drm.sdk.device.DataItem import com.axeda.drm.services.device.DataItemType /* * DeleteDataItem.groovy * * Delete a data item. * * @param model_number        -   (REQ):Str name of the model. * * @author Sara Streeter <sstreeter@axeda.com> */ def response = [:] def writer = new StringWriter() def xml = new MarkupBuilder(writer) try { // getUserContext is supported as of release 6.1.5 and higher     final def CONTEXT = Context.getUserContext() // find the model     def modelFinder = new ModelFinder(CONTEXT)     modelFinder.setName(parameters.model_name)     Model model = modelFinder.findOne() // throw exception if no model found     if (!model) {         throw new Exception("No model found for ${parameters.model_name}.")     } // Add a dummy data item DataItem dataitem = new DataItem(CONTEXT, model, DataItemType.STRING, "MyDataItem"); dataitem.store(); // find the data items on the model model.dataItems.each{     logger.info(it.name)     if (it.name=="MyDataItem"){         it.delete()     } } } catch (def ex) {       xml.Response() {     Fault {           Code('Groovy Exception')           Message(ex.getMessage())           StringWriter sw = new StringWriter();           PrintWriter pw = new PrintWriter(sw);           ex.printStackTrace(pw);           Detail(sw.toString())         }       } } return ['Content-Type': 'text/xml', 'Content': writer.toString()]
View full tip
For a recent project, I was needing to find all of the children in a Network Hierarchy of a particular template type... so I put together a little script that I thought I'd share. Maybe this will be useful to others as well.   In my situation, this script lived in the Location template. This was useful so that I could find all the Sensor Things under any particular node, no matter how deep they are.   For example, given a network like this: Location 1 Sensor 1 Location 1A Sensor 2 Sensor 3 Location 1AA Sensor 4 Location 1B Sensor 5 If you run this service in Location 1, you'll get an InfoTable with these Things: Sensor 1 Sensor 2 Sensor 3 Sensor 4 Sensor 5 From Location 1A: Sensor 2 Sensor 3 Sensor 4 From Location 1AA: Sensor 4 From Location 1B: Sensor 5   For this service, these are the inputs/outputs: Inputs: none Output: InfoTable of type NetworkConnection   // CreateInfoTableFromDataShape(infoTableName:STRING("InfoTable"), dataShapeName:STRING):INFOTABLE(AlertSummary) let result = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({ infoTableName : "InfoTable", dataShapeName : "NetworkConnection" }); // since the hierarchy could contain locations or sensors, need to recursively loop down to get all the sensors function findChildrenSensors(thingName) { let childrenThings = Networks["Hierarchy_NW"].GetChildConnections({ name: thingName /* STRING */ }); for each (var row in childrenThings.rows) { // row.to has the name of the child Thing if (Things[row.to].IsDerivedFromTemplate({thingTemplateName: "Location_TT"})) { findChildrenSensors(row.to); } else if (Things[row.to].IsDerivedFromTemplate({thingTemplateName: "Sensor_TT"})) { result.AddRow(row); } } } findChildrenSensors(me.name);    
View full tip
This script finds all the data items both current and historical on all the assets of a model and outputs them as XML. Parameters: model_name from_time to_time import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.device.DeviceFinder import com.axeda.drm.sdk.data.CurrentDataFinder import com.axeda.drm.sdk.device.Device import com.axeda.drm.sdk.data.HistoricalDataFinder import groovy.xml.MarkupBuilder /* * AllDataItems2XML.groovy * * Find all the historical and current data items for all assets in a given model. * * @param model_name        -   (REQ):Str name of the model. * @param from_time         -   (REQ):Long millisecond timestamp to begin query from. * @param to_time           -   (REQ):Long millisecond timestamp to end query at. * * @note from_time and to_time should be provided because it limits the query size. * * @author Sara Streeter <sstreeter@axeda.com> */ def response = [:] def writer = new StringWriter() def xml = new MarkupBuilder(writer) // measure the script run time def timeProfiles = [:] def scriptStartTime = new Date() try { // getUserContext is supported as of release 6.1.5 and higher     final def CONTEXT = Context.getUserContext() // confirm that required parameters have been provided     validateParameters(actual: parameters, expected: ["model_name", "from_time", "to_time"]) // find the model     def modelFinder = new ModelFinder(CONTEXT)     modelFinder.setName(parameters.model_name)     Model model = modelFinder.findOne() // throw exception if no model found     if (!model) {         throw new Exception("No model found for ${parameters.model_name}.")     } // find all assets of that model     def assetFinder = new DeviceFinder(CONTEXT)     assetFinder.setModel(model)     def assets = assetFinder.findAll() // find the current and historical data values for each asset //note: since device will be set on the datafinders going forward, a dummy device is set on instantiation which is not actually stored     def currentDataFinder = new CurrentDataFinder(CONTEXT, new Device(CONTEXT, "placeholder", model))     def historicalDataFinder = new HistoricalDataFinder(CONTEXT, new Device(CONTEXT, "placeholder", model))     historicalDataFinder.startDate = new Date(parameters.from_time as Long)     historicalDataFinder.endDate = new Date(parameters.to_time as Long) // assemble the response     xml.Response(){         assets.each { Device asset ->             currentDataFinder.device = asset             def currentValueList = currentDataFinder.find()             historicalDataFinder.device = asset             def valueList = historicalDataFinder.find()             Asset(){                     id(asset.id.value)                     name( asset.name)                     serial_number(asset.serialNumber)                     model_id( asset.model.id.value)                     model_name(asset.model.name)                     current_data(){                         currentValueList.each{ data ->                         timestamp( data?.getTimestamp()?.format("yyyyMMdd HH:mm"))                          name(data?.dataItem?.name)                          value( data?.asString())                     }}                     historical_data(){                         valueList.each { data ->                         timestamp( data?.getTimestamp()?.format("yyyyMMdd HH:mm"))                          name(data?.dataItem?.name)                          value( data?.asString())                     }}             }         }     } } catch (def ex) {       xml.Response() {     Fault {           Code('Groovy Exception')           Message(ex.getMessage())           StringWriter sw = new StringWriter();           PrintWriter pw = new PrintWriter(sw);           ex.printStackTrace(pw);           Detail(sw.toString())         }       } } return ['Content-Type': 'text/xml', 'Content': writer.toString()] private Map createTimeProfile(String label, Date startTime, Date endTime) {     [             (label): [                     startTime: [timestamp: startTime.time, readable: startTime.toString()],                     endTime: [timestamp: endTime.time, readable: endTime.toString()],                     profile: [                             elapsed_millis: endTime.time - startTime.time,                             elapsed_secs: (endTime.time - startTime.time) / 1000                     ]             ]     ] } private validateParameters(Map args) {     if (!args.containsKey("actual")) {         throw new Exception("validateParameters(args) requires 'actual' key.")     }     if (!args.containsKey("expected")) {         throw new Exception("validateParameters(args) requires 'expected' key.")     }     def config = [             require_username: false     ]     Map actualParameters = args.actual.clone() as Map     List expectedParameters = args.expected     config.each { key, value ->         if (args.options?.containsKey(key)) {             config[key] = args.options[key]         }     }     if (!config.require_username) { actualParameters.remove("username") }     expectedParameters.each { paramName ->         if (!actualParameters.containsKey(paramName) || !actualParameters[paramName]) {             throw new IllegalArgumentException(                     "Parameter '${paramName}' was not found in the query; '${paramName}' is a reqd. parameter.")         }     } } Sample Output: <Response>   <Asset>   <id>2864</id>   <name>keg24</name>   <serial_number>keg24</serial_number>   <model_id>1081</model_id>   <model_name>Kegerator</model_name>   <current_data>   <timestamp>20111103 14:44</timestamp>   <name>currKegPercentage</name>   <value>34.0</value>   <timestamp>20111103 14:38</timestamp>   <name>currTempF</name>   <value>43.0</value>   </current_data>   <historical_data />   </Asset>   <Asset>   <id>2861</id>   <name>keg28</name>   <serial_number>keg28</serial_number>   <model_id>1081</model_id>   <model_name>Kegerator</model_name>   <current_data>   <timestamp />   <name>currKegPercentage</name>   <value>?</value>   <timestamp>20111103 14:21</timestamp>   <name>currTempF</name>   <value>43.0</value>   </current_data>   <historical_data />   </Asset>   <Asset>   <id>2863</id>   <name>keg21</name>   <serial_number>keg21</serial_number>   <model_id>1081</model_id>   <model_name>Kegerator</model_name>   <current_data>   <timestamp />   <name>currKegPercentage</name>   <value>?</value>   <timestamp>20111103 14:39</timestamp>   <name>currTempF</name>   <value>42.0</value>   </current_data>   <historical_data />   </Asset>   <Asset>   <id>2862</id>   <name>keg25</name>   <serial_number>keg25</serial_number>   <model_id>1081</model_id>   <model_name>Kegerator</model_name>   <current_data>   <timestamp>20111103 14:36</timestamp>   <name>currKegPercentage</name>   <value>34.0</value>   <timestamp />   <name>currTempF</name>   <value>?</value>   </current_data>   <historical_data />   </Asset>   <Asset>   <id>2867</id>   <name>keg29</name>   <serial_number>keg29</serial_number>   <model_id>1081</model_id>   <model_name>Kegerator</model_name>   <current_data>   <timestamp>20111103 14:48</timestamp>   <name>currKegPercentage</name>   <value>35.0</value>   <timestamp />   <name>currTempF</name>   <value>?</value>   </current_data>   <historical_data />   </Asset>   <Asset>   <id>2865</id>   <name>keg27</name>   <serial_number>keg27</serial_number>   <model_id>1081</model_id>   <model_name>Kegerator</model_name>   <current_data>   <timestamp>20111103 14:39</timestamp>   <name>currKegPercentage</name>   <value>34.0</value>   <timestamp>20111103 14:44</timestamp>   <name>currTempF</name>   <value>42.0</value>   </current_data>   <historical_data />   </Asset>   <Asset>   <id>2866</id>   <name>keg23</name>   <serial_number>keg23</serial_number>   <model_id>1081</model_id>   <model_name>Kegerator</model_name>   <current_data>   <timestamp>20111103 14:46</timestamp>   <name>currKegPercentage</name>   <value>34.0</value>   <timestamp />   <name>currTempF</name>   <value>?</value>   </current_data>   <historical_data />   </Asset> </Response>
View full tip
This is an example of an advanced apc-metadata.xml file for use with Axeda Artisan that includes examples of how to create different data structures on the platform, including Expression Rules and System Timers. Step 1 - In the upload.xml make sure the artisan-installer is 1.2     <dependencies>         <dependency>             <groupId>com.axeda.community</groupId>             <artifactId>artisan-installer</artifactId>             <version>1.2</version>         </dependency>     </dependencies> Step 2 - <?xml version="1.0" encoding="UTF-8"?> <!--         apc-metadata.xml --> <apcmetadata>     <models>         <model>             <name>DemoModel</name>             <!--standalone or gateway-->             <type>gateway</type>             <DataItems>                 <DataItem>                     <name>PendingMessage</name>                     <!--string or digital or analog-->                     <type>string</type>                     <visible>false</visible>                     <stored>true</stored>                     <!--0 = no history,1 = Stored,2 = no storage,3 = on change-->                     <storageOption>2</storageOption>                     <readOnly>false</readOnly>                 </DataItem>                 <DataItem>                     <name>ConsumableData</name>                     <!--string or digital or analog-->                     <type>string</type>                     <visible>false</visible>                     <stored>true</stored>                     <!--0 = no history,1 = Stored,2 = no storage,3 = on change-->                     <storageOption>2</storageOption>                     <readOnly>false</readOnly>                 </DataItem>             </DataItems>         </model>     </models>     <ruleTimers>           <ruletimer>               <name>SFTP Retry</name>               <description></description>               <!--midnight gmt-->               <schedule>0 0 0 * * ?</schedule>               <rules>                   <rule>SFTP Retry</rule>               </rules>           </ruletimer>     </ruleTimers>     <expressionRules>                 <rule>             <name>SFTP Retry</name>             <description></description>             <enabled>true</enabled>             <applyToAll>true</applyToAll>             <type>SystemTimer</type>             <ifExpression><![CDATA[true]]></ifExpression>             <thenExpression>                 <![CDATA[ExecuteCustomObject("SFTPRetry")]]></thenExpression>             <elseExpression></elseExpression>             <consecutive>true</consecutive>             <models>                 <model>DemoMOdel</model>             </models>         </rule>     </expressionRules>     <customobjects>         <customobject>             <name>GetChartData</name>             <type>Action</type>             <sourcefile>GetChartData.groovy</sourcefile>             <params>                 <param name="username" description="(REQUIRED) The name of the calling user"/>             </params>         </customobject>         <customobject>             <name>GetChartData_rss</name>             <type>Action</type>             <sourcefile>GetChartData_rss.groovy</sourcefile>             <params>                 <param name="username" description="(REQUIRED) The name of the calling user"/>             </params>         </customobject>         <customobject>             <name>GetAddress</name>             <type>Action</type>             <sourcefile>GetAddress.groovy</sourcefile>             <params>                 <!--<param name="username" description="(REQUIRED) The name of the calling user"/>-->             </params>         </customobject>     </customobjects>     <applications>         <application>             <description>Chart Example</description>             <applicationId>chartexample</applicationId>             <indexFile>index.html</indexFile>             <!--<zipFile></zipFile>-->             <sourcePath>artisan-starter-html/src/main/webapp</sourcePath>         </application>     </applications> </apcmetadata>
View full tip
Applicable Releases: ThingWorx Platform 7.0 to 8.5   Description:   Covers how to apply patch upgrades to ThingWorx installation, with the following agenda: How to read ThingWorx version Upgrading to a major/minor version of the platform Focus on upgrading to a patch version of the platform Upgrading extensions       Always check the patch release notes for additional information and specific steps
View full tip
Announcements