Skip to content

Commit 60be2cb

Browse files
ludeeusfrenck
authored andcommitted
Handle the new JSON payload from traccar clients (home-assistant#147254)
1 parent ddf8e0d commit 60be2cb

File tree

3 files changed

+82
-8
lines changed

3 files changed

+82
-8
lines changed

homeassistant/components/traccar/__init__.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""Support for Traccar Client."""
22

33
from http import HTTPStatus
4+
from json import JSONDecodeError
5+
import logging
46

57
from aiohttp import web
68
import voluptuous as vol
9+
from voluptuous.humanize import humanize_error
710

811
from homeassistant.components import webhook
912
from homeassistant.config_entries import ConfigEntry
@@ -20,7 +23,6 @@
2023
ATTR_LATITUDE,
2124
ATTR_LONGITUDE,
2225
ATTR_SPEED,
23-
ATTR_TIMESTAMP,
2426
DOMAIN,
2527
)
2628

@@ -29,6 +31,7 @@
2931

3032
TRACKER_UPDATE = f"{DOMAIN}_tracker_update"
3133

34+
LOGGER = logging.getLogger(__name__)
3235

3336
DEFAULT_ACCURACY = 200
3437
DEFAULT_BATTERY = -1
@@ -49,21 +52,50 @@ def _id(value: str) -> str:
4952
vol.Optional(ATTR_BATTERY, default=DEFAULT_BATTERY): vol.Coerce(float),
5053
vol.Optional(ATTR_BEARING): vol.Coerce(float),
5154
vol.Optional(ATTR_SPEED): vol.Coerce(float),
52-
vol.Optional(ATTR_TIMESTAMP): vol.Coerce(int),
5355
},
5456
extra=vol.REMOVE_EXTRA,
5557
)
5658

5759

60+
def _parse_json_body(json_body: dict) -> dict:
61+
"""Parse JSON body from request."""
62+
location = json_body.get("location", {})
63+
coords = location.get("coords", {})
64+
battery_level = location.get("battery", {}).get("level")
65+
return {
66+
"id": json_body.get("device_id"),
67+
"lat": coords.get("latitude"),
68+
"lon": coords.get("longitude"),
69+
"accuracy": coords.get("accuracy"),
70+
"altitude": coords.get("altitude"),
71+
"batt": battery_level * 100 if battery_level is not None else DEFAULT_BATTERY,
72+
"bearing": coords.get("heading"),
73+
"speed": coords.get("speed"),
74+
}
75+
76+
5877
async def handle_webhook(
59-
hass: HomeAssistant, webhook_id: str, request: web.Request
78+
hass: HomeAssistant,
79+
webhook_id: str,
80+
request: web.Request,
6081
) -> web.Response:
6182
"""Handle incoming webhook with Traccar Client request."""
83+
if not (requestdata := dict(request.query)):
84+
try:
85+
requestdata = _parse_json_body(await request.json())
86+
except JSONDecodeError as error:
87+
LOGGER.error("Error parsing JSON body: %s", error)
88+
return web.Response(
89+
text="Invalid JSON",
90+
status=HTTPStatus.UNPROCESSABLE_ENTITY,
91+
)
6292
try:
63-
data = WEBHOOK_SCHEMA(dict(request.query))
93+
data = WEBHOOK_SCHEMA(requestdata)
6494
except vol.MultipleInvalid as error:
95+
LOGGER.warning(humanize_error(requestdata, error))
6596
return web.Response(
66-
text=error.error_message, status=HTTPStatus.UNPROCESSABLE_ENTITY
97+
text=error.error_message,
98+
status=HTTPStatus.UNPROCESSABLE_ENTITY,
6799
)
68100

69101
attrs = {

homeassistant/components/traccar/const.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
ATTR_MOTION = "motion"
1818
ATTR_SPEED = "speed"
1919
ATTR_STATUS = "status"
20-
ATTR_TIMESTAMP = "timestamp"
2120
ATTR_TRACKER = "tracker"
2221
ATTR_TRACCAR_ID = "traccar_id"
2322

tests/components/traccar/test_init.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,12 @@ async def test_enter_and_exit(
146146
assert len(entity_registry.entities) == 1
147147

148148

149-
async def test_enter_with_attrs(hass: HomeAssistant, client, webhook_id) -> None:
150-
"""Test when additional attributes are present."""
149+
async def test_enter_with_attrs_as_query(
150+
hass: HomeAssistant,
151+
client,
152+
webhook_id,
153+
) -> None:
154+
"""Test when additional attributes are present URL query."""
151155
url = f"/api/webhook/{webhook_id}"
152156
data = {
153157
"timestamp": 123456789,
@@ -197,6 +201,45 @@ async def test_enter_with_attrs(hass: HomeAssistant, client, webhook_id) -> None
197201
assert state.attributes["altitude"] == 123
198202

199203

204+
async def test_enter_with_attrs_as_payload(
205+
hass: HomeAssistant, client, webhook_id
206+
) -> None:
207+
"""Test when additional attributes are present in JSON payload."""
208+
url = f"/api/webhook/{webhook_id}"
209+
data = {
210+
"location": {
211+
"coords": {
212+
"heading": "105.32",
213+
"latitude": "1.0",
214+
"longitude": "1.1",
215+
"accuracy": 10.5,
216+
"altitude": 102.0,
217+
"speed": 100.0,
218+
},
219+
"extras": {},
220+
"manual": True,
221+
"is_moving": False,
222+
"_": "&id=123&lat=1.0&lon=1.1&timestamp=2013-09-17T07:32:51Z&",
223+
"odometer": 0,
224+
"activity": {"type": "still"},
225+
"timestamp": "2013-09-17T07:32:51Z",
226+
"battery": {"level": 0.1, "is_charging": False},
227+
},
228+
"device_id": "123",
229+
}
230+
231+
req = await client.post(url, json=data)
232+
await hass.async_block_till_done()
233+
assert req.status == HTTPStatus.OK
234+
state = hass.states.get(f"{DEVICE_TRACKER_DOMAIN}.{data['device_id']}")
235+
assert state.state == STATE_NOT_HOME
236+
assert state.attributes["gps_accuracy"] == 10.5
237+
assert state.attributes["battery_level"] == 10.0
238+
assert state.attributes["speed"] == 100.0
239+
assert state.attributes["bearing"] == 105.32
240+
assert state.attributes["altitude"] == 102.0
241+
242+
200243
async def test_two_devices(hass: HomeAssistant, client, webhook_id) -> None:
201244
"""Test updating two different devices."""
202245
url = f"/api/webhook/{webhook_id}"

0 commit comments

Comments
 (0)