diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df5c1fb..7c1aac7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,3 +5,18 @@ repos: - id: black language_version: python3 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: flake8 + +- repo: https://github.com/pre-commit/mirrors-isort + rev: v4.3.21 + hooks: + - id: isort + +#- repo: https://github.com/pre-commit/mirrors-mypy +# rev: v0.740 +# hooks: +# - id: mypy +# args: [--no-strict-optional, --ignore-missing-imports] diff --git a/devinfos/HT-XT3.json b/devinfos/HT-XT3.json index 2e55a47..7e347c3 100644 --- a/devinfos/HT-XT3.json +++ b/devinfos/HT-XT3.json @@ -1,1922 +1,2106 @@ { - "settings" : [ - { - "titleTextID" : null, - "type" : "directory", - "deviceUIInfo" : null, - "usage" : "deviceConfig", - "isAvailable" : true, - "title" : null, - "settings" : [ - { - "type" : "directory", - "deviceUIInfo" : null, - "usage" : null, - "titleTextID" : "sound", - "settings" : [ - { - "type" : "booleanTarget", - "deviceUIInfo" : null, - "usage" : null, - "titleTextID" : "sound-clearaudio", - "settings" : null, - "apiMapping" : { - "service" : "audio", - "getApi" : { - "version" : "1.1", - "name" : "getSoundSettings" - }, - "setApi" : { - "version" : "1.1", - "name" : "setSoundSettings" - }, - "targetSuppl" : null, - "target" : "clearAudio" - }, - "isAvailable" : false, - "title" : "ClearAudio+" - }, - { - "apiMapping" : { - "target" : "soundField", - "service" : "audio", - "getApi" : { - "name" : "getSoundSettings", - "version" : "1.1" - }, - "targetSuppl" : null, - "setApi" : { - "version" : "1.1", - "name" : "setSoundSettings" + "interface_info": { + "interfaceVersion": "2.0.0", + "modelName": "HT-XT3", + "productCategory": "homeTheaterSystem", + "productName": "Bar", + "serverName": "" + }, + "settings": [ + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": { + "getApi": { + "name": "getSoundSettings", + "version": "1.1" + }, + "service": "audio", + "setApi": { + "name": "setSoundSettings", + "version": "1.1" + }, + "target": "clearAudio", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "ClearAudio+", + "titleTextID": "sound-clearaudio", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getSoundSettings", + "version": "1.1" + }, + "service": "audio", + "setApi": { + "name": "setSoundSettings", + "version": "1.1" + }, + "target": "soundField", + "targetSuppl": null + }, + "deviceUIInfo": "soundFieldFig", + "isAvailable": true, + "settings": null, + "title": "Sound Field", + "titleTextID": "sound-soundfield", + "type": "enumTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getSoundSettings", + "version": "1.1" + }, + "service": "audio", + "setApi": { + "name": "setSoundSettings", + "version": "1.1" + }, + "target": "nightMode", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Night Mode", + "titleTextID": "sound-night", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getSoundSettings", + "version": "1.1" + }, + "service": "audio", + "setApi": { + "name": "setSoundSettings", + "version": "1.1" + }, + "target": "voice", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Voice", + "titleTextID": "sound-voice", + "type": "enumTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getSoundSettings", + "version": "1.1" + }, + "service": "audio", + "setApi": { + "name": "setSoundSettings", + "version": "1.1" + }, + "target": "subwooferLevel", + "targetSuppl": null + }, + "deviceUIInfo": "picker", + "isAvailable": true, + "settings": null, + "title": "Subwoofer Volume", + "titleTextID": "sound-subwoofer", + "type": "integerTarget", + "usage": null + }, + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": { + "getApi": { + "name": "getSoundSettings", + "version": "1.1" + }, + "service": "audio", + "setApi": { + "name": "setSoundSettings", + "version": "1.1" + }, + "target": "avSyncMs", + "targetSuppl": null + }, + "deviceUIInfo": "picker", + "isAvailable": true, + "settings": null, + "title": "A/V SYNC", + "titleTextID": "sound-adjustment-avsync", + "type": "integerTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getSoundSettings", + "version": "1.1" + }, + "service": "audio", + "setApi": { + "name": "setSoundSettings", + "version": "1.1" + }, + "target": "dualMono", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Dual Mono", + "titleTextID": "sound-adjustment-dualmono", + "type": "enumTarget", + "usage": null + } + ], + "title": "Sound Adjustment", + "titleTextID": "sound-adjustment", + "type": "directory", + "usage": null } - }, - "settings" : null, - "title" : "Sound Field", - "isAvailable" : true, - "usage" : null, - "type" : "enumTarget", - "deviceUIInfo" : "soundFieldFig", - "titleTextID" : "sound-soundfield" - }, - { - "apiMapping" : { - "target" : "nightMode", - "setApi" : { - "name" : "setSoundSettings", - "version" : "1.1" - }, - "targetSuppl" : null, - "service" : "audio", - "getApi" : { - "version" : "1.1", - "name" : "getSoundSettings" + ], + "title": "Sound Settings", + "titleTextID": "sound", + "type": "directory", + "usage": null + }, + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": { + "getApi": { + "name": "getSWUpdateInfo", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "actSWUpdate", + "version": "1.0" + }, + "target": null, + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Software Update", + "titleTextID": "system-update", + "type": "nullTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getPowerSettings", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setPowerSettings", + "version": "1.0" + }, + "target": "quickStartMode", + "targetSuppl": null + }, + "deviceUIInfo": "", + "isAvailable": true, + "settings": null, + "title": "Quick Start/Network Standby", + "titleTextID": "system-networkstandby", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getDeviceMiscSettings", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setDeviceMiscSettings", + "version": "1.0" + }, + "target": "deviceName", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Network Device Name", + "titleTextID": "system-networkdevicename", + "type": "stringTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getDeviceMiscSettings", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setDeviceMiscSettings", + "version": "1.0" + }, + "target": "swAutoUpdate", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Auto Update", + "titleTextID": "system-autoupdate", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getDeviceMiscSettings", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setDeviceMiscSettings", + "version": "1.0" + }, + "target": "timeZone", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Time Zone", + "titleTextID": "system-timezone", + "type": "stringTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getSystemInformation", + "version": "1.3" + }, + "service": "system", + "setApi": null, + "target": "", + "targetSuppl": "version" + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "HT-XT3 Version", + "titleTextID": "other-htversion-TBD", + "type": "stringTarget", + "usage": null } - }, - "settings" : null, - "title" : "Night Mode", - "isAvailable" : true, - "usage" : null, - "deviceUIInfo" : null, - "type" : "booleanTarget", - "titleTextID" : "sound-night" - }, - { - "isAvailable" : true, - "title" : "Voice", - "settings" : null, - "apiMapping" : { - "service" : "audio", - "getApi" : { - "version" : "1.1", - "name" : "getSoundSettings" - }, - "targetSuppl" : null, - "setApi" : { - "name" : "setSoundSettings", - "version" : "1.1" - }, - "target" : "voice" - }, - "titleTextID" : "sound-voice", - "deviceUIInfo" : null, - "type" : "enumTarget", - "usage" : null - }, - { - "usage" : null, - "type" : "integerTarget", - "deviceUIInfo" : "picker", - "titleTextID" : "sound-subwoofer", - "apiMapping" : { - "target" : "subwooferLevel", - "setApi" : { - "version" : "1.1", - "name" : "setSoundSettings" - }, - "targetSuppl" : null, - "getApi" : { - "name" : "getSoundSettings", - "version" : "1.1" - }, - "service" : "audio" - }, - "settings" : null, - "title" : "Subwoofer Volume", - "isAvailable" : true - }, - { - "apiMapping" : null, - "settings" : [ - { - "usage" : null, - "deviceUIInfo" : "picker", - "type" : "integerTarget", - "titleTextID" : "sound-adjustment-avsync", - "apiMapping" : { - "target" : "avSyncMs", - "service" : "audio", - "getApi" : { - "name" : "getSoundSettings", - "version" : "1.1" - }, - "setApi" : { - "version" : "1.1", - "name" : "setSoundSettings" - }, - "targetSuppl" : null - }, - "settings" : null, - "title" : "A/V SYNC", - "isAvailable" : true - }, - { - "isAvailable" : true, - "title" : "Dual Mono", - "apiMapping" : { - "targetSuppl" : null, - "setApi" : { - "name" : "setSoundSettings", - "version" : "1.1" - }, - "service" : "audio", - "getApi" : { - "name" : "getSoundSettings", - "version" : "1.1" - }, - "target" : "dualMono" - }, - "settings" : null, - "titleTextID" : "sound-adjustment-dualmono", - "usage" : null, - "type" : "enumTarget", - "deviceUIInfo" : null + ], + "title": "System", + "titleTextID": "system", + "type": "directory", + "usage": null + } + ], + "title": null, + "titleTextID": null, + "type": "directory", + "usage": "deviceConfig" + }, + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": { + "getApi": { + "name": "getPlaybackModeSettings", + "version": "1.0" + }, + "service": "avContent", + "setApi": { + "name": "setPlaybackModeSettings", + "version": "1.0" + }, + "target": "repeatType", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Repeat", + "titleTextID": "playbackMode-repeatType", + "type": "enumTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getPlaybackModeSettings", + "version": "1.0" + }, + "service": "avContent", + "setApi": { + "name": "setPlaybackModeSettings", + "version": "1.0" + }, + "target": "shuffleType", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Shuffle", + "titleTextID": "playbackMode-shuffleType", + "type": "enumTarget", + "usage": null } - ], - "isAvailable" : true, - "title" : "Sound Adjustment", - "usage" : null, - "deviceUIInfo" : null, - "type" : "directory", - "titleTextID" : "sound-adjustment" - } - ], - "apiMapping" : null, - "isAvailable" : true, - "title" : "Sound Settings" - }, - { - "titleTextID" : "system", - "usage" : null, - "type" : "directory", - "deviceUIInfo" : null, - "isAvailable" : true, - "title" : "System", - "apiMapping" : null, - "settings" : [ - { - "usage" : null, - "type" : "nullTarget", - "deviceUIInfo" : null, - "titleTextID" : "system-update", - "apiMapping" : { - "setApi" : { - "version" : "1.0", - "name" : "actSWUpdate" - }, - "targetSuppl" : null, - "service" : "system", - "getApi" : { - "name" : "getSWUpdateInfo", - "version" : "1.0" - }, - "target" : null - }, - "settings" : null, - "title" : "Software Update", - "isAvailable" : true - }, - { - "deviceUIInfo" : "", - "type" : "booleanTarget", - "usage" : null, - "titleTextID" : "system-networkstandby", - "settings" : null, - "apiMapping" : { - "target" : "quickStartMode", - "service" : "system", - "getApi" : { - "name" : "getPowerSettings", - "version" : "1.0" - }, - "setApi" : { - "version" : "1.0", - "name" : "setPowerSettings" - }, - "targetSuppl" : null - }, - "isAvailable" : true, - "title" : "Quick Start/Network Standby" - }, - { - "apiMapping" : { - "targetSuppl" : null, - "setApi" : { - "version" : "1.0", - "name" : "setDeviceMiscSettings" - }, - "service" : "system", - "getApi" : { - "name" : "getDeviceMiscSettings", - "version" : "1.0" - }, - "target" : "deviceName" - }, - "settings" : null, - "title" : "Network Device Name", - "isAvailable" : true, - "usage" : null, - "type" : "stringTarget", - "deviceUIInfo" : null, - "titleTextID" : "system-networkdevicename" - }, - { - "title" : "Auto Update", - "isAvailable" : true, - "settings" : null, - "apiMapping" : { - "targetSuppl" : null, - "setApi" : { - "version" : "1.0", - "name" : "setDeviceMiscSettings" - }, - "getApi" : { - "version" : "1.0", - "name" : "getDeviceMiscSettings" - }, - "service" : "system", - "target" : "swAutoUpdate" - }, - "titleTextID" : "system-autoupdate", - "deviceUIInfo" : null, - "type" : "booleanTarget", - "usage" : null - }, - { - "apiMapping" : { - "target" : "timeZone", - "setApi" : { - "version" : "1.0", - "name" : "setDeviceMiscSettings" - }, - "targetSuppl" : null, - "service" : "system", - "getApi" : { - "name" : "getDeviceMiscSettings", - "version" : "1.0" + ], + "title": "Playback Mode", + "titleTextID": "playbackMode", + "type": "directory", + "usage": null + } + ], + "title": null, + "titleTextID": null, + "type": "directory", + "usage": "playingControl" + }, + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": { + "getApi": { + "name": "getPowerSettings", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setPowerSettings", + "version": "1.0" + }, + "target": "quickStartMode", + "targetSuppl": null + }, + "deviceUIInfo": "", + "isAvailable": true, + "settings": null, + "title": "Quick Start/Network Standby", + "titleTextID": "system-networkstandby", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getDeviceMiscSettings", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setDeviceMiscSettings", + "version": "1.0" + }, + "target": "swAutoUpdate", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Auto Update", + "titleTextID": "system-autoupdate", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getDeviceMiscSettings", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setDeviceMiscSettings", + "version": "1.0" + }, + "target": "timeZone", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Time Zone", + "titleTextID": "system-timezone", + "type": "stringTarget", + "usage": null + } + ], + "title": null, + "titleTextID": null, + "type": "directory", + "usage": "initialSetting" + }, + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": null, + "deviceUIInfo": null, + "isAvailable": true, + "settings": [ + { + "apiMapping": { + "getApi": { + "name": "getWuTangInfo", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setWuTangInfo", + "version": "1.0" + }, + "target": "privacySetting", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Share usage data", + "titleTextID": "googlecast-shareusagedata", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getWuTangInfo", + "version": "1.0" + }, + "service": "system", + "setApi": { + "name": "setWuTangInfo", + "version": "1.0" + }, + "target": "activateStatus", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Activate status", + "titleTextID": "googlecast-activatestatus", + "type": "booleanTarget", + "usage": null + }, + { + "apiMapping": { + "getApi": { + "name": "getWuTangInfo", + "version": "1.0" + }, + "service": "system", + "setApi": null, + "target": "currentVersion", + "targetSuppl": null + }, + "deviceUIInfo": null, + "isAvailable": true, + "settings": null, + "title": "Version number", + "titleTextID": "googlecast-versionnumber", + "type": "stringTarget", + "usage": null } - }, - "settings" : null, - "isAvailable" : true, - "title" : "Time Zone", - "usage" : null, - "type" : "stringTarget", - "deviceUIInfo" : null, - "titleTextID" : "system-timezone" - }, - { - "type" : "stringTarget", - "deviceUIInfo" : null, - "usage" : null, - "titleTextID" : "other-htversion-TBD", - "settings" : null, - "apiMapping" : { - "service" : "system", - "getApi" : { - "version" : "1.3", - "name" : "getSystemInformation" - }, - "targetSuppl" : "version", - "setApi" : null, - "target" : "" - }, - "isAvailable" : true, - "title" : "HT-XT3 Version" - } - ] - } - ], - "apiMapping" : null - }, - { - "type" : "directory", - "deviceUIInfo" : null, - "usage" : "playingControl", - "titleTextID" : null, - "settings" : [ - { - "type" : "directory", - "deviceUIInfo" : null, - "usage" : null, - "titleTextID" : "playbackMode", - "settings" : [ - { - "titleTextID" : "playbackMode-repeatType", - "deviceUIInfo" : null, - "type" : "enumTarget", - "usage" : null, - "title" : "Repeat", - "isAvailable" : true, - "settings" : null, - "apiMapping" : { - "target" : "repeatType", - "targetSuppl" : null, - "setApi" : { - "name" : "setPlaybackModeSettings", - "version" : "1.0" - }, - "service" : "avContent", - "getApi" : { - "name" : "getPlaybackModeSettings", - "version" : "1.0" + ], + "title": "Google Cast", + "titleTextID": "googlecast", + "type": "directory", + "usage": null + } + ], + "title": null, + "titleTextID": null, + "type": "directory", + "usage": "wuTangSetting" + } + ], + "supported_methods": { + "audio": { + "methods": { + "getCustomEqualizerSettings": { + "input": { + "target": "str" + }, + "name": "getCustomEqualizerSettings", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "audio", + "version": "1.0" + }, + "getMethodTypes": { + "input": "str", + "name": "getMethodTypes", + "output": "str", + "service": "audio", + "version": "1.0" + }, + "getSoundSettings": { + "input": { + "target": "str" + }, + "name": "getSoundSettings", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "audio", + "version": "1.1" + }, + "getSpeakerSettings": { + "input": { + "target": "str" + }, + "name": "getSpeakerSettings", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "audio", + "version": "1.0" + }, + "getVersions": { + "input": null, + "name": "getVersions", + "output": "str", + "service": "audio", + "version": "1.0" + }, + "getVolumeInformation": { + "input": { + "output": "str" + }, + "name": "getVolumeInformation", + "output": { + "maxVolume": "int", + "minVolume": "int", + "mute": "str", + "output": "str", + "step": "int", + "volume": "int" + }, + "service": "audio", + "version": "1.1" + }, + "setAudioMute": { + "input": { + "mute": "str", + "output": "str" + }, + "name": "setAudioMute", + "output": null, + "service": "audio", + "version": "1.1" + }, + "setAudioVolume": { + "input": { + "output": "str", + "volume": "str" + }, + "name": "setAudioVolume", + "output": null, + "service": "audio", + "version": "1.1" + }, + "setCustomEqualizerSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setCustomEqualizerSettings", + "output": null, + "service": "audio", + "version": "1.0" + }, + "setSoundSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setSoundSettings", + "output": null, + "service": "audio", + "version": "1.1" + }, + "setSpeakerSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setSpeakerSettings", + "output": null, + "service": "audio", + "version": "1.0" + }, + "switchNotifications": { + "input": { + "disabled": "ApiIdentity[]", + "enabled": "ApiIdentity[]" + }, + "name": "switchNotifications", + "output": { + "disabled": "ApiIdentity[]", + "enabled": "ApiIdentity[]", + "unsupported": "ApiIdentity[]" + }, + "service": "audio", + "version": "1.0" + } + }, + "notifications": { + "notifyVolumeInformation": { + "name": "notifyVolumeInformation", + "version": "1.0" + } + }, + "protocols": [] + }, + "avContent": { + "methods": { + "getAvailablePlaybackFunction": { + "input": { + "output": "str" + }, + "name": "getAvailablePlaybackFunction", + "output": { + "functions": "FunctionInfo[]", + "output": "str", + "uri": "str" + }, + "service": "avContent", + "version": "1.0" + }, + "getBluetoothSettings": { + "input": { + "target": "str" + }, + "name": "getBluetoothSettings", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "avContent", + "version": "1.0" + }, + "getContentCount": { + "input": { + "path": "str", + "target": "str", + "type": "string*", + "uri": "str", + "view": "str" + }, + "name": "getContentCount", + "output": { + "capability": "int", + "count": "int" + }, + "service": "avContent", + "version": "1.3" + }, + "getContentList": { + "input": { + "cnt": "int", + "path": "str", + "sort": "str", + "stIdx": "int", + "target": "str", + "type": "string*", + "uri": "str", + "view": "str" + }, + "name": "getContentList", + "output": { + "albumName": "str", + "artist": "str", + "audioInfo": "AudioInfo[]", + "broadcastFreq": "int", + "broadcastFreqBand": "str", + "channelName": "str", + "channelSurfingVisibility": "str", + "chapterCount": "int", + "content": "ContentInfo", + "contentKind": "str", + "contentType": "str", + "createdTime": "str", + "directRemoteNum": "int", + "dispNum": "str", + "durationMsec": "int", + "epgVisibility": "str", + "fileNo": "str", + "fileSizeByte": "int", + "folderNo": "str", + "genre": "string*", + "index": "int", + "is3D": "str", + "isAlreadyPlayed": "str", + "isBrowsable": "str", + "isPlayable": "str", + "isProtected": "str", + "originalDispNum": "str", + "parentIndex": "int", + "parentUri": "str", + "parentalInfo": "ParentalInfo[]", + "path": "str", + "playlistName": "str", + "podcastName": "str", + "productID": "str", + "programMediaType": "str", + "programNum": "int", + "remotePlayType": "string*", + "sizeMB": "int", + "startDateTime": "str", + "storageUri": "str", + "subtitleInfo": "SubtitleInfo[]", + "title": "str", + "tripletStr": "str", + "uri": "str", + "userContentFlag": "bool", + "videoInfo": "VideoInfo", + "visibility": "str" + }, + "service": "avContent", + "version": "1.4" + }, + "getCurrentExternalTerminalsStatus": { + "input": null, + "name": "getCurrentExternalTerminalsStatus", + "output": { + "active": "str", + "connection": "str", + "iconUrl": "str", + "label": "str", + "meta": "str", + "outputs": "string*", + "title": "str", + "uri": "str" + }, + "service": "avContent", + "version": "1.0" + }, + "getMethodTypes": { + "input": "str", + "name": "getMethodTypes", + "output": "str", + "service": "avContent", + "version": "1.0" + }, + "getPlaybackModeSettings": { + "input": { + "target": "str", + "uri": "str" + }, + "name": "getPlaybackModeSettings", + "output": { + "candidate": "PlaybackModeSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str", + "uri": "str" + }, + "service": "avContent", + "version": "1.0" + }, + "getPlayingContentInfo": { + "input": { + "output": "str" + }, + "name": "getPlayingContentInfo", + "output": { + "albumName": "str", + "applicationName": "str", + "artist": "str", + "audioInfo": "AudioInfo[]", + "bivl_assetId": "str", + "bivl_provider": "str", + "bivl_serviceId": "str", + "broadcastFreq": "int", + "broadcastFreqBand": "str", + "channelName": "str", + "chapterCount": "int", + "chapterIndex": "int", + "contentKind": "str", + "dabInfo": "DabInfo", + "dispNum": "str", + "durationMsec": "int", + "durationSec": "double", + "fileNo": "str", + "genre": "string*", + "index": "int", + "is3D": "str", + "mediaType": "str", + "originalDispNum": "str", + "output": "str", + "parentIndex": "int", + "parentUri": "str", + "path": "str", + "playSpeed": "str", + "playSpeedStep": "int", + "playlistName": "str", + "podcastName": "str", + "positionMsec": "int", + "positionSec": "double", + "programNum": "int", + "programTitle": "str", + "repeatType": "str", + "service": "str", + "source": "str", + "sourceLabel": "str", + "startDateTime": "str", + "stateInfo": "StateInfo", + "subtitleIndex": "int", + "title": "str", + "totalCount": "int", + "tripletStr": "str", + "uri": "str", + "videoInfo": "VideoInfo" + }, + "service": "avContent", + "version": "1.2" + }, + "getSchemeList": { + "input": null, + "name": "getSchemeList", + "output": { + "scheme": "str" + }, + "service": "avContent", + "version": "1.0" + }, + "getSourceList": { + "input": { + "scheme": "str" + }, + "name": "getSourceList", + "output": { + "iconUrl": "str", + "isBrowsable": "bool", + "isPlayable": "bool", + "meta": "str", + "outputs": "string*", + "playAction": "str", + "source": "str", + "title": "str" + }, + "service": "avContent", + "version": "1.1" + }, + "getSupportedPlaybackFunction": { + "input": { + "uri": "str" + }, + "name": "getSupportedPlaybackFunction", + "output": { + "functions": "SupportedFunctionInfo[]", + "uri": "str" + }, + "service": "avContent", + "version": "1.0" + }, + "getVersions": { + "input": null, + "name": "getVersions", + "output": "str", + "service": "avContent", + "version": "1.0" + }, + "pausePlayingContent": { + "input": { + "output": "str" + }, + "name": "pausePlayingContent", + "output": null, + "service": "avContent", + "version": "1.1" + }, + "presetBroadcastStation": { + "input": { + "frequency": "int", + "uri": "str" + }, + "name": "presetBroadcastStation", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "scanPlayingContent": { + "input": { + "direction": "str", + "output": "str" + }, + "name": "scanPlayingContent", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "seekBroadcastStation": { + "input": { + "direction": "str", + "tuning": "str" + }, + "name": "seekBroadcastStation", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "setActiveTerminal": { + "input": { + "active": "str", + "uri": "str" + }, + "name": "setActiveTerminal", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "setBluetoothSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setBluetoothSettings", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "setPlayContent": { + "input": { + "keepLastFrame": "bool", + "output": "str", + "positionMsec": "int", + "positionSec": "double", + "repeatType": "str", + "requester": "str", + "resume": "bool", + "uri": "str" + }, + "name": "setPlayContent", + "output": null, + "service": "avContent", + "version": "1.2" + }, + "setPlayNextContent": { + "input": { + "output": "str" + }, + "name": "setPlayNextContent", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "setPlayPreviousContent": { + "input": { + "output": "str" + }, + "name": "setPlayPreviousContent", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "setPlaybackModeSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setPlaybackModeSettings", + "output": null, + "service": "avContent", + "version": "1.0" + }, + "startContentBrowsing": { + "input": { + "uri": "str" + }, + "name": "startContentBrowsing", + "output": { + "errorMessage": "str", + "status": "str" + }, + "service": "avContent", + "version": "1.0" + }, + "stopPlayingContent": { + "input": { + "keepLastFrame": "bool", + "output": "str" + }, + "name": "stopPlayingContent", + "output": null, + "service": "avContent", + "version": "1.1" + }, + "switchNotifications": { + "input": { + "disabled": "ApiIdentity[]", + "enabled": "ApiIdentity[]" + }, + "name": "switchNotifications", + "output": { + "disabled": "ApiIdentity[]", + "enabled": "ApiIdentity[]", + "unsupported": "ApiIdentity[]" + }, + "service": "avContent", + "version": "1.0" + } + }, + "notifications": { + "notifyAvailablePlaybackFunction": { + "name": "notifyAvailablePlaybackFunction", + "version": "1.0" + }, + "notifyExternalTerminalStatus": { + "name": "notifyExternalTerminalStatus", + "version": "1.0" + }, + "notifyPlayingContentInfo": { + "name": "notifyPlayingContentInfo", + "version": "1.0" + } + }, + "protocols": [] + }, + "guide": { + "methods": { + "getMethodTypes": { + "input": "str", + "name": "getMethodTypes", + "output": "str", + "service": "guide", + "version": "1.0" + }, + "getServiceProtocols": { + "input": null, + "name": "getServiceProtocols", + "output": "str", + "service": "guide", + "version": "1.0" + }, + "getSupportedApiInfo": { + "input": { + "services": "string*" + }, + "name": "getSupportedApiInfo", + "output": { + "apis": "ApiInfo[]", + "protocols": "string*", + "service": "str" + }, + "service": "guide", + "version": "1.0" + }, + "getVersions": { + "input": null, + "name": "getVersions", + "output": "str", + "service": "guide", + "version": "1.0" + } + }, + "notifications": {}, + "protocols": [] + }, + "system": { + "methods": { + "actSWUpdate": { + "input": null, + "name": "actSWUpdate", + "output": null, + "service": "system", + "version": "1.0" + }, + "connectBluetoothDevice": { + "input": { + "bdAddr": "str" + }, + "name": "connectBluetoothDevice", + "output": null, + "service": "system", + "version": "1.0" + }, + "getDeviceMiscSettings": { + "input": { + "target": "str" + }, + "name": "getDeviceMiscSettings", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "system", + "version": "1.0" + }, + "getInterfaceInformation": { + "input": null, + "name": "getInterfaceInformation", + "output": { + "interfaceVersion": "str", + "modelName": "str", + "productCategory": "str", + "productName": "str", + "serverName": "str" + }, + "service": "system", + "version": "1.0" + }, + "getMethodTypes": { + "input": "str", + "name": "getMethodTypes", + "output": "str", + "service": "system", + "version": "1.0" + }, + "getPowerSettings": { + "input": { + "target": "str" + }, + "name": "getPowerSettings", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "system", + "version": "1.0" + }, + "getPowerStatus": { + "input": null, + "name": "getPowerStatus", + "output": { + "standbyDetail": "str", + "status": "str" + }, + "service": "system", + "version": "1.1" + }, + "getSWUpdateInfo": { + "input": { + "network": "str" + }, + "name": "getSWUpdateInfo", + "output": { + "isUpdatable": "str", + "swInfo": "SWInfo[]" + }, + "service": "system", + "version": "1.0" + }, + "getSettingsTree": { + "input": { + "usage": "str" + }, + "name": "getSettingsTree", + "output": { + "settings": "SettingsTreeList[]" + }, + "service": "system", + "version": "1.1" + }, + "getSleepTimerSettings": { + "input": { + "target": "str" + }, + "name": "getSleepTimerSettings", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "system", + "version": "1.0" + }, + "getStorageList": { + "input": { + "uri": "str" + }, + "name": "getStorageList", + "output": { + "deviceName": "str", + "error": "str", + "format": "str", + "formattable": "str", + "formatting": "str", + "freeCapacityMB": "int", + "isAvailable": "str", + "lun": "int", + "mounted": "str", + "permission": "str", + "position": "str", + "systemAreaCapacityMB": "int", + "type": "str", + "uri": "str", + "volumeLabel": "str", + "wholeCapacityMB": "int" + }, + "service": "system", + "version": "1.1" + }, + "getSystemInformation": { + "input": null, + "name": "getSystemInformation", + "output": { + "area": "str", + "bdAddr": "str", + "cid": "str", + "deviceID": "str", + "duid": "str", + "esn": "str", + "generation": "str", + "helpUrl": "str", + "iconUrl": "str", + "language": "str", + "macAddr": "str", + "model": "str", + "name": "str", + "product": "str", + "region": "str", + "serial": "str", + "ssid": "str", + "updatableVersion": "str", + "version": "str", + "wirelessMacAddr": "str" + }, + "service": "system", + "version": "1.3" + }, + "getVersions": { + "input": null, + "name": "getVersions", + "output": "str", + "service": "system", + "version": "1.0" + }, + "getWuTangInfo": { + "input": { + "target": "str" + }, + "name": "getWuTangInfo", + "output": { + "candidate": "GeneralSettingsCandidate[]", + "currentValue": "str", + "deviceUIInfo": "str", + "isAvailable": "bool", + "target": "str", + "title": "str", + "titleTextID": "str", + "type": "str" + }, + "service": "system", + "version": "1.0" + }, + "setClientInfo": { + "input": { + "target": "str", + "value": "str" + }, + "name": "setClientInfo", + "output": null, + "service": "system", + "version": "1.0" + }, + "setDeviceMiscSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setDeviceMiscSettings", + "output": null, + "service": "system", + "version": "1.0" + }, + "setPowerSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setPowerSettings", + "output": null, + "service": "system", + "version": "1.0" + }, + "setPowerStatus": { + "input": { + "standbyDetail": "str", + "status": "str" + }, + "name": "setPowerStatus", + "output": null, + "service": "system", + "version": "1.1" + }, + "setSleepTimerSettings": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setSleepTimerSettings", + "output": null, + "service": "system", + "version": "1.0" + }, + "setWuTangInfo": { + "input": { + "settings": "GeneralSettings[]" + }, + "name": "setWuTangInfo", + "output": null, + "service": "system", + "version": "1.0" + }, + "switchNotifications": { + "input": { + "disabled": "ApiIdentity[]", + "enabled": "ApiIdentity[]" + }, + "name": "switchNotifications", + "output": { + "disabled": "ApiIdentity[]", + "enabled": "ApiIdentity[]", + "unsupported": "ApiIdentity[]" + }, + "service": "system", + "version": "1.0" + } + }, + "notifications": { + "notifyPowerStatus": { + "name": "notifyPowerStatus", + "version": "1.0" + }, + "notifySWUpdateInfo": { + "name": "notifySWUpdateInfo", + "version": "1.0" + }, + "notifySettingsUpdate": { + "name": "notifySettingsUpdate", + "version": "1.1" + }, + "notifyStorageStatus": { + "name": "notifyStorageStatus", + "version": "1.1" + } + }, + "protocols": [] + } + }, + "supported_methods_response": { + "id": 11, + "result": [ + [ + { + "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" + } + ] } - } - }, - { - "titleTextID" : "playbackMode-shuffleType", - "usage" : null, - "deviceUIInfo" : null, - "type" : "enumTarget", - "isAvailable" : true, - "title" : "Shuffle", - "apiMapping" : { - "target" : "shuffleType", - "getApi" : { - "version" : "1.0", - "name" : "getPlaybackModeSettings" - }, - "service" : "avContent", - "setApi" : { - "version" : "1.0", - "name" : "setPlaybackModeSettings" - }, - "targetSuppl" : null - }, - "settings" : null - } - ], - "apiMapping" : null, - "isAvailable" : true, - "title" : "Playback Mode" - } - ], - "apiMapping" : null, - "isAvailable" : true, - "title" : null - }, - { - "apiMapping" : null, - "settings" : [ - { - "title" : "Quick Start/Network Standby", - "isAvailable" : true, - "settings" : null, - "apiMapping" : { - "target" : "quickStartMode", - "setApi" : { - "name" : "setPowerSettings", - "version" : "1.0" - }, - "targetSuppl" : null, - "getApi" : { - "version" : "1.0", - "name" : "getPowerSettings" - }, - "service" : "system" - }, - "titleTextID" : "system-networkstandby", - "type" : "booleanTarget", - "deviceUIInfo" : "", - "usage" : null - }, - { - "deviceUIInfo" : null, - "type" : "booleanTarget", - "usage" : null, - "titleTextID" : "system-autoupdate", - "settings" : null, - "apiMapping" : { - "setApi" : { - "version" : "1.0", - "name" : "setDeviceMiscSettings" - }, - "targetSuppl" : null, - "service" : "system", - "getApi" : { - "version" : "1.0", - "name" : "getDeviceMiscSettings" - }, - "target" : "swAutoUpdate" - }, - "title" : "Auto Update", - "isAvailable" : true - } - ], - "title" : null, - "isAvailable" : true, - "usage" : "initialSetting", - "deviceUIInfo" : null, - "type" : "directory", - "titleTextID" : null - }, - { - "apiMapping" : null, - "settings" : [ - { - "settings" : [ - { - "settings" : null, - "apiMapping" : { - "service" : "system", - "getApi" : { - "version" : "1.0", - "name" : "getWuTangInfo" - }, - "targetSuppl" : null, - "setApi" : { - "version" : "1.0", - "name" : "setWuTangInfo" - }, - "target" : "privacySetting" - }, - "title" : "Share usage data", - "isAvailable" : true, - "deviceUIInfo" : null, - "type" : "booleanTarget", - "usage" : null, - "titleTextID" : "googlecast-shareusagedata" - }, - { - "deviceUIInfo" : null, - "type" : "booleanTarget", - "usage" : null, - "titleTextID" : "googlecast-activatestatus", - "settings" : null, - "apiMapping" : { - "setApi" : { - "version" : "1.0", - "name" : "setWuTangInfo" - }, - "targetSuppl" : null, - "getApi" : { - "name" : "getWuTangInfo", - "version" : "1.0" - }, - "service" : "system", - "target" : "activateStatus" - }, - "title" : "Activate status", - "isAvailable" : true - }, - { - "settings" : null, - "apiMapping" : { - "targetSuppl" : null, - "setApi" : null, - "getApi" : { - "version" : "1.0", - "name" : "getWuTangInfo" - }, - "service" : "system", - "target" : "currentVersion" - }, - "title" : "Version number", - "isAvailable" : true, - "deviceUIInfo" : null, - "type" : "stringTarget", - "usage" : null, - "titleTextID" : "googlecast-versionnumber" - } - ], - "apiMapping" : null, - "isAvailable" : true, - "title" : "Google Cast", - "deviceUIInfo" : null, - "type" : "directory", - "usage" : null, - "titleTextID" : "googlecast" - } - ], - "isAvailable" : true, - "title" : null, - "usage" : "wuTangSetting", - "type" : "directory", - "deviceUIInfo" : null, - "titleTextID" : null - } - ], - "sysinfo" : { - "macAddr" : "10:4f:a8:xx:xx:xx", - "version" : "M28.R.0476", - "bssid" : null, - "bleID" : null, - "ssid" : null, - "wirelessMacAddr" : "68:14:01:xx:xx:xx", - "bdAddr" : "68:14:01:xx:xx:xx" - }, - "interface_info" : { - "interfaceVersion" : "2.0.0", - "serverName" : "", - "productName" : "Bar", - "modelName" : "HT-XT3", - "productCategory" : "homeTheaterSystem" - }, - "supported_methods" : { - "guide" : { - "notifications" : {}, - "protocols" : [ - "xhrpost:jsonizer" - ], - "methods" : { - "getMethodTypes" : { - "name" : "getMethodTypes", - "signature" : { - "input" : [], - "output" : [ - "string", - "string*", - "string*" - ], - "version" : "1.0", - "name" : "getMethodTypes" - }, - "inputs" : "str", - "version" : "1.0", - "service" : "guide", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/guide", - "outputs" : "str" - }, - "getSupportedApiInfo" : { - "outputs" : { - "protocols" : "string*", - "service" : "str", - "apis" : "ApiInfo[]" - }, - "version" : "1.0", - "inputs" : { - "services" : "string*" - }, - "service" : "guide", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/guide", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.0", - "name" : "getSupportedApiInfo" - }, - "name" : "getSupportedApiInfo" - }, - "getServiceProtocols" : { - "outputs" : "str", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/guide", - "version" : "1.0", - "inputs" : null, - "service" : "guide", - "signature" : { - "version" : "1.0", - "output" : [ - "string" - ], - "input" : [], - "name" : "getServiceProtocols" - }, - "name" : "getServiceProtocols" - }, - "getVersions" : { - "inputs" : null, - "version" : "1.0", - "service" : "guide", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/guide", - "outputs" : "str", - "name" : "getVersions", - "signature" : { - "name" : "getVersions", - "output" : [], - "input" : [], - "version" : "1.0" - } - } - } - }, - "audio" : { - "notifications" : { - "notifyVolumeInformation" : { - "name" : "notifyVolumeInformation", - "version" : "1.0" - } - }, - "protocols" : [ - "xhrpost:jsonizer", - "websocket:jsonizer" - ], - "methods" : { - "getMethodTypes" : { - "signature" : { - "name" : "getMethodTypes", - "output" : [ - "string", - "string*", - "string*" - ], - "input" : [], - "version" : "1.0" - }, - "name" : "getMethodTypes", - "outputs" : "str", - "inputs" : "str", - "version" : "1.0", - "service" : "audio", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio" - }, - "setAudioVolume" : { - "signature" : { - "input" : [], - "output" : [], - "version" : "1.1", - "name" : "setAudioVolume" - }, - "name" : "setAudioVolume", - "outputs" : null, - "inputs" : { - "volume" : "str", - "output" : "str" - }, - "version" : "1.1", - "service" : "audio", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio" - }, - "getCustomEqualizerSettings" : { - "name" : "getCustomEqualizerSettings", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.0", - "name" : "getCustomEqualizerSettings" - }, - "version" : "1.0", - "service" : "audio", - "inputs" : { - "target" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "outputs" : { - "target" : "str", - "titleTextID" : "str", - "type" : "str", - "deviceUIInfo" : "str", - "isAvailable" : "bool", - "title" : "str", - "currentValue" : "str", - "candidate" : "GeneralSettingsCandidate[]" - } - }, - "setSpeakerSettings" : { - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "service" : "audio", - "outputs" : null, - "name" : "setSpeakerSettings", - "signature" : { - "version" : "1.0", - "input" : [], - "output" : [], - "name" : "setSpeakerSettings" - } - }, - "setCustomEqualizerSettings" : { - "outputs" : null, - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "service" : "audio", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "setCustomEqualizerSettings" - }, - "name" : "setCustomEqualizerSettings" - }, - "switchNotifications" : { - "name" : "switchNotifications", - "signature" : { - "name" : "switchNotifications", - "output" : [], - "input" : [], - "version" : "1.0" - }, - "inputs" : { - "enabled" : "ApiIdentity[]", - "disabled" : "ApiIdentity[]" - }, - "version" : "1.0", - "service" : "audio", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "outputs" : { - "unsupported" : "ApiIdentity[]", - "disabled" : "ApiIdentity[]", - "enabled" : "ApiIdentity[]" - } - }, - "setAudioMute" : { - "outputs" : null, - "inputs" : { - "output" : "str", - "mute" : "str" - }, - "version" : "1.1", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "service" : "audio", - "signature" : { - "version" : "1.1", - "output" : [], - "input" : [], - "name" : "setAudioMute" - }, - "name" : "setAudioMute" - }, - "getVersions" : { - "outputs" : "str", - "version" : "1.0", - "inputs" : null, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "service" : "audio", - "signature" : { - "version" : "1.0", - "input" : [], - "output" : [], - "name" : "getVersions" - }, - "name" : "getVersions" - }, - "setSoundSettings" : { - "outputs" : null, - "version" : "1.1", - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "service" : "audio", - "signature" : { - "version" : "1.1", - "output" : [], - "input" : [], - "name" : "setSoundSettings" - }, - "name" : "setSoundSettings" - }, - "getSpeakerSettings" : { - "inputs" : { - "target" : "str" - }, - "version" : "1.0", - "service" : "audio", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "outputs" : { - "target" : "str", - "titleTextID" : "str", - "deviceUIInfo" : "str", - "type" : "str", - "isAvailable" : "bool", - "title" : "str", - "currentValue" : "str", - "candidate" : "GeneralSettingsCandidate[]" - }, - "name" : "getSpeakerSettings", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "getSpeakerSettings" - } - }, - "getVolumeInformation" : { - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "version" : "1.1", - "inputs" : { - "output" : "str" - }, - "service" : "audio", - "outputs" : { - "output" : "str", - "volume" : "int", - "step" : "int", - "mute" : "str", - "minVolume" : "int", - "maxVolume" : "int" - }, - "name" : "getVolumeInformation", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.1", - "name" : "getVolumeInformation" - } - }, - "getSoundSettings" : { - "inputs" : { - "target" : "str" - }, - "version" : "1.1", - "service" : "audio", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/audio", - "outputs" : { - "deviceUIInfo" : "str", - "type" : "str", - "target" : "str", - "titleTextID" : "str", - "candidate" : "GeneralSettingsCandidate[]", - "isAvailable" : "bool", - "title" : "str", - "currentValue" : "str" - }, - "name" : "getSoundSettings", - "signature" : { - "name" : "getSoundSettings", - "input" : [], - "output" : [], - "version" : "1.1" - } - } - } - }, - "avContent" : { - "methods" : { - "getBluetoothSettings" : { - "name" : "getBluetoothSettings", - "signature" : { - "name" : "getBluetoothSettings", - "output" : [], - "input" : [], - "version" : "1.0" - }, - "inputs" : { - "target" : "str" - }, - "version" : "1.0", - "service" : "avContent", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "outputs" : { - "type" : "str", - "deviceUIInfo" : "str", - "titleTextID" : "str", - "target" : "str", - "candidate" : "GeneralSettingsCandidate[]", - "currentValue" : "str", - "isAvailable" : "bool", - "title" : "str" - } - }, - "getPlaybackModeSettings" : { - "name" : "getPlaybackModeSettings", - "signature" : { - "name" : "getPlaybackModeSettings", - "output" : [], - "input" : [], - "version" : "1.0" - }, - "version" : "1.0", - "inputs" : { - "target" : "str", - "uri" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent", - "outputs" : { - "title" : "str", - "isAvailable" : "bool", - "currentValue" : "str", - "candidate" : "PlaybackModeSettingsCandidate[]", - "target" : "str", - "titleTextID" : "str", - "uri" : "str", - "deviceUIInfo" : "str", - "type" : "str" - } - }, - "setPlaybackModeSettings" : { - "outputs" : null, - "version" : "1.0", - "service" : "avContent", - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.0", - "name" : "setPlaybackModeSettings" - }, - "name" : "setPlaybackModeSettings" - }, - "getPlayingContentInfo" : { - "name" : "getPlayingContentInfo", - "signature" : { - "name" : "getPlayingContentInfo", - "input" : [], - "output" : [], - "version" : "1.2" - }, - "version" : "1.2", - "service" : "avContent", - "inputs" : { - "output" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "outputs" : { - "audioInfo" : "AudioInfo[]", - "durationSec" : "double", - "totalCount" : "int", - "channelName" : "str", - "genre" : "string*", - "tripletStr" : "str", - "title" : "str", - "output" : "str", - "is3D" : "str", - "subtitleIndex" : "int", - "albumName" : "str", - "applicationName" : "str", - "stateInfo" : "StateInfo", - "broadcastFreq" : "int", - "fileNo" : "str", - "videoInfo" : "VideoInfo", - "programNum" : "int", - "podcastName" : "str", - "positionSec" : "double", - "path" : "str", - "chapterCount" : "int", - "sourceLabel" : "str", - "bivl_assetId" : "str", - "mediaType" : "str", - "chapterIndex" : "int", - "playlistName" : "str", - "playSpeed" : "str", - "broadcastFreqBand" : "str", - "bivl_provider" : "str", - "originalDispNum" : "str", - "source" : "str", - "parentUri" : "str", - "bivl_serviceId" : "str", - "service" : "str", - "programTitle" : "str", - "durationMsec" : "int", - "repeatType" : "str", - "contentKind" : "str", - "parentIndex" : "int", - "artist" : "str", - "startDateTime" : "str", - "uri" : "str", - "dispNum" : "str", - "positionMsec" : "int", - "index" : "int", - "playSpeedStep" : "int", - "dabInfo" : "DabInfo" - } - }, - "getAvailablePlaybackFunction" : { - "signature" : { - "name" : "getAvailablePlaybackFunction", - "version" : "1.0", - "input" : [], - "output" : [] - }, - "name" : "getAvailablePlaybackFunction", - "outputs" : { - "output" : "str", - "functions" : "FunctionInfo[]", - "uri" : "str" - }, - "inputs" : { - "output" : "str" - }, - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent" - }, - "presetBroadcastStation" : { - "name" : "presetBroadcastStation", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "presetBroadcastStation" - }, - "service" : "avContent", - "version" : "1.0", - "inputs" : { - "uri" : "str", - "frequency" : "int" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "outputs" : null - }, - "stopPlayingContent" : { - "name" : "stopPlayingContent", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.1", - "name" : "stopPlayingContent" - }, - "version" : "1.1", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "inputs" : { - "output" : "str", - "keepLastFrame" : "bool" - }, - "service" : "avContent", - "outputs" : null - }, - "getCurrentExternalTerminalsStatus" : { - "version" : "1.0", - "inputs" : null, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent", - "outputs" : { - "uri" : "str", - "label" : "str", - "outputs" : "string*", - "iconUrl" : "str", - "active" : "str", - "meta" : "str", - "title" : "str", - "connection" : "str" - }, - "name" : "getCurrentExternalTerminalsStatus", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "getCurrentExternalTerminalsStatus" - } - }, - "switchNotifications" : { - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "switchNotifications" - }, - "name" : "switchNotifications", - "outputs" : { - "disabled" : "ApiIdentity[]", - "enabled" : "ApiIdentity[]", - "unsupported" : "ApiIdentity[]" - }, - "service" : "avContent", - "version" : "1.0", - "inputs" : { - "disabled" : "ApiIdentity[]", - "enabled" : "ApiIdentity[]" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent" - }, - "setPlayContent" : { - "name" : "setPlayContent", - "signature" : { - "version" : "1.2", - "input" : [], - "output" : [], - "name" : "setPlayContent" - }, - "inputs" : { - "resume" : "bool", - "positionMsec" : "int", - "requester" : "str", - "uri" : "str", - "output" : "str", - "positionSec" : "double", - "repeatType" : "str", - "keepLastFrame" : "bool" - }, - "version" : "1.2", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent", - "outputs" : null - }, - "scanPlayingContent" : { - "signature" : { - "name" : "scanPlayingContent", - "input" : [], - "output" : [], - "version" : "1.0" - }, - "name" : "scanPlayingContent", - "outputs" : null, - "version" : "1.0", - "inputs" : { - "output" : "str", - "direction" : "str" - }, - "service" : "avContent", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent" - }, - "getMethodTypes" : { - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "version" : "1.0", - "inputs" : "str", - "service" : "avContent", - "outputs" : "str", - "name" : "getMethodTypes", - "signature" : { - "input" : [], - "output" : [ - "string", - "string*", - "string*" - ], - "version" : "1.0", - "name" : "getMethodTypes" - } - }, - "getContentList" : { - "outputs" : { - "programMediaType" : "str", - "chapterCount" : "int", - "podcastName" : "str", - "isAlreadyPlayed" : "str", - "isProtected" : "str", - "path" : "str", - "isPlayable" : "str", - "programNum" : "int", - "remotePlayType" : "string*", - "fileNo" : "str", - "broadcastFreq" : "int", - "userContentFlag" : "bool", - "videoInfo" : "VideoInfo", - "albumName" : "str", - "is3D" : "str", - "directRemoteNum" : "int", - "genre" : "string*", - "isBrowsable" : "str", - "storageUri" : "str", - "tripletStr" : "str", - "title" : "str", - "audioInfo" : "AudioInfo[]", - "contentType" : "str", - "channelName" : "str", - "productID" : "str", - "index" : "int", - "sizeMB" : "int", - "visibility" : "str", - "folderNo" : "str", - "epgVisibility" : "str", - "uri" : "str", - "dispNum" : "str", - "durationMsec" : "int", - "contentKind" : "str", - "parentIndex" : "int", - "startDateTime" : "str", - "artist" : "str", - "content" : "ContentInfo", - "channelSurfingVisibility" : "str", - "parentUri" : "str", - "fileSizeByte" : "int", - "originalDispNum" : "str", - "createdTime" : "str", - "subtitleInfo" : "SubtitleInfo[]", - "playlistName" : "str", - "parentalInfo" : "ParentalInfo[]", - "broadcastFreqBand" : "str" - }, - "inputs" : { - "sort" : "str", - "path" : "str", - "uri" : "str", - "type" : "string*", - "cnt" : "int", - "stIdx" : "int", - "target" : "str", - "view" : "str" - }, - "version" : "1.4", - "service" : "avContent", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "signature" : { - "name" : "getContentList", - "version" : "1.4", - "output" : [], - "input" : [] - }, - "name" : "getContentList" - }, - "setPlayNextContent" : { - "name" : "setPlayNextContent", - "signature" : { - "name" : "setPlayNextContent", - "output" : [], - "input" : [], - "version" : "1.0" - }, - "version" : "1.0", - "service" : "avContent", - "inputs" : { - "output" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "outputs" : null - }, - "seekBroadcastStation" : { - "version" : "1.0", - "service" : "avContent", - "inputs" : { - "direction" : "str", - "tuning" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "outputs" : null, - "name" : "seekBroadcastStation", - "signature" : { - "name" : "seekBroadcastStation", - "output" : [], - "input" : [], - "version" : "1.0" - } - }, - "getSchemeList" : { - "name" : "getSchemeList", - "signature" : { - "name" : "getSchemeList", - "input" : [], - "output" : [], - "version" : "1.0" - }, - "version" : "1.0", - "inputs" : null, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent", - "outputs" : { - "scheme" : "str" - } - }, - "getContentCount" : { - "signature" : { - "version" : "1.3", - "output" : [], - "input" : [], - "name" : "getContentCount" - }, - "name" : "getContentCount", - "outputs" : { - "capability" : "int", - "count" : "int" - }, - "version" : "1.3", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "inputs" : { - "view" : "str", - "path" : "str", - "target" : "str", - "type" : "string*", - "uri" : "str" - }, - "service" : "avContent" - }, - "pausePlayingContent" : { - "signature" : { - "output" : [], - "input" : [], - "version" : "1.1", - "name" : "pausePlayingContent" - }, - "name" : "pausePlayingContent", - "outputs" : null, - "inputs" : { - "output" : "str" - }, - "version" : "1.1", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent" - }, - "startContentBrowsing" : { - "version" : "1.0", - "inputs" : { - "uri" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent", - "outputs" : { - "status" : "str", - "errorMessage" : "str" - }, - "name" : "startContentBrowsing", - "signature" : { - "name" : "startContentBrowsing", - "version" : "1.0", - "input" : [], - "output" : [] - } - }, - "setBluetoothSettings" : { - "signature" : { - "name" : "setBluetoothSettings", - "input" : [], - "output" : [], - "version" : "1.0" - }, - "name" : "setBluetoothSettings", - "outputs" : null, - "version" : "1.0", - "service" : "avContent", - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent" - }, - "setPlayPreviousContent" : { - "inputs" : { - "output" : "str" - }, - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent", - "outputs" : null, - "name" : "setPlayPreviousContent", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.0", - "name" : "setPlayPreviousContent" - } - }, - "getVersions" : { - "signature" : { - "version" : "1.0", - "input" : [], - "output" : [], - "name" : "getVersions" - }, - "name" : "getVersions", - "outputs" : "str", - "inputs" : null, - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent" - }, - "getSupportedPlaybackFunction" : { - "version" : "1.0", - "inputs" : { - "uri" : "str" - }, - "service" : "avContent", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "outputs" : { - "functions" : "SupportedFunctionInfo[]", - "uri" : "str" - }, - "name" : "getSupportedPlaybackFunction", - "signature" : { - "version" : "1.0", - "input" : [], - "output" : [], - "name" : "getSupportedPlaybackFunction" - } - }, - "getSourceList" : { - "name" : "getSourceList", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.1", - "name" : "getSourceList" - }, - "version" : "1.1", - "inputs" : { - "scheme" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "service" : "avContent", - "outputs" : { - "playAction" : "str", - "iconUrl" : "str", - "outputs" : "string*", - "source" : "str", - "isPlayable" : "bool", - "title" : "str", - "isBrowsable" : "bool", - "meta" : "str" - } - }, - "setActiveTerminal" : { - "outputs" : null, - "version" : "1.0", - "inputs" : { - "active" : "str", - "uri" : "str" - }, - "service" : "avContent", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/avContent", - "signature" : { - "version" : "1.0", - "input" : [], - "output" : [], - "name" : "setActiveTerminal" - }, - "name" : "setActiveTerminal" - } - }, - "notifications" : { - "notifyAvailablePlaybackFunction" : { - "version" : "1.0", - "name" : "notifyAvailablePlaybackFunction" - }, - "notifyPlayingContentInfo" : { - "name" : "notifyPlayingContentInfo", - "version" : "1.0" - }, - "notifyExternalTerminalStatus" : { - "version" : "1.0", - "name" : "notifyExternalTerminalStatus" - } - }, - "protocols" : [ - "xhrpost:jsonizer", - "websocket:jsonizer" - ] - }, - "system" : { - "methods" : { - "actSWUpdate" : { - "outputs" : null, - "version" : "1.0", - "service" : "system", - "inputs" : null, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.0", - "name" : "actSWUpdate" - }, - "name" : "actSWUpdate" - }, - "getSystemInformation" : { - "name" : "getSystemInformation", - "signature" : { - "version" : "1.3", - "input" : [], - "output" : [], - "name" : "getSystemInformation" - }, - "version" : "1.3", - "inputs" : null, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "service" : "system", - "outputs" : { - "updatableVersion" : "str", - "generation" : "str", - "product" : "str", - "bdAddr" : "str", - "name" : "str", - "model" : "str", - "region" : "str", - "area" : "str", - "cid" : "str", - "duid" : "str", - "wirelessMacAddr" : "str", - "version" : "str", - "deviceID" : "str", - "iconUrl" : "str", - "macAddr" : "str", - "language" : "str", - "esn" : "str", - "serial" : "str", - "ssid" : "str", - "helpUrl" : "str" - } - }, - "getMethodTypes" : { - "outputs" : "str", - "inputs" : "str", - "version" : "1.0", - "service" : "system", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "signature" : { - "version" : "1.0", - "output" : [ - "string", - "string*", - "string*" - ], - "input" : [], - "name" : "getMethodTypes" - }, - "name" : "getMethodTypes" - }, - "setSleepTimerSettings" : { - "outputs" : null, - "service" : "system", - "version" : "1.0", - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "signature" : { - "version" : "1.0", - "output" : [], - "input" : [], - "name" : "setSleepTimerSettings" - }, - "name" : "setSleepTimerSettings" - }, - "getStorageList" : { - "signature" : { - "version" : "1.1", - "output" : [], - "input" : [], - "name" : "getStorageList" - }, - "name" : "getStorageList", - "outputs" : { - "position" : "str", - "systemAreaCapacityMB" : "int", - "uri" : "str", - "lun" : "int", - "isAvailable" : "str", - "mounted" : "str", - "formattable" : "str", - "deviceName" : "str", - "permission" : "str", - "format" : "str", - "volumeLabel" : "str", - "wholeCapacityMB" : "int", - "error" : "str", - "formatting" : "str", - "type" : "str", - "freeCapacityMB" : "int" - }, - "inputs" : { - "uri" : "str" - }, - "version" : "1.1", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "service" : "system" - }, - "setDeviceMiscSettings" : { - "signature" : { - "name" : "setDeviceMiscSettings", - "input" : [], - "output" : [], - "version" : "1.0" - }, - "name" : "setDeviceMiscSettings", - "outputs" : null, - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "service" : "system" - }, - "switchNotifications" : { - "signature" : { - "name" : "switchNotifications", - "version" : "1.0", - "output" : [], - "input" : [] - }, - "name" : "switchNotifications", - "outputs" : { - "unsupported" : "ApiIdentity[]", - "disabled" : "ApiIdentity[]", - "enabled" : "ApiIdentity[]" - }, - "inputs" : { - "enabled" : "ApiIdentity[]", - "disabled" : "ApiIdentity[]" - }, - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "service" : "system" - }, - "getSWUpdateInfo" : { - "name" : "getSWUpdateInfo", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "getSWUpdateInfo" - }, - "service" : "system", - "version" : "1.0", - "inputs" : { - "network" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "outputs" : { - "swInfo" : "SWInfo[]", - "isUpdatable" : "str" - } - }, - "setClientInfo" : { - "outputs" : null, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "version" : "1.0", - "inputs" : { - "target" : "str", - "value" : "str" - }, - "service" : "system", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.0", - "name" : "setClientInfo" - }, - "name" : "setClientInfo" - }, - "getWuTangInfo" : { - "outputs" : { - "target" : "str", - "titleTextID" : "str", - "deviceUIInfo" : "str", - "type" : "str", - "isAvailable" : "bool", - "title" : "str", - "currentValue" : "str", - "candidate" : "GeneralSettingsCandidate[]" - }, - "version" : "1.0", - "inputs" : { - "target" : "str" - }, - "service" : "system", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "signature" : { - "name" : "getWuTangInfo", - "version" : "1.0", - "input" : [], - "output" : [] - }, - "name" : "getWuTangInfo" - }, - "setPowerSettings" : { - "name" : "setPowerSettings", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "setPowerSettings" - }, - "service" : "system", - "version" : "1.0", - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "outputs" : null - }, - "getPowerStatus" : { - "name" : "getPowerStatus", - "signature" : { - "name" : "getPowerStatus", - "input" : [], - "output" : [], - "version" : "1.1" - }, - "version" : "1.1", - "inputs" : null, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "service" : "system", - "outputs" : { - "standbyDetail" : "str", - "status" : "str" - } - }, - "getDeviceMiscSettings" : { - "outputs" : { - "titleTextID" : "str", - "target" : "str", - "type" : "str", - "deviceUIInfo" : "str", - "currentValue" : "str", - "title" : "str", - "isAvailable" : "bool", - "candidate" : "GeneralSettingsCandidate[]" - }, - "version" : "1.0", - "service" : "system", - "inputs" : { - "target" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "signature" : { - "input" : [], - "output" : [], - "version" : "1.0", - "name" : "getDeviceMiscSettings" - }, - "name" : "getDeviceMiscSettings" - }, - "getSleepTimerSettings" : { - "name" : "getSleepTimerSettings", - "signature" : { - "name" : "getSleepTimerSettings", - "output" : [], - "input" : [], - "version" : "1.0" - }, - "version" : "1.0", - "inputs" : { - "target" : "str" - }, - "service" : "system", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "outputs" : { - "titleTextID" : "str", - "target" : "str", - "type" : "str", - "deviceUIInfo" : "str", - "currentValue" : "str", - "isAvailable" : "bool", - "title" : "str", - "candidate" : "GeneralSettingsCandidate[]" - } - }, - "setPowerStatus" : { - "outputs" : null, - "version" : "1.1", - "service" : "system", - "inputs" : { - "status" : "str", - "standbyDetail" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "signature" : { - "version" : "1.1", - "output" : [], - "input" : [], - "name" : "setPowerStatus" - }, - "name" : "setPowerStatus" - }, - "setWuTangInfo" : { - "inputs" : { - "settings" : "GeneralSettings[]" - }, - "version" : "1.0", - "service" : "system", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "outputs" : null, - "name" : "setWuTangInfo", - "signature" : { - "output" : [], - "input" : [], - "version" : "1.0", - "name" : "setWuTangInfo" - } - }, - "getVersions" : { - "outputs" : "str", - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "inputs" : null, - "service" : "system", - "signature" : { - "name" : "getVersions", - "input" : [], - "output" : [], - "version" : "1.0" - }, - "name" : "getVersions" - }, - "getPowerSettings" : { - "outputs" : { - "title" : "str", - "isAvailable" : "bool", - "currentValue" : "str", - "candidate" : "GeneralSettingsCandidate[]", - "target" : "str", - "titleTextID" : "str", - "deviceUIInfo" : "str", - "type" : "str" - }, - "version" : "1.0", - "inputs" : { - "target" : "str" - }, - "service" : "system", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "signature" : { - "name" : "getPowerSettings", - "output" : [], - "input" : [], - "version" : "1.0" - }, - "name" : "getPowerSettings" - }, - "getInterfaceInformation" : { - "name" : "getInterfaceInformation", - "signature" : { - "version" : "1.0", - "output" : [], - "input" : [], - "name" : "getInterfaceInformation" - }, - "version" : "1.0", - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "inputs" : null, - "service" : "system", - "outputs" : { - "serverName" : "str", - "interfaceVersion" : "str", - "productCategory" : "str", - "productName" : "str", - "modelName" : "str" - } - }, - "getSettingsTree" : { - "name" : "getSettingsTree", - "signature" : { - "version" : "1.1", - "output" : [], - "input" : [], - "name" : "getSettingsTree" - }, - "version" : "1.1", - "inputs" : { - "usage" : "str" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "service" : "system", - "outputs" : { - "settings" : "SettingsTreeList[]" - } - }, - "connectBluetoothDevice" : { - "name" : "connectBluetoothDevice", - "signature" : { - "name" : "connectBluetoothDevice", - "output" : [], - "input" : [], - "version" : "1.0" - }, - "endpoint" : "http://192.168.xxx.xxx:10000/sony/system", - "version" : "1.0", - "inputs" : { - "bdAddr" : "str" - }, - "service" : "system", - "outputs" : null - } - }, - "protocols" : [ - "xhrpost:jsonizer", - "websocket:jsonizer" - ], - "notifications" : { - "notifySettingsUpdate" : { - "version" : "1.1", - "name" : "notifySettingsUpdate" - }, - "notifySWUpdateInfo" : { - "version" : "1.0", - "name" : "notifySWUpdateInfo" - }, - "notifyStorageStatus" : { - "version" : "1.1", - "name" : "notifyStorageStatus" - }, - "notifyPowerStatus" : { - "name" : "notifyPowerStatus", - "version" : "1.0" - } - } - } - } -} + ], + "notifications": [ + { + "name": "notifyVolumeInformation", + "versions": [ + { + "version": "1.0" + } + ] + } + ], + "protocols": [ + "xhrpost:jsonizer", + "websocket:jsonizer" + ], + "service": "audio" + }, + { + "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" + } + ] + }, + { + "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": "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" + } + ] + }, + { + "name": "getSystemInformation", + "versions": [ + { + "version": "1.3" + } + ] + }, + { + "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" + } + ] + } + ], + "protocols": [ + "xhrpost:jsonizer", + "websocket:jsonizer" + ], + "service": "system" + } + ] + ] + }, + "sysinfo": { + "bdAddr": "68:14:01:8b:20:aa", + "bleID": null, + "bssid": null, + "macAddr": "10:4f:a8:bd:4a:ca", + "ssid": null, + "version": "M28.R.0476", + "wirelessMacAddr": "68:14:01:8b:20:a9" + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9ad9070..82c8654 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ aiohttp click async_upnp_client attrs +pre-commit diff --git a/songpal/__init__.py b/songpal/__init__.py index e4d5756..a9e754d 100644 --- a/songpal/__init__.py +++ b/songpal/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa from songpal.common import SongpalException +from songpal.discovery import Discover from songpal.device import Device from songpal.notification import ( Notification, diff --git a/songpal/containers.py b/songpal/containers.py index 433987a..544d857 100644 --- a/songpal/containers.py +++ b/songpal/containers.py @@ -67,7 +67,10 @@ class Scheme: make = classmethod(make) scheme = attr.ib() + sources_getter = attr.ib() + async def get_sources(self): + return [Source.make(**x) for x in self.sources_getter(scheme=self.scheme)] @attr.s class PlaybackFunction: @@ -230,6 +233,11 @@ class SoftwareUpdateInfo: target = attr.ib() updatableVersion = attr.ib() forcedUpdate = attr.ib(converter=convert_to_bool) + service = attr.ib() + + async def update(self): + return await self.service["actSWUpdate"]() + @attr.s @@ -259,7 +267,7 @@ class Volume: """Volume information.""" make = classmethod(make) - services = attr.ib(repr=False) + service = attr.ib(repr=False) maxVolume = attr.ib() minVolume = attr.ib() mute = attr.ib() @@ -287,19 +295,19 @@ async def set_mute(self, activate: bool): if activate: enabled = "on" - return await self.services["audio"]["setAudioMute"]( + return await self.service["setAudioMute"]( mute=enabled, output=self.output ) async def toggle_mute(self): """Toggle mute.""" - return await self.services["audio"]["setAudioMute"]( + return await self.service["setAudioMute"]( mute="toggle", output=self.output ) async def set_volume(self, volume: int): """Set volume level.""" - return await self.services["audio"]["setAudioVolume"]( + return await self.service["setAudioVolume"]( volume=str(volume), output=self.output ) @@ -338,7 +346,7 @@ class Zone: title = attr.ib(converter=convert_title) uri = attr.ib() - services = attr.ib(repr=False) + service = attr.ib(repr=False) active = attr.ib(converter=convert_is_active) label = attr.ib() iconUrl = attr.ib() @@ -351,7 +359,7 @@ def __str__(self): async def activate(self, activate): """Activate this zone.""" - return await self.services["avContent"]["setActiveTerminal"](active='active' if activate else 'inactive', uri=self.uri) + return await self["setActiveTerminal"](active='active' if activate else 'inactive', uri=self.uri) @attr.s @@ -364,7 +372,7 @@ class Input: title = attr.ib(converter=convert_title) uri = attr.ib() - services = attr.ib(repr=False) + service = attr.ib(repr=False) active = attr.ib(converter=convert_is_active) label = attr.ib() iconUrl = attr.ib() @@ -379,7 +387,7 @@ def __str__(self): async def activate(self, output: Zone=None): """Activate this input.""" output_uri = output.uri if output else "" - return await self.services["avContent"]["setPlayContent"](uri=self.uri, output=output_uri) + return await self["setPlayContent"](uri=self.uri, output=output_uri) @attr.s @@ -509,3 +517,5 @@ def _create_candidates(x): titleTextID = attr.ib() deviceUIInfo = attr.ib() uri = attr.ib() + setter = attr.ib() + value = attr.ib() diff --git a/songpal/device.py b/songpal/device.py index 404a4cc..ebc4e9f 100644 --- a/songpal/device.py +++ b/songpal/device.py @@ -1,20 +1,15 @@ """Module presenting a single supported device.""" import asyncio -import aiohttp -from collections import defaultdict import itertools -import json import logging -from pprint import pformat as pf +from collections import defaultdict from typing import Any, Dict, List from urllib.parse import urlparse -from songpal.common import SongpalException +from songpal.common import ProtocolType, SongpalException from songpal.containers import ( Content, - ContentInfo, Input, - Zone, InterfaceInfo, PlayInfo, Power, @@ -27,9 +22,12 @@ SupportedFunctions, Sysinfo, Volume, + Zone, ) -from songpal.notification import Notification, ConnectChange -from songpal.service import Service +from songpal.services import Audio, AVContent, Guide, System + +from .notification import ConnectChange, Notification +from .services.service import Service _LOGGER = logging.getLogger(__name__) @@ -67,139 +65,68 @@ def __init__(self, endpoint, force_protocol=None, debug=0): self.callbacks = defaultdict(set) + self.system = None # type: System + self.audio = None # type: Audio + self.avcontent = None # type: AVContent + async def __aenter__(self): """Asynchronous context manager, initializes the list of available methods.""" await self.get_supported_methods() - async def create_post_request(self, method: str, params: Dict = None): - """Call the given method over POST. - - :param method: Name of the method - :param params: dict of parameters - :return: JSON object - """ - if params is None: - params = {} - headers = {"Content-Type": "application/json"} - payload = { - "method": method, - "params": [params], - "id": next(self.idgen), - "version": "1.0", - } - - if self.debug > 1: - _LOGGER.debug("> POST %s with body: %s", self.guide_endpoint, payload) - - async with aiohttp.ClientSession(headers=headers) as session: - res = await session.post(self.guide_endpoint, json=payload, headers=headers) - if self.debug > 1: - _LOGGER.debug("Received %s: %s" % (res.status_code, res.text)) - if res.status != 200: - raise SongpalException( - "Got a non-ok (status %s) response for %s" % (res.status, method), - error=await res.json()["error"], - ) - - res = await res.json() - - # TODO handle exceptions from POST? This used to raise SongpalException - # on requests.RequestException (Unable to get APIs). - - if "error" in res: - raise SongpalException("Got an error for %s" % method, error=res["error"]) - - if self.debug > 1: - _LOGGER.debug("Got %s: %s", method, pf(res)) - - return res - - async def request_supported_methods(self): - """Return JSON formatted supported API.""" - return await self.create_post_request("getSupportedApiInfo") - async def get_supported_methods(self): """Get information about supported methods. Calling this as the first thing before doing anything else is necessary to fill the available services table. """ - response = await self.request_supported_methods() - - if "result" in response: - services = response["result"][0] - _LOGGER.debug("Got %s services!" % len(services)) - - for x in services: - serv = await Service.from_payload( - x, self.endpoint, self.idgen, self.debug, self.force_protocol - ) - if serv is not None: - self.services[x["service"]] = serv - else: - _LOGGER.warning("Unable to create service %s", x["service"]) - - for service in self.services.values(): - if self.debug > 1: - _LOGGER.debug("Service %s", service) - for api in service.methods: - # self.logger.debug("%s > %s" % (service, api)) - if self.debug > 1: - _LOGGER.debug("> %s" % api) - return self.services - - return None + guide = Guide( + "guide", + endpoint=self.guide_endpoint, + protocol=ProtocolType.XHRPost, + idgen=self.idgen, + debug=self.debug, + ) + self.services = await guide.get_supported_apis( + self.endpoint, self.force_protocol + ) + print(self.services.keys()) + if "system" in self.services: + self.system = self.services["system"] + if "audio" in self.services: + self.audio = self.services["audio"] + if "avContent" in self.services: + self.avcontent = self.services["avContent"] async def get_power(self) -> Power: """Get the device state.""" - res = await self.services["system"]["getPowerStatus"]() - return Power.make(**res) + return await self.system.get_power() async def set_power(self, value: bool): """Toggle the device on and off.""" - if value: - status = "active" - else: - status = "off" - # TODO WoL works when quickboot is not enabled - return await self.services["system"]["setPowerStatus"](status=status) - - async def get_play_info(self) -> PlayInfo: - """Return of the device.""" - info = await self.services["avContent"]["getPlayingContentInfo"]({}) - return PlayInfo.make(**info.pop()) + return await self.system.set_power(value) async def get_power_settings(self) -> List[Setting]: """Get power settings.""" - return [ - Setting.make(**x) - for x in await self.services["system"]["getPowerSettings"]({}) - ] + return await self.system.get_power_settings() async def set_power_settings(self, target: str, value: str) -> None: """Set power settings.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["system"]["setPowerSettings"](params) + await self.system.set_power_settings(target, value) async def get_googlecast_settings(self) -> List[Setting]: """Get Googlecast settings.""" - return [ - Setting.make(**x) - for x in await self.services["system"]["getWuTangInfo"]({}) - ] + return await self.system.get_googlecast_settings() async def set_googlecast_settings(self, target: str, value: str): """Set Googlecast settings.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["system"]["setWuTangInfo"](params) + return await self.system.set_googlecast_settings(target, value) async def request_settings_tree(self): """Get raw settings tree JSON. Prefer :func:get_settings: for containerized settings. """ - settings = await self.services["system"]["getSettingsTree"](usage="") - return settings + return await self.system.request_settings_tree() async def get_settings(self) -> List[SettingsEntry]: """Get a list of available settings. @@ -211,63 +138,47 @@ async def get_settings(self) -> List[SettingsEntry]: async def get_misc_settings(self) -> List[Setting]: """Return miscellaneous settings such as name and timezone.""" - misc = await self.services["system"]["getDeviceMiscSettings"](target="") - return [Setting.make(**x) for x in misc] + return await self.system.get_misc_settings() async def set_misc_settings(self, target: str, value: str): """Change miscellaneous settings.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["system"]["setDeviceMiscSettings"](params) + return await self.system.set_misc_setting(target, value) async def get_interface_information(self) -> InterfaceInfo: """Return generic product information.""" - iface = await self.services["system"]["getInterfaceInformation"]() - return InterfaceInfo.make(**iface) + return await self.system.get_interface_information() async def get_system_info(self) -> Sysinfo: """Return system information including mac addresses and current version.""" - return Sysinfo.make(**await self.services["system"]["getSystemInformation"]()) + return await self.system.get_system_info() async def get_sleep_timer_settings(self) -> List[Setting]: """Get sleep timer settings.""" - return [ - Setting.make(**x) - for x in await self.services["system"]["getSleepTimerSettings"]({}) - ] + return await self.system.get_sleep_timer_settings() async def get_storage_list(self) -> List[Storage]: """Return information about connected storage devices.""" - return [ - Storage.make(**x) - for x in await self.services["system"]["getStorageList"]({}) - ] + return await self.system.get_storage_list() async def get_update_info(self, from_network=True) -> SoftwareUpdateInfo: """Get information about updates.""" - if from_network: - from_network = "true" - else: - from_network = "false" - # from_network = "" - info = await self.services["system"]["getSWUpdateInfo"](network=from_network) - return SoftwareUpdateInfo.make(**info) + return await self.system.get_update_info(from_network=from_network) async def activate_system_update(self) -> None: """Start a system update if available.""" - return await self.services["system"]["actSWUpdate"]() + return await self.system.activate_system_update() + + async def get_play_info(self) -> PlayInfo: + """Return of the device.""" + return await self.avcontent.get_play_info() async def get_inputs(self) -> List[Input]: """Return list of available outputs.""" - res = await self.services["avContent"]["getCurrentExternalTerminalsStatus"]() - return [Input.make(services=self.services, **x) for x in res if 'meta:zone:output' not in x['meta']] + return await self.avcontent.get_inputs() async def get_zones(self) -> List[Zone]: """Return list of available zones.""" - res = await self.services["avContent"]["getCurrentExternalTerminalsStatus"]() - zones = [Zone.make(services=self.services, **x) for x in res if 'meta:zone:output' in x['meta']] - if not zones: - raise SongpalException("Device has no zones") - return zones + return await self.avcontent.get_zones() async def get_zone(self, name) -> Zone: zones = await self.get_zones() @@ -289,64 +200,45 @@ async def get_setting(self, service: str, method: str, target: str): async def get_bluetooth_settings(self) -> List[Setting]: """Get bluetooth settings.""" - bt = await self.services["avContent"]["getBluetoothSettings"]({}) - return [Setting.make(**x) for x in bt] + return await self.avcontent.get_bluetooth_settings() async def set_bluetooth_settings(self, target: str, value: str) -> None: """Set bluetooth settings.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["avContent"]["setBluetoothSettings"](params) + return await self.avcontent.set_bluetooth_setting(target, value) async def get_custom_eq(self): """Get custom EQ settings.""" - return await self.services["audio"]["getCustomEqualizerSettings"]({}) + return await self.audio.get_custom_eq() async def set_custom_eq(self, target: str, value: str) -> None: """Set custom EQ settings.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["audio"]["setCustomEqualizerSettings"](params) + return await self.audio.set_custom_eq(target, value) async def get_supported_playback_functions( self, uri="" ) -> List[SupportedFunctions]: """Return list of inputs and their supported functions.""" - return [ - SupportedFunctions.make(**x) - for x in await self.services["avContent"]["getSupportedPlaybackFunction"]( - uri=uri - ) - ] + return await self.avcontent.get_supported_playback_functions(uri=uri) async def get_playback_settings(self) -> List[Setting]: """Get playback settings such as shuffle and repeat.""" - return [ - Setting.make(**x) - for x in await self.services["avContent"]["getPlaybackModeSettings"]({}) - ] + return await self.avcontent.get_playback_settings() async def set_playback_settings(self, target, value) -> None: """Set playback settings such a shuffle and repeat.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["avContent"]["setPlaybackModeSettings"](params) + return await self.avcontent.set_playback_settings(target, value) async def get_schemes(self) -> List[Scheme]: """Return supported uri schemes.""" - return [ - Scheme.make(**x) - for x in await self.services["avContent"]["getSchemeList"]() - ] + return await self.avcontent.get_schemes() async def get_source_list(self, scheme: str = "") -> List[Source]: """Return available sources for playback.""" - res = await self.services["avContent"]["getSourceList"](scheme=scheme) - return [Source.make(**x) for x in res] + return await self.avcontent.get_source_list(scheme=scheme) async def get_content_count(self, source: str): """Return file listing for source.""" - params = {"uri": source, "type": None, "target": "all", "view": "flat"} - return ContentInfo.make( - **await self.services["avContent"]["getContentCount"](params) - ) + return await self.avcontent.get_content_count(source) async def get_contents(self, uri) -> List[Content]: """Request content listing recursively for the given URI. @@ -354,70 +246,41 @@ async def get_contents(self, uri) -> List[Content]: :param uri: URI for the source. :return: List of Content objects. """ - contents = [ - Content.make(**x) - for x in await self.services["avContent"]["getContentList"](uri=uri) - ] - contentlist = [] - - for content in contents: - if content.contentKind == "directory" and content.index >= 0: - # print("got directory %s" % content.uri) - res = await self.get_contents(content.uri) - contentlist.extend(res) - else: - contentlist.append(content) - # print("%s%s" % (' ' * depth, content)) - return contentlist - - async def get_volume_information(self) -> List[Volume]: + return await self.avcontent.get_contents() + + async def get_available_playback_functions(self, output=""): + """Return available playback functions. + + If no output is given the current is assumed. + """ + return await self.avcontent.get_available_playback_functions(output=output) + + async def get_volume(self) -> List[Volume]: """Get the volume information.""" - res = await self.services["audio"]["getVolumeInformation"]({}) - volume_info = [Volume.make(services=self.services, **x) for x in res] - if len(volume_info) < 1: - logging.warning("Unable to get volume information") - elif len(volume_info) > 1: - logging.debug("The device seems to have more than one volume setting.") - return volume_info + return await self.audio.get_volume_information() async def get_sound_settings(self, target="") -> List[Setting]: """Get the current sound settings. :param str target: settings target, defaults to all. """ - res = await self.services["audio"]["getSoundSettings"]({"target": target}) - return [Setting.make(**x) for x in res] - - async def get_soundfield(self) -> List[Setting]: - """Get the current sound field settings.""" - res = await self.services["audio"]["getSoundSettings"]({"target": "soundField"}) - return Setting.make(**res[0]) + return await self.audio.get_sound_settings(target) async def set_soundfield(self, value): """Set soundfield.""" - return await self.set_sound_settings("soundField", value) + return await self.audio.set_sound_settings("soundField", value) async def set_sound_settings(self, target: str, value: str): """Change a sound setting.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["audio"]["setSoundSettings"](params) + return await self.audio.set_sound_setting(target, value) async def get_speaker_settings(self) -> List[Setting]: """Return speaker settings.""" - speaker_settings = await self.services["audio"]["getSpeakerSettings"]({}) - return [Setting.make(**x) for x in speaker_settings] + return await self.audio.get_speaker_settings() async def set_speaker_settings(self, target: str, value: str): """Set speaker settings.""" - params = {"settings": [{"target": target, "value": value}]} - return await self.services["audio"]["setSpeakerSettings"](params) - - async def get_available_playback_functions(self, output=""): - """Return available playback functions. - - If no output is given the current is assumed. - """ - await self.services["avContent"]["getAvailablePlaybackFunction"](output=output) + return await self.audio.set_speaker_setting(target, value) def on_notification(self, type_, callback): """Register a notification callback. @@ -453,10 +316,10 @@ async def handle_notification(notification): for cb in self.callbacks[type(notification)]: await cb(notification) - for serv in self.services.values(): + for service in self.services.values(): tasks.append( asyncio.ensure_future( - serv.listen_all_notifications(handle_notification) + service.listen_all_notifications(handle_notification) ) ) @@ -471,8 +334,8 @@ async def handle_notification(notification): async def stop_listen_notifications(self): """Stop listening on notifications.""" _LOGGER.debug("Stopping listening for notifications..") - for serv in self.services.values(): - await serv.stop_listen_notifications() + for service in self.services.values(): + await service.stop_listen_notifications() return True @@ -485,8 +348,8 @@ async def get_notifications(self) -> List[Notification]: :return: List of Notification objects """ notifications = [] - for serv in self.services: - for notification in self.services[serv].notifications: + for service in self.services: + for notification in self.services[service].notifications: notifications.append(notification) return notifications diff --git a/songpal/discovery.py b/songpal/discovery.py index a71e90a..27b943e 100644 --- a/songpal/discovery.py +++ b/songpal/discovery.py @@ -1,10 +1,15 @@ import logging +from functools import partial + from async_upnp_client.search import async_search +from async_upnp_client import UpnpFactory +from async_upnp_client.aiohttp import AiohttpRequester from xml import etree import attr _LOGGER = logging.getLogger(__name__) + @attr.s class DiscoveredDevice: name = attr.ib() @@ -16,53 +21,56 @@ class DiscoveredDevice: version = attr.ib() upnp_services = attr.ib() + class Discover: @staticmethod - async def discover(timeout, debug=0, callback=None): - """Discover supported devices.""" - ST = "urn:schemas-sony-com:service:ScalarWebAPI:1" - _LOGGER.info("Discovering for %s seconds" % timeout) + async def _handle_discovered_device(device, debug=0, callback=None): - from async_upnp_client import UpnpFactory - from async_upnp_client.aiohttp import AiohttpRequester + requester = AiohttpRequester() + factory = UpnpFactory(requester) - async def parse_device(device): - requester = AiohttpRequester() - factory = UpnpFactory(requester) + url = device["location"] + device = await factory.async_create_device(url) - url = device["location"] - device = await factory.async_create_device(url) + if debug > 0: + print(etree.ElementTree.tostring(device.xml).decode()) - if debug > 0: - print(etree.ElementTree.tostring(device.xml).decode()) + NS = { + 'av': 'urn:schemas-sony-com:av', + } - NS = { - 'av': 'urn:schemas-sony-com:av', - } + info = device.xml.find(".//av:X_ScalarWebAPI_DeviceInfo", NS) + if not info: + _LOGGER.error("Unable to find X_ScalaerWebAPI_DeviceInfo") + return - info = device.xml.find(".//av:X_ScalarWebAPI_DeviceInfo", NS) - if not info: - _LOGGER.error("Unable to find X_ScalaerWebAPI_DeviceInfo") - return + endpoint = info.find(".//av:X_ScalarWebAPI_BaseURL", NS).text + version = info.find(".//av:X_ScalarWebAPI_Version", NS).text + services = [x.text for x in info.findall(".//av:X_ScalarWebAPI_ServiceType", NS)] - endpoint = info.find(".//av:X_ScalarWebAPI_BaseURL", NS).text - version = info.find(".//av:X_ScalarWebAPI_Version", NS).text - services = [x.text for x in info.findall(".//av:X_ScalarWebAPI_ServiceType", NS)] + dev = DiscoveredDevice(name=device.name, + model_number=device.model_number, + udn=device.udn, + endpoint=endpoint, + version=version, + services=services, + upnp_services=list(device.services.keys()), + upnp_location=url) - dev = DiscoveredDevice(name=device.name, - model_number=device.model_number, - udn=device.udn, - endpoint=endpoint, - version=version, - services=services, - upnp_services=list(device.services.keys()), - upnp_location=url) + _LOGGER.debug("Discovered: %s" % dev) - _LOGGER.debug("Discovered: %s" % dev) + if callback is not None: + await callback(dev) + + @staticmethod + async def discover(timeout, debug=0, callback=None): + """Discover supported devices.""" + ST = "urn:schemas-sony-com:service:ScalarWebAPI:1" + _LOGGER.info("Discovering for %s seconds" % timeout) - if callback is not None: - await callback(dev) + cb = partial(Discover._handle_discovered_device, + debug=debug, callback=callback) await async_search(timeout=timeout, service_type=ST, - async_callback=parse_device) + async_callback=cb) diff --git a/songpal/main.py b/songpal/main.py index 2ed8288..5e50fae 100644 --- a/songpal/main.py +++ b/songpal/main.py @@ -104,6 +104,7 @@ def print_settings(settings, depth=0): pass_dev = click.make_pass_decorator(Device) + @click.group(invoke_without_command=False) @click.option("--endpoint", envvar="SONGPAL_ENDPOINT", required=False) @click.option("-d", "--debug", default=False, count=True) @@ -159,7 +160,7 @@ async def status(dev: Device): power = await dev.get_power() click.echo(click.style("%s" % power, bold=power)) - vol = await dev.get_volume_information() + vol = await dev.get_volume() click.echo(vol.pop()) play_info = await dev.get_play_info() @@ -205,18 +206,15 @@ async def print_discovered(dev): @cli.command() -@click.argument("cmd", required=False) -@click.argument("target", required=False) -@click.argument("value", required=False) +@click.argument("cmd", required=False, type=ONOFF_BOOL) @pass_dev @coro -async def power(dev: Device, cmd, target, value): +async def power(dev: Device, cmd): """Turn on and off, control power settings. Accepts commands 'on', 'off', and 'settings'. """ async def try_turn(cmd): - state = True if cmd == "on" else False try: return await dev.set_power(state) except SongpalException as ex: @@ -225,13 +223,8 @@ async def try_turn(cmd): else: raise ex - if cmd == "on" or cmd == "off": + if cmd is not None: click.echo(await try_turn(cmd)) - elif cmd == "settings": - settings = await dev.get_power_settings() - print_settings(settings) - elif cmd == "set" and target and value: - click.echo(await dev.set_power_settings(target, value)) else: power = await dev.get_power() click.echo(click.style(str(power), bold=power)) @@ -352,7 +345,7 @@ async def volume(dev: Device, volume, output): 'unmute' removes it. """ vol = None - vol_controls = await dev.get_volume_information() + vol_controls = await dev.get_volume() if output is not None: click.echo("Using output: %s" % output) output_uri = (await dev.get_zone(output)).uri @@ -447,13 +440,26 @@ async def misc(dev: Device): print_settings(await dev.get_misc_settings()) -@cli.command() +@cli.group() @pass_dev @coro async def settings(dev: Device): """Print out all possible settings.""" settings_tree = await dev.get_settings() + """ + elif cmd == "settings": + settings = await dev.get_power_settings() + print_settings(settings) + elif cmd == "set" and target and value: + click.echo(await dev.set_power_settings(target, value)) + """ + + from pprint import pprint as pp + pp(settings_tree) + + return + for module in settings_tree: await traverse_settings(dev, module.usage, module.settings) @@ -468,7 +474,7 @@ async def storage(dev: Device): click.echo(storage) -@cli.command() +@settings.command() @click.argument("target", required=False) @click.argument("value", required=False) @pass_dev @@ -488,6 +494,7 @@ async def sound(dev: Device, target, value): @coro async def soundfield(dev: Device, soundfield: str): """Get or set sound field.""" + raise Exception("use settings sound soundField") if soundfield is not None: await dev.set_sound_settings("soundField", soundfield) soundfields = await dev.get_sound_settings("soundField") @@ -502,7 +509,7 @@ async def eq(dev: Device): click.echo(await dev.get_custom_eq()) -@cli.command() +@settings.command() @click.argument("cmd", required=False) @click.argument("target", required=False) @click.argument("value", required=False) @@ -511,7 +518,7 @@ async def eq(dev: Device): async def playback(dev: Device, cmd, target, value): """Get and set playback settings, e.g. repeat and shuffle..""" if target and value: - dev.set_playback_settings(target, value) + await dev.set_playback_settings(target, value) if cmd == "support": click.echo("Supported playback functions:") supported = await dev.get_supported_playback_functions("storage:usb1") @@ -519,14 +526,11 @@ async def playback(dev: Device, cmd, target, value): print(i) elif cmd == "settings": print_settings(await dev.get_playback_settings()) - # click.echo("Playback functions:") - # funcs = await dev.get_available_playback_functions() - # print(funcs) else: click.echo("Currently playing: %s" % await dev.get_play_info()) -@cli.command() +@settings.command() @click.argument("target", required=False) @click.argument("value", required=False) @pass_dev @@ -581,7 +585,7 @@ async def handle_notification(x): click.echo("* %s" % notification) -@cli.command() +@settings.command() @pass_dev @coro async def sleep(dev: Device): @@ -628,6 +632,7 @@ async def dump_devinfo(dev: Device, file): methods = await dev.get_supported_methods() res = { + "supported_methods_response": await dev.request_supported_methods(), "supported_methods": {k: v.asdict() for k, v in methods.items()}, "settings": [attr.asdict(x) for x in await dev.get_settings()], "sysinfo": attr.asdict(await dev.get_system_info()), diff --git a/songpal/method.py b/songpal/method.py index a9f50fb..cd96358 100644 --- a/songpal/method.py +++ b/songpal/method.py @@ -55,7 +55,8 @@ def from_payload(name, inputs, outputs, version): return MethodSignature(name=name, input=ins, output=outs, version=version) - def _serialize_types(self, x): + @staticmethod + def _serialize_types(x): """Convert type to string.""" if x is None: return x @@ -85,8 +86,6 @@ def serialize(self): version = attr.ib() - - class Method: """A Method (int. API) represents a single API method. @@ -128,7 +127,7 @@ async def __call__(self, *args, **kwargs): raise SongpalException("Unable to make a request: %s" % ex) from ex if self.debug > 1: - _LOGGER.debug("got payload: %s" % res) + _LOGGER.debug("got payload: %s" % pf(res)) if "error" in res: _LOGGER.debug(self) @@ -140,10 +139,14 @@ async def __call__(self, *args, **kwargs): if self.debug > 0: _LOGGER.debug("got res: %s" % pf(res)) - if "result" not in res: + if "result" not in res and "results" not in res: _LOGGER.error("No result in response, how to handle? %s" % res) return + if "results" in res: + _LOGGER.debug("Got results instead of result, returning the array") + return res["results"] + res = res["result"] if len(res) > 1: _LOGGER.warning("Got a response with len > 1: %s" % res) diff --git a/songpal/notification.py b/songpal/notification.py index 7e4ce0d..eab0b49 100644 --- a/songpal/notification.py +++ b/songpal/notification.py @@ -8,47 +8,6 @@ _LOGGER = logging.getLogger(__name__) - -class Notification: - """Wrapper for notifications. - - In order to listen for notifications, call `activate(callback)` - with a coroutine to be called when a notification is received. - """ - def __init__(self, endpoint, switch_method, payload): - """Notification constructor. - - :param endpoint: Endpoint. - :param switch_method: `Method` for switching this notification. - :param payload: JSON data containing name and available versions. - """ - self.endpoint = endpoint - self.switch_method = switch_method - self.versions = payload["versions"] - self.name = payload["name"] - self.version = max(x["version"] for x in self.versions if "version" in x) - - _LOGGER.debug("notification payload: %s", pf(payload)) - - def asdict(self): - """Return a dict containing the notification information.""" - return {"name": self.name, "version": self.version} - - async def activate(self, callback): - """Start listening for this notification. - - Emits received notifications by calling the passed `callback`. - """ - await self.switch_method({"enabled": [self.asdict()]}, _consumer=callback) - - def __repr__(self): - return "" % ( - self.name, - self.versions, - self.endpoint, - ) - - class ChangeNotification: """Dummy base-class for notifications.""" pass @@ -74,8 +33,8 @@ def _convert_if_available(x): if x is not None: return SoftwareUpdateInfo.make(**x[0]) - isUpdatable = attr.ib(convert=convert_to_bool) - swInfo = attr.ib(convert=_convert_if_available) + isUpdatable = attr.ib(converter=convert_to_bool) + swInfo = attr.ib(converter=_convert_if_available) @attr.s @@ -83,7 +42,7 @@ class VolumeChange(ChangeNotification): """Notification for volume changes.""" make = classmethod(make) - mute = attr.ib(convert=lambda x: True if x == "on" else False) + mute = attr.ib(converter=lambda x: True if x == "on" else False) volume = attr.ib() @@ -146,11 +105,77 @@ class NotificationChange(ChangeNotification): """Container for storing information about state of Notifications.""" make = classmethod(make) - enabled = attr.ib(convert=lambda x: [x["name"] for x in x]) - disabled = attr.ib(convert=lambda x: [x["name"] for x in x]) + enabled = attr.ib(converter=lambda x: [x["name"] for x in x]) + disabled = attr.ib(converter=lambda x: [x["name"] for x in x]) def __str__(self): return "" % ( ",".join(self.enabled), ",".join(self.disabled), ) + + +class Notification: + """Wrapper for notifications. + + In order to listen for notifications, call `activate(callback)` + with a coroutine to be called when a notification is received. + """ + SUPPORTED = { + "notifyPowerStatus": PowerChange, + "notifyVolumeInformation": VolumeChange, + "notifyPlayingContentInfo": ContentChange, + "notifySettingsUpdate": SettingChange, + "notifySWUpdateInfo": SoftwareUpdateChange, + } + + def __init__(self, endpoint, switch_method, payload): + """Notification constructor. + + :param endpoint: Endpoint. + :param switch_method: `Method` for switching this notification. + :param payload: JSON data containing name and available versions. + """ + self.endpoint = endpoint + self.switch_method = switch_method + self.versions = payload["versions"] + self.name = payload["name"] + self.version = max(x["version"] for x in self.versions if "version" in x) + + _LOGGER.debug("notification payload: %s", pf(payload)) + + def asdict(self): + """Return a dict containing the notification information.""" + return {"name": self.name, "version": self.version} + + def wrap_notification(self, data): + """Convert notification JSON to a notification class.""" + if "method" in data: + method = data["method"] + params = data["params"] + change = params[0] + if method in Notification.SUPPORTED: + return Notification.SUPPORTED[method] + else: + _LOGGER.warning("Got unknown notification type: %s", method) + elif "result" in data: + result = data["result"][0] + if "enabled" in result and "enabled" in result: + return NotificationChange(**result) + else: + _LOGGER.warning("Unknown notification, returning raw: %s", data) + return data + + async def activate(self, callback): + """Start listening for this notification. + + Emits received notifications by calling the passed `callback`. + """ + await self.switch_method({"enabled": [self.asdict()]}, _consumer=callback) + + def __repr__(self): + return "" % ( + self.name, + self.versions, + self.endpoint, + ) diff --git a/songpal/services/__init__.py b/songpal/services/__init__.py new file mode 100644 index 0000000..ed8612e --- /dev/null +++ b/songpal/services/__init__.py @@ -0,0 +1,5 @@ +from .service import Service +from .system import System +from .guide import Guide +from .audio import Audio +from .avcontent import AVContent \ No newline at end of file diff --git a/songpal/services/audio.py b/songpal/services/audio.py new file mode 100644 index 0000000..bdb1492 --- /dev/null +++ b/songpal/services/audio.py @@ -0,0 +1,64 @@ +from typing import List +import logging + +from .service import Service, command +from songpal.containers import Setting, Volume + +_LOGGER = logging.getLogger(__name__) + + +class Audio(Service): + @command("setSoundSettings") + async def set_sound_settings(self, settings): + """Change a sound setting.""" + return await self.set_settings(self["setSoundSettings"], settings) + + async def set_sound_setting(self, target: str, value: str): + return await self.set_setting(self["setSoundSettings"], target, value) + + @command("getSpeakerSettings") + async def get_speaker_settings(self) -> List[Setting]: + """Return speaker settings.""" + return await self.get_settings(self["getSpeakerSettings"], self["setSpeakerSettings"]) + + async def set_speaker_setting(self, target: str, value: str): + """Set speaker settings.""" + return await self.set_setting(self["setSpeakerSettings"], target, value) + + @command("setSpeakerSettings") + async def set_speaker_settings(self, settings): + """Change speaker settings, pass a list of setting objects.""" + return await self.set_settings(self["setSpeakerSettings"], settings) + + @command("getVolumeInformation") + async def get_volume_information(self) -> List[Volume]: + """Get the volume information.""" + res = await self["getVolumeInformation"]({}) + volume_info = [Volume.make(service=self, **x) for x in res] + if len(volume_info) < 1: + _LOGGER.warning("Unable to get volume information") + elif len(volume_info) > 1: + _LOGGER.debug("The device seems to have more than one volume setting.") + return volume_info + + @command("getSoundSettings") + async def get_sound_settings(self, target="") -> List[Setting]: + """Get the current sound settings. + + :param str target: settings target, defaults to all. + """ + return await self.get_settings(self["getSoundSettings"], self["getSoundSettings"], target="") + + async def get_soundfield(self) -> List[Setting]: + """Get the current sound field settings.""" + _LOGGER.warning("prefer get_sound_setting(s) calls..") + return await self.get_settings(self["getSoundSettings"], self["setSoundSettings"], "soundField") + + @command(["getCustomEqualizerSettings", "setCustomEqualizerSettings"]) + async def get_custom_eq(self): + """Get custom EQ settings.""" + return await self.get_settings(self["getCustomEqualizerSettings"], self["setCustomEqualizerSettings"]) + + async def set_custom_eq(self, target: str, value: str) -> None: + """Set custom EQ settings.""" + return await self.set_setting(self["setCustomEqualizerSettings"], target, value) diff --git a/songpal/services/avcontent.py b/songpal/services/avcontent.py new file mode 100644 index 0000000..2474504 --- /dev/null +++ b/songpal/services/avcontent.py @@ -0,0 +1,128 @@ +from typing import List + +from songpal.common import SongpalException +from songpal.containers import ( + Content, + ContentInfo, + Input, + PlayInfo, + Scheme, + Setting, + Source, + SupportedFunctions, + Zone, +) + +from .service import Service, command + + +class AVContent(Service): + @command("getCurrentExternalTerminalsStatus") + async def get_inputs(self) -> List[Input]: + """Return list of available outputs.""" + res = await self["getCurrentExternalTerminalsStatus"]() + return [ + Input.make(service=self, **x) + for x in res + if "meta:zone:output" not in x["meta"] + ] + + @command("getCurrentExternalTerminalsStatus") + async def get_zones(self) -> List[Zone]: + """Return list of available zones.""" + res = await self["getCurrentExternalTerminalsStatus"]() + zones = [ + Zone.make(service=self, **x) for x in res if "meta:zone:output" in x["meta"] + ] + if not zones: + raise SongpalException("Device has no zones") + return zones + + @command(["getPlaybackModeSettings", "setPlaybackModeSettings"]) + async def get_playback_settings(self) -> List[Setting]: + """Get playback settings such as shuffle and repeat.""" + return await self.get_settings( + self["getPlaybackModeSettings"], self["setPlaybackModeSettings"] + ) + + async def set_playback_settings(self, target, value) -> None: + """Set playback settings such a shuffle and repeat.""" + return await self.set_setting(self["setPlaybackModeSettings"], target, value) + + @command(["getBluetoothSettings", "setBluetoothSettings"]) + async def get_bluetooth_settings(self) -> List[Setting]: + """Get bluetooth settings.""" + return await self.get_settings( + self["getBluetoothSettings"], self["setBluetoothSettings"] + ) + + async def set_bluetooth_setting(self, target: str, value: str) -> None: + """Set bluetooth settings.""" + return await self.set_setting(self["setBluetoothSetting"], target, value) + + @command("getSupportedPlaybackFunction") + async def get_supported_playback_functions( + self, uri="" + ) -> List[SupportedFunctions]: + """Return list of inputs and their supported functions.""" + return [ + SupportedFunctions.make(**x) + for x in await self["getSupportedPlaybackFunction"](uri=uri) + ] + + @command("getSchemeList") + async def get_schemes(self) -> List[Scheme]: + """Return supported uri schemes.""" + return [ + Scheme.make(**x, sources_getter=self["getSourceList"]) + for x in await self["getSchemeList"]() + ] + + @command("getSourceList") + async def get_source_list(self, scheme: str = "") -> List[Source]: + """Return available sources for playback.""" + for sch in await self.get_schemes(): + if sch.scheme == scheme: + return await sch.get_sources() + + raise SongpalException("Unable to find source list for %s" % scheme) + + @command("getContentCount") + async def get_content_count(self, source: str): + """Return file listing for source.""" + params = {"uri": source, "type": None, "target": "all", "view": "flat"} + return ContentInfo.make(**await self["getContentCount"](params)) + + @command("getContentList") + async def get_contents(self, uri) -> List[Content]: + """Request content listing recursively for the given URI. + + :param uri: URI for the source. + :return: List of Content objects. + """ + contents = [Content.make(**x) for x in await self["getContentList"](uri=uri)] + contentlist = [] + + for content in contents: + if content.contentKind == "directory" and content.index >= 0: + # print("got directory %s" % content.uri) + res = await self.get_contents(content.uri) + contentlist.extend(res) + else: + contentlist.append(content) + # print("%s%s" % (' ' * depth, content)) + return contentlist + + @command("getAvailablePlaybackFunction") + async def get_available_playback_functions(self, output=""): + """Return available playback functions. + + If no output is given the current is assumed. + """ + await self["getAvailablePlaybackFunction"](output=output) + + @command("getPlayingContentInfo") + async def get_play_info(self) -> PlayInfo: + """Return of the device.""" + info = await self["getPlayingContentInfo"]({}) + return PlayInfo.make(**info.pop()) diff --git a/songpal/services/guide.py b/songpal/services/guide.py new file mode 100644 index 0000000..2e484bf --- /dev/null +++ b/songpal/services/guide.py @@ -0,0 +1,56 @@ +from .service import Service, command +from .system import System +from .audio import Audio +from .avcontent import AVContent +import logging + +_LOGGER = logging.getLogger(__name__) + + +class Guide(Service): + @command("getServiceProtocols") + async def get_service_protocols(self): + service_protocols = await self._methods["getServiceProtocols"]() + return service_protocols + + @command("getSupportedApiInfo") + async def get_supported_api_info(self): + return await self._methods["getSupportedApiInfo"]({}) + + async def get_supported_apis(self, base_endpoint, force_protocol): + _LOGGER.info("Versions: %s" % await self.get_versions()) + await self.initialize_methods() + + service_protocols = await self.get_service_protocols() + print(service_protocols) + for service_name in service_protocols: + service_name, protocols = service_name + _LOGGER.info("Service %s supports %s", service_name, protocols) + + apis = await self.get_supported_api_info() + + supported_services = { + 'guide': self, + 'system': System, + 'audio': Audio, + 'avContent': AVContent, + } + + initialized_services = {} + + for api in apis: + service_name = api["service"] + _LOGGER.info("Got service: %s", service_name) + if service_name not in supported_services: + _LOGGER.warning("Service %s is not implemented", service_name) + continue + + service = await supported_services[service_name].from_payload( + api, base_endpoint, self.idgen, self.debug, force_protocol + ) + + _LOGGER.info("Initialized service %s", service) + + initialized_services[service_name] = service + + return initialized_services diff --git a/songpal/service.py b/songpal/services/service.py similarity index 65% rename from songpal/service.py rename to songpal/services/service.py index 1ebf77f..8dc134f 100644 --- a/songpal/service.py +++ b/songpal/services/service.py @@ -1,27 +1,50 @@ """Service presentation for a single endpoint (e.g. audio or avContent).""" import logging from typing import List +from functools import wraps import aiohttp -from songpal.common import ProtocolType, SongpalException -from songpal.method import Method, MethodSignature -from songpal.notification import ( - Notification, - ConnectChange, - ContentChange, - NotificationChange, - PowerChange, - SettingChange, - SoftwareUpdateChange, - VolumeChange, -) +from ..common import ProtocolType, SongpalException +from ..method import Method, MethodSignature +from ..containers import Setting +from ..notification import Notification _LOGGER = logging.getLogger(__name__) +def command(impls): + def wrapper(func): + func.implements = impls + @wraps(func) + async def wrapped(*args, **kwargs): + return await func(*args, **kwargs) -class Service: + return wrapped + + return wrapper + + +class ServiceImplementation(type): + """Metaclass to collect information about implemented interfaces.""" + def __init__(cls, name, bases, attrs): + cls._implemented_commands = set() + for name, method in attrs.items(): + if hasattr(method, 'implements'): + impls = getattr(method, 'implements') + if isinstance(impls, list): + cls._implemented_commands.update(impls) + else: + cls._implemented_commands.add(impls) + + #print("impl: %s" % cls._implemented_commands) + #_LOGGER.info("implemented commands: %s" % cls._implemented_commands) + + +class Service(metaclass=ServiceImplementation): """Service presents an endpoint providing a set of methods.""" + WEBSOCKET_PROTOCOL = "websocket:jsonizer" + XHRPOST_PROTOCOL = "xhrpost:jsonizer" + def __init__(self, name, endpoint, protocol, idgen, debug=0): """Service constructor. @@ -33,31 +56,75 @@ def __init__(self, name, endpoint, protocol, idgen, debug=0): self.idgen = idgen self._protocols = [] self._notifications = [] + self._methods = {} self.debug = debug self.timeout = 2 self.listening = False - @staticmethod - async def fetch_signatures(endpoint, protocol, idgen): + @command("getMethodTypes") + async def get_method_types(self): + return await self.call_method_raw("getMethodTypes", ['']) + + async def call_method_raw(self, method, params=None): """Request available methods for the service.""" async with aiohttp.ClientSession() as session: req = { - "method": "getMethodTypes", - "params": [''], + "method": method, + "params": params, "version": "1.0", - "id": next(idgen), + "id": next(self.idgen), } + _LOGGER.info("Going to call %s on %s", req, self.endpoint) + + try: + if self.active_protocol == ProtocolType.WebSocket: + async with session.ws_connect(self.endpoint, timeout=2) as s: + await s.send_json(req) + res = await s.receive_json() + return res + else: + res = await session.post(self.endpoint, json=req) + json = await res.json() + + return json + except aiohttp.ClientError as ex: + raise SongpalException("Unable to request %s" % method) from ex + + @command("getVersions") + async def get_versions(self): + return await self.call_method_raw('getVersions', []) + + async def initialize_methods(self): + sigs = await self.get_method_types() + + if self.debug > 1: + _LOGGER.debug("Signatures: %s", sigs) + if "error" in sigs: + _LOGGER.error("Got error when fetching sigs: %s", sigs["error"]) + return None - if protocol == ProtocolType.WebSocket: - async with session.ws_connect(endpoint, timeout=2) as s: - await s.send_json(req) - res = await s.receive_json() - return res + methods = {} + + if "results" not in sigs: + _LOGGER.error("Unable to get method signatures for %s" % self.name) + raise SongpalException("Got no method signature results? response: %s" % sigs) + + for sig in sigs["results"]: + name = sig[0] + parsed_sig = MethodSignature.from_payload(*sig) + if name in methods: + _LOGGER.debug("Got duplicate signature for %s, existing was %s. Keeping the existing one", + parsed_sig, methods[name]) else: - res = await session.post(endpoint, json=req) - json = await res.json() + methods[name] = Method(self, parsed_sig, self.debug) + if name not in self._implemented_commands: + _LOGGER.warning("Method %s of %s not implemented, please report the issue.", methods[name], self.name) - return json + self.methods = methods + _LOGGER.info("Initialized %s with %s methods", self.name, len(self.methods)) + if self.debug > 1: + for method in self.methods: + _LOGGER.debug(" - %s" % method) @classmethod async def from_payload(cls, payload, endpoint, idgen, debug, force_protocol=None): @@ -73,9 +140,9 @@ async def from_payload(cls, payload, endpoint, idgen, debug, force_protocol=None _LOGGER.debug("Available protocols for %s: %s", service_name, protocols) if force_protocol and force_protocol.value in protocols: protocol = force_protocol - elif "websocket:jsonizer" in protocols: + elif Service.WEBSOCKET_PROTOCOL in protocols: protocol = ProtocolType.WebSocket - elif "xhrpost:jsonizer" in protocols: + elif Service.XHRPOST_PROTOCOL in protocols: protocol = ProtocolType.XHRPost else: raise SongpalException( @@ -88,33 +155,13 @@ async def from_payload(cls, payload, endpoint, idgen, debug, force_protocol=None # creation here we want to pass the created service class to methods. service = cls(service_name, service_endpoint, protocol, idgen, debug) - sigs = await cls.fetch_signatures( - service_endpoint, protocol, idgen - ) - - if debug > 1: - _LOGGER.debug("Signatures: %s", sigs) - if "error" in sigs: - _LOGGER.error("Got error when fetching sigs: %s", sigs["error"]) - return None - - methods = {} - - for sig in sigs["results"]: - name = sig[0] - parsed_sig = MethodSignature.from_payload(*sig) - if name in methods: - _LOGGER.debug("Got duplicate signature for %s, existing was %s. Keeping the existing one", - parsed_sig, methods[name]) - else: - methods[name] = Method(service, parsed_sig, debug) - - service.methods = methods + await service.initialize_methods() + #await service.initialize_notifications() - if "notifications" in payload and "switchNotifications" in methods: + if "notifications" in payload and "switchNotifications" in service._methods: notifications = [ Notification( - service_endpoint, methods["switchNotifications"], notification + service_endpoint, service.activate_notification, notification ) for notification in payload["notifications"] ] @@ -123,6 +170,10 @@ async def from_payload(cls, payload, endpoint, idgen, debug, force_protocol=None return service + @command("switchNotifications") + async def activate_notification(self, *args, **kwargs): + return await self["switchNotifications"](*args, **kwargs) + async def call_method(self, method, *args, **kwargs): """Call a method (internal). @@ -183,7 +234,7 @@ async def call_method(self, method, *args, **kwargs): self.listening = True while self.listening: res_raw = await s.receive_json() - res = self.wrap_notification(res_raw) + res = Notification.wrap_notification(res_raw) _LOGGER.debug("Got notification: %s", res) if self.debug > 1: _LOGGER.debug("Got notification raw: %s", res_raw) @@ -196,31 +247,24 @@ async def call_method(self, method, *args, **kwargs): res = await session.post(self.endpoint, json=req) return await res.json() - def wrap_notification(self, data): - """Convert notification JSON to a notification class.""" - if "method" in data: - method = data["method"] - params = data["params"] - change = params[0] - if method == "notifyPowerStatus": - return PowerChange.make(**change) - elif method == "notifyVolumeInformation": - return VolumeChange.make(**change) - elif method == "notifyPlayingContentInfo": - return ContentChange.make(**change) - elif method == "notifySettingsUpdate": - return SettingChange.make(**change) - elif method == "notifySWUpdateInfo": - return SoftwareUpdateChange.make(**change) - else: - _LOGGER.warning("Got unknown notification type: %s", method) - elif "result" in data: - result = data["result"][0] - if "enabled" in result and "enabled" in result: - return NotificationChange(**result) - else: - _LOGGER.warning("Unknown notification, returning raw: %s", data) - return data + async def get_settings(self, getter, setter, target=None): + if target is None: + target = {} + settings = await getter(target) + return [Setting.make(**x, setter=setter) for x in settings] + + async def set_settings(self, setter, settings): + change_settings = [] + for setting in settings: + change_settings.append({"target": setting.target, "value": setting.value}) + + params = {"settings": [x for x in change_settings]} + return await setter(params) + + async def set_setting(self, setter, target: str, value: str): + """Set speaker settings.""" + setting = Setting(target=target, value=value) + return await setter([setting]) def __getitem__(self, item) -> Method: """Return a method for the given name. diff --git a/songpal/services/system.py b/songpal/services/system.py new file mode 100644 index 0000000..1a4f9d8 --- /dev/null +++ b/songpal/services/system.py @@ -0,0 +1,117 @@ +from typing import List + +from .service import Service, command, ServiceImplementation +from ..containers import Power, Setting, InterfaceInfo, Sysinfo, Storage, SoftwareUpdateInfo + + +_implemented_commands = set() + + +class System(Service, metaclass=ServiceImplementation): + """Implementation of songpal's system service.""" + + @command("getPowerStatus") + async def get_power(self) -> Power: + """Get the device state.""" + res = await self["getPowerStatus"]() + return Power.make(**res) + + @command("setPowerStatus") + async def set_power(self, value: bool): + """Toggle the device on and off.""" + if value: + status = "active" + else: + status = "off" + # TODO WoL works when quickboot is not enabled + return await self["setPowerStatus"](status=status) + + @command("getPowerSettings") + async def get_power_settings(self) -> List[Setting]: + """Get power settings.""" + return [ + Setting.make(**x) + for x in await self["getPowerSettings"]({}) + ] + + @command("setPowerSettings") + async def set_power_settings(self, target: str, value: str) -> None: + """Set power settings.""" + params = {"settings": [{"target": target, "value": value}]} + return await self["setPowerSettings"](params) + + @command("getWuTangInfo") + async def get_googlecast_settings(self) -> List[Setting]: + """Get Googlecast settings.""" + return [ + Setting.make(**x) + for x in await self["getWuTangInfo"]({}) + ] + + @command("setWuTangInfo") + async def set_googlecast_settings(self, target: str, value: str): + """Set Googlecast settings.""" + params = {"settings": [{"target": target, "value": value}]} + return await self["setWuTangInfo"](params) + + @command("getSettingsTree") + async def request_settings_tree(self): + """Get raw settings tree JSON. + + Prefer :func:get_settings: for containerized settings. + """ + return await self["getSettingsTree"](usage="") + + @command("getDeviceMiscSettings") + async def get_misc_settings(self) -> List[Setting]: + """Return miscellaneous settings such as name and timezone.""" + return await self.get_settings(self["getDeviceMiscSettings"], self["setDeviceMiscSettings"], target="") + + @command("setDeviceMiscSettings") + async def set_misc_setting(self, target: str, value: str): + """Change miscellaneous settings.""" + return await self.set_setting(self["setDeviceMiscSetting"], target, value) + + @command("getInterfaceInformation") + async def get_interface_information(self) -> InterfaceInfo: + """Return generic product information.""" + iface = await self["getInterfaceInformation"]() + return InterfaceInfo.make(**iface) + + @command("getSystemInformation") + async def get_system_info(self) -> Sysinfo: + """Return system information including mac addresses and current version.""" + return Sysinfo.make(**await self["getSystemInformation"]()) + + @command("getSleepTimerSettings") + async def get_sleep_timer_settings(self) -> List[Setting]: + """Get sleep timer settings.""" + return await self.get_settings(self["getSleepTimerSettings"], self["setSleepTimerSettings"]) + + @command("getStorageList") + async def get_storage_list(self) -> List[Storage]: + """Return information about connected storage devices.""" + return [ + Storage.make(**x) + for x in await self["getStorageList"]({}) + ] + + @command("getRemoteControllerInfo") + async def get_remote_controls(self): + raise NotImplementedError() + + @command("getSWUpdateInfo") + async def get_update_info(self, from_network=True) -> SoftwareUpdateInfo: + """Get information about updates.""" + if from_network: + from_network = "true" + else: + from_network = "false" + # from_network = "" + info = await self["getSWUpdateInfo"](network=from_network) + return SoftwareUpdateInfo.make(**info, service=self) + + async def activate_system_update(self) -> None: + """Start a system update if available.""" + return await self.get_update_info(from_network=True).update() + raise Exception("call softwareupdateinfo.update()") diff --git a/songpal/tests/__init__.py b/songpal/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/songpal/tests/conftest.py b/songpal/tests/conftest.py new file mode 100644 index 0000000..6c5ce1d --- /dev/null +++ b/songpal/tests/conftest.py @@ -0,0 +1,45 @@ +import pytest +from aiohttp import web +import json +from aiohttp.test_utils import TestServer, loop_context +from songpal import Device +import logging + +logging.basicConfig(level=logging.DEBUG) + +_LOGGER = logging.getLogger(__name__) + +routes = web.RouteTableDef() + + + +async def get_iface(x): + _LOGGER.info("got call %s" % x) + +@pytest.fixture +def devinfo(shared_datadir): + devinfo = shared_datadir / "devinfos/HT-XT3.json" + + return devinfo.read_text() + + +@routes.post('/sony/guide') +async def guide_endpoint(req): + + with open("/home/hass/home-assistant/libs/python-sony/songpal/tests/data/devinfos/HT-XT3.json") as f: + devinfo = json.load(f) + _LOGGER.info("Guide endpoint called.") + return web.json_response(devinfo["supported_methods_response"]) + + +@pytest.fixture +async def server(loop): + app = web.Application() + app.router.add_routes(routes) + + server = TestServer(app) + + + await server.start_server() + return server + #return loop.run_until_complete(aiohttp_client(app)) \ No newline at end of file diff --git a/songpal/tests/disco.xml b/songpal/tests/disco.xml new file mode 100644 index 0000000..ed21d9d --- /dev/null +++ b/songpal/tests/disco.xml @@ -0,0 +1,82 @@ + + + 1 + 0 + + + urn:schemas-upnp-org:device:MediaServer:1 + BDV-N5200W + Sony Corporation + http://www.sony.net/ + BDV-N5200W + BDV-2014 + uuid:00000001-0000-1010-8000-ac9b0ae0238d + + + image/jpeg + 120 + 120 + 24 + /bdv_nv_device_icon_large.jpg + + + image/png + 120 + 120 + 24 + /bdv_nv_device_icon_large.png + + + image/jpeg + 48 + 48 + 24 + /bdv_nv_device_icon_small.jpg + + + image/png + 48 + 48 + 24 + /bdv_nv_device_icon_small.png + + + + + + + 1 + + + 1.0 + http://192.168.2.189:10000/sony + + guide + system + illumination + audio + + + + diff --git a/songpal/tests/test_device.py b/songpal/tests/test_device.py new file mode 100644 index 0000000..6ef1d02 --- /dev/null +++ b/songpal/tests/test_device.py @@ -0,0 +1,19 @@ +import asyncio +from .conftest import server +from songpal import Device, SongpalException +import pytest + + +async def test_invalid_endpoint(server): + dev = Device(endpoint=str(server._root)) + dev.guide_endpoint += "1234" + with pytest.raises(SongpalException): + await dev.get_supported_methods() + + +async def test_fetch_get_supported(server): + print(server) + dev = Device(endpoint=str(server._root)) + + supported_methods = await dev.get_supported_methods() + assert len(supported_methods) == 4 diff --git a/songpal/tests/test_discovery.py b/songpal/tests/test_discovery.py new file mode 100644 index 0000000..43f68fc --- /dev/null +++ b/songpal/tests/test_discovery.py @@ -0,0 +1,52 @@ +from songpal import Discover +from songpal.discovery import DiscoveredDevice +from async_upnp_client import UpnpFactory +import xml.etree.ElementTree as ET +from unittest.mock import MagicMock +import pytest +import os + +THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + + +async def async_magic(): + pass + +MagicMock.__await__ = lambda x: async_magic().__await__() + + +@pytest.mark.asyncio +async def test_handle_discovered_device(mocker): + mocker.patch('asyncio.sleep') + mock_device = MagicMock(spec=dict) + + d = { + 'location': 'x' + } + + mock_device.__getitem__.side_effect = d.__getitem__ + + async def xml(): + return ET.parse(os.path.join(THIS_DIR, 'disco.xml')) + + mocker.patch.object(UpnpFactory, 'async_create_service') + + get_xml = mocker.patch.object(UpnpFactory, "_async_get_url_xml", autospec=True) + get_xml.return_value = xml() + + device_discovered = False + + async def discovered_device(dev: DiscoveredDevice): + nonlocal device_discovered + + assert dev.name is not None + assert dev.model_number is not None + assert dev.udn is not None + assert len(dev.services) > 0 + assert dev.endpoint is not None + + device_discovered = True + + await Discover._handle_discovered_device(mock_device, callback=discovered_device) + + assert device_discovered diff --git a/songpal/tests/test_method.py b/songpal/tests/test_method.py new file mode 100644 index 0000000..1d98ff8 --- /dev/null +++ b/songpal/tests/test_method.py @@ -0,0 +1,46 @@ +import pytest +from songpal.method import Method, MethodSignature +from songpal import SongpalException + + +def test_methodsignature_rettype(): + assert MethodSignature.return_type("string") == str + assert MethodSignature.return_type("Boolean") == bool + assert MethodSignature.return_type("int") == int + + # return input when not matching. + assert MethodSignature.return_type("foofoo") == "foofoo" + + +def test_methodsignature_parse_json_types(): + assert False + + +def test_methodsignature_serialize_types(): + # None stays None + assert MethodSignature._serialize_types(None) == None + + types_to_test = {'str_test': str, + 'int_test': int} + res = MethodSignature._serialize_types(types_to_test) + assert len(res) == len(types_to_test) + assert res["str_test"] == "str" + assert res["int_test"] == "int" + + +def test_method_call_failed(): + with pytest.raises(SongpalException): + print("when communication with the device fails " + "this exception should be raised and the error " + "attribute should be kept empty?") + + +def test_method_call_error_from_device(): + with pytest.raises(SongpalException): + print("when service.call_method result contains error, " + "this exception should be reaised and its error attribute" + " should be set") + + +def test_method_serialize_types(): + assert False diff --git a/songpal/tests/test_service.py b/songpal/tests/test_service.py new file mode 100644 index 0000000..f23a0f1 --- /dev/null +++ b/songpal/tests/test_service.py @@ -0,0 +1,29 @@ +import pytest + + +@pytest.mark.asyncio +async def test_from_payload(): + pass + + +@pytest.mark.asyncio +async def test_fetch_signatures(): + pass + + +@pytest.mark.asyncio +async def test_call_method(): + pass + + +@pytest.mark.asyncio +async def test_wrap_notification(): + assert False + + +@pytest.mark.asyncio +async def test_listen_notifications(): + assert False + # activate notifications works + # deactivate notification works + # notification objects are emited diff --git a/songpal/tests/testserver.py b/songpal/tests/testserver.py new file mode 100644 index 0000000..331c728 --- /dev/null +++ b/songpal/tests/testserver.py @@ -0,0 +1,13 @@ +from aiohttp import web + +async def hello(request): + return web.Response(text='Hello, world') + +async def test_hello(aiohttp_client, loop): + app = web.Application() + app.router.add_get('/', hello) + client = await aiohttp_client(app) + resp = await client.get('/') + assert resp.status == 200 + text = await resp.text() + assert 'Hello, world' in text \ No newline at end of file