Repository: postlund/pyatv
Pull request #2539 (2024-11-03 19:04:37)

core: Update device info
Add Apple TV 1, update some tables and fix OS bug.

Relates to #2517

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/postlund/pyatv/pull/2539?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) Report
Attention: Patch coverage is `93.33333%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 88.90%. Comparing base [(`658fd06`)](https://app.codecov.io/gh/postlund/pyatv/commit/658fd0626a94cdd64af47354a58e4cf2588f7176?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) to head [(`d925073`)](https://app.codecov.io/gh/postlund/pyatv/commit/d9250736160ac5ba83ec8c7bdf4900b53295efb5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl).

| [Files with missing lines](https://app.codecov.io/gh/postlund/pyatv/pull/2539?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) | Patch % | Lines |
|---|---|---|
| [pyatv/support/device\_info.py](https://app.codecov.io/gh/postlund/pyatv/pull/2539?src=pr&el=tree&filepath=pyatv%2Fsupport%2Fdevice_info.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl#diff-cHlhdHYvc3VwcG9ydC9kZXZpY2VfaW5mby5weQ==) | 92.30% | [1 Missing :warning: ](https://app.codecov.io/gh/postlund/pyatv/pull/2539?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #2539      +/-   ##
==========================================
- Coverage   88.92%   88.90%   -0.03%     
==========================================
  Files         168      168              
  Lines       11541    11553      +12     
==========================================
+ Hits        10263    10271       +8     
- Misses       1278     1282       +4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/postlund/pyatv/pull/2539?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Pierre+St%C3%A5hl).


Patch:
diff --git a/docs/api/pyatv.const.html b/docs/api/pyatv.const.html
index 7ae5c65f..1fca6d19 100644
--- a/docs/api/pyatv.const.html
+++ b/docs/api/pyatv.const.html
@@ -24,6 +24,7 @@ link_group: api
 <li><code><a title="pyatv.const.DeviceModel.AirPortExpressGen2" href="#pyatv.const.DeviceModel.AirPortExpressGen2">AirPortExpressGen2</a></code></li>
 <li><code><a title="pyatv.const.DeviceModel.AppleTV4KGen2" href="#pyatv.const.DeviceModel.AppleTV4KGen2">AppleTV4KGen2</a></code></li>
 <li><code><a title="pyatv.const.DeviceModel.AppleTV4KGen3" href="#pyatv.const.DeviceModel.AppleTV4KGen3">AppleTV4KGen3</a></code></li>
+<li><code><a title="pyatv.const.DeviceModel.AppleTVGen1" href="#pyatv.const.DeviceModel.AppleTVGen1">AppleTVGen1</a></code></li>
 <li><code><a title="pyatv.const.DeviceModel.Gen2" href="#pyatv.const.DeviceModel.Gen2">Gen2</a></code></li>
 <li><code><a title="pyatv.const.DeviceModel.Gen3" href="#pyatv.const.DeviceModel.Gen3">Gen3</a></code></li>
 <li><code><a title="pyatv.const.DeviceModel.Gen4" href="#pyatv.const.DeviceModel.Gen4">Gen4</a></code></li>
@@ -224,7 +225,7 @@ link_group: api
 </header>
 <section id="section-intro">
 <p>Constants used in the public API.</p>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L1-L457" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L1-L460" class="git-link">Browse git</a></div>
 </section>
 <section>
 </section>
@@ -243,7 +244,7 @@ link_group: api
 <section class="desc"><p>Hardware device model.</p>
 <p>Gen2-Gen4K are Apple TV model names and will be renamed to AppleTVGenX in the
 future.</p></section>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L150-L194" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L150-L197" class="git-link">Browse git</a></div>
 <h3>Ancestors</h3>
 <ul class="hlist">
 <li>enum.Enum</li>
@@ -266,6 +267,10 @@ future.</p></section>
 <dd>
 <section class="desc"><p>Device model is seventh generation Apple TV (Apple TV 4K gen 3).</p></section>
 </dd>
+<dt id="pyatv.const.DeviceModel.AppleTVGen1"><code class="name">var <span class="ident">AppleTVGen1</span> = 13</code></dt>
+<dd>
+<section class="desc"><p>Device model is first generation Apple TV.</p></section>
+</dd>
 <dt id="pyatv.const.DeviceModel.Gen2"><code class="name">var <span class="ident">Gen2</span> = 1</code></dt>
 <dd>
 <section class="desc"><p>Device model is second generation Apple TV (Apple TV 2).</p></section>
@@ -349,7 +354,7 @@ future.</p></section>
 </code></dt>
 <dd>
 <section class="desc"><p>All supported features.</p></section>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L249-L448" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L252-L451" class="git-link">Browse git</a></div>
 <h3>Ancestors</h3>
 <ul class="hlist">
 <li>enum.Enum</li>
@@ -628,7 +633,7 @@ future.</p></section>
 </code></dt>
 <dd>
 <section class="desc"><p>State of a particular feature.</p></section>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L229-L245" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L232-L248" class="git-link">Browse git</a></div>
 <h3>Ancestors</h3>
 <ul class="hlist">
 <li>enum.Enum</li>
@@ -660,7 +665,7 @@ future.</p></section>
 </code></dt>
 <dd>
 <section class="desc"><p>Type of input when pressing a button.</p></section>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L197-L207" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L200-L210" class="git-link">Browse git</a></div>
 <h3>Ancestors</h3>
 <ul class="hlist">
 <li>enum.Enum</li>
@@ -785,7 +790,7 @@ is Apple TV Software (pre-tvOS).</p></section>
 </code></dt>
 <dd>
 <section class="desc"><p>Pairing requirement for a service.</p></section>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L210-L226" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L213-L229" class="git-link">Browse git</a></div>
 <h3>Ancestors</h3>
 <ul class="hlist">
 <li>enum.Enum</li>
@@ -936,7 +941,7 @@ is Apple TV Software (pre-tvOS).</p></section>
 </code></dt>
 <dd>
 <section class="desc"><p>Touch action constants.</p></section>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L451-L457" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/const.py#L454-L460" class="git-link">Browse git</a></div>
 <h3>Ancestors</h3>
 <ul class="hlist">
 <li>enum.Enum</li>
diff --git a/docs/api/pyatv.convert.html b/docs/api/pyatv.convert.html
index a1f43412..cce1f608 100644
--- a/docs/api/pyatv.convert.html
+++ b/docs/api/pyatv.convert.html
@@ -33,7 +33,7 @@ link_group: api
 </header>
 <section id="section-intro">
 <p>Various types of extraction and conversion functions.</p>
-<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/convert.py#L1-L80" class="git-link">Browse git</a></div>
+<div class="git-link-div"><a href="https://github.com/postlund/pyatv/blob/master/pyatv/convert.py#L1-L81" class="git-link">Browse git</a></div>
 </section>
 <section>
 </section>
diff --git a/pyatv/const.py b/pyatv/const.py
index c10383c7..7bf7b0ce 100644
--- a/pyatv/const.py
+++ b/pyatv/const.py
@@ -193,6 +193,9 @@ class DeviceModel(Enum):
     HomePodGen2 = 12
     """Device model is HomePod (second generation)."""
 
+    AppleTVGen1 = 13
+    """Device model is first generation Apple TV."""
+
 
 class InputAction(Enum):
     """Type of input when pressing a button."""
diff --git a/pyatv/convert.py b/pyatv/convert.py
index f467bec7..8e41a9fe 100644
--- a/pyatv/convert.py
+++ b/pyatv/convert.py
@@ -65,6 +65,7 @@ def protocol_str(protocol: Protocol) -> str:
 def model_str(device_model: DeviceModel) -> str:
     """Convert device model to string."""
     return {
+        DeviceModel.AppleTVGen1: "Apple TV 1",
         DeviceModel.Gen2: "Apple TV 2",
         DeviceModel.Gen3: "Apple TV 3",
         DeviceModel.Gen4: "Apple TV 4",
diff --git a/pyatv/support/device_info.py b/pyatv/support/device_info.py
index 23284189..f5369c80 100644
--- a/pyatv/support/device_info.py
+++ b/pyatv/support/device_info.py
@@ -1,13 +1,14 @@
 """Lookup methods for device data."""
 
 import re
-from typing import Dict, Optional
+from typing import Dict, Optional, Union
 
 from pyatv.const import DeviceModel, OperatingSystem
 
 _MODEL_LIST: Dict[str, DeviceModel] = {
     "AirPort4,107": DeviceModel.AirPortExpress,
     "AirPort10,115": DeviceModel.AirPortExpressGen2,
+    "AppleTV1,1": DeviceModel.AppleTVGen1,
     "AppleTV2,1": DeviceModel.Gen2,
     "AppleTV3,1": DeviceModel.Gen3,
     "AppleTV3,2": DeviceModel.Gen3,
@@ -18,6 +19,7 @@ _MODEL_LIST: Dict[str, DeviceModel] = {
     "AudioAccessory1,1": DeviceModel.HomePod,
     "AudioAccessory1,2": DeviceModel.HomePod,
     "AudioAccessory5,1": DeviceModel.HomePodMini,
+    "AudioAccessorySingle5,1": DeviceModel.HomePodMini,
     "AudioAccessory6,1": DeviceModel.HomePodGen2,
 }
 
@@ -72,6 +74,18 @@ _VERSION_LIST: Dict[str, str] = {
     "20L497": "16.4",
     "20L498": "16.4.1",
     "20L563": "16.5",
+    "20M73": "16.6",
+    "22J354": "17.0",
+    "21K69": "17.1",
+    "21K365": "17.2",
+    "21K646": "17.3",
+    "21L227": "17.4",
+    "21L569": "17.5",
+    "21L580": "17.5.1",
+    "21M71": "17.6",
+    "21M80": "17.6.1",
+    "22J357": "18.0",
+    "22J580": "18.1",
 }
 
 _OS_IDENTIFIER_FORMATS = [
@@ -113,14 +127,37 @@ def lookup_version(build: Optional[str]) -> Optional[str]:
     return None
 
 
-def lookup_os(identifier: str) -> OperatingSystem:
+def lookup_os(id_or_model: Union[str, DeviceModel]) -> OperatingSystem:
     """Lookup operating system based on identifier.
 
-    An identifier has the format similar to "MacbookAir10,1". Only
-    macOS is supported here.
+    An identifier has the format similar to "MacbookAir10,1" or
+    a DeviceModel.
     """
-    return (
-        OperatingSystem.MacOS
-        if any(re.match(os_format, identifier) for os_format in _OS_IDENTIFIER_FORMATS)
-        else OperatingSystem.Unknown
-    )
+    if isinstance(id_or_model, str):
+        return (
+            OperatingSystem.MacOS
+            if any(
+                re.match(os_format, id_or_model) for os_format in _OS_IDENTIFIER_FORMATS
+            )
+            else OperatingSystem.Unknown
+        )
+
+    if id_or_model in [DeviceModel.AirPortExpress, DeviceModel.AirPortExpressGen2]:
+        return OperatingSystem.AirPortOS
+    if id_or_model in [
+        DeviceModel.HomePod,
+        DeviceModel.HomePodMini,
+        DeviceModel.HomePodGen2,
+    ]:
+        return OperatingSystem.TvOS
+    if id_or_model in [DeviceModel.AppleTVGen1, DeviceModel.Gen2, DeviceModel.Gen3]:
+        return OperatingSystem.Legacy
+    if id_or_model in [
+        DeviceModel.Gen4,
+        DeviceModel.Gen4K,
+        DeviceModel.AppleTV4KGen2,
+        DeviceModel.AppleTV4KGen3,
+    ]:
+        return OperatingSystem.TvOS
+
+    return OperatingSystem.Unknown


Test patch:
diff --git a/tests/support/test_device_info.py b/tests/support/test_device_info.py
index 4f0b1da3..4ea63e5e 100644
--- a/tests/support/test_device_info.py
+++ b/tests/support/test_device_info.py
@@ -49,7 +49,7 @@ def test_lookup_existing_version(version, expected_version):
 
 
 @pytest.mark.parametrize(
-    "identifier,expected_os",
+    "id_or_model,expected_os",
     [
         ("bad", OperatingSystem.Unknown),
         ("MacBookAir10,1", OperatingSystem.MacOS),
@@ -58,7 +58,19 @@ def test_lookup_existing_version(version, expected_version):
         ("MacBookPro5,67", OperatingSystem.MacOS),
         ("Mac1,4", OperatingSystem.MacOS),
         ("MacPro19,4", OperatingSystem.MacOS),
+        (DeviceModel.AirPortExpress, OperatingSystem.AirPortOS),
+        (DeviceModel.AirPortExpressGen2, OperatingSystem.AirPortOS),
+        (DeviceModel.HomePod, OperatingSystem.TvOS),
+        (DeviceModel.HomePodGen2, OperatingSystem.TvOS),
+        (DeviceModel.HomePodMini, OperatingSystem.TvOS),
+        (DeviceModel.AppleTVGen1, OperatingSystem.Legacy),
+        (DeviceModel.Gen2, OperatingSystem.Legacy),
+        (DeviceModel.Gen3, OperatingSystem.Legacy),
+        (DeviceModel.Gen4, OperatingSystem.TvOS),
+        (DeviceModel.Gen4K, OperatingSystem.TvOS),
+        (DeviceModel.AppleTV4KGen2, OperatingSystem.TvOS),
+        (DeviceModel.AppleTV4KGen3, OperatingSystem.TvOS),
     ],
 )
-def test_lookup_os(identifier, expected_os):
-    assert lookup_os(identifier) == expected_os
+def test_lookup_os(id_or_model, expected_os):
+    assert lookup_os(id_or_model) == expected_os
diff --git a/tests/test_convert.py b/tests/test_convert.py
index 9e22c404..0a88f61b 100644
--- a/tests/test_convert.py
+++ b/tests/test_convert.py
@@ -87,6 +87,7 @@ def test_protocol_str(protocol, output):
 @pytest.mark.parametrize(
     "model,output",
     [
+        (DeviceModel.AppleTVGen1, "Apple TV 1"),
         (DeviceModel.Gen2, "Apple TV 2"),
         (DeviceModel.Gen3, "Apple TV 3"),
         (DeviceModel.Gen4, "Apple TV 4"),