Repository: bimmerconnected/bimmer_connected (python)

Issue:
Update overver position checks
The observer position wasn't being set in my version (`bimmer-connected==0.16.0.0b2`). After adding it, the guard fails if 0.0 is used :):
```
    if args.lat and args.lng:
        account.set_observer_position(args.lat, args.lng)
```

_Originally posted by @sshaikh in https://github.com/bimmerconnected/bimmer_connected/discussions/631#discussioncomment-10168095_

Maybe take inspiration from https://github.com/home-assistant/core/blob/dff964582bb52882805b50a5f5bd235aab35e20b/homeassistant/helpers/config_validation.py#L136-L142.

Pull request description:
Update GPS position verification
## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue in the
  additional information section.
-->
Verify GPS position based on actual possible values (taken from https://github.com/home-assistant/core/blob/dff964582bb52882805b50a5f5bd235aab35e20b/homeassistant/helpers/config_validation.py#L136-L142).

## Type of change
- [x] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (which adds functionality to this library)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #640
- This PR is related to issue: 

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [x] The code change is tested and works locally.
- [x] Tests have been added to verify that the new code works.


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7da8f1c..855bcdb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,12 +1,12 @@
 repos:
   - repo: https://github.com/charliermarsh/ruff-pre-commit
-    rev: v0.5.5
+    rev: v0.5.6
     hooks:
       - id: ruff
         args:
           - --fix
   - repo: https://github.com/psf/black
-    rev: 24.4.2
+    rev: 24.8.0
     hooks:
       - id: black
         args:
@@ -24,7 +24,7 @@ repos:
         exclude_types: [csv, json]
         exclude: ^test/responses/
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.11.0
+    rev: v1.11.1
     hooks: 
       - id: mypy
         name: mypy
diff --git a/bimmer_connected/models.py b/bimmer_connected/models.py
index 4a6adaf..f9506b2 100644
--- a/bimmer_connected/models.py
+++ b/bimmer_connected/models.py
@@ -71,6 +71,10 @@ class GPSPosition:
             value = getattr(self, field_name)
             if value is not None and not isinstance(value, (float, int)):
                 raise TypeError(f"'{field_name}' not of type '{Optional[Union[float, int]]}'")
+            if field_name == "latitude" and not (-90 <= value <= 90):
+                raise ValueError(f"'latitude' must be between -90 and 90, but got '{value}'")
+            elif field_name == "longitude" and not (-180 <= value <= 180):
+                raise ValueError(f"'longitude' must be between -180 and 180, but got '{value}'")
 
     def __iter__(self):
         yield from self.__dict__.values()
diff --git a/bimmer_connected/vehicle/remote_services.py b/bimmer_connected/vehicle/remote_services.py
index 604691a..8a3e5e3 100644
--- a/bimmer_connected/vehicle/remote_services.py
+++ b/bimmer_connected/vehicle/remote_services.py
@@ -313,8 +313,9 @@ class RemoteServices:
 
     async def trigger_remote_vehicle_finder(self) -> RemoteServiceStatus:
         """Trigger the vehicle finder."""
-        if not self._vehicle.is_vehicle_tracking_enabled:
-            raise ValueError(f"Vehicle does not support remote service '{Services.VEHICLE_FINDER.value}'.")
+        # Even if the API reports this as False, calling the service still works
+        # if not self._vehicle.is_vehicle_tracking_enabled:
+        #     raise ValueError(f"Vehicle does not support remote service '{Services.VEHICLE_FINDER.value}'.")
 
         status = await self.trigger_remote_service(Services.VEHICLE_FINDER)
         result = await self._get_event_position(status.event_id)


Test patch:
diff --git a/bimmer_connected/tests/responses/remote_services/eadrax_service_eventposition.json b/bimmer_connected/tests/responses/remote_services/eadrax_service_eventposition.json
index c737d60..3d58948 100644
--- a/bimmer_connected/tests/responses/remote_services/eadrax_service_eventposition.json
+++ b/bimmer_connected/tests/responses/remote_services/eadrax_service_eventposition.json
@@ -2,7 +2,7 @@
     "positionData": {
         "status": "OK",
         "position": {
-            "latitude": 123.456,
+            "latitude": 12.345,
             "longitude": 34.5678,
             "formattedAddress": "some_formatted_address",
             "heading": 121
diff --git a/bimmer_connected/tests/test_remote_services.py b/bimmer_connected/tests/test_remote_services.py
index 494931b..b377b88 100644
--- a/bimmer_connected/tests/test_remote_services.py
+++ b/bimmer_connected/tests/test_remote_services.py
@@ -256,10 +256,16 @@ async def test_vehicles_without_enabled_services(bmw_fixture: respx.Router):
     vehicle.update_state({"capabilities": {}})
 
     for service in ALL_SERVICES.values():
-        with pytest.raises(ValueError):
+        # Vehicle finder always works, even if API capabilities say different
+        if service["call"] == "trigger_remote_vehicle_finder":
             await getattr(vehicle.remote_services, service["call"])(  # type: ignore[call-overload]
                 *service.get("args", []), **service.get("kwargs", {})
             )
+        else:
+            with pytest.raises(ValueError):
+                await getattr(vehicle.remote_services, service["call"])(  # type: ignore[call-overload]
+                    *service.get("args", []), **service.get("kwargs", {})
+                )
 
 
 @pytest.mark.asyncio
@@ -314,12 +320,12 @@ async def test_get_remote_position(bmw_fixture: respx.Router):
 
     # Check updated position
     await vehicle.remote_services.trigger_remote_vehicle_finder()
-    assert location.location == (123.456, 34.5678)
+    assert location.location == (12.345, 34.5678)
     assert location.heading == 121
 
     # Position should still be from vehicle finder after status update
     await account.get_vehicles()
-    assert location.location == (123.456, 34.5678)
+    assert location.location == (12.345, 34.5678)
     assert location.heading == 121
 
 
diff --git a/bimmer_connected/tests/test_vehicle.py b/bimmer_connected/tests/test_vehicle.py
index c1f3d53..9897a94 100644
--- a/bimmer_connected/tests/test_vehicle.py
+++ b/bimmer_connected/tests/test_vehicle.py
@@ -284,6 +284,18 @@ def test_gpsposition():
     assert pos != "(1, 2)"
     assert pos[0] == 1
 
+    with pytest.raises(TypeError, match="Either none or all arguments must be 'None'."):
+        GPSPosition(1, None)
+
+    with pytest.raises(TypeError, match="'longitude' not of type"):
+        GPSPosition(0, "49.7")
+
+    with pytest.raises(ValueError, match="'latitude' must be between -90 and 90"):
+        GPSPosition(91, 0)
+
+    with pytest.raises(ValueError, match="'longitude' must be between -180 and 180"):
+        GPSPosition(90, 181)
+
 
 @pytest.mark.asyncio
 async def test_headunit_data(caplog, bmw_fixture: respx.Router):