feat: Implement search API for owntracks geo-fence time ranges
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
76
main.py
76
main.py
@@ -529,19 +529,89 @@ async def history(request):
|
|||||||
|
|
||||||
|
|
||||||
async def search(request):
|
async def search(request):
|
||||||
|
api_key = request.rel_url.query.get('api_key', '')
|
||||||
|
authed = api_key == settings.SENSORS_API_KEY
|
||||||
|
|
||||||
measurement = request.match_info.get('measurement')
|
measurement = request.match_info.get('measurement')
|
||||||
name = request.match_info.get('name')
|
name = request.match_info.get('name')
|
||||||
params = request.rel_url.query
|
|
||||||
|
if not authed:
|
||||||
|
return web.json_response([])
|
||||||
|
|
||||||
|
if name not in [x.name for x in sensors.sensors]:
|
||||||
|
raise
|
||||||
|
|
||||||
|
if measurement != 'owntracks':
|
||||||
|
return web.json_response({'error': 'not implemented for this measurement'}, status=400)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
post_data = await request.json()
|
post_data = await request.json()
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
post_data = await request.post()
|
return web.json_response({'error': 'invalid json'}, status=400)
|
||||||
|
|
||||||
|
params = request.rel_url.query
|
||||||
logging.info('Search request: meas=%s, name=%s, params=%s, data=%s',
|
logging.info('Search request: meas=%s, name=%s, params=%s, data=%s',
|
||||||
measurement, name, params, post_data)
|
measurement, name, params, post_data)
|
||||||
|
|
||||||
return web.json_response({})
|
areas = post_data.get('areas')
|
||||||
|
if not areas or not isinstance(areas, list):
|
||||||
|
return web.json_response({'error': 'invalid areas format'}, status=400)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for area in areas:
|
||||||
|
_ = area['southWest']['lat']
|
||||||
|
_ = area['southWest']['lng']
|
||||||
|
_ = area['northEast']['lat']
|
||||||
|
_ = area['northEast']['lng']
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
return web.json_response({'error': 'invalid area format in areas list'}, status=400)
|
||||||
|
|
||||||
|
client = sensors_client
|
||||||
|
q = 'select "lat", "lon" from owntracks where "acc" < 100 and "name" = \'{}\' order by time asc'.format(name)
|
||||||
|
points = list(client.query(q).get_points())
|
||||||
|
|
||||||
|
ranges = []
|
||||||
|
current_range = None
|
||||||
|
|
||||||
|
for point in points:
|
||||||
|
if point.get('lat') is None or point.get('lon') is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_inside = False
|
||||||
|
for area in areas:
|
||||||
|
sw = area['southWest']
|
||||||
|
ne = area['northEast']
|
||||||
|
if sw['lat'] <= point['lat'] <= ne['lat'] and sw['lng'] <= point['lon'] <= ne['lng']:
|
||||||
|
is_inside = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# InfluxDB time format can vary. It's UTC (Z suffix).
|
||||||
|
point_time_str = point['time']
|
||||||
|
if '.' in point_time_str:
|
||||||
|
point_dt = datetime.strptime(point_time_str, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||||
|
else:
|
||||||
|
point_dt = datetime.strptime(point_time_str, '%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
|
||||||
|
if is_inside:
|
||||||
|
if current_range is None:
|
||||||
|
current_range = {'start': point_dt, 'end': point_dt}
|
||||||
|
else:
|
||||||
|
current_range['end'] = point_dt
|
||||||
|
else:
|
||||||
|
if current_range is not None:
|
||||||
|
ranges.append({
|
||||||
|
'start': int(current_range['start'].timestamp()),
|
||||||
|
'end': int(current_range['end'].timestamp())
|
||||||
|
})
|
||||||
|
current_range = None
|
||||||
|
|
||||||
|
if current_range is not None:
|
||||||
|
ranges.append({
|
||||||
|
'start': int(current_range['start'].timestamp()),
|
||||||
|
'end': int(current_range['end'].timestamp())
|
||||||
|
})
|
||||||
|
|
||||||
|
return web.json_response(ranges)
|
||||||
|
|
||||||
|
|
||||||
async def options_handler(request):
|
async def options_handler(request):
|
||||||
|
|||||||
Reference in New Issue
Block a user