Axeda: Create and Find Extended List of Data Items - Output to XML

    This code creates an Extended List (supported since 6.1.6) of data item names, then finds that list and returns only the current and historical data items on the list for all the assets of a given model in XML.  In this example, Extended Lists are used instead of data item groups to store a certain subset of data items that should be returned.  In a real use case, the list of data item names would be created in a separate call, then the list would be accessed after the fact.  For purposes of demonstration, this script does both.

     

    Parameters:

    1. model_name
    2. data_item_names
    3. from_time
    4. to_time

     

    import com.axeda.drm.sdk.Context
    import com.axeda.drm.sdk.device.ModelFinder
    import com.axeda.drm.sdk.device.Model
    import com.axeda.drm.sdk.device.DeviceFinder
    import com.axeda.drm.sdk.data.CurrentDataFinder
    import com.axeda.drm.sdk.device.Device
    import com.axeda.drm.sdk.data.HistoricalDataFinder
    import com.axeda.sdk.v2.bridge.ExtendedListBridge
    import com.axeda.sdk.v2.dsl.Bridges
    import com.axeda.services.v2.ExtendedList
    import com.axeda.services.v2.ExtendedListCriteria
    import com.axeda.services.v2.ExtendedListReference
    import groovy.xml.MarkupBuilder
    
    /*
    * DIListToXML.groovy
    *
    * Create a list of data items on an asset, then use that list to find selected data items for all assets in a given model.
    *
    * @param model_name        -   (REQ):Str name of the model.
    * @param data_item_names   -    (REQ): Str list of data item names comma delimited, ie  DataName1,DataName2
    * @param from_time         -   (REQ):Long millisecond timestamp to begin query from.
    * @param to_time           -   (REQ):Long millisecond timestamp to end query at.
    *
    * @note from_time and to_time should be provided because it limits the query size.
    *
    * @author Sara Streeter <sstreeter@axeda.com>
    */
    
    def response = [:]
    def writer = new StringWriter()
    def xml = new MarkupBuilder(writer)
    
    // measure the script run time
    def timeProfiles = [:]
    def scriptStartTime = new Date()
    
    try {
    
    // getUserContext is supported as of release 6.1.5 and higher
        final def CONTEXT = Context.getUserContext()
    
    // confirm that required parameters have been provided
        validateParameters(actual: parameters, expected: ["model_name", "data_item_names", "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 or create our Extended List
        def listName = "${parameters.model_name}-data_item_list"
        ExtendedListBridge listBridge = Bridges.extendedListBridge
    
        def results = listBridge.find(listName)
    
        def data_item_list
    
        if (results.list != null) {
            data_item_list = results
        }
        else {
            data_item_list = new ExtendedList()
            data_item_list.name = listName
        }
    
    // split out the data items into an Extended List
        def items = parameters.data_item_names.tokenize(",")
    
        items.each { entry ->
    
            if (!data_item_list.list.contains(entry)) {
                data_item_list.list.add(entry)
            }
        }
        listBridge.save(data_item_list)
        data_item_list.list.each {
    
        }
    
    // 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 the data items on the list 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)
    
        def currentValue
        def valueList = []
    
        xml.Response() {
            Assets() {
                assets.each { Device asset ->
                    currentDataFinder.device = asset
                    Asset() {
                        id(asset.id)
                        name(asset.name)
                        serial_number(asset.serialNumber)
                        model_id(asset.model.id)
                        model_name(asset.model.name)
                        current_data() {
                            data_item_list.list.each { data ->
                                currentValue = currentDataFinder.find(data)
                                data_item() {
                                    timestamp(currentValue?.getTimestamp()?.format("yyyyMMdd HH:mm"))
                                    name(currentValue?.dataItem?.name)
                                    value(currentValue?.asString())
                                }
                            }
                        }
                        historical_data() {
                            data_item_list.list.each { data ->
                                historicalDataFinder.device = asset
                                valueList = historicalDataFinder.find(currentValue.dataItem)
                                valueList.each { hd ->
                                    data_item() {
                                        timestamp(hd?.getTimestamp()?.format("yyyyMMdd HH:mm"))
                                        name(hd?.dataItem?.name)
                                        value(hd?.asString())
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    
    } catch (def ex) {
        xml.Response() {
            Fault {
                Code('Groovy Exception')
                Message(ex.getMessage())
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                ex.printStackTrace(pw);
                Detail(sw.toString())
            }
        }
    }
    
    
    return ['Content-Type': 'text/xml', 'Content': writer.toString()]
    
    private Map createTimeProfile(String label, Date startTime, Date endTime) {
        [
                (label): [
                        startTime: [timestamp: startTime.time, readable: startTime.toString()],
                        endTime: [timestamp: endTime.time, readable: endTime.toString()],
                        profile: [
                                elapsed_millis: endTime.time - startTime.time,
                                elapsed_secs: (endTime.time - startTime.time) / 1000
                        ]
                ]
        ]
    }
    
    private validateParameters(Map args) {
        if (!args.containsKey("actual")) {
            throw new Exception("validateParameters(args) requires 'actual' key.")
        }
        if (!args.containsKey("expected")) {
            throw new Exception("validateParameters(args) requires 'expected' key.")
        }
    
        def config = [
                require_username: false
        ]
    
        Map actualParameters = args.actual.clone() as Map
        List expectedParameters = args.expected
    
        config.each { key, value ->
            if (args.options?.containsKey(key)) {
                config[key] = args.options[key]
            }
        }
    
        if (!config.require_username) { actualParameters.remove("username") }
    
        expectedParameters.each { paramName ->
            if (!actualParameters.containsKey(paramName) || !actualParameters[paramName]) {
                throw new IllegalArgumentException(
                        "Parameter '${paramName}' was not found in the query; '${paramName}' is a reqd. parameter.")
            }
        }
    }
    

     

    Sample Output:

     

    <Response>
      <Assets>
        <Asset>
          <id>5052</id>
          <name>VendingMachine_1</name>
          <serial_number>VendingMachine_1</serial_number>
          <model_id>1616</model_id>
          <model_name>VendingMachineModel</model_name>
          <current_data>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentBalance</name>
              <value>0.0</value>
            </data_item>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentStock</name>
              <value>180.0</value>
            </data_item>
          </current_data>
          <historical_data />
        </Asset>
        <Asset>
          <id>5056</id>
          <name>VendingMachine_5</name>
          <serial_number>VendingMachine_5</serial_number>
          <model_id>1616</model_id>
          <model_name>VendingMachineModel</model_name>
          <current_data>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentBalance</name>
              <value>0.0</value>
            </data_item>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentStock</name>
              <value>180.0</value>
            </data_item>
          </current_data>
          <historical_data />
        </Asset>
        <Asset>
          <id>5054</id>
          <name>VendingMachine_3</name>
          <serial_number>VendingMachine_3</serial_number>
          <model_id>1616</model_id>
          <model_name>VendingMachineModel</model_name>
          <current_data>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentBalance</name>
              <value>0.0</value>
            </data_item>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentStock</name>
              <value>180.0</value>
            </data_item>
          </current_data>
          <historical_data />
        </Asset>
        <Asset>
          <id>5053</id>
          <name>VendingMachine_2</name>
          <serial_number>VendingMachine_2</serial_number>
          <model_id>1616</model_id>
          <model_name>VendingMachineModel</model_name>
          <current_data>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentBalance</name>
              <value>0.0</value>
            </data_item>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentStock</name>
              <value>180.0</value>
            </data_item>
          </current_data>
          <historical_data />
        </Asset>
        <Asset>
          <id>5055</id>
          <name>VendingMachine_4</name>
          <serial_number>VendingMachine_4</serial_number>
          <model_id>1616</model_id>
          <model_name>VendingMachineModel</model_name>
          <current_data>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentBalance</name>
              <value>0.0</value>
            </data_item>
            <data_item>
              <timestamp>20120419 02:55</timestamp>
              <name>CurrentStock</name>
              <value>180.0</value>
            </data_item>
          </current_data>
          <historical_data />
        </Asset>
      </Assets>
    </Response>