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

Community Tip - Stay updated on what is happening on the PTC Community by subscribing to PTC Community Announcements. X

IoT Tips

Sort by:
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
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 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
This Groovy script is called from an Expression Rule of type Location. For example, in an Expression rule ExecuteCustomObject("SendTweetWithLocation","user","password","Asset is on the move")   calls the script "SendTweetWithLocation" with the parameters in order. The twitterStatus is the text to send to twitter. Use the user/password for an actual twitter account.  Also, the script uses the implicit objects context and mobileLocation. Parameters Variable Name      Display Name twitterUser                twitterUser twitterPassword      twitterPassword twitterStatus            twitterStatus import groovyx.net.http.RESTClient import static groovyx.net.http.ContentType.* import com.axeda.drm.sdk.geofence.Geofence twitter = new RESTClient('http://twitter.com/statuses/') twitter.auth.basic parameters.twitterUser, parameters.twitterPassword twitter.client.params.setBooleanParameter 'http.protocol.expect-continue', false def statusText = "'${parameters.twitterStatus}' for device: ${context.device.serialNumber} on ${new Date()}" resp = twitter.post(path: 'update.xml',         requestContentType: URLENC,         body: [status: statusText, lat: mobileLocation.lat, long: mobileLocation.lng]) logger.info resp.status logger.info "Posted update $statusText"  
View full tip
This Groovy script is called from an ExpressionRule of type Alarm. For example, in an Expression rule IF: Alarm.severity > 500 THEN: ExecuteCustomObject("SMSMe", "[numberToSMS]")    calls the script "SMSMe" with the parameter phoneNumber.  The ExecuteCustomObject provides a way to call from this simple two-line business rule into a modern programming environment with access to the complete Platform SDK, as well as all the features of the Groovy language.  In Groovy, it's straightforward to use the built-in httpclient library to POST an HTTP request to Twilio to send an SMS to the specified cellphone number. Groovy Scripts The groovy script named SMSMe is executed by the Expression Rule above and connects to the Twilio Server passing in a list of parameters. When you register with Twilio, you will be given an ACCOUNT SID (apiID) and an AUTH TOKEN (apiPass). These two strings need to be in the Groovy Script below: String apiID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'  String apiPass = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'     Parameters Variable Name      Display Name phoneNumber      phoneNumber import org.apache.commons.httpclient.Credentials import org.apache.commons.httpclient.HostConfiguration import org.apache.commons.httpclient.HttpClient import org.apache.commons.httpclient.UsernamePasswordCredentials import org.apache.commons.httpclient.auth.AuthScope import org.apache.commons.httpclient.methods.GetMethod import org.apache.commons.httpclient.methods.PostMethod import org.apache.commons.httpclient.NameValuePair //logger.info "Calling ${parameters.phoneNumber}" String twilioHost = 'api.twilio.com' String apiID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' String aipPass = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' HostConfiguration hc = new HostConfiguration() hc.setHost(twilioHost, 443, "https") def url = "/2008-08-01/Accounts/$apiID/SMS/Messages" def client = new HttpClient() Credentials defaultcreds = new UsernamePasswordCredentials(apiID, aipPass) client.getState().setCredentials(null, null, defaultcreds) PostMethod post = new PostMethod(url); post.addParameter 'IfMachine', 'Continue' post.addParameter 'Method', 'POST' post.addParameter 'From', '[YourNumber]' post.addParameter 'To', parameters.phoneNumber post.addParameter 'Body', 'This is an SMS from Axeda' client.executeMethod(hc, post); //logger.info message = "Status:" + post.getStatusText() //logger.info post.getResponseBodyAsString() post.releaseConnection();   
View full tip
When an Expression Rule of type MobileLocation calls a Groovy script, the script is provided with the implicit object mobileLocation.  This example shows how the mobileLocation object can be used. This Expression Rule calls the Groovy script 'getAddress' to retrieve the location and translate it into a street address: Type:  MobileLocation IF:      some condition e.g. true THEN:  SetDataItem("location", str(ExecuteCustomObject("getAddress"))) The 'getAddress' script uses the mobileLocation object to retrieve the asset's reported location, and then calls a REST service to translate a given latitude and longitude to a street address.  The street address is returned. import groovyx.net.http.RESTClient String rmdHostname =  "http://ws.geonames.org"; if (mobileLocation != null) { rmd = new RESTClient(rmdHostname); try {       def resp = rmd.get( path: 'findNearestAddress',                       query:[lat:mobileLocation.lat , lng:mobileLocation.lng] )        streetnum = resp.data.address.streetNumber.text()        street = resp.data.address.street.text()        town = resp.data.address.adminName2.text()        state = resp.data.address.adminCode1.text()        postalcode = resp.data.address.postalcode.text()            return streetnum + " " + street + " " +  town + " " + state + " " + postalcode } catch (groovyx.net.http.HttpResponseException e) {     e.printStackTrace();     } }
View full tip
When an Expression Rule of type File calls a Groovy script, the script is provided with the implicit object compressedFile.  This example shows how the compressedFile object can be used. This Expression Rule uses the Groovy script 'LastLine' to return the last line of the file, and sets the dataItem 'lastLine' to the returned value: Type:  File IF:      some condition e.g. File.hint=="hint" THEN:  SetDataItem("lastline", str(ExecuteCustomObject("LastLine"))) The LastLine script uses the implicit object 'compressedFile': import com.axeda.drm.sdk.scm.CompressedFile if (compressedFile != null) {     File file = compressedFile.getFiles()[0].extractFile()     def result =  file.eachLine { line ->         return line     } }
View full tip
When an Expression Rule of type Data calls a Groovy script, the script is provided with the implicit object dataItems.  This example shows how the dataItems object can be used.to get the dataitem information (value, name, type and update time) import com.axeda.drm.sdk.data.* import com.axeda.drm.sdk.device.DataItem try {         def deviceName = context.device.name         // implicit object dataItems passes a list of dataItem objects         def dataItemsList = dataItems         for(dio in dataItemsList) {                logger.info("Checking " + dio.name + " Value: " + dio.value)                if(dio.name == "updateTime") {                        logger.info("Found: " + dio.name + " Value: " + dio.value + " Type: " +    dio.perceptType + " Last Updated: " + new Date(dio.timeInMillis)) // perceptType = analog, digital or string                }         } } catch (Exception e) {         logger.error e.message }
View full tip
This groovy script will return a list of DeviceGroups based off a given Asset's serialnumber. import com.axeda.drm.sdk.device.*; import com.axeda.drm.sdk.Context; Context sysContext = Context.create(); DeviceGroupFinder dgf = new DeviceGroupFinder(sysContext); DeviceFinder devFinder = new DeviceFinder(sysContext); devFinder.setSerialNumber("[your serialnumber here]"); Device myDevice= devFinder.find(); dgf.setDeviceId(myDevice.getId()); List<DeviceGroup> allGroups = dgf.findAll(); allGroups.each { group -> logger.debug(group.getName()); }
View full tip
This Groovy script gets the weather forecast for a given lat/long by calling an external web service. Use in an Expression rule like this: If: something Then: SetDataItem ("precipitation", round(ExecuteCustomObject ("GetPrecipitation", location) )) This sets the dataitem "precipitation" to the value returned by this script. Parameters: Variable Name               Display Name location                         localtion (lat, lon) import org.apache.commons.httpclient.methods.* import org.apache.commons.httpclient.* import java.text.SimpleDateFormat def location = parameters.location.toString() def locparts = location.split(',') def lat = locparts[0] def lon = locparts[1] def hostname = " www.weather.gov" def url = "/forecasts/xml/sample_products/browser_interface/ndfdXMLclient.php" String ndfdElement = "pop12" // see http://www.weather.gov/forecasts/xml/docs/elementInputNames.php def perceptTimeFormat = new SimpleDateFormat ("yyyy-MM-dd'T'HH:mm:ss"); def cal = Calendar.getInstance(); Date startDate = cal.getTime() cal.add(Calendar.HOUR,12) Date endDate = cal.getTime() def client = new HttpClient () HostConfiguration host = client.getHostConfiguration() host.setHost(hostname, 80, "http") GetMethod get = new GetMethod (url) NameValuePair [] params = new NameValuePair [6] params[0] = new NameValuePair ("lat", lat); params[1] = new NameValuePair ("lon", lon); params[2] = new NameValuePair ("product", 'time-series'); params[3] = new NameValuePair ("begin", perceptTimeFormat.format(startDate)); params[4] = new NameValuePair ("end", perceptTimeFormat.format(endDate)); params[5] = new NameValuePair (ndfdElement, ndfdElement); get.setQueryString(params) client.executeMethod(host, get); message = "Status:" + get.getStatusText() content = get.getResponseBodyAsString() get.releaseConnection() // parse result XML and compute average def dwml = new XmlSlurper ().parseText(content) readings = dwml.data.parameters."probability-of-precipitation".value.collect { Integer.parseInt(it.toString()) } average = readings.sum() / readings.size() //logger.info "Expected precipitation for $location is $readings" return readings[0]
View full tip
This Groovy script takes any dataitem values and writes them to properties of the same name - if they exist. The rule to call this script needs to be a data trigger such as: If: some condition Then: ExecuteCustomObject("CopyParameters") The script uses the default context that contains an asset (device) and the default parameter dataItems that contains the current reported dataitems (from an agent) import com.axeda.drm.sdk.user.User import com.axeda.drm.sdk.data.DataValue import groovy.lang.PropertyValue import com.axeda.drm.sdk.device.DevicePropertyFinder import com.axeda.drm.sdk.device.Property import com.axeda.drm.sdk.device.PropertyType import com.axeda.drm.sdk.device.DeviceProperty logger.info "Executing groovy script for device: " + context?.device?.serialNumber if (dataItems != null) {   logger.info "** Data Items **"   // show data item values   dataItems?.each {di ->     logger.info "dataitem: ${di.name} = ${di.value} = ${di.timestamp}" }   def dataItemMap = [:]   dataItems.each{ dataItemMap[it.name] = it }   DevicePropertyFinder dpf = new DevicePropertyFinder (context.context)   dpf.type = PropertyType.DEVICE_TYPE   dpf.id = context.device.id   DeviceProperty dp = dpf.findOne()   List<Property> props = dp.getProperties()   props.each {Property prop->     if (dataItemMap.containsKey(prop.name)) {       prop.value = dataItemMap[prop.name].value?.toString()       //logger.info "Setting ${prop.name} to ${dataItemMap[prop.name].value?.toString()}"     }   }   dp.store() }
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 groovy script will return a list of users based off a given UserGroup and allows for filtering by username. import com.axeda.drm.sdk.Context import groovyx.net.http.HTTPBuilder import static groovyx.net.http.ContentType.* import static groovyx.net.http.Method.* import net.sf.json.JSONObject import groovy.json.* import com.axeda.drm.sdk.data.* import com.axeda.drm.sdk.device.* import com.axeda.drm.sdk.user.UserFinder import com.axeda.drm.sdk.user.User import com.axeda.drm.sdk.user.UserGroupFinder //-------------------------------------------------------------------------------------------------------------------- // Example of getting Users from a User Group and filtering by username // //-------------------------------------------------------------------------------------------------------------------- def response = [:] def result = [] try {     final def CONTEXT = Context.create(parameters.username)       UserFinder uFinder = new UserFinder(CONTEXT)     UserGroupFinder ugFinder = new UserGroupFinder(CONTEXT)     List userGroups = getUserGroupsList(ugFinder, "*Demo*")     List SmithsInDemoGroup = userGroups.collect{ usergroup ->         usergroup.getUsers().findResults{ user ->             if (user.username =~ /Smith/){                                      user                                      }                      }     }.flatten()     SmithsInDemoGroup.each{ u ->         result << u.fullName     }   response = [     result: [             items: result     ]   ] } catch (Exception e) {     def m = ""     e.message.each { ex -> m += ex }     response = [                 faultcode: 'Groovy Exception',                 faultstring: m             ]; } return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(response).toString(2)];   def getUserGroupsList(UserGroupFinder ugFinder, String name){     ugFinder.setName(StringQuery.like(name))     def userGroup = ugFinder.findOne()     List userGroups = new ArrayList();     userGroups.add(userGroup);     return userGroups }
View full tip
This script dumps all the alarms for a model or asset to JSON. Parameters (one or the other must be provided): modelName - (OPTIONAL) String - name of the model assetId - (OPTIONAL) String - id of the asset import com.axeda.common.sdk.id.Identifier import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.audit.AuditCategory import com.axeda.drm.sdk.audit.AuditMessage import com.axeda.drm.sdk.scripto.Request import groovy.json.* import net.sf.json.JSONObject import java.net.URLDecoder import static com.axeda.sdk.v2.dsl.Bridges.* import com.axeda.services.v2.CustomObjectCriteria import com.axeda.services.v2.CustomObjectType import com.axeda.services.v2.CustomObject import com.axeda.services.v2.ExecutionResult import com.axeda.services.v2.ExtendedMap import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.device.DeviceFinder import com.axeda.drm.sdk.device.Device import com.axeda.services.v2.ModelCriteria import com.axeda.services.v2.ModelType import com.axeda.services.v2.FindModelResult import com.axeda.services.v2.AssetCriteria import com.axeda.services.v2.FindAssetResult import com.axeda.services.v2.AlarmCriteria import com.axeda.sdk.v2.bridge.MobileLocationBridge import com.axeda.drm.sdk.mobilelocation.MobileLocationFinder import com.axeda.drm.sdk.mobilelocation.CurrentMobileLocationFinder import com.axeda.drm.sdk.mobilelocation.MobileLocation import com.axeda.drm.sdk.data.AlarmState import com.axeda.drm.sdk.data.AlarmFinder import com.axeda.drm.sdk.data.Alarm import com.axeda.platform.sdk.v1.services.ServiceFactory import com.axeda.drm.sdk.data.CurrentDataFinder import com.axeda.drm.sdk.device.DataItem import com.axeda.drm.sdk.data.HistoricalDataFinder import com.axeda.drm.sdk.data.DataValue import com.axeda.drm.sdk.data.DataValueList import com.axeda.platform.sdk.v1.services.extobject.ExtendedObjectSearchCriteria import com.axeda.common.date.DateRange import com.axeda.common.date.ExplicitDateRange /** * GetModel_Or_Asset_Alarms.groovy * ----------------------- * * Returns assets with organizations, alarms, and current mobile location. * * @params * modelName (OPTIONAL) Str - the name of the model to retrieve assets * assetId (OPTIONAL) Long - the id of the asset - one of the two is REQUIRED * * * @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 = "GetModel_Or_Asset_Alarms.groovy" def root = [:] def timings = [:] timings.dataItemList = 0 timings.currentdata = 0 timings.histdata = 0 timings.wholescript = 0 timings.alarms = 0 timings.loop = 0 timings.filter = 0 timings.devices = 0 timings.geocode = 0 wholestart = System.currentTimeMillis() final def Context CONTEXT = Context.getSDKContext() def deviceList List<Device> devices try {     /* BUSINESS LOGIC GOES HERE */       def modelName = Request.parameters.modelName     def assetId     def alarms     AlarmFinder alarmFinder = new AlarmFinder(CONTEXT)       if (Request.parameters.assetId != null && Request.parameters.assetId != ""){         assetId = Request.parameters.assetId         DeviceFinder deviceFinder = new DeviceFinder(CONTEXT, new Identifier(assetId as Long));         def device = deviceFinder.find()         if (device){             alarmFinder.setDevice(device)             modelName = device.model.name         }     }     else if (modelName){               try{         modelName = new URLDecoder().decode(modelName)         }         catch(e){ logger.info(e.localizedMessage) }         if (modelName != null && modelName !=""){             ModelFinder modelFinder = new ModelFinder(CONTEXT)             modelFinder.setName(modelName)             Model model = modelFinder.find()                      if (model){                 modelName = model?.name                 alarmFinder.setModel(model)             }         }      }       alarms = alarmFinder.findAll()     // build the json from the models          root = [              "result": [              "model": modelName,              "assetId": assetId,              "alarms":alarms?.inject([]){ aList, alarm ->                    aList << [                         "deviceId": alarm.device?.id?.value,                        "deviceName": alarm.device.name,                        "deviceSerial": alarm.device.serialNumber,                         "name": alarm.name,                         "id": alarm.id.value,                         "state": alarm.state.name,                         "description": alarm.description,                         "severity": alarm.severity,                         "timestamp": alarm.date.time                    ]                                      aList               }             ]          ]     /* BUSINESS LOGIC ENDS HERE */ } catch (Exception e) {     def errorCode = "123456"     processException(scriptName,json,e,errorCode) } finally {     timings.wholescript = System.currentTimeMillis() - wholestart     root += [params: Request.parameters]     root += [timings: timings] } return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(root).toString(2)] /* * * ACTIVE CODE ENDS HERE * */ //---------------------------------------------------------------// /* * * HELPER METHODS START BELOW * */ /** * Wrap-up the response in our standard return map * @param contentType The global contentType variable * @param response The contents of the response (String ONLY) */ private def createReturnMap(String contentType, String response) {     return ["Content-Type": contentType,"Content":response] } /*     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, String code) {     // 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 {             errorCode   "${code}"             message     "[${scriptName}]: " + e.getMessage()             timestamp   "${System.currentTimeMillis()}"         }     }     return json } /*     Log a message. This will log a message and add it to info String     @param logger The injected logger     @param scriptName The name of the script being executed     @param info The infoString to append to     @param message The actual message to log */ private def logMessage(def logger, String scriptName, StringBuilder info, String message) {     logger.info(message)     info.append(message+"\n") } /*     Audit a message. This will store a message in the Audit log, based on the supplied category.     @param category The category for this audit message. One of: "scripting", "network", "device" or "data". Anything not recognized will be treated as "data".     @param message The actual message to audit     @param assetId If supplied, will associate the audit message with the asset at this ID */ private def auditMessage(String category, String message, String assetId) {     AuditCategory auditCategory = null     switch (category) {         case "scripting":             auditCategory = AuditCategory.SCRIPTING;             break;         case "network":             auditCategory = AuditCategory.NETWORK;             break;         case "device":             auditCategory = AuditCategory.DEVICE_COMMUNICATION;             break;         default:             auditCategory = AuditCategory.DATA_MANAGEMENT;             break;     }     if (assetId == null) {         new AuditMessage(Context.create(),"com.axeda.drm.rules.functions.AuditLogAction",auditCategory,[message]).store()     } else {         new AuditMessage(Context.create(),"com.axeda.drm.rules.functions.AuditLogAction",auditCategory,[message],new Identifier(Long.valueOf(assetId))).store()     } } def findOrCreateExtendedMap(String name){        // should take a name of Extended Map and output an object of type Extended Map, if it outputs null we throw an Exception        def outcome = [:]        outcome.extendedMap        ExtendedMap extendedMap = extendedMapBridge.find(name)        if (!extendedMap){             extendedMap = new ExtendedMap(name: name)            extendedMapBridge.create(extendedMap)        }        if (extendedMap) {         ExecutionResult result = new ExecutionResult()         result.setSuccessful(true)         result.setTotalCount(1)         outcome.result = result         outcome.extendedMap = extendedMap        }        else {            ExecutionResult result = new ExecutionResult()            result.setSuccessful(false)            result.setTotalCount(1)            outcome.result = result        }         return outcome    }    def retrieveModels(){       // retrieves the list populated by a separate script        def outcome = [:]        outcome.modelList        ModelCriteria modelCriteria = new ModelCriteria()        modelCriteria.type = ModelType.STANDALONE        FindModelResult modelResult = modelBridge.find(modelCriteria)        if (modelResult.models.size() > 0){         ExecutionResult result = new ExecutionResult()         result.setSuccessful(true)         result.setTotalCount(1)         outcome.result = result         outcome.modelList = modelResult.models        }        else {            ExecutionResult result = new ExecutionResult()            result.setSuccessful(false)            result.setTotalCount(1)            outcome.result = result        }         return outcome    }    def returnModelsWithAssets(List<com.axeda.services.v2.Model> modelList){        def outcome = [:]        outcome.modelList        outcome.message        if (!modelList || modelList?.size() ==0){            ExecutionResult result = new ExecutionResult()           result.setSuccessful(false)           result.setTotalCount(1)           outcome.result = result           outcome.message = "returnModelsWithAssets: Model list was not supplied or was of size zero."           return outcome        }        DeviceFinder deviceFinder = new DeviceFinder(CONTEXT)        ModelFinder modelFinder = new ModelFinder(CONTEXT)        List<com.axeda.drm.sdk.device.Model> sortedList = modelList.inject([]){ target, amodel ->             modelFinder.setName(amodel.modelNumber)            com.axeda.drm.sdk.device.Model bmodel = modelFinder.find()            deviceFinder.setModel(bmodel)            def numAssets = deviceFinder.findAll().size()            if (numAssets > 0 ){                   target << bmodel             }             target        }.sort{ amodel, bmodel ->  amodel.name <=> bmodel.name}        if (sortedList.size() > 0){         ExecutionResult result = new ExecutionResult()         result.setSuccessful(true)         result.setTotalCount(1)         outcome.result = result         outcome.modelList = sortedList        }        else {           ExecutionResult result = new ExecutionResult()           result.setSuccessful(false)           result.setTotalCount(1)           outcome.result = result       }         return outcome    }     def addMapEntry(String mapName, String key, String value){        def outcome = [:]         outcome.key         outcome.value         ExecutionResult result = extendedMapBridge.append(mapName, key, value)         outcome.result = result         if (result.successful){             outcome.key = key             outcome.value = value         }         return outcome    }
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
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 net.sf.json.JSONObject /* * DataItemEachDevice.groovy * * Find the current data item and historical data items for all assets in a given model. * * @param model_name        -   (REQ):Str name of the model. * @param data_item_name    -   (REQ):Str name of the data item to query on. * @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 = [:] // 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", "data_item_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     assets = assets.collect { Device asset ->         currentDataFinder.device = asset         def currentValue = currentDataFinder.find(parameters.data_item_name)         historicalDataFinder.device = asset         def valueList = historicalDataFinder.find(currentValue?.dataItem)         [                 id: asset.id.value,                 name: asset.name,                 serialNumber: asset.serialNumber,                 model: [id: asset.model.id.value, name: asset.model.name],                 current_data: currentValue.asString(),                 historical_data: valueList.collect { [timestamp: it.getTimestamp().format("yyyyMMdd HH:mm"), value: it.asString()] }         ]     }     response = [result: [items: assets]] } catch (def ex) {     logger.error ex     response += [             error: [                     type: "Backend Application Error", msg: ex.getLocalizedMessage()             ]     ] } finally { // create and output the running time profile     timeProfiles << createTimeProfile("DataItemEachDevice", scriptStartTime, new Date())     response += [params: parameters, meta: [:], timeProfiles: timeProfiles] } return ['Content-Type': 'application/json', 'Content': JSONObject.fromObject(response).toString(2)] 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: {   "result": {     "items": [{       "id": 4240,       "name": "ASVM_9",       "serialNumber": "ASVM_9",       "model": {         "id": 1535,         "name": "SimVM4"       },       "current_data": "142.0",       "historical_data": [{         "timestamp": "20120331 17:00", "value": "142.0"       }, {         "timestamp": "20120331 16:59", "value": "143.0"       }, {         "timestamp": "20120331 16:59", "value": "144.0"       }, {         "timestamp": "20120331 16:58", "value": "145.0"       }, {         "timestamp": "20120331 16:58", "value": "146.0"       }, {         "timestamp": "20120331 16:57", "value": "147.0"       }, {         "timestamp": "20120331 16:57", "value": "148.0"       }, {         "timestamp": "20120330 19:30",         "value": "0.0"       }]     }, {       "id": 4246,       "name": "ASVM_12",       "serialNumber": "ASVM_12",       "model": {         "id": 1535,         "name": "SimVM4"       },       "current_data": "138.0",       "historical_data": [{         "timestamp": "20120331 17:00",        "value": "138.0"       }, {         "timestamp": "20120331 17:00",        "value": "139.0"       }, {         "timestamp": "20120331 16:59",        "value": "140.0"       }, {         "timestamp": "20120331 16:59",        "value": "141.0"       }, {         "timestamp": "20120331 16:59",        "value": "142.0"       }, {         "timestamp": "20120331 16:59",        "value": "143.0"       }, {         "timestamp": "20120330 19:32",         "value": "0.0"       }]      //      // MORE ASSETS HERE      //     }]   },   "params": {     "username": "sstreeter",     "from_time": "1332272219000",     "data_item_name": "CurrentStock",     "sessionid": "JOQ5I7ofRXYA-RnA37Vk93bRUH718yoFF5 9p0JbCnfyoHolFprf",     "model_name": "SimVM4",     "to_time": "1335469008000"   },   "meta": {},   "timeProfiles": {     "DataItemEachDevice": {       "startTime": {         "timestamp": 1335469168725,         "readable": "Thu Apr 26 19:39:28 GMT 2012"       },       "endTime": {         "timestamp": 1335469180569,         "readable": "Thu Apr 26 19:39:40 GMT 2012"       },       "profile": {         "elapsed_millis": 11844,         "elapsed_secs": 11.844       }     }   } }
View full tip
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
This tutorial applies to Axeda version 6.1.6+, with sections applicable to 6.5+ (indicated below) Custom objects (or Groovy scripts) are the backbone of Axeda custom applications.  As the developer, you decide what content type to give the data returned by the script. What this tutorial covers? This tutorial provides examples of outputting data in different formats from Groovy scripts and consuming that data via Javascript using the jQuery framework.  While Javascript and jQuery are preferred by the Axeda Innovation team, any front end technology that can consume web services can be used to build applications on the Axeda Machine Cloud.  On the same note, the formats discussed in this article are only a few examples of the wide variety of content types that Groovy scripts can output via Scripto.  The content types available via Scripto are limited only by their portability over the TCP protocol, a qualification which includes all text-based and downloadable binary mime types.  As of July 2013, the UDP protocol (content streaming) is not supported by the current version of the Axeda Platform. Formats discussed in this article: 1) JSON 2) XML 3) CSV 4) Binary content with an emphasis on image files (6.5+) For a tutorial on how to create custom objects that work with custom applications, check out Using Google Charts API with Scripto.  For a discussion of what Scripto is and how it relates to Groovy scripts and Axeda web services, take a look at Unleashing the Power of the Axeda Platform via Scripto. Serializing Data JSON For those building custom applications with Javascript, serializing data from scripts into JSON is a great choice, as the data is easily consumable as native Javascript objects. The net.sf.json JSON library is available to use in the SDK.  It offers an easy way to serialize objects on the Platform, particularly v2 SDK objects. import net.sf.json.JSONArray import static com.axeda.sdk.v2.dsl.Bridges.* def asset = assetBridge.findById(parameters.assetId) def response = JSONArray.fromObject(asset).toString(2) return ["Content-Type": "application/json", "Content": response] Outputs: [{     "buildVersion": "",     "condition": {         "detail": "",         "id": "3",         "label": "",         "restUrl": "",         "systemId": "3"     },     "customer": {         "detail": "",         "id": "2",         "label": "Default Organization",         "restUrl": "",         "systemId": "2"     },     "dateRegistered": {         "date": 11,         "day": 1,         "hours": 18,         "minutes": 7,         "month": 2,         "seconds": 49,         "time": 1363025269253,         "timezoneOffset": 0,         "year": 113     },     "description": "",     "detail": "testasset",     "details": null,     "gateways": [],     "id": "12345",     "label": "",     "location": {         "detail": "Default Organization",         "id": "2",         "label": "Default Location",         "restUrl": "",         "systemId": "2"     },     "model": {         "detail": "testmodel",         "id": "2345",         "label": "standalone",         "restUrl": "",         "systemId": "2345"     },     "name": "testasset",     "pingRate": 0,     "properties": [         {             "detail": "",             "id": "1",             "label": "TestProperty",             "name": "TestProperty",             "parentId": "2345",             "restUrl": "",             "systemId": "1",             "value": ""         },         {             "detail": "",             "id": "4",             "label": "TestProperty0",             "name": "TestProperty0",             "parentId": "2345",             "restUrl": "",             "systemId": "4",             "value": ""         },         {             "detail": "",             "id": "3",             "label": "TestProperty1",             "name": "TestProperty1",             "parentId": "2345",             "restUrl": "",             "systemId": "3",             "value": ""         },         {             "detail": "",             "id": "2",             "label": "TestProperty2",             "name": "TestProperty2",             "parentId": "2345",             "restUrl": "",             "systemId": "2",             "value": ""         }     ],     "restUrl": "",     "serialNumber": "testasset",     "sharedKey": [],     "systemId": "12345",     "timeZone": "GMT" }] This output can be traversed as Javascript object with its nodes accessible using dot (.) notation. For example, if you set the above JSON as the content of variable "json", you can access it in the following way, without any preliminary parsing needed: assert json[0].condition.id == 3 If you use jQuery, a Javascript library, feel free to make use of axeda.js, which contains utility functions to pass data to and from the Axeda Platform.  One function in particular is used in most example custom applications found on this site, the axeda.callScripto function.  It relies on the jQuery ajax function to make the underlying call. /**   * makes a call to the enterprise platform services with the name of a script and passes   * the script any parameters provided.   *   * default is GET if the method is unknown   *   * Notes: Added POST semantics - plombardi @ 2011-09-07   *   * original author: Zack Klink & Philip Lombardi   * added on: 2011/7/23   */ // options - localstoreoff: "yes" for no local storage, contentType: "application/json; charset=utf-8", axeda.callScripto = function (method, scriptName, scriptParams, attempts, callback, options) {   var reqUrl = axeda.host + SERVICES_PATH + 'Scripto/execute/' + scriptName + '?sessionid=' + SESSION_ID   var contentType = options.contentType ? options.contentType : "application/json; charset=utf-8"   var local   var daystring = keygen()   if (options.localstoreoff == null) {   if (localStorage) {   local = localStorage.getItem(scriptName + JSON.stringify(scriptParams))   }   if (local != null && local == daystring) {   return dfdgen(reqUrl + JSON.stringify(scriptParams))   } else {   localStorage.setItem(scriptName + JSON.stringify(scriptParams), daystring)   }   }   return $.ajax({   type: method,   url: reqUrl,   data: scriptParams,   contentType: contentType,   dataType: "text",   error: function () {   if (attempts) {   expiredSessionLogin();   setTimeout(function () {   axeda.callScripto('POST', scriptName, scriptParams, attempts - 1, callback, options)   }, 1500);   }   },   success: function (data) {   if (options.localstoreoff == null) {   localStorage.setItem(reqUrl + JSON.stringify(scriptParams), JSON.stringify([data]))   }   if (contentType.match("json")) {   callback(unwrapResponse(data))   } else {   callback(data)   }   }   }) }; Using the axeda.callScripto function: var postToPlatform = function (scriptname, callback, map) {         var options = {             localstoreoff: "yes",             contentType: "application/json; charset=utf-8"         }        // Javascript object "map" has to be stringified to post to Axeda Platform         axeda.callScripto("POST", scriptname, JSON.stringify(map), 2, function (json) {             // callback gets the JSON object output by the Groovy script             callback(json)         }, options)     } The JSON object is discussed in more detail here. Back to Top XML XML is the preferred language of integration with external applications and services. Groovy provides utilities to make XML serialization a trivial exercise. import groovy.xml.MarkupBuilder import static com.axeda.sdk.v2.dsl.Bridges.* def writer = new StringWriter() def xml = new MarkupBuilder(writer) def findAssetResult = assetBridge.find(new AssetCriteria(modelNumber: parameters.modelName)) // find operation returns AssetReference class. Contains asset id only def assets = findAssetResult.assets      xml.Response() {   Assets() {   assets.each { AssetReference assetRef ->   def asset = assetBridge.findById(assetRef.id)               // asset contains a ModelReference object instead of a Model.  ModelReference has a detail property, not a name property   Asset() {   id(asset.id)   name(asset.name)   serial_number(asset.serialNumber)   model_id(asset.model.id)   model_name(asset.model.detail)   }   }   }   } return ['Content-Type': 'text/xml', 'Content': writer.toString()] Output: <Assets>   <Asset>   <id>98765</id>   <name>testasset</name>   <serial_number>testasset</serial_number>   <model_id>4321</model_id>   <model_name>testmodel</model_name>   </Asset> </Assets Although XML is not a native Javascript object as is JSON, Javascript libraries and utilities are available for parsing XML into an object traversable in Javascript. For more information on parsing XML in Javascript, see W3 Schools XML Parser.  For those using jQuery, check out the jQuery.parseXML function. Back to Top Outputting Files (Binary content types) CSV CSV comes in handy for spreadsheet generation as it is compatible with Microsoft Excel. The following example is suitable for Axeda version 6.1.6+ as it makes use of the Data Accumulator feature to create a downloadable file. import com.axeda.drm.sdk.device.ModelFinder import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.scripto.Request import com.axeda.common.sdk.id.Identifier import com.axeda.drm.sdk.device.Model import com.axeda.drm.sdk.device.DataItem import com.axeda.drm.sdk.device.DataItemValue import com.axeda.drm.sdk.data.DataValue import com.axeda.drm.sdk.device.DeviceFinder import com.axeda.drm.sdk.device.Device import com.axeda.drm.sdk.mobilelocation.MobileLocation import com.axeda.drm.sdk.data.DataValueList import com.axeda.drm.sdk.data.CurrentDataFinder import com.axeda.drm.sdk.mobilelocation.CurrentMobileLocationFinder import groovy.xml.MarkupBuilder import com.axeda.platform.sdk.v1.services.ServiceFactory /* * ExportObjectToCSV.groovy * * Creates a csv file from either all assets of a model of a single asset that can then be used to import them back into another system. * * @param model        -   (REQ):Str model name. * @param serial        -   (OPT):Str serial number. * * @author Sara Streeter <sstreeter@axeda.com> */ def writer = new StringWriter() def xml = new MarkupBuilder(writer) InputStream is try {    Context CONTEXT = Context.getSDKContext()    ModelFinder modelFinder = new ModelFinder(CONTEXT)     modelFinder.setName(Request.parameters.model)    Model model = modelFinder.find()    DeviceFinder deviceFinder = new DeviceFinder(CONTEXT)    deviceFinder.setModel(model)    List<Device> devices = [] def exportkey = model.name Device founddevice if (Request.parameters.serial){     deviceFinder.setSerialNumber(Request.parameters.serial)    founddevice = deviceFinder.find()    logger.info(founddevice?.serialNumber)    if (founddevice != null){    devices.add(founddevice)    }    else throw new Exception("Device ${Request.parameters.serial} cannot be found.")    exportkey += "${founddevice.serialNumber}" } else {     devices = deviceFinder.findAll()     exportkey += "all" } // use a Data Accumulator to store the information def dataStoreIdentifier = "FILE-CSV-export_____" + exportkey def daSvc = new ServiceFactory().dataAccumulatorService if (daSvc.doesAccumulationExist(dataStoreIdentifier, devices[0].id.value)) {   daSvc.deleteAccumulation(dataStoreIdentifier, devices[0].id.value) } List<DataItem> dataItemList = devices[0].model.dataItems def firstrow = [ "model", "serial", "devicename", "conditionname", "currentlat","currentlng" ]                     def tempfirstrow = dataItemList.inject([]){list, dataItem ->             list << dataItem.name;             list         }         firstrow += tempfirstrow            firstrow = firstrow.join(',')         firstrow += '\n'         daSvc.writeChunk(dataStoreIdentifier, devices[0].id.value, firstrow);     CurrentMobileLocationFinder currentMobileLocationFinder = new CurrentMobileLocationFinder(CONTEXT) devices.each{ device ->                 CurrentDataFinder currentDataFinder = new CurrentDataFinder(CONTEXT, device)                 currentMobileLocationFinder.deviceId = device.id.value                 MobileLocation mobileLocation = currentMobileLocationFinder.find()                 def lat = 0                 def lng = 0                 if (mobileLocation){                     lat = mobileLocation?.lat                     lng = mobileLocation?.lng                 }                 def row =                 [                     device.model.name,                     device.serialNumber,                     device.name,                     device.condition?.name,                     lat,                     lng                     ]                                     def temprow = dataItemList.inject([]){ subList,dataItem ->                         DataValue value = currentDataFinder.find(dataItem.name)                                             def val = "NULL"                         val = value?.asString() != "?" ? value?.asString() : val                         subList <<  val                         subList                     }                 row += temprow                 row = row.join(',')                 row += '\n'                 daSvc.writeChunk(dataStoreIdentifier, devices[0].id.value, row);             }    // stream the data accumulator to create the file is = daSvc.streamAccumulation(dataStoreIdentifier, devices[0].id.value) def disposition = 'attachment; filename=CSVFile' + exportkey + '.csv' return ['Content-Type': 'text/csv', 'Content-Disposition':disposition, 'Content': is.text] } 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())        }    } logger.info(writer.toString()) return ['Content-Type': 'text/xml', 'Content': writer.toString()] } return ['Content-Type': 'text/xml', 'Content': writer.toString()] Back to Top Image Files (6.5+) The FileStore in Axeda version 6.5+ allows fine-grained control of uploaded and downloaded files. As Groovy scripts can return binary data via Scripto, this allows use cases such as embedding a Groovy script url as the source for an image. The following example uses the FileStore API to create an Image out of a valid image file, scales it to a smaller size and stores this smaller file. import com.axeda.drm.sdk.Context import com.axeda.drm.sdk.data.* import com.axeda.drm.sdk.device.* import com.axeda.drm.sdk.mobilelocation.CurrentMobileLocationFinder import com.axeda.drm.sdk.mobilelocation.MobileLocation import com.axeda.drm.sdk.mobilelocation.MobileLocationFinder import com.axeda.sdk.v2.bridge.FileInfoBridge import static com.axeda.sdk.v2.dsl.Bridges.* import com.axeda.services.v2.ExecutionResult import com.axeda.services.v2.FileInfo import com.axeda.services.v2.FileInfoReference import com.axeda.services.v2.FileUploadSession import net.sf.json.JSONObject import groovy.json.JsonBuilder import net.sf.json.JSONArray import com.axeda.drm.sdk.scripto.Request import org.apache.commons.io.IOUtils import org.apache.commons.lang.exception.ExceptionUtils import com.axeda.common.sdk.id.Identifier import groovy.json.* import javax.imageio.ImageIO; import java.awt.RenderingHints import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream; import java.awt.* import java.awt.geom.* import javax.imageio.* import java.awt.image.* import java.awt.Graphics2D import javax.imageio.stream.ImageInputStream /*    Image-specific FileStore entry point to post and store files */ def contentType = "application/json" final def serviceName = "StoreScaledImage" // Create a JSON Builder def json = new JsonBuilder() // Global try/catch. Gotta have it, you never know when your code will be exceptional! try {       Context CONTEXT = Context.getSDKContext()     def filesList = []     def datestring = new Date().time     InputStream inputStream = Request.inputStream       def reqbody = Request.body     // all of our Request Parameters are available here     def params = Request.parameters     def filename = Request?.headers?.'Content-Disposition' ?     Request?.headers?.'Content-Disposition' : "file___" + datestring + ".txt"     def filelabel = Request.parameters.filelabel ?: filename     def description = Request.parameters.description ?: filename     def contType = Request.headers?."content-type" ?: "image/jpeg"     def tag = Request.parameters.tag ?: "cappimg"     def encoded = Request.parameters.encoded?.toBoolean()   def dimlimit = params.dimlimit ? params.dimlimit : 280     // host is available in the headers when the script is called with AJAX     def domain = Request.headers?.host     byte[] bytes = IOUtils.toByteArray(inputStream);     def fileext = filename.substring(filename.indexOf(".") + 1,filename.size())     def outerMap = [:]     // check that file extension matches an image type     if (fileext ==~ /([^\s]+(\.(?i)(jpg|jpeg|png|gif|bmp))$)/){         if (inputStream.available() > 0) {                 def scaledImg                               try {                     def img = ImageIO.read(inputStream)                     def width = img?.width                              def height = img?.height                     def ratio = 1.0                     def newBytes                                       if (img){                                               if (width > dimlimit || height > dimlimit){                             // shrink by the smaller side so it can still be over the limit                             def dimtochange = width > height ? height : width                             ratio = dimlimit / dimtochange                                                       width = Math.floor(width * ratio).toInteger()                             height = Math.floor(height * ratio).toInteger()                         }                                             newBytes = doScale(img, width, height, ratio, fileext)                      if (newBytes?.size() > 0){                         bytes = newBytes                      }                     }                 }                 catch(Exception e){                     logger.info(e.localizedMessage)                                   }                                           outerMap.byteCount = bytes.size()                    FileInfoBridge fib = fileInfoBridge                 FileInfo myImageFile = new FileInfo(filelabel: filelabel,                                                     filename: filename,                                                     filesize: bytes?.size(),                                                     description: description,                                                     tags: tag                                                     )                    myImageFile.contentType = contType                    FileUploadSession fus = new FileUploadSession();                 fus.files = [myImageFile]                    ExecutionResult fer = fileUploadSessionBridge.create(fus);                 myImageFile.sessionId = fer.succeeded.getAt(0)?.id                               ExecutionResult fileInfoResult = fib.create(myImageFile)                               if (fileInfoResult.successful) {                     outerMap.fileInfoSave = "File Info Saved"                     outerMap.sessionId = "File Upload SessionID: "+fer.succeeded.getAt(0)?.id                     outerMap.fileInfoId = "FileInfo ID: "+fileInfoResult?.succeeded.getAt(0)?.id                     ExecutionResult er = fib.saveOrUpdate(fileInfoResult.succeeded.getAt(0).id,new ByteArrayInputStream(bytes))                     def fileInfoId = fileInfoResult?.succeeded.getAt(0)?.id                     String url = "${domain}/services/v1/rest/Scripto/execute/DownloadFile?fileId=${fileInfoId}"                     if (er.successful) {                         outerMap.url = url                     } else {                         outerMap.save = "false"                         logger.info(logFailure(er,outerMap))                     }                 } else {                     logger.info(logFailure(fileInfoResult, outerMap))                 }                } else {                 outerMap.bytesAvail = "No bytes found to upload"             }         } else {             outerMap.imagetype = "Extension $fileext is not a supported image file type."         }     filesList << outerMap     // return the JSONBuilder contents     // we specify the content type, and any object as the return (even an outputstream!)     return ["Content-Type": contentType,"Content":JSONArray.fromObject(filesList).toString(2)]     // alternately you may just want to serial an Object as JSON:     // return ["Content-Type": contentType,"Content":JSONArray.fromObject(invertedMessages).toString(2)] } catch (Exception e) {     // I knew you were exceptional!     // we'll capture the output of the stack trace and return it in JSON     json.Exception(             description: "Execution Failed!!! An Exception was caught...",             stack: ExceptionUtils.getFullStackTrace(e)     )     // return the output     return ["Content-Type": contentType, "Content": json.toPrettyString()] } def doScale(image, width, height, ratio, fileext){     if (image){     ByteArrayOutputStream baos = new ByteArrayOutputStream();     def bytes      def scaledImg = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB )        Graphics2D g = scaledImg.createGraphics();         g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);         g.scale(ratio,ratio)         g.drawImage(image, null, null);         g.dispose();              ImageIO.write( scaledImg, fileext, baos )       baos.flush()       bytes = baos.toByteArray()       baos.close()     }     else {         logger.info("image to be scaled is null")         return false     }   return bytes   } private void logFailure(ExecutionResult fileInfoResult, LinkedHashMap outerMap) {     outerMap.message = fileInfoResult.failures.getAt(0)?.message     outerMap.source = fileInfoResult.failures.getAt(0)?.sourceOfFailure     outerMap.details = fileInfoResult.failures.getAt(0)?.details?.toString()     outerMap.fileInfoSave = "false" } The next example makes use of the jQuery framework to upload an image to this script via an http POST. Note: This snippet is available as a jsFiddle at http://jsfiddle.net/LrxWF/18/ With HTML5 button: <input type="file" id="fileinput" value="Upload" /> var PLATFORM_HOST = document.URL.split('/apps/')[0]; // this is how you would retrieve the host on an Axeda instance var SESSION_ID = null // usually retrieved from login function included below /*** * Depends on jQuery 1.7+ and HTML5, assumes an HTML5 element such as the following: * <input type="file" id="fileinput" value="Upload" /> * **/ $("#fileinput").off("click.filein").on("click.filein", function () {     fileUpload() }) var fileUpload = function () {     $("#fileinput").off('change.fileinput')     $("#fileinput").on('change.fileinput', function (event) {         if (this.files && this.files.length > 0) {             handleFiles("http://" + PLATFORM_HOST, this.files)         }     }) } var handleFiles = function (host, files) {     $.each(files, function (index, file) {         var formData = new FormData();         var filename = file.name         formData.append(filename, file)         var url = host + '/services/v1/rest/Scripto/execute/StoreScaledImage?filelabel=' + filename + "&tag=myimg"         url = setSessionId(url)         jQuery.ajax(url, {             beforeSend: function (xhr) {                 xhr.setRequestHeader('Content-Disposition', filename);             },             cache: false,             cache: false,             processData: false,             type: 'POST',             contentType: false,             data: formData,             success: function (json) {                 refreshPage(json)                 console.log(json)             }         });     }) } var setSessionId = function (url) {     // you would already have this from logging in     return url + "&sessionid=" + SESSION_ID } var refreshPage = function (json) {     // here you would refresh your page with the returned JSON     return } /*** *  The following functions are not used in this demonstration, however they are necessary for a complete app and are found in axeda.js  http://gist.github.com/axeda/4340789 ***/     function login(username, password, success, failure) {         var reqUrl = host + SERVICES_PATH + 'Auth/login';         localStorage.clear()         return $.get(reqUrl, {             'principal.username': username,                 'password': password         }, function (xml) {             var sessionId = $(xml).find("ns1\\:sessionId, sessionId").text()             // var sessionId = $(xml).find("[nodeName='ns1:sessionId']").text(); - no longer works past jquery 1.7             if (sessionId) {                 // set the username and password vars for future logins.                         storeSession(sessionId);                 success(SESSION_ID); // return the freshly stored contents of SESSION_ID             } else {                 failure($(xml).find("faultstring").text());             }         }).error(function () {             $('#loginerror').html('Login Failed, please try again')         });     }; function storeSession(sessionId) {     var date = new Date();     date.setTime(date.getTime() + SESSION_EXPIRATION);     SESSION_ID = sessionId     document.cookie = APP_NAME + '_sessionId=' + SESSION_ID + '; expires=' + date.toGMTString() + '; path=/';     return true; }; The return JSON includes a URL that you can use as the source for images: [{   "byteCount": 14863,   "fileInfoSave": "File Info Saved",   "sessionId": "File Upload SessionID: 01234",   "fileInfoId": "FileInfo ID: 12345",   "url": "http://yourdomain.axeda.com/services/v1/rest/Scripto/execute/DownloadFile?fileId=12345" }] The DownloadFile Custom Object looks like the following: import static com.axeda.sdk.v2.dsl.Bridges.* import javax.activation.MimetypesFileTypeMap import com.axeda.services.v2.* import com.axeda.sdk.v2.exception.* import com.axeda.drm.sdk.scripto.Request def knowntypes = [          [png: 'image/png']         ,[gif: 'image/gif']         ,[jpg: 'image/jpeg']     ] def params = Request.parameters.size() > 0 ? Request.parameters : parameters def response = fileInfoBridge.getFileData(params.fileId) def fileinfo = fileInfoBridge.findById(params.fileId) def type = fileinfo.filename.substring(fileinfo.filename.indexOf('.') + 1,fileinfo.filename.size()) type = returnType(knowntypes, type) def contentType = params.type ?: (type ?: 'image/jpg') return ['Content': response, 'Content-Disposition': contentType, 'Content-Type':contentType] def returnType(knowntypes, ext){     return knowntypes.find{ it.containsKey(ext) }?."$ext" } Make sure to append a valid session id to the end of the URL when using it as the source for an image. The techniques discussed above can be applied to any type of binary file output with consideration for the type of file being processed. A Word on Streaming Content streaming such as streaming of video or audio files over UDP is not currently supported by the Axeda Platform.
View full tip