How do I pass an XML child element to a sub-function?

LAN device consisting of a server and multiple environmental sensors pushes XML data to HE. The XML data has one parent element (for the server) and one or more child elements (the sensors). The sensors can be several different "types".

Every time data is received by the parent driver's parse() function, I want to process each child element (sensor) with the updated data by calling a function for the sensor device type. Here's the code so far...

    xmlObj.owd_DS18B20.each{ sensor->
        log.debug ("Calling DS18B20 sensor update function...")
        sensor.updateDS18B20()
    }

    xmlObj.owd_EDS0068.each{ sensor->
        log.debug ("Calling EDS0068 sensor update function...")
        sensor.updateEDS0068()
    }

    return
}

def updateDS18B20(String sensor) {
    
//    Update DS18B20 child device -- Temp sensor

    log.debug ("Updating DS18B20 Sensor...")
    sensorROMId = sensor.ROMId;
    log.debug ("ROM ID: ${sensorROMId}")

    return
}

def updateEDS0068(String sensor) {
    
//    Update EDS0068 child device -- Temp/Humidity/Pressure/Light sensor

    log.debug ("Updating EDS0068 Sensor...")
    sensorROMId = sensor.ROMId;
    log.debug ("ROM ID: ${sensorROMId}")
  
    return
}

There's probably several errors in the above code, but it does "compile" like it is. I've tried several different things, but it always gets a compile error or throws an exception. What class should be used instead of "String"?

The above version of the code gets throws this exception...

groovy.lang.MissingMethodException: No signature of method: groovy.util.slurpersupport.NodeChild.updateDS18B20() is applicable for argument types: () values: on line 228 (method parse)

Note that if I move the code from the child functions into the calling function, the code works as expected, i.e., this works as expected...

    xmlObj.owd_DS18B20.each{ sensor->
        sensorROMId = sensor.ROMId;
        log.debug ("ROM ID: ${sensorROMId}")
    }

    xmlObj.owd_EDS0068.each{ sensor->
        sensorROMId = sensor.ROMId;
        log.debug ("ROM ID: ${sensorROMId}")
    }

    return
}

Try this when you call it:

updateDS18B20(sensor)

But your sensor member is probably a NodeChild or some other complex type. You can track that down and type it explicitly in the parameter list, or you can just declare it like this and then sort it out inside updateXXX function:

def updateDS18B20(sensor) { 
//your function
}

I changed the calling function to what you suggested and specified groovy.util.slurpersupport.NodeChild as the parameter class for the called functions.

Now the called functions are being driven, but I keep getting syntax errors in them when I try to access any of the XML sensor values, e.g.,...

temperature = sensor.Temperature

...saves fine in the calling function, but gets this syntax error in the called functions...

Expression [VariableExpression] is not allowed: sensor at line number 244

Stumped again.

Maybe NodeChild was bad advice. Maybe try something like this?

import groovy.util.slurpersupport.GPathResult

def printIt(GPathResult xmlObj)
{
    log.debug "PollCount = "
    log.debug xmlObj.PollCount
}

If that doesn't help, please post more code (with line numbers) and more log output of what the XML content looks like. It's impossible to guess what is going wrong without more context.

Here's the latest code and log. Note that I now have two sensors (DS1B820 and EDS0068)...

You should be able to break down the data just by parsing the XML, so you can make it a little bit more dynamic. Here's a version from one of the longer dumps you posted in your previous thread.

import groovy.util.slurpersupport.GPathResult

def processResponse(GPathResult xmlObj)
{
    log.debug "Inspecting XML..."
    xmlObj.children().each()
    {
        if(it.name().startsWith('owd'))
        {
            log.debug "Node found"
            log.debug "Name = ${it.Name}"
            
            it.children().each()
            {
                if(it.@Units != "")
                {
                    log.debug "Measurement type = ${it.name()}"
                    log.debug "Units = ${it.@Units}"
                    log.debug "Value = ${it.toFloat()}"
                }
            }
        }
    }
}

There are several different types of sensors. The last log I posted contains two of them. My goal is to pass a child node to a sensor-specific function for "individual processing" based on the child's tag (e.g., owd_DS18B20, owd_EDS0068, etc.) or the child's "Name" value (e.g., DS18B20, EDS0068, etc.). The sensor values vary by device. For example, the DS18B20 has about a dozen values, whereas the EDS0068 has about 70. Processing requirements will vary depending on the sensor type. That's why I want to separate the functions by sensor type, i.e., to make the code more readable/manageable.

Your function works, but it looks to me like a "general purpose" function that is passed the complete XML doc and it processes all types of sensors in the same function.

That was just an example of how to probe in to the data in the XML document. Here's how you might structure it differently if you were writing sensor handlers.

import groovy.util.slurpersupport.GPathResult

void processResponse(GPathResult xmlObj)
{
    log.debug "Inspecting XML..."
    xmlObj.children().each()
    {
        switch(it.name())
        {
            case "owd_EDS0068":
                owd_EDS0068(it)
                break
            
            default:
                log.error "no handler found for (${it.name()})"
                return
        }
    }
}

void commonOwdProcessing(GPathResult xmlObj)
{
    xmlObj.children().each()
    {
        if(it.@Units != "")
        {
            log.debug "Measurement type = ${it.name()}"
            log.debug "Units = ${it.@Units}"
            log.debug "Value = ${it.toFloat()}"
        }
    }
}

void owd_EDS0068(GPathResult xmlObj)
{
    commonOwdProcessing(xmlObj)
    
    // do anything specific to this sensor type
    log.debug "Health = ${xmlObj.Health}"    
}
1 Like

That works. If you look at the previous code I posted, lines 219-230 were copied from the driver I am using as a template. I'm not sure why but replacing that code with the code in your processResponse function works the way I wanted it to.

Again, thanks for the help.

2 Likes