--- layout: template title: API - pyatv.conf permalink: /api//conf link_group: api ---

# Module `pyatv.conf`

Configuration used when connecting to a device.

A configuration describes a device, e.g. it's name, IP address and credentials. It is possible to manually create a configuration, but generally scanning for devices will provide configurations for you.

For a configuration to be usable ("ready") it must have either a `DMAP` or `MRP` configuration (or both), as connecting to plain `AirPlay` devices it not supported.

Expand source code

    """Configuration used when connecting to a device.

    A configuration describes a device, e.g. it's name, IP address and credentials. It is
    possible to manually create a configuration, but generally scanning for devices will
    provide configurations for you.

    For a configuration to be usable ("ready") it must have either a `DMAP` or `MRP`
    configuration (or both), as connecting to plain `AirPlay` devices it not supported.
    """
    from ipaddress import IPv4Address
    from typing import Dict, List, Mapping, Optional, Tuple, cast

    from pyatv import exceptions
    from pyatv.const import DeviceModel, OperatingSystem, Protocol
    from pyatv.interface import BaseService, DeviceInfo
    from pyatv.support.device_info import lookup_model, lookup_version

    class AppleTV:
        """Representation of an Apple TV configuration.

        An instance of this class represents a single device. A device can have
        several services depending on the protocols it supports, e.g. DMAP or
        AirPlay.
        """

        def __init__(
            self,
            address: IPv4Address,
            name: str,
            deep_sleep: bool = False,
            model: DeviceModel = DeviceModel.Unknown,
            properties: Optional[Mapping[str, str]] = None,
        ) -> None:
            """Initialize a new AppleTV."""
            self._address = address
            self._name = name
            self._deep_sleep = deep_sleep
            self._model = model
            self._services: Dict[Protocol, BaseService] = {}
            self._properties: Mapping[str, str] = properties or {}

        @property
        def address(self) -> IPv4Address:
            """IP address of device."""
            return self._address

        @property
        def name(self) -> str:
            """Name of device."""
            return self._name

        @property
        def deep_sleep(self) -> bool:
            """If device is in deep sleep."""
            return self._deep_sleep

        @property
        def ready(self) -> bool:
            """Return if configuration is ready, i.e. has a main service."""
            ready_protocols = set(list(Protocol))

            # Companion has no unique identifier so it's the only protocol that can't be
            # used independently for now
            ready_protocols.remove(Protocol.Companion)

            intersection = ready_protocols.intersection(self._services.keys())
            return len(intersection) > 0

        @property
        def identifier(self) -> Optional[str]:
            """Return the main identifier associated with this device."""
            for prot in [Protocol.MRP, Protocol.DMAP, Protocol.AirPlay, Protocol.RAOP]:
                service = self._services.get(prot)
                if service:
                    return service.identifier
            return None

        @property
        def all_identifiers(self) -> List[str]:
            """Return all unique identifiers for this device."""
            return [x.identifier for x in self.services if x.identifier is not None]

        def add_service(self, service: BaseService) -> None:
            """Add a new service.

            If the service already exists, it will be merged.
            """
            existing = self._services.get(service.protocol)
            if existing is not None:
                existing.merge(service)
            else:
                self._services[service.protocol] = service

        def get_service(self, protocol: Protocol) -> Optional[BaseService]:
            """Look up a service based on protocol.

            If a service with the specified protocol is not available, None is
            returned.
            """
            return self._services.get(protocol)

        @property
        def services(self) -> List[BaseService]:
            """Return all supported services."""
            return list(self._services.values())

        def main_service(self, protocol: Optional[Protocol] = None) -> BaseService:
            """Return suggested service used to establish connection."""
            protocols = (
                [protocol]
                if protocol is not None
                else [Protocol.MRP, Protocol.DMAP, Protocol.AirPlay, Protocol.RAOP]
            )

            for prot in protocols:
                service = self._services.get(prot)
                if service is not None:
                    return service

            raise exceptions.NoServiceError("no service to connect to")

        def set_credentials(self, protocol: Protocol, credentials: str) -> bool:
            """Set credentials for a protocol if it exists."""
            service = self.get_service(protocol)
            if service:
                service.credentials = credentials
                return True
            return False

        # TODO: The extraction should be generialized and moved somewhere else. It is
        # very hard to test rght now.
        @property
        def device_info(self) -> DeviceInfo:
            """Return general device information."""
            properties = self._all_properties()

            build: Optional[str] = properties.get("systembuildversion")
            version = properties.get("ov")
            if not version:
                version = properties.get("osvers", lookup_version(build))

            model_name: Optional[str] = properties.get("model", properties.get("am"))
            if model_name:
                model = lookup_model(model_name)
            else:
                model = self._model

            # MRP devices run tvOS (as far as we know now) as well as HomePods for
            # some reason
            if Protocol.MRP in self._services or model in [
                DeviceModel.HomePod,
                DeviceModel.HomePodMini,
            ]:
                os_type = OperatingSystem.TvOS
            elif Protocol.DMAP in self._services:
                os_type = OperatingSystem.Legacy
            elif model in [DeviceModel.AirPortExpress, DeviceModel.AirPortExpressGen2]:
                os_type = OperatingSystem.AirPortOS
            else:
                os_type = OperatingSystem.Unknown

            mac = properties.get("macaddress", properties.get("deviceid"))
            if mac:
                mac = mac.upper()

            # The waMA property comes from the _airport._tcp.local service, announced by
            # AirPort Expresses (used by the admin tool). It contains various information,
            # for instance MAC address and software version.
            wama = properties.get("wama")
            if wama:
                props: Mapping[str, str] = dict(
                    cast(Tuple[str, str], prop.split("=", maxsplit=1))
                    for prop in ("macaddress=" + wama).split(",")
                )
                if not mac:
                    mac = props["macaddress"].replace("-", ":").upper()
                version = props.get("syVs")

            return DeviceInfo(os_type, version, build, model, mac)

        def _all_properties(self) -> Mapping[str, str]:
            properties: Dict[str, str] = {}
            properties.update(self._properties)
            for service in self.services:
                properties.update(service.properties)
            return properties

        def __eq__(self, other) -> bool:
            """Compare instance with another instance."""
            if isinstance(other, self.__class__):
                return self.identifier == other.identifier
            return False

        def __str__(self) -> str:
            """Return a string representation of this object."""
            device_info = self.device_info
            services = "\n".join([" - {0}".format(s) for s in self._services.values()])
            identifiers = "\n".join([" - {0}".format(x) for x in self.all_identifiers])
            return (
                f"       Name: {self.name}\n"
                f"   Model/SW: {device_info}\n"
                f"    Address: {self.address}\n"
                f"        MAC: {self.device_info.mac}\n"
                f" Deep Sleep: {self.deep_sleep}\n"
                f"Identifiers:\n"
                f"{identifiers}\n"
                f"Services:\n"
                f"{services}"
            )

    # pylint: disable=too-few-public-methods
    class DmapService(BaseService):
        """Representation of a DMAP service."""

        def __init__(
            self,
            identifier: Optional[str],
            credentials: Optional[str],
            port: int = 3689,
            properties: Optional[Mapping[str, str]] = None,
        ) -> None:
            """Initialize a new DmapService."""
            super().__init__(
                identifier,
                Protocol.DMAP,
                port,
                properties,
            )
            self.credentials = credentials

    # pylint: disable=too-few-public-methods
    class MrpService(BaseService):
        """Representation of a MediaRemote Protocol (MRP) service."""

        def __init__(
            self,
            identifier: Optional[str],
            port: int,
            credentials: Optional[str] = None,
            properties: Optional[Mapping[str, str]] = None,
        ) -> None:
            """Initialize a new MrpService."""
            super().__init__(identifier, Protocol.MRP, port, properties)
            self.credentials = credentials

    # pylint: disable=too-few-public-methods
    class AirPlayService(BaseService):
        """Representation of an AirPlay service."""

        def __init__(
            self,
            identifier: Optional[str],
            port: int = 7000,
            credentials: Optional[str] = None,
            properties: Optional[Mapping[str, str]] = None,
        ) -> None:
            """Initialize a new AirPlayService."""
            super().__init__(identifier, Protocol.AirPlay, port, properties)
            self.credentials = credentials

    # pylint: disable=too-few-public-methods
    class CompanionService(BaseService):
        """Representation of a Companion link service."""

        def __init__(
            self,
            port: int,
            credentials: Optional[str] = None,
            properties: Optional[Mapping[str, str]] = None,
        ) -> None:
            """Initialize a new CompaniomService."""
            super().__init__(None, Protocol.Companion, port, properties)
            self.credentials = credentials

    # pylint: disable=too-few-public-methods
    class RaopService(BaseService):
        """Representation of an RAOP service."""

        def __init__(
            self,
            identifier: Optional[str],
            port: int = 7000,
            credentials: Optional[str] = None,
            properties: Optional[Mapping[str, str]] = None,
        ) -> None:
            """Initialize a new RaopService."""
            super().__init__(identifier, Protocol.RAOP, port, properties)
            self.credentials = credentials

## Classes

`class AirPlayService (identifier: Optional[str], port: int = 7000, credentials: Optional[str] = None, properties: Optional[Mapping[str, str]] = None)`

  Representation of an AirPlay service.

  Initialize a new AirPlayService.

  Expand source code

      class AirPlayService(BaseService):
          """Representation of an AirPlay service."""

          def __init__(
              self,
              identifier: Optional[str],
              port: int = 7000,
              credentials: Optional[str] = None,
              properties: Optional[Mapping[str, str]] = None,
          ) -> None:
              """Initialize a new AirPlayService."""
              super().__init__(identifier, Protocol.AirPlay, port, properties)
              self.credentials = credentials

  ### Ancestors

  - BaseService

  ### Inherited members

  - `BaseService`:
    - `identifier`
    - `merge`
`class AppleTV (address: ipaddress.IPv4Address, name: str, deep_sleep: bool = False, model: DeviceModel = DeviceModel.Unknown, properties: Optional[Mapping[str, str]] = None)`

  Representation of an Apple TV configuration.

  An instance of this class represents a single device. A device can have several services depending on the protocols it supports, e.g. DMAP or AirPlay.

  Initialize a new AppleTV.

  Expand source code

      class AppleTV:
          """Representation of an Apple TV configuration.

          An instance of this class represents a single device. A device can have
          several services depending on the protocols it supports, e.g. DMAP or
          AirPlay.
          """

          def __init__(
              self,
              address: IPv4Address,
              name: str,
              deep_sleep: bool = False,
              model: DeviceModel = DeviceModel.Unknown,
              properties: Optional[Mapping[str, str]] = None,
          ) -> None:
              """Initialize a new AppleTV."""
              self._address = address
              self._name = name
              self._deep_sleep = deep_sleep
              self._model = model
              self._services: Dict[Protocol, BaseService] = {}
              self._properties: Mapping[str, str] = properties or {}

          @property
          def address(self) -> IPv4Address:
              """IP address of device."""
              return self._address

          @property
          def name(self) -> str:
              """Name of device."""
              return self._name

          @property
          def deep_sleep(self) -> bool:
              """If device is in deep sleep."""
              return self._deep_sleep

          @property
          def ready(self) -> bool:
              """Return if configuration is ready, i.e. has a main service."""
              ready_protocols = set(list(Protocol))

              # Companion has no unique identifier so it's the only protocol that can't be
              # used independently for now
              ready_protocols.remove(Protocol.Companion)

              intersection = ready_protocols.intersection(self._services.keys())
              return len(intersection) > 0

          @property
          def identifier(self) -> Optional[str]:
              """Return the main identifier associated with this device."""
              for prot in [Protocol.MRP, Protocol.DMAP, Protocol.AirPlay, Protocol.RAOP]:
                  service = self._services.get(prot)
                  if service:
                      return service.identifier
              return None

          @property
          def all_identifiers(self) -> List[str]:
              """Return all unique identifiers for this device."""
              return [x.identifier for x in self.services if x.identifier is not None]

          def add_service(self, service: BaseService) -> None:
              """Add a new service.

              If the service already exists, it will be merged.
              """
              existing = self._services.get(service.protocol)
              if existing is not None:
                  existing.merge(service)
              else:
                  self._services[service.protocol] = service

          def get_service(self, protocol: Protocol) -> Optional[BaseService]:
              """Look up a service based on protocol.

              If a service with the specified protocol is not available, None is
              returned.
              """
              return self._services.get(protocol)

          @property
          def services(self) -> List[BaseService]:
              """Return all supported services."""
              return list(self._services.values())

          def main_service(self, protocol: Optional[Protocol] = None) -> BaseService:
              """Return suggested service used to establish connection."""
              protocols = (
                  [protocol]
                  if protocol is not None
                  else [Protocol.MRP, Protocol.DMAP, Protocol.AirPlay, Protocol.RAOP]
              )

              for prot in protocols:
                  service = self._services.get(prot)
                  if service is not None:
                      return service

              raise exceptions.NoServiceError("no service to connect to")

          def set_credentials(self, protocol: Protocol, credentials: str) -> bool:
              """Set credentials for a protocol if it exists."""
              service = self.get_service(protocol)
              if service:
                  service.credentials = credentials
                  return True
              return False

          # TODO: The extraction should be generialized and moved somewhere else. It is
          # very hard to test rght now.
          @property
          def device_info(self) -> DeviceInfo:
              """Return general device information."""
              properties = self._all_properties()

              build: Optional[str] = properties.get("systembuildversion")
              version = properties.get("ov")
              if not version:
                  version = properties.get("osvers", lookup_version(build))

              model_name: Optional[str] = properties.get("model", properties.get("am"))
              if model_name:
                  model = lookup_model(model_name)
              else:
                  model = self._model

              # MRP devices run tvOS (as far as we know now) as well as HomePods for
              # some reason
              if Protocol.MRP in self._services or model in [
                  DeviceModel.HomePod,
                  DeviceModel.HomePodMini,
              ]:
                  os_type = OperatingSystem.TvOS
              elif Protocol.DMAP in self._services:
                  os_type = OperatingSystem.Legacy
              elif model in [DeviceModel.AirPortExpress, DeviceModel.AirPortExpressGen2]:
                  os_type = OperatingSystem.AirPortOS
              else:
                  os_type = OperatingSystem.Unknown

              mac = properties.get("macaddress", properties.get("deviceid"))
              if mac:
                  mac = mac.upper()

              # The waMA property comes from the _airport._tcp.local service, announced by
              # AirPort Expresses (used by the admin tool). It contains various information,
              # for instance MAC address and software version.
              wama = properties.get("wama")
              if wama:
                  props: Mapping[str, str] = dict(
                      cast(Tuple[str, str], prop.split("=", maxsplit=1))
                      for prop in ("macaddress=" + wama).split(",")
                  )
                  if not mac:
                      mac = props["macaddress"].replace("-", ":").upper()
                  version = props.get("syVs")

              return DeviceInfo(os_type, version, build, model, mac)

          def _all_properties(self) -> Mapping[str, str]:
              properties: Dict[str, str] = {}
              properties.update(self._properties)
              for service in self.services:
                  properties.update(service.properties)
              return properties

          def __eq__(self, other) -> bool:
              """Compare instance with another instance."""
              if isinstance(other, self.__class__):
                  return self.identifier == other.identifier
              return False

          def __str__(self) -> str:
              """Return a string representation of this object."""
              device_info = self.device_info
              services = "\n".join([" - {0}".format(s) for s in self._services.values()])
              identifiers = "\n".join([" - {0}".format(x) for x in self.all_identifiers])
              return (
                  f"       Name: {self.name}\n"
                  f"   Model/SW: {device_info}\n"
                  f"    Address: {self.address}\n"
                  f"        MAC: {self.device_info.mac}\n"
                  f" Deep Sleep: {self.deep_sleep}\n"
                  f"Identifiers:\n"
                  f"{identifiers}\n"
                  f"Services:\n"
                  f"{services}"
              )

  ### Instance variables

  `var address -> ipaddress.IPv4Address`

    IP address of device.

    Expand source code

        @property
        def address(self) -> IPv4Address:
            """IP address of device."""
            return self._address

  `var all_identifiers -> List[str]`

    Return all unique identifiers for this device.

    Expand source code

        @property
        def all_identifiers(self) -> List[str]:
            """Return all unique identifiers for this device."""
            return [x.identifier for x in self.services if x.identifier is not None]

  `var deep_sleep -> bool`

    If device is in deep sleep.

    Expand source code

        @property
        def deep_sleep(self) -> bool:
            """If device is in deep sleep."""
            return self._deep_sleep

  `var device_info -> DeviceInfo`

    Return general device information.

    Expand source code

        @property
        def device_info(self) -> DeviceInfo:
            """Return general device information."""
            properties = self._all_properties()

            build: Optional[str] = properties.get("systembuildversion")
            version = properties.get("ov")
            if not version:
                version = properties.get("osvers", lookup_version(build))

            model_name: Optional[str] = properties.get("model", properties.get("am"))
            if model_name:
                model = lookup_model(model_name)
            else:
                model = self._model

            # MRP devices run tvOS (as far as we know now) as well as HomePods for
            # some reason
            if Protocol.MRP in self._services or model in [
                DeviceModel.HomePod,
                DeviceModel.HomePodMini,
            ]:
                os_type = OperatingSystem.TvOS
            elif Protocol.DMAP in self._services:
                os_type = OperatingSystem.Legacy
            elif model in [DeviceModel.AirPortExpress, DeviceModel.AirPortExpressGen2]:
                os_type = OperatingSystem.AirPortOS
            else:
                os_type = OperatingSystem.Unknown

            mac = properties.get("macaddress", properties.get("deviceid"))
            if mac:
                mac = mac.upper()

            # The waMA property comes from the _airport._tcp.local service, announced by
            # AirPort Expresses (used by the admin tool). It contains various information,
            # for instance MAC address and software version.
            wama = properties.get("wama")
            if wama:
                props: Mapping[str, str] = dict(
                    cast(Tuple[str, str], prop.split("=", maxsplit=1))
                    for prop in ("macaddress=" + wama).split(",")
                )
                if not mac:
                    mac = props["macaddress"].replace("-", ":").upper()
                version = props.get("syVs")

            return DeviceInfo(os_type, version, build, model, mac)

  `var identifier -> Optional[str]`

    Return the main identifier associated with this device.

    Expand source code

        @property
        def identifier(self) -> Optional[str]:
            """Return the main identifier associated with this device."""
            for prot in [Protocol.MRP, Protocol.DMAP, Protocol.AirPlay, Protocol.RAOP]:
                service = self._services.get(prot)
                if service:
                    return service.identifier
            return None

  `var name -> str`

    Name of device.

    Expand source code

        @property
        def name(self) -> str:
            """Name of device."""
            return self._name

  `var ready -> bool`

    Return if configuration is ready, i.e. has a main service.

    Expand source code

        @property
        def ready(self) -> bool:
            """Return if configuration is ready, i.e. has a main service."""
            ready_protocols = set(list(Protocol))

            # Companion has no unique identifier so it's the only protocol that can't be
            # used independently for now
            ready_protocols.remove(Protocol.Companion)

            intersection = ready_protocols.intersection(self._services.keys())
            return len(intersection) > 0

  `var services -> List[BaseService]`

    Return all supported services.

    Expand source code

        @property
        def services(self) -> List[BaseService]:
            """Return all supported services."""
            return list(self._services.values())

  ### Methods

  `def add_service(self, service: BaseService) -> NoneType`

    Add a new service.

    If the service already exists, it will be merged.

    Expand source code

        def add_service(self, service: BaseService) -> None:
            """Add a new service.

            If the service already exists, it will be merged.
            """
            existing = self._services.get(service.protocol)
            if existing is not None:
                existing.merge(service)
            else:
                self._services[service.protocol] = service

  `def get_service(self, protocol: Protocol) -> Optional[BaseService]`

    Look up a service based on protocol.

    If a service with the specified protocol is not available, None is returned.

    Expand source code

        def get_service(self, protocol: Protocol) -> Optional[BaseService]:
            """Look up a service based on protocol.

            If a service with the specified protocol is not available, None is
            returned.
            """
            return self._services.get(protocol)

  `def main_service(self, protocol: Optional[Protocol] = None) -> BaseService`

    Return suggested service used to establish connection.

    Expand source code

        def main_service(self, protocol: Optional[Protocol] = None) -> BaseService:
            """Return suggested service used to establish connection."""
            protocols = (
                [protocol]
                if protocol is not None
                else [Protocol.MRP, Protocol.DMAP, Protocol.AirPlay, Protocol.RAOP]
            )

            for prot in protocols:
                service = self._services.get(prot)
                if service is not None:
                    return service

            raise exceptions.NoServiceError("no service to connect to")

  `def set_credentials(self, protocol: Protocol, credentials: str) -> bool`

    Set credentials for a protocol if it exists.

    Expand source code

        def set_credentials(self, protocol: Protocol, credentials: str) -> bool:
            """Set credentials for a protocol if it exists."""
            service = self.get_service(protocol)
            if service:
                service.credentials = credentials
                return True
            return False

`class CompanionService (port: int, credentials: Optional[str] = None, properties: Optional[Mapping[str, str]] = None)`

  Representation of a Companion link service.

  Initialize a new CompaniomService.

  Expand source code

      class CompanionService(BaseService):
          """Representation of a Companion link service."""

          def __init__(
              self,
              port: int,
              credentials: Optional[str] = None,
              properties: Optional[Mapping[str, str]] = None,
          ) -> None:
              """Initialize a new CompaniomService."""
              super().__init__(None, Protocol.Companion, port, properties)
              self.credentials = credentials

  ### Ancestors

  - BaseService

  ### Inherited members

  - `BaseService`:
    - `identifier`
    - `merge`
`class DmapService (identifier: Optional[str], credentials: Optional[str], port: int = 3689, properties: Optional[Mapping[str, str]] = None)`

  Representation of a DMAP service.

  Initialize a new DmapService.

  Expand source code

      class DmapService(BaseService):
          """Representation of a DMAP service."""

          def __init__(
              self,
              identifier: Optional[str],
              credentials: Optional[str],
              port: int = 3689,
              properties: Optional[Mapping[str, str]] = None,
          ) -> None:
              """Initialize a new DmapService."""
              super().__init__(
                  identifier,
                  Protocol.DMAP,
                  port,
                  properties,
              )
              self.credentials = credentials

  ### Ancestors

  - BaseService

  ### Inherited members

  - `BaseService`:
    - `identifier`
    - `merge`
`class MrpService (identifier: Optional[str], port: int, credentials: Optional[str] = None, properties: Optional[Mapping[str, str]] = None)`

  Representation of a MediaRemote Protocol (MRP) service.

  Initialize a new MrpService.

  Expand source code

      class MrpService(BaseService):
          """Representation of a MediaRemote Protocol (MRP) service."""

          def __init__(
              self,
              identifier: Optional[str],
              port: int,
              credentials: Optional[str] = None,
              properties: Optional[Mapping[str, str]] = None,
          ) -> None:
              """Initialize a new MrpService."""
              super().__init__(identifier, Protocol.MRP, port, properties)
              self.credentials = credentials

  ### Ancestors

  - BaseService

  ### Inherited members

  - `BaseService`:
    - `identifier`
    - `merge`
`class RaopService (identifier: Optional[str], port: int = 7000, credentials: Optional[str] = None, properties: Optional[Mapping[str, str]] = None)`

  Representation of an RAOP service.

  Initialize a new RaopService.

  Expand source code

      class RaopService(BaseService):
          """Representation of an RAOP service."""

          def __init__(
              self,
              identifier: Optional[str],
              port: int = 7000,
              credentials: Optional[str] = None,
              properties: Optional[Mapping[str, str]] = None,
          ) -> None:
              """Initialize a new RaopService."""
              super().__init__(identifier, Protocol.RAOP, port, properties)
              self.credentials = credentials

  ### Ancestors

  - BaseService

  ### Inherited members

  - `BaseService`:
    - `identifier`
    - `merge`