# taken from github.com/rsc-dev/pyamaha, which is licensed under the MIT License
# ruff: noqa: D205,D400,D401
from __future__ import annotations
import asyncio
import json
import logging
import queue
import urllib
import xml.etree.ElementTree as ET
from asyncio import AbstractEventLoop
from asyncio.transports import BaseTransport
from collections.abc import Awaitable, Callable
from datetime import datetime
from typing import Any, ClassVar
from urllib.parse import urlparse
import aiohttp
from aiohttp import ClientError, ClientResponse, ClientTimeout
from aiomusiccast.exceptions import (
MusicCastConfigurationException,
MusicCastConnectionException,
MusicCastParamException,
)
BAND = ["common", "am", "fm", "dab"]
CD_PLAYBACK = [
"play",
"stop",
"pause",
"previous",
"next",
"fast_reverse_start",
"fast_reverse_end",
"fast_forward_start",
"fast_forward_end",
"track_select ",
]
DIR = ["next", "previous"]
PLAYBACK = [
"play",
"stop",
"pause",
"play_pause",
"previous",
"next",
"fast_reverse_start",
"fast_reverse_end",
"fast_forward_start",
"fast_forward_end",
]
PRESET_BAND = ["common", "separate"]
ZONES = ["main", "zone2", "zone3", "zone4"]
SERVICE_INFO_TYPE = ["account_list", "licensing", "activation_code"]
SLEEP = [0, 30, 60, 90, 120]
TUNING = ["up", "down", "cancel", "auto_up", "auto_down", "tp_up", "tp_down", "direct"]
TYPE = ["select", "play", "return"]
POWER = ["on", "standby", "toggle"]
LIST_ID = ["main", "auto_complete", "search_artist", "search_track"]
LANG = ["en", "ja", "fr", "de", "es", "ru", "it", "zh"]
WIFI = ["none", "wep", "wpa2-psk(aes)", "mixed_mode"]
WIFI_DIRECT = ["none", "wpa2-psk(aes)"]
STANDBY = ["off", "on", "auto"]
RESPONSE_CODE = {
0: "Successful request",
1: "Initializing",
2: "Internal Error",
3: "Invalid Request (A method did not exist, a method wasn't appropriate etc.)",
4: "Invalid Parameter (Out of range, invalid characters etc.)",
5: "Guarded (Unable to setup in current status etc.)",
6: "Time Out",
99: "Firmware Updating",
100: "Access Error",
101: "Other Errors",
102: "Wrong User Name",
103: "Wrong Password",
104: "Account Expired",
105: "Account Disconnected/Gone Off/Shut Down",
106: "Account Number Reached to the Limit",
107: "Server Maintenance",
108: "Invalid Account",
109: "License Error",
110: "Read Only Mode",
111: "Max Stations",
112: "Access Denied",
}
_LOGGER = logging.getLogger(__name__)
class MusicCastUdpProtocol(asyncio.DatagramProtocol):
transport: BaseTransport
def __init__(self, handle_event) -> None:
super().__init__()
self.handle_event = handle_event
def connection_made(self, transport):
self.transport = transport
def datagram_received(self, data, _addr):
message_data = None
message_str = ""
try:
message_str = data.decode()
message_data = json.loads(message_str)
except UnicodeDecodeError:
_LOGGER.error("Received non UTF-8 compliant message: %s", data)
except ValueError:
_LOGGER.error("Received invalid message: %s", message_str)
except Exception:
_LOGGER.exception("An unexpected error occurred while handling an UDP message.")
finally:
task = asyncio.create_task(self.handle_event(message_data))
task.add_done_callback(lambda _task: None)
class UrlBuilder:
@classmethod
def build_query_str(cls, query_params: dict[str, str], **kwargs):
if not all(param in query_params for param in kwargs):
raise MusicCastParamException("Unknown parameter while building query string.")
if not all(param in kwargs for param, req in query_params.items() if req):
raise MusicCastParamException("Not all required params were provided.")
return urllib.parse.urlencode({key: val for key, val in kwargs.items() if val is not None})
@classmethod
def build_url(cls, url: tuple[str, dict[str, Any]], **kwargs: Any) -> str:
return f"{url[0]}?{cls.build_query_str(url[1], **kwargs)}"
@classmethod
def build_zone_url(cls, url: tuple[str, dict[str, Any]], zone: str, **kwargs: Any) -> str:
base_url = url[0].format(host="{host}", zone=zone)
return f"{base_url}?{cls.build_query_str(url[1], **kwargs)}"
class AsyncDevice:
"""Yamaha async device abstraction class."""
ip: str
handle_event: Callable[[dict[str, Any] | None], Awaitable[None]] | None
_messages: queue.Queue[Any]
_transport: BaseTransport | None
def __init__(
self,
client: aiohttp.ClientSession,
ip: str,
loop: AbstractEventLoop,
handle_event: Callable[[dict[str, Any] | None], Awaitable[None]] | None = None,
upnp_description: str | None = None,
) -> None:
"""Ctor.
Parameters
----------
client : Any
aiohttp client session.
ip : Any
Yamaha device IP.
"""
self.ip = ip
self.client: aiohttp.ClientSession = client
self.loop = loop
self.handle_event = handle_event
self.upnp_description = upnp_description
self.upnp_avt_ns = None
self.upnp_avt_ctrl = None
self._messages = queue.Queue()
self._headers = {}
self._transport = None
# end-of-method __init__
@property
def transport(self):
return self._transport
async def enable_polling(self):
# One protocol instance will be created to serve all
# client requests.
self._transport, _ = await self.loop.create_datagram_endpoint(
lambda: MusicCastUdpProtocol(self.handle_event), local_addr=("0.0.0.0", 0)
)
socket = self._transport.get_extra_info("socket")
if socket is None:
self.disable_polling()
_LOGGER.error("Failed to open UDP connection")
return
port = socket.getsockname()[1]
self._headers.update({"X-AppName": "MusicCast/1.0", "X-AppPort": str(port)})
await self.request_json(System.get_device_info())
def disable_polling(self):
self._headers = {}
self._transport.close()
self._transport = None
async def request(self, *args):
"""Request YamahaExtendedControl API URI.
Parameters
----------
args : Any
URI link for GET or tuple (URI, data) for POST.
"""
try:
# If it is only a URI, send GET...
if isinstance(args[0], str):
return await self.get(args[0])
# ...otherwise unpack tuple and send POST
return await self.post(*(args[0]))
except ClientError as ce:
raise MusicCastConnectionException() from ce
except TimeoutError as te:
raise MusicCastConnectionException() from te
# end-of-method request
@classmethod
async def build_json(cls, response: ClientResponse):
"""A method, which tries to decode the response with errors
being ignored.
Parameters
----------
response : ClientResponse
The ClientResponse, which the data should be extracted from.
Returns
-------
dict
A dictionary on success.
"""
try:
text = await response.text()
except UnicodeDecodeError:
_LOGGER.warning("Failed to decode response. Trying to decode it with errors being ignored")
text = await response.text(errors="ignore")
try:
return json.loads(text)
except ValueError:
_LOGGER.error("Failed to generate JSON from %s", text)
raise
async def request_json(self, *args):
"""Request YamahaExtendedControl API URI.
Parameters
----------
args : Any
URI link for GET or tuple (URI, data) for POST.
"""
try:
# If it is only a URI, send GET...
if isinstance(args[0], str):
response = await self.get(args[0])
else:
# ...otherwise unpack tuple and send POST
response = await self.post(*(args[0]))
return await self.build_json(response)
except ClientError as ce:
raise MusicCastConnectionException() from ce
except TimeoutError as te:
raise MusicCastConnectionException() from te
# end-of-method request_json
async def get(self, uri):
"""Request given URI. Returns response object.
Parameters
----------
uri : Any
URI to request
"""
return await self.client.get(uri.format(host=self.ip), headers=self._headers, timeout=ClientTimeout(total=5))
# end-of-method get
async def post(self, uri, data):
"""Send POST request. Returns response object.
Parameters
----------
uri : Any
URI to send POST
data : Any
POST data
"""
return await self.client.post(uri.format(host=self.ip), data=json.dumps(data), headers=self._headers)
# end-of-method post
async def dlna_avt_request(self, action: str, dlna_body_args: dict):
if not self.upnp_description:
raise MusicCastConfigurationException("The UPNP description has to be set to perform this action.")
upnp_port = urlparse(self.upnp_description).port
if not self.upnp_avt_ctrl or not self.upnp_avt_ns:
desc = await (await self.client.get(self.upnp_description)).text()
service_list = desc[desc.find("") : desc.find("") + 14]
services_xml = ET.fromstring(service_list)
res = None
for child in services_xml:
service_id = child.find("serviceId")
if service_id.text.find("AVT") != -1:
res = child
break
if not res:
raise MusicCastConfigurationException("Did not find the AVTransport service.")
self.upnp_avt_ns = res.find("serviceType").text
self.upnp_avt_ctrl = res.find("controlURL").text
avt_ctrl_url = f"http://{self.ip}:{upnp_port}{self.upnp_avt_ctrl}"
dlna_body = "".join([f"<{key}>{value}{key}>" for key, value in dlna_body_args.items()])
body = (
''
''
""
f''
f"{dlna_body}"
f""
""
""
)
headers = {
"Content-Type": 'text/xml; charset="utf-8"',
"SOAPACTION": f'"{self.upnp_avt_ns}#{action}"',
"Accept": "*/*",
"User-Agent": "MusicCast/4673 (iOS)", # Otherwise the main zone switches to server source on play commands
"Content-Length": str(len(body)),
}
return await self.client.request("POST", avt_ctrl_url, headers=headers, data=body)
# end-of-class Device
class Dist:
"""APIs for link distribution settings and information."""
URI: ClassVar[dict[str, str]] = {
"GET_DISTRIBUTION_INFO": "http://{host}/YamahaExtendedControl/v1/dist/getDistributionInfo",
"SET_SERVER_INFO": "http://{host}/YamahaExtendedControl/v1/dist/setServerInfo",
"SET_CLIENT_INFO": "http://{host}/YamahaExtendedControl/v1/dist/setClientInfo",
"START_DISTRIBUTION": "http://{host}/YamahaExtendedControl/v1/dist/startDistribution?num={num}",
"STOP_DISTRIBUTION": "http://{host}/YamahaExtendedControl/v1/dist/stopDistribution",
"SET_GROUP_NAME": "http://{host}/YamahaExtendedControl/v1/dist/setGroupName",
}
@staticmethod
def get_distribution_info():
"""Retrieve link distribution information from the device."""
return Dist.URI["GET_DISTRIBUTION_INFO"]
# end-of-method get_distribution_info
@staticmethod
def set_server_info(group_id, zone=None, type=None, client_list=None):
"""For setting a Link distribution server (Link master).
Parameters
----------
group_id : Any
Specify Group ID in 32-digit hex.
Specify "" (empty text) here to cancel a Device being the Link
distribution server. Group ID will be initialized ("000...")
after the cancel operation.
zone : Any
Specifies which target Zone ID to be the Link distribution
server. If nothing is specified, current setting is kept. Zone
ID to be the Link distribution server is confirmable using
system/getFeatures server_zone_list. Values: "main" / "zone2" / "zone3" / "zone4"
type : Any
Specifies a type of adding or removing clients. Not necessary
to specify when canceling the Link master status. Values: "add" / "remove"
client_list : Any
Specifies IP addresses of adding/removing clients. Specifiable
up to 9 clients
"""
data = {"group_id": group_id}
if zone is not None:
data["zone"] = zone
if type is not None:
data["type"] = type
if client_list is not None:
data["client_list"] = client_list
return Dist.URI["SET_SERVER_INFO"], data
# end-of-method set_server_info
@staticmethod
def set_client_info(group_id, zones=None, server_ip_address=None):
"""
For setting Link distributed clients. If a Device is already setup as Link distribution server, this
client setup is denied by that Device: use this API after canceling a Device's Link distribution
server setup using setServerInfo, then confirming that the target Device's role is changed to other
values than "server" using getDistributionInfo.
Parameters
----------
group_id : Any
Specifies Group ID in 32-digit hex.
Specify "" (empty text) here to cancel a Device being a Link
distributed client. Group ID will be initialized ("000...") after
the cancel operation.
zones : Any
Specifies which target Zone ID to be a Link distributed
client. Not necessary to specify when cancelling a client status. Values: "main" / "zone2" / "zone3" / "zone4"
server_ip_address : Any
Specifies the IP Address of the Link distribution server.
"""
data = {"group_id": group_id}
if zones is not None:
data["zone"] = zones
if server_ip_address is not None:
data["server_ip_address"] = server_ip_address
return Dist.URI["SET_CLIENT_INFO"], data
# end-of-method set_client_info
@staticmethod
def start_distribution(num):
"""For initiating Link distribution. This is valid to a Device
that is setup as Link distribution server.
Parameters
----------
num : Any
Specifies Link distribution number on current MusicCast Network.
"""
return Dist.URI["START_DISTRIBUTION"].format(host="{host}", num=num)
# end-of-method start_distribution
@staticmethod
def stop_distribution():
"""For quitting Link distribution.
This is valid to a Device that is setup as Link distribution
server.
"""
return Dist.URI["STOP_DISTRIBUTION"]
# end-of-method stop_distribution
@staticmethod
def set_group_name(name):
"""For setting up Group Name. Note that Group Name is reserved
in volatile memory.
Parameters
----------
name : Any
Specifies Group Name. Use UTF-8 within 128 bytes. Default name
would be used if it's not setup or "" (empty text) is specified.
"""
data = {"name": name}
return Dist.URI["SET_GROUP_NAME"], data
# end-of-method set_group_name
# end-of-class Dist
class System:
"""System commands."""
URI: ClassVar[dict[str, str]] = {
"GET_DEVICE_INFO": "http://{host}/YamahaExtendedControl/v1/system/getDeviceInfo",
"GET_FEATURES": "http://{host}/YamahaExtendedControl/v1/system/getFeatures",
"GET_NETWORK_STATUS": "http://{host}/YamahaExtendedControl/v1/system/getNetworkStatus",
"GET_FUNC_STATUS": "http://{host}/YamahaExtendedControl/v1/system/getFuncStatus",
"SET_AUTOPOWER_STANDBY": "http://{host}/YamahaExtendedControl/v1/system/setAutoPowerStandby?enable={enable}",
"GET_LOCATION_INFO": "http://{host}/YamahaExtendedControl/v1/system/getLocationInfo",
"SEND_IR_CODE": "http://{host}/YamahaExtendedControl/v1/system/sendIrCode?code={code}",
"SET_WIRED_LAN": "http://{host}/YamahaExtendedControl/v1/system/setWiredLan",
"SET_WIRELESS_LAN": "http://{host}/YamahaExtendedControl/v1/system/setWirelessLan",
"SET_WIRELESS_DIRECT": "http://{host}/YamahaExtendedControl/v1/system/setWirelessDirect",
"SET_IP_SETTINGS": "http://{host}/YamahaExtendedControl/v1/system/setIpSettings",
"SET_NETWORK_NAME": "http://{host}/YamahaExtendedControl/v1/system/setNetworkName",
"SET_AIRPLAY_PIN": "http://{host}/YamahaExtendedControl/v1/system/setAirPlayPin",
"GET_MAC_ADDRESS_FILTER": "http://{host}/YamahaExtendedControl/v1/system/getMacAddressFilter",
"SET_MAC_ADDRESS_FILTER": "http://{host}/YamahaExtendedControl/v1/system/setMacAddressFilter",
"GET_NETWORK_STANDBY": "http://{host}/YamahaExtendedControl/v1/system/getNetworkStandby",
"SET_NETWORK_STANDBY": "http://{host}/YamahaExtendedControl/v1/system/setNetworkStandby?standby={standby}",
"GET_BLUETOOTH_INFO": "http://{host}/YamahaExtendedControl/v1/system/getBluetoothInfo",
"SET_BLUETOOTH_STANDBY": "http://{host}/YamahaExtendedControl/v1/system/setBluetoothStandby?enable={enable}",
"SET_BLUETOOTH_TX_SETTING": "http://{host}/YamahaExtendedControl/v1/system/setBluetoothTxSetting?enable={enable}",
"GET_BLUETOOTH_DEVICE_LIST": "http://{host}/YamahaExtendedControl/v1/system/getBluetoothDeviceList",
"UPDATE_BLUETOOTH_DEVICE_LIST": "http://{host}/YamahaExtendedControl/v1/system/updateBluetoothDeviceList",
"CONNECT_BLUETOOTH_DEVICE": "http://{host}/YamahaExtendedControl/v1/system/connectBluetoothDevice?address={address}",
"DISCONNECT_BLUETOOTH_DEVICE": "http://{host}/YamahaExtendedControl/v1/system/disconnectBluetoothDevice",
"SET_SPEAKER_A": "http://{host}/YamahaExtendedControl/v1/system/setSpeakerA?enable={enable}",
"SET_SPEAKER_B": "http://{host}/YamahaExtendedControl/v1/system/setSpeakerB?enable={enable}",
"SET_DIMMER": "http://{host}/YamahaExtendedControl/v1/system/setDimmer?value={value}",
"SET_ZONE_B_VOLUME_SYNC": "http://{host}/YamahaExtendedControl/v1/system/setZoneBVolumeSync?enable={enable}",
"SET_HDMI_OUT_1": "http://{host}/YamahaExtendedControl/v1/system/setHdmiOut1?enable={enable}",
"SET_HDMI_OUT_2": "http://{host}/YamahaExtendedControl/v1/system/setHdmiOut2?enable={enable}",
"GET_NAME_TEXT": "http://{host}/YamahaExtendedControl/v1/system/getNameText?id={id}",
"SET_NAME_TEXT": "http://{host}/YamahaExtendedControl/v1/system/setNameText",
"SET_SPEAKER_PATTERN": "http://{host}/YamahaExtendedControl/v1/system/setSpeakerPattern?num={num}",
"SET_PARTYMODE": "http://{host}/YamahaExtendedControl/v1/system/setPartyMode?enable={enable}",
}
@staticmethod
def get_device_info():
"""For retrieving basic information of a Device."""
return System.URI["GET_DEVICE_INFO"]
# end-of-method get_device_info
@staticmethod
def get_features():
"""For retrieving feature information equipped with a Device."""
return System.URI["GET_FEATURES"]
# end-of-method get_features
@staticmethod
def get_network_status():
"""For retrieving network related setup/information."""
return System.URI["GET_NETWORK_STATUS"]
# end-of-method get_network_status
@staticmethod
def get_func_status():
"""For retrieving setup/information of overall system function.
Parameters are readable only when corresponding functions are
available in "func_list" of /system/getFeatures.
"""
return System.URI["GET_FUNC_STATUS"]
# end-of-method get_func_status
@staticmethod
def set_autopower_standby(enable=True):
"""For setting Auto Power Standby status. Actual
operations/reactions of enabling Auto Power Standby depend on
each Device.
Parameters
----------
enable : Any
Specifies Auto Power Standby status.
"""
return System.URI["SET_AUTOPOWER_STANDBY"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_autopower_standby
@staticmethod
def get_location_info():
"""For retrieving Location information."""
return System.URI["GET_LOCATION_INFO"]
# end-of-method get_location_info
@staticmethod
def send_ir_code(code):
"""For sending specific remote IR code. A Device is operated
same as remote IR code reception. But continuous IR code cannot
be used in this command. Refer to each Device's IR code list for
details..
Parameters
----------
code : Any
Specifies IR code in 8-digit hex.
"""
return System.URI["SEND_IR_CODE"].format(host="{host}", code=code)
# end-of-method send_ir_code
@staticmethod
def set_wired_lan(
dhcp=None,
ip_address=None,
subnet_mask=None,
default_gateway=None,
dns_server_1=None,
dns_server_2=None,
):
"""For setting Wired Network. Network connection is switched to
wired by using this API. If no parameter is specified, current
parameter is used. If set parameter is incomplete, it is
possible not to provide network availability.
Parameters
----------
dhcp : Any
Specifies DHCP setting.
ip_address : Any
Specifies IP Address.
subnet_mask : Any
Specifies Subnet Mask.
default_gateway : Any
Specifies Default Gateway.
dns_server_1 : Any
Specifies DNS Server 1.
dns_server_2 : Any
Specifies DNS Server 2.
"""
data = {}
if dhcp is not None:
data["dhcp"] = dhcp
if ip_address is not None:
data["ip_address"] = ip_address
if subnet_mask is not None:
data["subnet_mask"] = subnet_mask
if default_gateway is not None:
data["default_gateway"] = default_gateway
if dns_server_1 is not None:
data["dns_server_1"] = dns_server_1
if dns_server_2 is not None:
data["dns_server_2"] = dns_server_2
return System.URI["SET_WIRED_LAN"].format(host="{host}"), data
# end-of-method set_wired_lan
@staticmethod
def set_wireless_lan(
ssid=None,
wifi_type=None,
key=None,
dhcp=None,
ip_address=None,
subnet_mask=None,
default_gateway=None,
dns_server_1=None,
dns_server_2=None,
):
"""For setting Wireless Network (Wi-Fi).
Network connection is switched to wireless (Wi-Fi) by using this
API. If no parameter is specified, current parameter is used. If
set parameter is incomplete, it is possible not to provide
network availability.
"""
data = {}
if ssid is not None:
data["ssid"] = ssid
if wifi_type is not None:
assert wifi_type in WIFI, "Invalid TYPE value!"
data["type"] = wifi_type
if key is not None:
data["key"] = key
if dhcp is not None:
data["dhcp"] = dhcp
if ip_address is not None:
data["ip_address"] = ip_address
if subnet_mask is not None:
data["subnet_mask"] = subnet_mask
if default_gateway is not None:
data["default_gateway"] = default_gateway
if dns_server_1 is not None:
data["dns_server_1"] = dns_server_1
if dns_server_2 is not None:
data["dns_server_2"] = dns_server_2
return System.URI["SET_WIRELESS_LAN"].format(host="{host}"), data
# end-of-method set_wireless_lan
@staticmethod
def set_wireless_direct(wifi_type=None, key=None):
"""For setting Wireless Network (Wireless Direct).
Network connection is switched to wireless (Wireless Direct) by
using this API. If no parameter is specified, current parameter
is used. If set parameter is incomplete, it is possible not to
provide network availability.
"""
data = {}
if wifi_type is not None:
assert wifi_type in WIFI_DIRECT, "Invalid TYPE value!"
data["type"] = wifi_type
if key is not None:
data["key"] = key
return System.URI["SET_WIRELESS_DIRECT"].format(host="{host}"), data
# end-of-method set_wireless_direct
@staticmethod
def set_ip_settings(dhcp, ip_address, subnet_mask, default_gateway, dns_server_1, dns_server_2):
"""For setting IP.
This API only set IP as maintain same network connection status
(Wired/Wireless Lan/Wireless Direct/Extend). If no parameter is
specified, current parameter is used. If set parameter is
incomplete, it is possible not to provide network availability.
"""
data = {}
if dhcp is not None:
data["dhcp"] = dhcp
if ip_address is not None:
data["ip_address"] = ip_address
if subnet_mask is not None:
data["subnet_mask"] = subnet_mask
if default_gateway is not None:
data["default_gateway"] = default_gateway
if dns_server_1 is not None:
data["dns_server_1"] = dns_server_1
if dns_server_2 is not None:
data["dns_server_2"] = dns_server_2
return System.URI["SET_WIRED_LAN"].format(host="{host}"), data
# end-of-method set_ip_settings
@staticmethod
def set_network_name(name):
"""For setting Network Name (Friendly Name)."""
return System.URI["SET_NETWORK_NAME"].format(host="{host}"), {"name": name}
# end-of-method set_network_name
@staticmethod
def set_airplay_pin(pin):
"""For setting AirPlay PIN.
This is valid only when "airplay" exists in "func_list" found in
/system/getFuncStatus.
"""
return System.URI["SET_AIRPLAY_PIN"].format(host="{host}"), {"pin": pin}
# end-of-method set_airplay_pin
@staticmethod
def get_mac_address_filter():
"""For retrieving setup of MAC Address Filter."""
return System.URI["GET_MAC_ADDRESS_FILTER"]
# end-of-method get_mac_address_filter
@staticmethod
def set_mac_address_filter(filter, *macs):
"""For setting MAC Address Filter."""
data = {"filter": filter}
for i, address in enumerate(macs):
data[f"address_{i + 1}"] = address
if i >= 9:
break
return System.URI["SET_MAC_ADDRESS_FILTER"].format(host="{host}"), data
# end-of-method set_mac_address_filter
@staticmethod
def get_network_standby():
"""For retrieving setup of Network Standby."""
return System.URI["GET_NETWORK_STANDBY"]
# end-of-method get_network_standby
@staticmethod
def set_network_standby(standby):
"""For setting Network Standby."""
assert standby in STANDBY, "Invalid STANDBY value!"
return System.URI["SET_NETWORK_STANDBY"].format(host="{host}", standby=standby)
# end-of-method set_network_standby
@staticmethod
def get_bluetooth_info():
"""For retrieving setup/information of Bluetooth.
Parameters are readable only when corresponding functions are
available in "func_list" of /system/getFuncStatus.
"bluetooth_device" parameter is contained in
"bluetooth_tx_setting".
"""
return System.URI["GET_BLUETOOTH_INFO"]
# end-of-method get_bluetooth_info
@staticmethod
def set_bluetooth_standby(enable=True):
"""For setting Bluetooth Standby."""
return System.URI["SET_BLUETOOTH_STANDBY"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_bluetooth_standby
@staticmethod
def set_bluetooth_tx_setting(enable=True):
"""For setting Bluetooth transmission."""
return System.URI["SET_BLUETOOTH_TX_SETTING"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_bluetooth_tx_setting
@staticmethod
def get_bluetooth_device_list():
"""For retrieving Bluetooth (Sink) device list.
This API is available only when "bluetooth_tx_setting" is true
under /system/getFuncStatus. This device list information is in
the cache. If update device list information, execute
/system/updateBluetoothDeviceList.
"""
return System.URI["GET_BLUETOOTH_DEVICE_LIST"]
# end-of-method get_bluetooth_device_list
@staticmethod
def update_bluetooth_device_list():
"""For updating Bluetooth (Sink) device list.
This API is available only when "bluetooth_tx_setting" is true
under /system/getFuncStatus. Retrieve update status and list
information after finish updating via
/system/getBluetoothDeviceList.
"""
return System.URI["UPDATE_BLUETOOTH_DEVICE_LIST"]
# end-of-method update_bluetooth_device_list
@staticmethod
def connect_bluetooth_device(address):
"""For connecting Bluetooth (Sink) device.
This API is available only when "bluetooth_tx_setting" is true
under /system/getFuncStatus. It is possible to take time to
return this API response issued after connection status is
fixed.
"""
return System.URI["CONNECT_BLUETOOTH_DEVICE"].format(host="{host}", address=address)
# end-of-method connect_bluetooth_device
@staticmethod
def disconnect_bluetooth_device():
"""For disconnecting Bluetooth (Sink) device.
This API is available only when "bluetooth_tx_setting" is true
under /system/getFuncStatus. This API response is issued
immediately after disconnect request is accepted.
"""
return System.URI["DISCONNECT_BLUETOOTH_DEVICE"]
# end-of-method disconnect_bluetooth_device
@staticmethod
def set_speaker_a(enable=True):
"""For setting Speaker A status."""
return System.URI["SET_SPEAKER_A"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_speaker_a
@staticmethod
def set_speaker_b(enable=True):
"""For setting Speaker A status."""
return System.URI["SET_SPEAKER_B"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_speaker_b
@staticmethod
def set_dimmer(value):
"""For setting FL/LED Dimmer.
Parameters
----------
value : Any
Setting Dimmer. Specifies -1 in case of auto setting.
Specifies 0 or more than 0 in case of manual setting.
Auto setting is available only when -1 is exists in vale range under
/system/getFeatures.
Value Range: calculated by minimum/maximum/step values gotten
via /system/getFeatures
"""
return System.URI["SET_DIMMER"].format(host="{host}", value=value)
# end-of-method set_dimmer
@staticmethod
def set_zone_b_volume_sync(enable):
"""For setting Zone B volume sync."""
return System.URI["SET_ZONE_B_VOLUME_SYNC"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_zone_b_volume_sync
@staticmethod
def set_hdmi_out_1(enable):
"""set_hdmi_out_1."""
return System.URI["SET_HDMI_OUT_1"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_hdmi_out_1
@staticmethod
def set_hdmi_out_2(enable):
"""set_hdmi_out_1."""
return System.URI["SET_HDMI_OUT_2"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_hdmi_out_2
@staticmethod
def get_name_text(id):
"""For retrieving text information of Zone, Input, Sound
program. If they can be renamed, can retrieve text information
renamed.
Parameters
----------
id : Any
Specifies ID. If no ID is specified, retrieve all information of
Zone, Input, Sound program. Refer to "All ID List" for details (documentation).
"""
return System.URI["GET_NAME_TEXT"].format(host="{host}", id=id)
# end-of-method get_name_text
@staticmethod
def set_name_text(id, text):
"""For setting text information related to each ID of Zone,
Input.
Parameters
----------
id : Any
Specifies ID. Input ID can be specified only when
" rename_enable " is true under /system/getFeatures.
Sound Program ID can not be specified.
Note:
If "main" is specified, Network Name is overwritten with same
text information to be acceptable both MusicCast CONTROLLER
(Yamaha) and Spotify App. If Network Name is changed, "main"
text information is not changed.
text : Any
Specifies text information (UTF-8 within 64 bytes).
If "" (empty text) is specified, specifies default text information.
"""
data = {"id": id, "text": text}
return System.URI["SET_NAME_TEXT"], data
# end-of-method set_name_text
@staticmethod
def set_partymode(enable=True):
"""For setting Party Mode. Available only when "party_mode"
exists in system func_list under /system/getFeatures.
Parameters
----------
enable : Any
boolean
"""
return System.URI["SET_PARTYMODE"].format(host="{host}", enable=_bool_to_str(enable))
# end-of-method set_partymode
@staticmethod
def set_speaker_pattern(num):
"""For setting speaker of device. Available only when
"speaker_pattern" function exists in system func_list under
/system/getFeatures.
Parameters
----------
num : Any
int Specifies Speaker pattern number. Values: speaker_pattern
number from /system/getFeatures
"""
return System.URI["SET_SPEAKER_PATTERN"].format(host="{host}", num=num)
# end-of-method set_speaker_pattern
# end-of-class System
class Zone:
"""Zone commands."""
URI: ClassVar[dict[str, str | tuple[str, dict[str, bool]]]] = {
"GET_STATUS": "http://{host}/YamahaExtendedControl/v1/{zone}/getStatus",
"GET_SOUND_PROGRAM_LIST": "http://{host}/YamahaExtendedControl/v1/{zone}/getSoundProgramList",
"SET_POWER": "http://{host}/YamahaExtendedControl/v1/{zone}/setPower?power={power}",
"SET_SLEEP": "http://{host}/YamahaExtendedControl/v1/{zone}/setSleep?sleep={sleep}",
"SET_VOLUME": "http://{host}/YamahaExtendedControl/v1/{zone}/setVolume?volume={volume}",
"SET_MUTE": "http://{host}/YamahaExtendedControl/v1/{zone}/setMute?enable={enable}",
"SET_INPUT": "http://{host}/YamahaExtendedControl/v1/{zone}/setInput?input={input}&mode={mode}",
"SET_SOUND_PROGRAM": "http://{host}/YamahaExtendedControl/v1/{zone}/setSoundProgram?program={program}",
"PREPARE_INPUT_CHANGE": "http://{host}/YamahaExtendedControl/v1/{zone}/prepareInputChange?input={input}",
"SET_SURROUND_3D": "http://{host}/YamahaExtendedControl/v1/{zone}/set3dSurround?enable={enable}",
"SET_DIRECT": "http://{host}/YamahaExtendedControl/v1/{zone}/setDirect?enable={enable}",
"SET_PURE_DIRECT": "http://{host}/YamahaExtendedControl/v1/{zone}/setPureDirect?enable={enable}",
"SET_ENHANCER": "http://{host}/YamahaExtendedControl/v1/{zone}/setEnhancer?enable={enable}",
"SET_TONE_CONTROL": (
"http://{host}/YamahaExtendedControl/v1/{zone}/setToneControl",
{"mode": False, "bass": False, "treble": False},
),
"SET_EQUALIZER": (
"http://{host}/YamahaExtendedControl/v1/{zone}/setEqualizer",
{"mode": False, "low": False, "mid": False, "high": False},
),
"SET_BALANCE": "http://{host}/YamahaExtendedControl/v1/{zone}/setBalance?value={value}",
"SET_DIALOGUE_LEVEL": "http://{host}/YamahaExtendedControl/v1/{zone}/setDialogueLevel?value={value}",
"SET_DIALOGUE_LIFT": "http://{host}/YamahaExtendedControl/v1/{zone}/setDialogueLift?value={value}",
"SET_DTS_DIALOGUE_CONTROL": "http://{host}/YamahaExtendedControl/v1/{zone}/setDtsDialogueControl?num={value}",
"SET_CLEAR_VOICE": "http://{host}/YamahaExtendedControl/v1/{zone}/setClearVoice?enable={enable}",
"SET_SUBWOOFER_VOLUME": "http://{host}/YamahaExtendedControl/v1/{zone}/setSubwooferVolume?volume={volume}",
"SET_BASS_EXTENSION": "http://{host}/YamahaExtendedControl/v1/{zone}/setBassExtension?enable={enable}",
"SET_EXTRA_BASS": "http://{host}/YamahaExtendedControl/v1/{zone}/setExtraBass?enable={enable}",
"GET_SIGNAL_INFO": "http://{host}/YamahaExtendedControl/v1/{zone}/getSignalInfo",
"SET_LINK_CONTROL": "http://{host}/YamahaExtendedControl/v1/{zone}/setLinkControl?control={control}",
"SET_LINK_AUDIO_DELAY": "http://{host}/YamahaExtendedControl/v1/{zone}/setLinkAudioDelay?delay={delay}",
"SET_LINK_AUDIO_QUALITY": "http://{host}/YamahaExtendedControl/v1/{zone}/setLinkAudioQuality?mode={mode}",
"SET_ADAPTIVE_DRC": "http://{host}/YamahaExtendedControl/v1/{zone}/setAdaptiveDrc?enable={enable}",
"SET_SURR_DECODER_TYPE": "http://{host}/YamahaExtendedControl/v1/{zone}/setSurroundDecoderType?type={option}",
}
@staticmethod
def get_status(zone):
"""For retrieving basic information of each Zone like power,
volume, input and so on.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["GET_STATUS"].format(host="{host}", zone=zone)
# end-of-method get_status
@staticmethod
def get_sound_program_list(zone):
"""For retrieving a list of Sound Program available in each
Zone. It is possible for the list contents to be dynamically
changed.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["GET_SOUND_PROGRAM_LIST"].format(host="{host}", zone=zone)
# end-of-method get_sound_program_list
@staticmethod
def set_power(zone, power):
"""For setting power status of each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
power : Any
Specifies power status. Values: 'on', 'standby', 'toggle'
"""
assert zone in ZONES, "Invalid ZONE value!"
assert power in POWER, "Invalid POWER value!"
return Zone.URI["SET_POWER"].format(host="{host}", zone=zone, power=power)
# end-of-method set_power
@staticmethod
def set_sleep(zone, sleep):
"""For setting Sleep Timer for each Zone. With Zone B enabled
Devices, target Zone is described as Master Power, but Main Zone
is used to set it up via YXC.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
sleep : Any
Specifies Sleep Time (unit in minutes) Values: 0, 30, 60, 90, 120
"""
assert zone in ZONES, "Invalid ZONE value!"
assert sleep in SLEEP, "Invalid SLEEP value!"
return Zone.URI["SET_SLEEP"].format(host="{host}", zone=zone, sleep=sleep)
# end-of-method set_sleep
@staticmethod
def set_volume(zone, volume, step):
"""For setting volume in each Zone. Values of specifying range
and steps are different. There are some Devices that cannot
allow this value to be go up to Device's maximum volume.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
volume : Any
Specifies volume value
Value Range: calculated by minimum/maximum/step values gotten via /system/getFeatures.
(Available on and after API Version 1.17) 'up', 'down'
step : Any
Specifies volume step value if the volume is 'up' or 'down'. If
nothing specified, minimum step value is used implicitly.
(Available on and after API Version 1.17) Values: Value range calculated by minimum/maximum/step values gotten via /system/getFeatures.
"""
assert zone in ZONES, "Invalid ZONE value!"
url = Zone.URI["SET_VOLUME"].format(host="{host}", zone=zone, volume=volume)
if step:
url += f"&step={step}"
return url
# end-of-method set_volume
@staticmethod
def set_mute(zone, enable=True):
"""For setting mute status in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifying mute status. Default: True.
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_MUTE"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_mute
@staticmethod
def set_input(zone, input, mode):
"""For selecting each Zone input.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
input : Any
Specifies Input ID. Values: Input IDs gotten via /system/getFeatures
mode : Any
Specifies select mode. If no parameter is specified, actions of input change depend on a
Device's specification Value: "autoplay_disabled" (Restricts Auto Play of Net/USB related Inputs).
Available on and after API Version 1.12
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_INPUT"].format(host="{host}", zone=zone, input=input, mode=mode)
# end-of-method set_input
@staticmethod
def set_sound_program(zone, program):
"""For selecting Sound Programs.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
program : Any
Specifies Sound Program ID. Values: Sound Program IDs gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_SOUND_PROGRAM"].format(host="{host}", zone=zone, program=program)
# end-of-method set_sound_program
@staticmethod
def prepare_input_change(zone, input):
"""Let a Device do necessary process before changing input in a
specific zone. This is valid only when 'prepare_input_change'
exists in 'func_list' found in /system/getFuncStatus. MusicCast
CONTROLLER executes this API when an input icon is selected in a
Room, right before sending various APIs (of retrieving list
information etc.) regarding selecting input.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
input : Any
Specifies Input ID. Values: Input IDs gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["PREPARE_INPUT_CHANGE"].format(host="{host}", zone=zone, input=input)
# end-of-method prepare_input_change
@staticmethod
def set_surround_3d(zone, enable):
"""For setting 3D Surround status.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifies 3D Surround status.
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_SURROUND_3D"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_surround_3d
@staticmethod
def set_direct(zone, enable):
"""For setting Direct status.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifies Direct status.
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_DIRECT"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_direct
@staticmethod
def set_pure_direct(zone, enable):
"""For setting Pure Direct status.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifies Pure Direct status.
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_PURE_DIRECT"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_pure_direct
@staticmethod
def set_enhancer(zone, enable):
"""For setting Enhancer status.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifies Enhancer status.
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_ENHANCER"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_enhancer
@staticmethod
def set_tone_control(zone, mode, bass, treble):
"""For setting Tone Control in each Zone. Values of specifying
range and steps are different.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
mode : Any
Specifies Mode setting. If no parameter is specified, current Mode
setting is not changed.
Regardless of the Mode setting, bass/treble setting can be changed,
but valid only when Mode setting is "manual".
bass : Any
Specifies Bass value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
treble : Any
Specifies Treble value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return UrlBuilder.build_zone_url(Zone.URI["SET_TONE_CONTROL"], zone, mode=mode, bass=bass, treble=treble)
# end-of-method set_tone_control
@staticmethod
def set_equalizer(zone, mode, low, mid, high):
"""For setting Equalizer in each Zone. Values of specifying
range and steps are different.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
mode : Any
Specifies Mode setting. If no parameter is specified, current Mode
setting is not changed.
Regardless of the Mode setting, low/mid/high setting can be
changed, but valid only when Mode setting is "manual". Values: Values gotten via /system/getFeatures
low : Any
Specifies Low value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
mid : Any
Specifies Mid value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
high : Any
Specifies High value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return UrlBuilder.build_zone_url(Zone.URI["SET_EQUALIZER"], zone, mode=mode, low=low, mid=mid, high=high)
# end-of-method set_equalizer
@staticmethod
def set_balance(zone, value):
"""For setting L/R Balance in each Zone's speaker. Values of
specifying range and steps are different.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
value : Any
Specifies L/R Balance value. Negative values are for left side,
positive values are for right side balance. Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_BALANCE"].format(host="{host}", zone=zone, value=value)
# end-of-method set_balance
@staticmethod
def set_dialogue_level(zone, value):
"""For setting Dialogue Level in each Zone. Values of specifying
range and steps are different.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
value : Any
Specifies Dialogue Level value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_DIALOGUE_LEVEL"].format(host="{host}", zone=zone, value=value)
# end-of-method set_dialogue_level
@staticmethod
def set_dialogue_lift(zone, value):
"""For setting Dialogue Lift in each Zone. Values of specifying
range and steps are different.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
value : Any
Specifies Dialogue Lift value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_DIALOGUE_LIFT"].format(host="{host}", zone=zone, value=value)
# end-of-method set_dialogue_lift
@staticmethod
def set_dts_dialogue_control(zone, value):
"""For setting DTS Dialogue Control in each Zone. Values of
specifying range and steps are different. Undocumented method.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
value : Any
Specifies DTS Dialogue Control value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_DTS_DIALOGUE_CONTROL"].format(host="{host}", zone=zone, value=value)
# end-of-method set_dts_dialogue_control
@staticmethod
def set_clear_voice(zone, enable):
"""For setting Clear Voice in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifies Clear Voice setting
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_CLEAR_VOICE"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_clear_voice
@staticmethod
def set_subwoofer_volume(zone, volume):
"""For setting Subwoofer Volume in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
volume : Any
Specifies volume value Values: Value range calculated by minimum/maximum/step values
gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_SUBWOOFER_VOLUME"].format(host="{host}", zone=zone, volume=volume)
# end-of-method set_subwoofer_volume
@staticmethod
def set_bass_extension(zone, enable):
"""For setting Bass Extension in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifies Bass Extension setting
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_BASS_EXTENSION"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_bass_extension
@staticmethod
def set_extra_bass(zone, enable):
"""For setting Extra Bass in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
enable : Any
Specifies Extra Bass setting
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_EXTRA_BASS"].format(host="{host}", zone=zone, enable=_bool_to_str(enable))
# end-of-method set_bass_extension
@staticmethod
def get_signal_info(zone):
"""For retrieving current playback signal information in each
Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["GET_SIGNAL_INFO"].format(host="{host}", zone=zone)
# end-of-method get_signal_info
@staticmethod
def set_link_control(zone, control):
"""For setting Link Control in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
control : Any
Specifies Link Control setting Values: Values gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_LINK_CONTROL"].format(host="{host}", zone=zone, control=control)
# end-of-method set_link_control
@staticmethod
def set_link_audio_delay(zone, delay):
"""For setting Link Audio Delay in each Zone. This setting is
invalid when Link Control setting is "Stability Boost".
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
delay : Any
Specifies Link Audio Delay setting Values: Values gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_LINK_AUDIO_DELAY"].format(host="{host}", zone=zone, delay=delay)
# end-of-method set_link_audio_delay
@staticmethod
def set_link_audio_quality(zone, quality):
"""For setting Link Audio Quality in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
quality : Any
Specifies Link Audio Quality setting Values: Values gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_LINK_AUDIO_QUALITY"].format(host="{host}", zone=zone, mode=quality)
# end-of-method set_link_audio_delay
@classmethod
def set_adaptive_drc(cls, zone, value):
"""For setting Link Audio Quality in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
value : Any
Specifies drc enable
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_ADAPTIVE_DRC"].format(host="{host}", zone=zone, enable=_bool_to_str(value))
@classmethod
def set_surr_decoder_type(cls, zone, option):
"""For setting Surround decoder type in each Zone.
Parameters
----------
zone : Any
Specifies target Zone. Values: 'main', 'zone2', 'zone3', 'zone4'
option : Any
the surround decoder type to set
"""
assert zone in ZONES, "Invalid ZONE value!"
return Zone.URI["SET_SURR_DECODER_TYPE"].format(host="{host}", zone=zone, option=option)
# end-of-class Zone
class Tuner:
"""APIs in regard to Tuner setting and getting information.
Target inputs: AM / FM / DAB
"""
URI: ClassVar[dict[str, str]] = {
"GET_PRESET_INFO": "http://{host}/YamahaExtendedControl/v1/tuner/getPresetInfo?band={band}",
"GET_PLAY_INFO": "http://{host}/YamahaExtendedControl/v1/tuner/getPlayInfo",
"SET_FREQ": "http://{host}/YamahaExtendedControl/v1/tuner/setFreq?band={band}&tuning={tuning}&num={num}",
"RECALL_PRESET": "http://{host}/YamahaExtendedControl/v1/tuner/recallPreset?zone={zone}&band={band}&num={num}",
"SWITCH_PRESET": "http://{host}/YamahaExtendedControl/v1/tuner/switchPreset?dir={dir}",
"STORE_PRESET": "http://{host}/YamahaExtendedControl/v1/tuner/storePreset?num={num}",
"SET_DAB_SERVICE": "http://{host}/YamahaExtendedControl/v1/tuner/setDabService?dir={dir}",
}
@staticmethod
def get_preset_info(band):
"""For retrieving Tuner preset information.
Parameters
----------
band : Any
Specifying a band. Values depend on Preset Type gotten via /system/getFeatures. Values: 'common' (common), 'am', 'fm', 'dab' (separate)
"""
assert band in BAND, "Invalid BAND value!"
return Tuner.URI["GET_PRESET_INFO"].format(host="{host}", band=band)
# end-of-method get_preset_info
@staticmethod
def get_play_info():
"""For retrieving playback information of Tuner."""
return Tuner.URI["GET_PLAY_INFO"]
# end-of-method get_play_info
@staticmethod
def set_freq(band, tuning, num):
"""For setting Tuner frequency.
Parameters
----------
band : Any
Specifies Band. Values : 'am', 'fm'
tuning : Any
Specifies a tuning method. Use 'tp_up' and 'tp_down' only when Band is RDS. Values: 'up', 'down', 'cancel', 'auto_up', 'auto_down',
'tp_up', 'tp_down', 'direct'
num : Any
Specifies frequency (unit in kHz). Valid only when tuning is 'direct'
"""
assert band in BAND, "Invalid BAND value!"
assert tuning in TUNING, "Invalid TUNING value!"
return Tuner.URI["SET_FREQ"].format(host="{host}", band=band, tuning=tuning, num=num)
# end-of-method set_freq
@staticmethod
def recall_preset(zone, band, num):
"""For recalling a Tuner preset.
Parameters
----------
zone : Any
Specifies station recalling zone. This causes input change in specified zone. Values: 'main', 'zone2', 'zone3', 'zone4'
band : Any
Specifies Band type. Depending on Preset Type gotten via
/system/getFeatures, specifying value is different Values: 'common' (band common), 'separate' (each band preset)
num : Any
Specifies Preset number. Value: one in the range gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
assert band in PRESET_BAND, "Invalid BAND value!"
return Tuner.URI["RECALL_PRESET"].format(host="{host}", zone=zone, band=band, num=num)
# end-of-method recall_preset
@staticmethod
def switch_preset(dir):
"""
For selecting Tuner preset. Call this API after change the target zone's input to Tuner. It is
possible to change Band in case of preset type is 'common'. In case of preset type is 'separate', need
to change the target Band before calling this API. This API is available on and after API Version
1.17.
Parameters
----------
dir : Any
Specifies change direction of preset. Values: 'next', 'previous'
"""
assert dir in DIR, "Invalid DIR value!"
return Tuner.URI["SWITCH_PRESET"].format(host="{host}", dir=dir)
# end-of-method switch_preset
@staticmethod
def store_preset(num):
"""For registering current station to a preset.
Parameters
----------
num : Any
Specifying a preset number. Value: one in the range gotten via /system/getFeatures
"""
return Tuner.URI["STORE_PRESET"].format(host="{host}", num=num)
# end-of-method store_preset
@staticmethod
def set_dab_service(dir):
"""For selecting DAB Service. Available only when DAB is valid
to use.
Parameters
----------
dir : Any
Specifies change direction of services. Values: 'next', 'previous'
"""
assert dir in DIR, "Invalid DIR value!"
return Tuner.URI["SET_DAB_SERVICE"].format(host="{host}", dir=dir)
# end-of-method set_dab_service
# end-of-class Tuner
class NetUSB:
"""APIs in regard to Network/USB related setting and getting information
Target Inputs: USB / Network related ones (Server / Net Radio / Pandora / Spotify / AirPlay etc.).
"""
URI: ClassVar[dict[str, str]] = {
"GET_PRESET_INFO": "http://{host}/YamahaExtendedControl/v1/netusb/getPresetInfo",
"GET_PLAY_INFO": "http://{host}/YamahaExtendedControl/v1/netusb/getPlayInfo",
"SET_PLAYBACK": "http://{host}/YamahaExtendedControl/v1/netusb/setPlayback?playback={playback}",
"TOGGLE_REPEAT": "http://{host}/YamahaExtendedControl/v1/netusb/toggleRepeat",
"TOGGLE_SHUFFLE": "http://{host}/YamahaExtendedControl/v1/netusb/toggleShuffle",
"GET_LIST_INFO": "http://{host}/YamahaExtendedControl/v1/netusb/getListInfo?input={input}&index={index}&size={size}&lang={lang}&list_id={list_id}",
"SET_LIST_CONTROL": "http://{host}/YamahaExtendedControl/v1/netusb/setListControl?list_id={list_id}&type={type}&index={index}&zone={zone}",
"SET_SEARCH_STRING": "http://{host}/YamahaExtendedControl/v1/netusb/setSearchString",
"RECALL_PRESET": "http://{host}/YamahaExtendedControl/v1/netusb/recallPreset?zone={zone}&num={num}",
"STORE_PRESET": "http://{host}/YamahaExtendedControl/v1/netusb/storePreset?num={num}",
"GET_ACCOUNT_STATUS": "http://{host}/YamahaExtendedControl/v1/netusb/getAccountStatus",
"SWITCH_ACCOUNT": "http://{host}/YamahaExtendedControl/v1/netusb/switchAccount?input={input}&index={index}&timeout={timeout}",
"GET_SERVICE_INFO": "http://{host}/YamahaExtendedControl/v1/netusb/getServiceInfo?input={input}&type={type}&timeout={timeout}",
"SET_REPEAT": "http://{host}/YamahaExtendedControl/v1/netusb/setRepeat?mode={mode}",
"SET_SHUFFLE": "http://{host}/YamahaExtendedControl/v1/netusb/setShuffle?mode={mode}",
}
@staticmethod
def get_preset_info():
"""For retrieving preset information.
Presets are common use among Net/USB related input sources.
"""
return NetUSB.URI["GET_PRESET_INFO"]
# end-of-method get_preset_info
@staticmethod
def get_play_info():
"""For retrieving playback information."""
return NetUSB.URI["GET_PLAY_INFO"]
# end-of-method get_play_info
@staticmethod
def set_playback(playback):
"""For controlling playback status.
Parameters
----------
playback : Any
Specifies playback status. Values: 'play', 'stop', 'pause', 'play_pause', 'previous', 'next',
'fast_reverse_start', 'fast_reverse_end', 'fast_forward_start',
'fast_forward_end'
"""
assert playback in PLAYBACK, "Invalid PLAYBACK value!"
return NetUSB.URI["SET_PLAYBACK"].format(host="{host}", playback=playback)
# end-of-method set_playback
@staticmethod
def toggle_repeat():
"""For toggling repeat setting.
No direct / discrete setting commands available.
"""
return NetUSB.URI["TOGGLE_REPEAT"]
# end-of-method toggle_repeat
@staticmethod
def set_repeat(mode):
"""For setting repeat.
Available on after API version 1.19.
Parameters
----------
mode : Any
Specifies the repeat setting. Value : "off" / "one" / "all"
"""
return NetUSB.URI["SET_REPEAT"].format(host="{host}", mode=mode)
@staticmethod
def set_shuffle(mode):
"""For setting shuffle.
Available on after API version 1.19.
Parameters
----------
mode : Any
Specifies the shuffle setting. Value : "off" / "on" / "songs" / "albums"
"""
return NetUSB.URI["SET_SHUFFLE"].format(host="{host}", mode=mode)
@staticmethod
def toggle_shuffle():
"""For toggling shuffle setting.
No direct / discrete setting commands available.
"""
return NetUSB.URI["TOGGLE_SHUFFLE"]
# end-of-method toggle_shuffle
@staticmethod
def get_list_info(input, index, size, lang, list_id):
"""For retrieving list information. Basically this info is
available to all relevant inputs, not limited to or independent
from current input.
Parameters
----------
input : Any
Specifies target Input ID. Controls for setListControl are to work
with the input specified here. Values: Input IDs for Net/USB related sources
index : Any
Specifies the reference index (offset from the beginning of the list).
Note that this index must be in multiple of 8. If nothing was
specified, the reference index previously specified would be used. Values: 0, 8, 16, 24, ..., 64984, 64992
size : Any
Specifies max list size retrieved at a time.
Value Range: 1 - 8
lang : Any
Specifies list language. But menu names or text info are not
always necessarily pulled in a language specified here. If nothing
specified, English ("en") is used implicitly Values: 'en' (English), 'ja' (Japanese), 'fr' (French), 'de'
(German), 'es' (Spanish), 'ru' (Russian), 'it' (Italy), 'zh' (Chinese)
list_id : Any
Specifies list ID. If nothing specified, 'main' is chosen implicitly Values: 'main' (common for all Net/USB sources)
'auto_complete' (Pandora)
'search_artist' (Pandora)
'search_track' (Pandora)
"""
assert lang in LANG, "Invalid LANG value!"
return NetUSB.URI["GET_LIST_INFO"].format(
host="{host}",
input=input,
index=index,
size=size,
lang=lang,
list_id=list_id,
)
# end-of-method get_list_info
@staticmethod
def set_list_control(list_id, type, index, zone):
"""For control a list. Controllable list info is not limited to
or independent from current input.
Parameters
----------
list_id : Any
Specifies list ID. If nothing specified, 'main' is chosen implicitly Values: 'main' (common for all Net/USB sources)
'auto_complete' (Pandora)
'search_artist' (Pandora)
'search_track' (Pandora)
type : Any
Specifies list transition type. 'select' is to enter and get into one deeper layer than the current
layer where the element specified by the index belongs to. 'play' is to start playback current index
element, 'return' is to go back one upper layer than current. 'select' and 'play' needs to specify
an index at the same time. In case to 'select' an element with its attribute being 'Capable of Search',
specify search text using setSearchString in advance. (Or it is possible to specify search text and
move layers at the same time by specifying an index in setSearchString). Values: 'select', 'play', 'return'
"""
assert type in TYPE, "Invalid TYPE value!"
assert zone in ZONES, "Invalid ZONE value!"
return NetUSB.URI["SET_LIST_CONTROL"].format(host="{host}", list_id=list_id, type=type, index=index, zone=zone)
# end-of-method set_list_control
@staticmethod
def set_search_string(search_string, list_id=None, index=None):
"""For setting search text.
Specifies string executing this API before select an element with its attribute being “Capable of Search” or
retrieve info about searching list(Pandora).
Parameters
----------
search_string : Any
Value to search for.
list_id : Any
Specifies list ID. If nothing specified, 'main' is chosen implicitly Values: 'main' (common for all Net/USB sources)
'auto_complete' (Pandora)
'search_artist' (Pandora)
'search_track' (Pandora)
index : Any
Specifies an element position in the list being selected
(offset from the beginning of the list). Valid only when the list_id is "main".
Specifies index an element with its attribute being "Capable of Search"
Controls same as setListControl "select" are to work with the index an element specified.
If no index is specified, non-actions of select Values : 0 ~ 64999
"""
assert isinstance(search_string, str), "search_string has to be a str"
payload = {"string": search_string}
if list_id is not None:
search_list_ids = ["main", "auto_complete", "search_artist", "search_track"]
assert list_id in search_list_ids, "list_id has to be one of the following " + str(search_list_ids)
payload["list_id"] = list_id
if index is not None:
assert isinstance(index, int), "index has to be an int"
payload["index"] = index
return NetUSB.URI["SET_SEARCH_STRING"], payload
# end-of-method set_search_string
@staticmethod
def recall_preset(zone, num):
"""For recalling a content preset.
Parameters
----------
zone : Any
Specifies station recalling zone. This causes input change in specified zone. Values: 'main', 'zone2', 'zone3', 'zone4'
num : Any
Specifies Preset number. Value: one in the range gotten via /system/getFeatures
"""
assert zone in ZONES, "Invalid ZONE value!"
return NetUSB.URI["RECALL_PRESET"].format(host="{host}", zone=zone, num=num)
# end-of-method recall_preset
@staticmethod
def store_preset(num):
"""For registering current content to a preset. Presets are
common use among Net/USB related input sources.
Parameters
----------
num : Any
Specifying a preset number. Value: one in the range gotten via /system/getFeatures
"""
return NetUSB.URI["STORE_PRESET"].format(host="{host}", num=num)
# end-of-method store_preset
@staticmethod
def get_account_status():
"""For retrieving account information registered on Device."""
return NetUSB.URI["GET_ACCOUNT_STATUS"]
# end-of-method get_account_status
@staticmethod
def switch_account(input, index, timeout):
"""For switching account for service corresponding multi
account.
Parameters
----------
input : Any
Specifies target Input ID. Value: 'pandora'
index : Any
Specifies switch account index. Value : 0 - 7 (Pandora)
timeout : Any
Specifies timeout duration(ms) for this API process. If specifies 0,
treat as maximum value. Value: 0 - 60000
"""
return NetUSB.URI["SWITCH_ACCOUNT"].format(host="{host}", input=input, index=index, timeout=timeout)
# end-of-method switch_account
@staticmethod
def get_service_info(input, type, timeout):
"""For retrieving information of various Streaming Service. The
combination of Input/Type is available as follows;
Account List (account_list) : retrieving list of account registed on Device
Licensing (licensing) : checking license
Activation Code (activation_code) : retrieving Activation Code
* Disable to check Rhapsody license by refering the value of this APIs response_code. a
Device issues events of netusb - account_updated by condition, retrieve the info excute
/netusb/getAccountStatus. (Sometimes Deivice not issue events)
* Before retrieve Activation Code, retrieve Account List and check not to reach Max about
the num of registration.
Note: Rhapsody service name will be changed to Napster.
Parameters
----------
timeout : Any
Specifies type of retrieving info Value:
"account_list" (Pandora) "licensing" (Napster / Pandora) "activation_code" (Pandora)
type : Any
Specifies type of retrieving info Value:
"account_list" (Pandora) "licensing" (Napster / Pandora) "activation_code" (Pandora)
input : Any
Specifies target Input ID. Value: 'pandora', 'rhapsody', 'napster'
"""
return NetUSB.URI["GET_SERVICE_INFO"].format(host="{host}", input=input, type=type, timeout=timeout)
# end-of-method switch_account
# end-of-class Network_USB
class CD:
"""APIs in regard to CD setting and getting information."""
URI: ClassVar[dict[str, str]] = {
"GET_PLAY_INFO": "http://{host}/YamahaExtendedControl/v1/cd/getPlayInfo",
"SET_PLAYBACK": "http://{host}/YamahaExtendedControl/v1/cd/setPlayback?playback={playback}&num={num}",
"TOGGLE_TRAY": "http://{host}/YamahaExtendedControl/v1/cd/toggleTray",
"TOGGLE_REPEAT": "http://{host}/YamahaExtendedControl/v1/cd/toggleRepeat",
"TOGGLE_SHUFFLE": "http://{host}/YamahaExtendedControl/v1/cd/toggleShuffle",
}
@staticmethod
def get_play_info():
"""For retrieving playback information of CD."""
return CD.URI["GET_PLAY_INFO"]
# end-of-method get_play_info
@staticmethod
def set_playback(playback, num):
"""For controlling playback status.
Parameters
----------
playback : Any
Specifies playback status Values: 'play', 'stop', 'pause', 'previous', 'next',
'fast_reverse_start', 'fast_reverse_end', 'fast_forward_start',
'fast_forward_end', 'track_select'
num : Any
Specifies target track number to playback.
This parameter is valid only when playback "track_select" is specified. Values: 1-512
"""
assert playback in PLAYBACK, "Invalid PLAYBACK value!"
return CD.URI["SET_PLAYBACK"].format(host="{host}", playback=playback, num=num)
# end-of-method set_playback
@staticmethod
def toggle_tray():
"""For toggling CD tray Open/Close setting."""
return CD.URI["TOGGLE_TRAY"]
# end-of-method toggle_tray
@staticmethod
def toggle_repeat():
"""For toggling repeat setting.
No direct / discrete setting commands available.
"""
return CD.URI["TOGGLE_REPEAT"]
# end-of-method toggle_repeat
@staticmethod
def toggle_shuffle():
"""For toggling shuffle setting.
No direct / discrete setting commands available.
"""
return CD.URI["TOGGLE_SHUFFLE"]
# end-of-method toggle_shuffle
# end-of-class CD
class Debug:
"""Undocumented Debug commands."""
URI: ClassVar[dict[str, str]] = {
"GET_DIAG_INFO": "http://{host}/YamahaExtendedControl/v1/debug/getDiagInfo",
"GET_STATUS": "http://{host}/YamahaExtendedControl/v1/debug/getStatus",
}
@staticmethod
def get_diag_info():
"""None."""
return Debug.URI["GET_DIAG_INFO"]
# end-of-method get_diag_info
@staticmethod
def get_status():
"""None."""
return Debug.URI["GET_STATUS"]
# end-of-method get_status
# end-of-class Debug
class Clock:
"""APIs for clock and alarm configuration."""
URI: ClassVar[dict[str, str]] = {
"GET_CLOCK_SETTINGS": "http://{host}/YamahaExtendedControl/v1/clock/getSettings",
"SET_AUTO_SYNC": "http://{host}/YamahaExtendedControl/v1/clock/setAutoSync?enable={enable}",
"SET_DATE_AND_TIME": "http://{host}/YamahaExtendedControl/v1/clock/setDateAndTime?date_time={date_time}",
"SET_CLOCK_FORMAT": "http://{host}/YamahaExtendedControl/v1/clock/setClockFormat?format={format}",
"SET_ALARM_SETTINGS": "http://{host}/YamahaExtendedControl/v1/clock/setAlarmSettings",
}
DAYS: ClassVar[list[str]] = [
"oneday",
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
]
@staticmethod
def get_clock_settings():
"""For retrieving setting related to Clock function."""
return Clock.URI["GET_CLOCK_SETTINGS"]
@staticmethod
def set_auto_sync(enable=True):
"""For setting clock time auto sync.
Available only when "date_and_time" exists in clock - func_list under /system/getFeatures.
Parameters
----------
enable : Any
Specifies whether or not clock auto sync is valid
"""
assert isinstance(enable, bool)
return Clock.URI["SET_AUTO_SYNC"].format(host="{host}", enable=_bool_to_str(enable))
@staticmethod
def set_date_and_time(date_time: list[datetime, str]):
"""For setting date and clock time.
Available only when "date_and_time" exists in clock - func_list under /system/getFeatures.
Parameters
----------
date_time : Any
Specifies date and time set on device. Format is "YYMMDDhhmmss". Value : YY : 00 ~ 99 (Year / 2000 ~ 2099)
MM : 01 ~ 12 (Month) DD : 01 ~ 31 (Day)
hh : 00 ~ 23 (Hour) mm : 00 ~ 59 (Minute) ss : 00 ~ 59 (Second).
Alternatively a python datetime object can be used.
"""
if isinstance(date_time, datetime):
dat_str = date_time.strftime("%y%m%d%H%M%S")
else:
assert isinstance(date_time, str), "date_time has to be a str or datetime object."
dat_str = date_time
return Clock.URI["SET_DATE_AND_TIME"].format(host="{host}", date_time=dat_str)
@staticmethod
def set_clock_format(clock_format: int):
"""For setting format of time display.
Available only when " clock_format " exists in clock - func_list under /system/getFeatures.
Parameters
----------
clock_format : Any
format of time display Values: 12 (12-hour notation) / 24 (24-hour notation)
"""
assert clock_format in {12, 24}, "Only 12 and 24 are possible formats"
return Clock.URI["SET_CLOCK_FORMAT"].format(host="{host}", format=str(clock_format) + "h")
@staticmethod
def set_alarm_settings(
alarm_on=None,
volume=None,
fade_interval=None,
fade_type=None,
mode=None,
repeat=None,
day=None,
enable=None,
alarm_time=None,
beep=None,
playback_type=None,
resume_input=None,
preset_type=None,
preset_num=None,
preset_snooze=None,
):
"""For setting alarm function.
Parameters
----------
alarm_on : Any
Specifies alarm function status on/off
volume : Any
Specifies alarm volume value
Value Range : calculated by minimum/maximum/step value gotten via /system/getFeatures "alarm_volume"
fade_interval : Any
Specifies alarm fade interval (unit in second)
Value Range : calculated by minimum/maximum/step
value gotten via /system/getFeatures "alarm_fade"
fade_type : Any
Specifies alarm fade type Value : 1 ~ fade_type_max ( value gotten via /system/getFeatures)
mode : Any
Specifies alarm mode Value : one gotten via /system/getFeatures "alarm_mode_list"
repeat : Any
Specifies repeat setting. This parameter is valid only when alarm mode "oneday" is specified
day : Any
Specifies target date for alarm setting.
This parameter is specified certainly when set detail parameters. Value: "oneday" / "sunday" / "monday" / "tuesday" / "wednesday " / "thursday" / "friday" / "saturday"
enable : Any
対象日のアラーム設定の有効/無効を指定します。:> WTF?
According to google translate: Specify whether to enable/disable the alarm setting for the target day
alarm_time : Any
Specifies alarm start-up time.
Format is "hhmm" Values : hh : 00 ~ 23 (Hour) mm : 00 ~ 59 (Minute)
beep : Any
Specifies whether or not beep is valid.
playback_type : Any
Specifies playback type Value : "resume" / "preset"
resume_input : Any
Specifies target Input ID to playback for resume.
No playback when "none" is specified. Values: Input IDs gotten via /system/getFeatures "alarm_input_list"
This parameter is valid only when playback_type "resume" is specified.
preset_type : Any
Specifies preset type. Values: Type gotten via /system/getFeatures "alarm_preset_list".
This parameter is valid only when playback_type "preset" is specified.
preset_num : Any
Specifies preset number. Selectable preset number in each preset type is
readable in /system/getFeatures.
This parameter is valid only when playback_type "preset" is specified.
preset_snooze : Any
Returns snooze setting. Available only when "snooze" exists in func_list
under /system/getFeatures.
This parameter is valid only when playback_type "preset" is specified.
"""
payload = {}
if alarm_on is not None:
assert isinstance(alarm_on, bool), "alarm_on has to be a boolean"
payload["alarm_on"] = alarm_on
if volume is not None:
assert isinstance(volume, int), "volume has to be an integer"
payload["volume"] = volume
if fade_interval is not None:
assert isinstance(fade_interval, int), "fade_interval has to be an integer"
payload["fade_interval"] = fade_interval
if fade_type is not None:
assert isinstance(fade_type, int), "fade_type has to be an integer"
payload["fade_type"] = fade_type
if mode is not None:
assert isinstance(mode, str), "mode has to be a str"
payload["mode"] = mode
if repeat is not None:
assert isinstance(repeat, bool), "repeat has to be a bool"
assert day == "oneday", "repeat is only valid if day is oneday"
payload["repeat"] = repeat
if day is not None:
assert day in Clock.DAYS, "day has to be one of the following " + str(Clock.DAYS)
payload["detail"] = {}
payload["detail"]["day"] = day
if enable is not None:
assert isinstance(enable, bool), "enable has to be a bool"
payload["detail"]["enable"] = enable
if alarm_time is not None:
assert isinstance(alarm_time, str), "time has to be a str"
payload["detail"]["time"] = alarm_time
if beep is not None:
assert isinstance(beep, bool), "beep has to be a bool"
payload["detail"]["beep"] = beep
if playback_type is not None:
assert playback_type in [
"resume",
"preset",
], "playback_type has to be resume or preset"
payload["detail"]["playback_type"] = playback_type
if playback_type == "resume":
payload["detail"]["resume"] = {}
if resume_input is not None:
assert isinstance(resume_input, str), "resume_input has to be a str"
payload["detail"]["resume"]["input"] = resume_input
assert preset_type is None, "preset_type is not compatible with playback_type resume"
assert preset_num is None, "preset_num is not compatible with playback_type resume"
assert preset_snooze is None, "preset_snooze is not compatible with playback_type resume"
else:
payload["detail"]["preset"] = {}
if preset_num is not None:
assert isinstance(preset_num, int), "preset_num has to be an integer"
payload["detail"]["preset"]["num"] = preset_num
if preset_type is not None:
assert isinstance(preset_type, str), "preset_type has to be a str"
payload["detail"]["preset"]["type"] = preset_type
if preset_snooze is not None:
assert isinstance(preset_snooze, bool), "preset_snooze has to be a bool"
payload["detail"]["preset"]["snooze"] = preset_snooze
assert resume_input is None, "resume_input is not compatible with playback_type preset"
return Clock.URI["SET_ALARM_SETTINGS"], payload
# end-of-class Clock
def _bool_to_str(value):
return str(value).lower()
if __name__ == "__main__":
raise SystemExit("aiomusiccast.pyamaha is a library module")