Repository: postlund/pyatv
Pull request #1286 (2021-09-03 20:41:06)

mrp: Extract build number from DEVICE_INFO
Relates to #1279

<a href="https://gitpod.io/#https://github.com/postlund/pyatv/pull/1286"><img src="https://gitpod.io/button/open-in-gitpod.svg"/></a>



codecov[bot]: # [Codecov](https://codecov.io/gh/postlund/pyatv/pull/1286?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) Report
> Merging [#1286](https://codecov.io/gh/postlund/pyatv/pull/1286?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) (32801ae) into [master](https://codecov.io/gh/postlund/pyatv/commit/a548587e00f2284f037738c0eaf9473b9a096052?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) (a548587) will **decrease** coverage by `0.36%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/postlund/pyatv/pull/1286/graphs/tree.svg?width=650&height=150&src=pr&token=wb6azJjRSY&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl)](https://codecov.io/gh/postlund/pyatv/pull/1286?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl)

```diff
@@            Coverage Diff             @@
##           master    #1286      +/-   ##
==========================================
- Coverage   90.50%   90.14%   -0.37%     
==========================================
  Files          72       72              
  Lines        7024     7020       -4     
==========================================
- Hits         6357     6328      -29     
- Misses        667      692      +25     
```


| [Impacted Files](https://codecov.io/gh/postlund/pyatv/pull/1286?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) | Coverage Δ | |
|---|---|---|
| [pyatv/mrp/\_\_init\_\_.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvbXJwL19faW5pdF9fLnB5) | `95.90% <100.00%> (+0.04%)` | :arrow_up: |
| [pyatv/raop/raop.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvcmFvcC9yYW9wLnB5) | `87.76% <0.00%> (-3.53%)` | :arrow_down: |
| [pyatv/raop/audio\_source.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvcmFvcC9hdWRpb19zb3VyY2UucHk=) | `86.00% <0.00%> (-2.75%)` | :arrow_down: |
| [pyatv/mrp/connection.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvbXJwL2Nvbm5lY3Rpb24ucHk=) | `83.48% <0.00%> (-1.84%)` | :arrow_down: |
| [pyatv/mrp/protocol.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvbXJwL3Byb3RvY29sLnB5) | `91.89% <0.00%> (-1.40%)` | :arrow_down: |
| [pyatv/support/mdns.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvc3VwcG9ydC9tZG5zLnB5) | `90.14% <0.00%> (-0.71%)` | :arrow_down: |
| [pyatv/support/http.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvc3VwcG9ydC9odHRwLnB5) | `93.09% <0.00%> (-0.66%)` | :arrow_down: |
| [pyatv/airplay/player.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvYWlycGxheS9wbGF5ZXIucHk=) | `95.83% <0.00%> (-0.09%)` | :arrow_down: |
| [pyatv/dmap/\_\_init\_\_.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvZG1hcC9fX2luaXRfXy5weQ==) | `97.25% <0.00%> (-0.01%)` | :arrow_down: |
| [pyatv/core/protocol.py](https://codecov.io/gh/postlund/pyatv/pull/1286/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvY29yZS9wcm90b2NvbC5weQ==) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/postlund/pyatv/pull/1286?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/postlund/pyatv/pull/1286?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl). Last update [a548587...32801ae](https://codecov.io/gh/postlund/pyatv/pull/1286?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl).


Patch:
diff --git a/docs/documentation/concepts.md b/docs/documentation/concepts.md
index 28eacf0b..16ce96d5 100644
--- a/docs/documentation/concepts.md
+++ b/docs/documentation/concepts.md
@@ -46,9 +46,16 @@ that protocol.
 
 There are methods in {% include api i="conf.AppleTV" %} to add and retrieve services. When using {% include api i="pyatv.scan" %}, all discovered protocols will be added automatically and all relevant information (e.g. which port is used) is stored as well. When manually creating a configuration, you have to provide this information yourself (e.g. via  {% include api i="conf.DmapService" %} for `DMAP`, and so on).
 
-## Device Metadata
-
-In some cases it's interesting to show some more user friendly metadata, like which operating system and version a device runs.  There's no "good" way of getting this information, but pyatv will pull bits and pieces from the metadata received during scanning to make a good guess. Information from both the main protocol (`DMAP` or `MRP`) and `AirPlay` are used, so if you have disabled `AirPlay`, you will lose some metadata (e.g. tvOS version).
+## Device Information
+
+It is possible to extract some general information about a device,
+for instance which operating system it runs or its MAC address.
+There's generally no "good" way of obtaining this information, but pyatv
+will pull bits and pieces from the metadata received during scanning to
+make a good guess. It can also extract additional information after
+connecting to the device, since some protocols provide information once
+a connection has been made. The amount of device information available
+can because of that differ when scanning or connecting.
 
 ## Identifiers
 
diff --git a/docs/documentation/supported_features.md b/docs/documentation/supported_features.md
index 5a73e3f6..a1107487 100644
--- a/docs/documentation/supported_features.md
+++ b/docs/documentation/supported_features.md
@@ -29,7 +29,7 @@ This is the general feature list provided by the external interface.
 | --------------------------------------------------------------- | --------- |
 | Automatic discovery of devices (zeroconf/Bonjour)               | [Concept](../concepts/#scanning), [Doc](../../development/scan_pair_and_connect/#scanning), {% include api i="pyatv.scan" %}
 | --------------------------------------------------------------- | --------- |
-| Device Metadata (e.g. operating system and version)             | [Concept](../concepts/#device-metadata), [Doc](../../development/device_info), {% include api i="interface.DeviceInfo" %}
+| Device Metadata (e.g. operating system and version)             | [Concept](../concepts/#device-information), [Doc](../../development/device_info), {% include api i="interface.DeviceInfo" %}
 | --------------------------------------------------------------- | --------- |
 | Push updates                                                    | [Concept](../concepts/#metadata-and-push-updates), [Doc](../../development/listeners/#push-updates), {% include api i="interface.PushUpdater" %}
 | --------------------------------------------------------------- | --------- |
@@ -73,7 +73,7 @@ Some features are provided generally by pyatv and not bound to any particular pr
 *core* features include:
 
 * Automatic service discovery with zeroconf ([Scanning](../concepts#scanning))
-* Device information via information from service discovery ([Device Metadata](../concepts#device-metadata))
+* Device information via information from service discovery ([Device Metadata](../concepts#device-information))
 * Callbacks when connection is lost ([Device Updates](../../development/listeners#device-updates))
 
 # Protocols per Device
diff --git a/pyatv/mrp/__init__.py b/pyatv/mrp/__init__.py
index 05a6e55c..607be5f9 100644
--- a/pyatv/mrp/__init__.py
+++ b/pyatv/mrp/__init__.py
@@ -770,7 +770,14 @@ def create_with_connection(  # pylint: disable=too-many-locals
         return set()
 
     def _device_info() -> Dict[str, Any]:
-        return device_info(service.properties)
+        devinfo = device_info(service.properties)
+
+        # Extract build number from DEVICE_INFO_MESSAGE from device
+        if protocol.device_info:
+            build_number = protocol.device_info.inner().systemBuildVersion
+            devinfo[DeviceInfo.BUILD_NUMBER] = build_number
+
+        return devinfo
 
     # Features managed by this protocol
     features = set(


Test patch:
diff --git a/tests/fake_device/mrp.py b/tests/fake_device/mrp.py
index 2078a002..35dc83c1 100644
--- a/tests/fake_device/mrp.py
+++ b/tests/fake_device/mrp.py
@@ -70,6 +70,9 @@ PLAYER_IDENTIFIER = "com.github.postlund.pyatv"
 DEFAULT_PLAYER_ID = "MediaRemote-DefaultPlayer"
 DEFAULT_PLAYER_NAME = "Default Player"
 
+BUILD_NUMBER = "18M60"
+OS_VERSION = "14.7"  # Must match BUILD_NUMBER (take from device_info.py)
+
 
 def _fill_item(item, metadata):
     if metadata.identifier:
@@ -342,6 +345,7 @@ class FakeMrpService(MrpServerAuth, asyncio.Protocol):
         resp = messages.device_information(DEVICE_NAME, "1234", update=update)
         if identifier:
             resp.identifier = identifier
+        resp.inner().systemBuildVersion = BUILD_NUMBER
         resp.inner().logicalDeviceCount = 1 if self.state.powered_on else 0
         self.send_to_client(resp)
 
diff --git a/tests/mrp/test_mrp_functional.py b/tests/mrp/test_mrp_functional.py
index 0e3d3376..7cf87314 100644
--- a/tests/mrp/test_mrp_functional.py
+++ b/tests/mrp/test_mrp_functional.py
@@ -25,7 +25,7 @@ from pyatv.mrp.protobuf import CommandInfo_pb2
 from tests import common_functional_tests
 from tests.fake_device import FakeAppleTV
 from tests.fake_device.airplay import DEVICE_CREDENTIALS
-from tests.fake_device.mrp import APP_NAME, PLAYER_IDENTIFIER
+from tests.fake_device.mrp import APP_NAME, BUILD_NUMBER, OS_VERSION, PLAYER_IDENTIFIER
 from tests.utils import faketime, stub_sleep, until
 
 _LOGGER = logging.getLogger(__name__)
@@ -291,6 +291,8 @@ class MRPFunctionalTest(common_functional_tests.CommonFunctionalTests):
     @unittest_run_loop
     async def test_basic_device_info(self):
         self.assertEqual(self.atv.device_info.operating_system, OperatingSystem.TvOS)
+        self.assertEqual(self.atv.device_info.build_number, BUILD_NUMBER)
+        self.assertEqual(self.atv.device_info.version, OS_VERSION)
 
     @unittest_run_loop
     async def test_always_available_features(self):