Help Writing Device Driver for RestAPI Call for Sony Soundbar

I would recommend just passing the json response directly to jsonreturnaction:
jsonreturnaction(response)

The response you are getting back is already formatted json (you used the httpPostJson method), so the parseJson is not needed.

Okay, new github file since i decided to restart the driver and remove a bunch of junk i dont need yet.

  1. I was able to parse what i wanted from the httpPostJson method by just listing "response.data.id" and for a value i used response.data.result[0]?.status == "active" I couldnt find much documentation for this and just started trying things with a lot of logging on. (this eliminated having to roll out my own parsing method.
  2. I now can invoke each function in groups and it sends the post and parse in the order called. (YAY)
  3. My last problem is that line 69 works correctly, but line 90 always returns "unmuted", even though logging shows it has [on]. I have also changed "on" to "[on]" on line 90 as well and that doesnt work. It always shows unmuted. Any idea what is going on here?

debug logs are below.

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.565 pm [debug](http://192.168.1.11/device/edit/4609)Devicemute is 'unmuted'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.563 pm [debug](http://192.168.1.11/device/edit/4609)Mute is [on]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.562 pm [debug](http://192.168.1.11/device/edit/4609)result is [[[maxVolume:50, minVolume:0, mute:on, output:, step:1, volume:14]]]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.560 pm [debug](http://192.168.1.11/device/edit/4609)ID is 600

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.557 pm [debug](http://192.168.1.11/device/edit/4609)Sony Response: Success ([id:600, result:[[[maxVolume:50, minVolume:0, mute:on, output:, step:1, volume:14]]]])

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.535 pm [debug](http://192.168.1.11/device/edit/4609)[uri:http://192.168.1.206:10000, path:/sony/audio, query:null, body:{"method":"getVolumeInformation","version":"1.1","params":[{"output":""}],"id":600}]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.533 pm [debug](http://192.168.1.11/device/edit/4609)Executing 'getMuteStatus'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.530 pm [debug](http://192.168.1.11/device/edit/4609)no id found for result action

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.528 pm [debug](http://192.168.1.11/device/edit/4609)DeviceSublevel is '[12]'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.526 pm [debug](http://192.168.1.11/device/edit/4609)SubLevel is [12]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.525 pm [debug](http://192.168.1.11/device/edit/4609)result is [[[candidate:[[isAvailable:true, max:12, min:0, step:1, title:Subwoofer Volume, titleTextID:sound-subwoofer, value:subwooferLevel]], currentValue:12, deviceUIInfo:sliderHorizon, isAvailable:true, target:subwooferLevel, title:Subwoofer Volume, titleTextID:sound-subwoofer, type:integerTarget]]]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.522 pm [debug](http://192.168.1.11/device/edit/4609)ID is 59

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.520 pm [debug](http://192.168.1.11/device/edit/4609)Sony Response: Success ([id:59, result:[[[candidate:[[isAvailable:true, max:12, min:0, step:1, title:Subwoofer Volume, titleTextID:sound-subwoofer, value:subwooferLevel]], currentValue:12, deviceUIInfo:sliderHorizon, isAvailable:true, target:subwooferLevel, title:Subwoofer Volume, titleTextID:sound-subwoofer, type:integerTarget]]]])

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.500 pm [debug](http://192.168.1.11/device/edit/4609)[uri:http://192.168.1.206:10000, path:/sony/audio, query:null, body:{"method":"getSoundSettings","version":"1.1","params":[{"target":"subwooferLevel"}],"id":59}]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.497 pm [debug](http://192.168.1.11/device/edit/4609)Executing 'getSubLevel'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.495 pm [debug](http://192.168.1.11/device/edit/4609)no id found for result action

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.493 pm [debug](http://192.168.1.11/device/edit/4609)DeviceVolume is '[14]'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.489 pm [debug](http://192.168.1.11/device/edit/4609)Volume is [14]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.479 pm [debug](http://192.168.1.11/device/edit/4609)result is [[[maxVolume:50, minVolume:0, mute:on, output:, step:1, volume:14]]]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.476 pm [debug](http://192.168.1.11/device/edit/4609)ID is 78

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.472 pm [debug](http://192.168.1.11/device/edit/4609)Sony Response: Success ([id:78, result:[[[maxVolume:50, minVolume:0, mute:on, output:, step:1, volume:14]]]])

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.446 pm [debug](http://192.168.1.11/device/edit/4609)[uri:http://192.168.1.206:10000, path:/sony/audio, query:null, body:{"method":"getVolumeInformation","version":"1.1","params":[{"output":""}],"id":78}]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.443 pm [debug](http://192.168.1.11/device/edit/4609)Executing 'getSoundVolume'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.441 pm [debug](http://192.168.1.11/device/edit/4609)no id found for result action

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.439 pm [debug](http://192.168.1.11/device/edit/4609)Device State is 'on'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.437 pm [debug](http://192.168.1.11/device/edit/4609)Status is active

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.436 pm [debug](http://192.168.1.11/device/edit/4609)result is [[standbyDetail:, status:active]]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.434 pm [debug](http://192.168.1.11/device/edit/4609)ID is 2

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.432 pm [debug](http://192.168.1.11/device/edit/4609)Sony Response: Success ([id:2, result:[[standbyDetail:, status:active]]])

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.403 pm [debug](http://192.168.1.11/device/edit/4609)[uri:http://192.168.1.206:10000, path:/sony/system, query:null, body:{"id":2,"method":"getPowerStatus","version":"1.1","params":[]}]

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.401 pm [debug](http://192.168.1.11/device/edit/4609)Executing 'getPowerStatus'

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-12 04:06:23.398 pm [debug](http://192.168.1.11/device/edit/4609)UpdateAll pushed

I guess this is maybe more a generic groovy question, but

line 89
log.debug "Mute is ${response.data.result[0]?.mute}"

returns either [on] or [off]

so this is an array that i need to get "on" or "off" back from. How do i convert response.data.result.mute to a value without the brackets?

i added the below code to try to get this back (assuming its an array, and this threw a groovy error, expecting an array
def jsonData = parse(response.data.result.mute)

[dev:4609](http://192.168.1.11/logs#dev4609)2020-11-13 12:31:40.128 am [error](http://192.168.1.11/device/edit/4609)groovy.lang.MissingMethodException: No signature of method: user_driver_ajones_Sony_REST_API_1985.parse() is applicable for argument types: (java.util.ArrayList) values: [[[off]]] Possible solutions: use([Ljava.lang.Object;), wait(), run(), mute(), run(), grep() on line 100 (UpdateAll)

Normally you just call the array element name and place it's value into an appropriate variable. I think the problem is that the response has an oddly-layered array. The result is what, 3 layers deep?

A direct call to them would be something like (I could be wrong on the depth there, tough to tell at times for me):
response.data.result[0][0][0].maxVolume
response.data.result[0][0][0].minVolume
response.data.result[0][0][0].mute
response.data.result[0][0][0].output
response.data.result[0][0][0].step
response.data.result[0][0][0].volume

So the mute (line 90) should look like:
state.devicemute = response.data.result[0][0][0].mute

Maybe force it using:
state.devicemute = response.data.result[0][0][0].mute as string

Although the json tool I often use here, does not particularly like this response. I think it is a bit too processed already. You could probably not even use the httpPostJson method and just use httpPost itself because the responses coming back are json (just uncomment your requestContentType to be sure).

You are a lifesaver! By the way appriciate you walking me through this, hopefully another programmer new to groovy(and hubitat) can follow this a bit if they have some basic questions. I went through how many articles online that didn't really have a solution and took the [on] and did a find and replace function to strip the brackets out which is very hacky. it was two deep, so my final call was response.data.result[0][0].mute. I even did this for my volume and sublevel and those worked fine.

So now that i have a pretty simple base, I'm messing with the capabilities. I have added
capability "MusicPlayer
but it added all the command buttons, many i dont want. Is it possible to force hide some of the commands to clean up the page?

1 Like

I don't believe so, but there are a bunch of benefits to using the built-in device classes so that the commands show up in apps that use capability filtering, like RM and the like. When I really don't want to put the effort or just can't figure out how to support a built-in command, I just stub it out and print to debug that it is unsupported. Otherwise you'll get an unsightly exception printed in the log if someone uses it.

With that said, you are on a roll and should totally just implement the rest of MusicPlayer. This was really useful to me for the TTS-related operations.

@ajones: No problem! I have been grinding my way though learning to write drivers and am by no means an expert, but I try to help if I can.

As @tomw mentioned, there is no way to "dynamically" control commands. I always leave the code in (might need it) but comment out the command so it cannot be run. I certainly wish it was possible. That would make it a lot easier for many drivers (I have some that I want to support multiple devices, but then there are extra commands that only work with some).

Preferences are a bit different though. You can control whether those show up so almost all my drivers have a "Show All Preferences" option now, just to hide stuff that most people do not want to see once they have it all set. Plus some drivers end up with LOTS of preferences.

Was plugging away at this and found that Sony pretty much made all TV's backward compatible, but for audio devices, a specific product might be on 1.0, but another is on 1.2. In order to not make a driver for each specific model number, I will detect the current API version for specific methods that are used is the driver and set a state variable for each method. Luckily Sony at least gave an API to detect versions. Okay so on to my question.

My raw response.data is below. I am trying to bring back the version number for "getPowerStatus".
In some cases, like "getSystemInformation", there might be two. Preference would be to use the last, but I could use the first if its easier and will always be there.

Edit: To clarify, I would want to specify "system" and then in there "getPowerStatus" and recieve "1.1" back.

response.data

{
"id": 5,
"result": [
[
{
"apis": [
{
"name": "actSWUpdate",
"versions": [
{
"authLevel": "generic",
"version": "1.0"
}
]
},
{
"name": "connectBluetoothDevice",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getDeviceMiscSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getInterfaceInformation",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getMethodTypes",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getPowerSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getPowerStatus",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "getSWUpdateInfo",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getSettingsTree",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "getSleepTimerSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getStorageList",
"versions": [
{
"authLevel": "generic",
"version": "1.1"
},
{
"version": "1.2"
}
]
},
{
"name": "getSystemInformation",
"versions": [
{
"version": "1.3"
},
{
"version": "1.4"
}
]
},
{
"name": "getVersions",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getWuTangInfo",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setClientInfo",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setDeviceMiscSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setPowerSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setPowerStatus",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "setSleepTimerSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setWuTangInfo",
"versions": [
{
"authLevel": "generic",
"version": "1.0"
}
]
},
{
"name": "switchNotifications",
"versions": [
{
"protocols": [
"websocket:jsonizer"
],
"version": "1.0"
}
]
}
],
"notifications": [
{
"name": "notifyPowerStatus",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "notifySWUpdateInfo",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "notifySettingsUpdate",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "notifyStorageStatus",
"versions": [
{
"authLevel": "generic",
"version": "1.1"
},
{
"version": "1.2"
}
]
}
],
"protocols": [
"xhrpost:jsonizer",
"websocket:jsonizer"
],
"service": "system"
},
{
"apis": [
{
"name": "getAvailablePlaybackFunction",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getBluetoothSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getContentCount",
"versions": [
{
"version": "1.3"
}
]
},
{
"name": "getContentList",
"versions": [
{
"version": "1.4"
}
]
},
{
"name": "getCurrentExternalTerminalsStatus",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getMethodTypes",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getPlaybackModeSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getPlayingContentInfo",
"versions": [
{
"version": "1.2"
}
]
},
{
"name": "getSchemeList",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getSourceList",
"versions": [
{
"version": "1.1"
},
{
"version": "1.2"
}
]
},
{
"name": "getSupportedPlaybackFunction",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getVersions",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "pausePlayingContent",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "presetBroadcastStation",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "scanPlayingContent",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "seekBroadcastStation",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setActiveTerminal",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setBluetoothSettings",
"versions": [
{
"authLevel": "generic",
"version": "1.0"
}
]
},
{
"name": "setPlayContent",
"versions": [
{
"version": "1.2"
}
]
},
{
"name": "setPlayNextContent",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setPlayPreviousContent",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "setPlaybackModeSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "startContentBrowsing",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "stopPlayingContent",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "switchNotifications",
"versions": [
{
"protocols": [
"websocket:jsonizer"
],
"version": "1.0"
}
]
}
],
"notifications": [
{
"name": "notifyAvailablePlaybackFunction",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "notifyExternalTerminalStatus",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "notifyPlayingContentInfo",
"versions": [
{
"authLevel": "private",
"version": "1.0"
}
]
}
],
"protocols": [
"xhrpost:jsonizer",
"websocket:jsonizer"
],
"service": "avContent"
},
{
"apis": [
{
"name": "getMethodTypes",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getServiceProtocols",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getSupportedApiInfo",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getVersions",
"versions": [
{
"version": "1.0"
}
]
}
],
"protocols": [
"xhrpost:jsonizer"
],
"service": "guide"
},
{
"apis": [
{
"name": "getCustomEqualizerSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getMethodTypes",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getSoundSettings",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "getSpeakerSettings",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getVersions",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "getVolumeInformation",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "setAudioMute",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "setAudioVolume",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "setCustomEqualizerSettings",
"versions": [
{
"authLevel": "generic",
"version": "1.0"
}
]
},
{
"name": "setSoundSettings",
"versions": [
{
"version": "1.1"
}
]
},
{
"name": "setSpeakerSettings",
"versions": [
{
"authLevel": "generic",
"version": "1.0"
}
]
},
{
"name": "switchNotifications",
"versions": [
{
"protocols": [
"websocket:jsonizer"
],
"version": "1.0"
}
]
}
],
"notifications": [
{
"name": "notifyVolumeInformation",
"versions": [
{
"version": "1.0"
}
]
},
{
"name": "notifyWirelessSurroundInfo",
"versions": [
{
"version": "1.0"
}
]
}
],
"protocols": [
"xhrpost:jsonizer",
"websocket:jsonizer"
],
"service": "audio"
}
]
]
}

I have put the string into a json parser to try and make sense of it, but am not sure how to bring it back.

My other queries are using simpler responses like on the below, i use
response.data.result[0]?.modelName

response.data

{
"id": 33,
"result": [
{
"interfaceVersion": "4.0.0",
"modelName": "HT-CT800",
"productCategory": "homeTheaterSystem",
"productName": "Bar",
"serverName": ""
}
]
}

Try something like this: https://stackoverflow.com/a/14044969

Total shot in the dark, not tested (EDIT: and definitely missing some needed defensive null-safe additions).

def theKey = response?.data?.result[0].find { it.protocols[0] == "system" }?.key
log.debug "response.data.result[0][theKey].apis"

If that works then you can do something similar to find the one you want within that entry.

Ahh I see. It's like a vlookup for the json map. Will try this tomorrow. Wife grounded me for the night. Said I was squinting too much today. :slight_smile:

1 Like

Yeah, that comes back with null. Whats the null safe addition?

I misunderstood the Json diagram. Thanks for sharing the raw data -- that helped connect the dots.

Try this:

def theKey = (response?.data?.result[0].find { it.service == "system" })?.key
log.debug "response.data.result[0][theKey].apis"

its not recognizing the "theKey". When i log what that is, it shows null.

My code snippet is below.

if (response.data?.id == 998) {

    //Set the Global value of state.SupportedAPIs

    if (logEnable) log.debug "SupportedAPIs is ${response.data}"

    def sprtapirespX = response.data

    //state.sprtapiresp = response.data

    sendEvent(name: "SupportedAPI", value: sprtapirespX, isStateChange: true)

    //system version lookups

    def theKey = (response?.data?.result[0].find { it.service == "system" })?.key

    if (logEnable) log.debug "'thekey' is ${theKey}"

    if (logEnable) log.debug "response.data.result[0][theKey].apis"

My code example from the other night was probably wrong. Plus I still think maybe I don't understand the JSON structure, and trying to read the screenshot of the debug print makes my eyes start to cross.

Here's an example where I built up a JSON sample that I think mirrors what your data will look like. Then I start digging out various parts of it to get the data you want.

If the structure isn't quite right, hopefully this gives enough clues to keep you going.

def traceRandomCode()
{
def result = '''
{"data":
    {"id":"5", 
        "result":
        [        
            [
                {
                "apis":
                [
                    {"name":"getPowerStatus", "versions":[{"version":"1.1"}]},
                    {"name":"getPowerValue", "versions":[{"version":"1.2"}]}
                ], 
                "protocols":
                [
                    {"name":"protocolA"},
                    {"name":"protocolB"}
                ],
                "service":"system"
                },

                {
                "apis":
                [
                    {"name":"getPowerLevel", "versions":[{"version":"1.3"}]}
                ], 
                "protocols":
                [
                    {"name":"protocolC"},
                    {"name":"protocolD"}
                ],
                "service":"avContent"
                }
            ]        
        ]
    }
}'''
    def resJson = new groovy.json.JsonSlurper().parseText(result)

    def theRes = resJson?.data?.result?.get(0)?.find { it.service == "system" }
    log.debug "theRes = ${theRes}"
    def theApi = theRes?.apis?.find { it.name == "getPowerStatus" }
    log.debug "theApi = ${theApi}"
    for(thisVers in theApi?.versions) {log.debug "${thisVers?.version}"}
}

Did you get any better results with that, @ajones?

Umm not so much. Im not sure if its because i use the default json parser and dont run it through my own. I use httpPostJSON which already brings back a parsed response. I posted my develop branch at the below link for full reference.

However a few key snippets as to what im doing.

My button to send the JSON parms to the httpPostJSON call

Summary

def getSupportedAPIInfo(){

    if (logEnable) log.debug "Executing 'getSupportedAPIInfo' "

def lib = "/sony/guide"

def json = "{\"method\":\"getSupportedApiInfo\",\"id\":998,\"params\":[{\"services\":[\"system\",\"avContent\",\"guide\",\"appControl\",\"audio\",\"videoScreen\"]}],\"version\":\"1.0\"}"

postAPICall(lib,json)

}

My httpPOSTjson

Summary

private postAPICall(lib,json) {

def headers = [:]

    headers.put("HOST", "${settings.ipAddress}:${settings.ipPort}")

    //headers.put("Content-Type", "application/json")

    headers.put("X-Auth-PSK", "${settings.PSK}")



def requestParams = [

    uri:  "http://${settings.ipAddress}:${settings.ipPort}" ,

    path: lib,

    headers: headers,

//requestContentType: "application/json",

    query: null,

    body: json,

]

if (logEnable) log.debug "${requestParams}"

httpPostJson(requestParams) { response ->

    def msg = ""

    if (response?.status == 200) {

        msg = "Success"

    }

    else {

        msg = "${response?.status}"

    }

    if (logEnable) log.debug "Sony Response: ${msg} (${response.data})"

    if (response.data.id != 999){

        jsonreturnaction(response)

    }

    if (response.data.id == 999){

        jsonreturnaction(response)

    }

}

}

A shortened version of my response logic for the ID 998.

Summary

private jsonreturnaction(response){

if (logEnable) log.debug "ID is ${response.data.id}"

if (logEnable) log.debug "raw data result is ${response.data.result}"

String responsedataerror = response.data.error

if (logEnable) log.debug "dataerrorstring is ${responsedataerror}"

if (responsedataerror != null){

log.warn "data error is ${response.data.error}"

}

if (response.data?.id == 998) {

//Set the Global value of state.SupportedAPIs

if (logEnable) log.debug "SupportedAPIs is ${response.data}"

def sprtapirespX = response.data

sendEvent(name: "SupportedAPI", value: sprtapirespX, isStateChange: true)



def resJson = new groovy.json.JsonSlurper().parseText(response)

def theRes = resJson?.data?.result?.get(0)?.find { it.service == "system" }

log.debug "theRes = ${theRes}"

def theApi = theRes?.apis?.find { it.name == "getPowerStatus" }

log.debug "theApi = ${theApi}"

for(thisVers in theApi?.versions) {log.debug "${thisVers?.version}"}



else {if (logEnable) log.debug "no id found for result action"}

}

Debug Log when i send the request.

Summary

dev:52492020-12-17 09:23:30.917 pm errorjava.lang.IllegalArgumentException: Text must not be null or empty on line 268 (getCapability)

dev:52492020-12-17 09:23:30.807 pm debugSupportedAPIs is [id:998, result:[[[apis:[[name:actSWUpdate, versions:[[authLevel:generic, version:1.0]]], [name:connectBluetoothDevice, versions:[[version:1.0]]], [name:getDeviceMiscSettings, versions:[[version:1.0]]], [name:getInterfaceInformation, versions:[[version:1.0]]], [name:getMethodTypes, versions:[[version:1.0]]], [name:getPowerSettings, versions:[[version:1.0]]], [name:getPowerStatus, versions:[[version:1.1]]], [name:getSWUpdateInfo, versions:[[version:1.0]]], [name:getSettingsTree, versions:[[version:1.1]]], [name:getSleepTimerSettings, versions:[[version:1.0]]], [name:getStorageList, versions:[[authLevel:generic, version:1.1], [version:1.2]]], [name:getSystemInformation, versions:[[version:1.3], [version:1.4]]], [name:getVersions, versions:[[version:1.0]]], [name:getWuTangInfo, versions:[[version:1.0]]], [name:setClientInfo, versions:[[version:1.0]]], [name:setDeviceMiscSettings, versions:[[version:1.0]]], [name:setPowerSettings, versions:[[version:1.0]]], [name:setPowerStatus, versions:[[version:1.1]]], [name:setSleepTimerSettings, versions:[[version:1.0]]], [name:setWuTangInfo, versions:[[authLevel:generic, version:1.0]]], [name:switchNotifications, versions:[[protocols:[websocket:jsonizer], version:1.0]]]], notifications:[[name:notifyPowerStatus, versions:[[version:1.0]]], [name:notifySWUpdateInfo, versions:[[version:1.0]]], [name:notifySettingsUpdate, versions:[[version:1.1]]], [name:notifyStorageStatus, versions:[[authLevel:generic, version:1.1], [version:1.2]]]], protocols:[xhrpost:jsonizer, websocket:jsonizer], service:system], [apis:[[name:getAvailablePlaybackFunction, versions:[[version:1.0]]], [name:getBluetoothSettings, versions:[[version:1.0]]], [name:getContentCount, versions:[[version:1.3]]], [name:getContentList, versions:[[version:1.4]]], [name:getCurrentExternalTerminalsStatus, versions:[[version:1.0]]], [name:getMethodTypes, versions:[[version:1.0]]], [name:getPlaybackModeSettings, versions:[[version:1.0]]], [name:getPlayingContentInfo, versions:[[version:1.2]]], [name:getSchemeList, versions:[[version:1.0]]], [name:getSourceList, versions:[[version:1.1], [version:1.2]]], [name:getSupportedPlaybackFunction, versions:[[version:1.0]]], [name:getVersions, versions:[[version:1.0]]], [name:pausePlayingContent, versions:[[version:1.1]]], [name:presetBroadcastStation, versions:[[version:1.0]]], [name:scanPlayingContent, versions:[[version:1.0]]], [name:seekBroadcastStation, versions:[[version:1.0]]], [name:setActiveTerminal, versions:[[version:1.0]]], [name:setBluetoothSettings, versions:[[authLevel:generic, version:1.0]]], [name:setPlayContent, versions:[[version:1.2]]], [name:setPlayNextContent, versions:[[version:1.0]]], [name:setPlayPreviousContent, versions:[[version:1.0]]], [name:setPlaybackModeSettings, versions:[[version:1.0]]], [name:startContentBrowsing, versions:[[version:1.0]]], [name:stopPlayingContent, versions:[[version:1.1]]], [name:switchNotifications, versions:[[protocols:[websocket:jsonizer], version:1.0]]]], notifications:[[name:notifyAvailablePlaybackFunction, versions:[[version:1.0]]], [name:notifyExternalTerminalStatus, versions:[[version:1.0]]], [name:notifyPlayingContentInfo, versions:[[authLevel:private, version:1.0]]]], protocols:[xhrpost:jsonizer, websocket:jsonizer], service:avContent], [apis:[[name:getMethodTypes, versions:[[version:1.0]]], [name:getServiceProtocols, versions:[[version:1.0]]], [name:getSupportedApiInfo, versions:[[version:1.0]]], [name:getVersions, versions:[[version:1.0]]]], protocols:[xhrpost:jsonizer], service:guide], [apis:[[name:getCustomEqualizerSettings, versions:[[version:1.0]]], [name:getMethodTypes, versions:[[version:1.0]]], [name:getSoundSettings, versions:[[version:1.1]]], [name:getSpeakerSettings, versions:[[version:1.0]]], [name:getVersions, versions:[[version:1.0]]], [name:getVolumeInformation, versions:[[version:1.1]]], [name:setAudioMute, versions:[[version:1.1]]], [name:setAudioVolume, versions:[[version:1.1]]], [name:setCustomEqualizerSettings, versions:[[authLevel:generic, version:1.0]]], [name:setSoundSettings, versions:[[version:1.1]]], [name:setSpeakerSettings, versions:[[authLevel:generic, version:1.0]]], [name:switchNotifications, versions:[[protocols:[websocket:jsonizer], version:1.0]]]], notifications:[[name:notifyVolumeInformation, versions:[[version:1.0]]], [name:notifyWirelessSurroundInfo, versions:[[version:1.0]]]], protocols:[xhrpost:jsonizer, websocket:jsonizer], service:audio]]]]

OK, digesting some. Here are my initial impressions:

You can cut this line if your response is already JSON in a map.

def resJson = new groovy.json.JsonSlurper().parseText(response)

In that case, you can just replace all of my instances of resJson with just response like you were doing.

Let's start there and see if it is better.

You magnificent genius! This really just broadened the number of devices that I can make my driver easily support! I will update this to see how to best use, but soo awesome!

Below is my debug logs..

dev:52492020-12-17 10:03:05.637 pm debug1.1

dev:52492020-12-17 10:03:05.634 pm debugtheApi = [name:getPowerStatus, versions:[[version:1.1]]]

dev:52492020-12-17 10:03:05.623 pm debugtheRes = [apis:[[name:actSWUpdate, versions:[[authLevel:generic, version:1.0]]], [name:connectBluetoothDevice, versions:[[version:1.0]]], [name:getDeviceMiscSettings, versions:[[version:1.0]]], [name:getInterfaceInformation, versions:[[version:1.0]]], [name:getMethodTypes, versions:[[version:1.0]]], [name:getPowerSettings, versions:[[version:1.0]]], [name:getPowerStatus, versions:[[version:1.1]]], [name:getSWUpdateInfo, versions:[[version:1.0]]], [name:getSettingsTree, versions:[[version:1.1]]], [name:getSleepTimerSettings, versions:[[version:1.0]]], [name:getStorageList, versions:[[authLevel:generic, version:1.1], [version:1.2]]], [name:getSystemInformation, versions:[[version:1.3], [version:1.4]]], [name:getVersions, versions:[[version:1.0]]], [name:getWuTangInfo, versions:[[version:1.0]]], [name:setClientInfo, versions:[[version:1.0]]], [name:setDeviceMiscSettings, versions:[[version:1.0]]], [name:setPowerSettings, versions:[[version:1.0]]], [name:setPowerStatus, versions:[[version:1.1]]], [name:setSleepTimerSettings, versions:[[version:1.0]]], [name:setWuTangInfo, versions:[[authLevel:generic, version:1.0]]], [name:switchNotifications, versions:[[protocols:[websocket:jsonizer], version:1.0]]]], notifications:[[name:notifyPowerStatus, versions:[[version:1.0]]], [name:notifySWUpdateInfo, versions:[[version:1.0]]], [name:notifySettingsUpdate, versions:[[version:1.1]]], [name:notifyStorageStatus, versions:[[authLevel:generic, version:1.1], [version:1.2]]]], protocols:[xhrpost:jsonizer, websocket:jsonizer], service:system]

1 Like

Also, trying to find more info on the topic, but not sure how to phrase my question on the internet.

Your JSON query is
def theRes = response?.data?.result?.get(0)?.find { it.service == "system" }

I have some of mine showing
def soundfield = response.data.result[0][0]?.currentValue

You have used parentheses (0) instead of [0]. Why?

Also, sometimes there is question mark sprinkled in behind the json levels, whats the significance of this.

If anyone has a video explaining json queries or an article, I'm game. Not sure what to look for.

They're related.

The question mark thing is the safe navigation operator. It helps avoid null pointer exceptions if the thing you're trying to access (really, it's 'children') don't exist. For example, response.data will eval to null if data doesn't exist. But response.data.result will throw an exception if data doesn't exist.

I always use brackets for array accesses, except when I need to use the safe navigation operator to go deeper within the array members. Using get() has some potential issues, as discussed here, but it seems pretty safe to me if you're iterating over the known bounds of the collection (like with each).