Compare commits
4 Commits
main
...
full_examp
Author | SHA1 | Date |
---|---|---|
Mark Cotton | 155a0f7f14 | |
Mark Cotton | 7c66c9eaf2 | |
Mark Cotton | cbae8caf66 | |
Mark Cotton | aab2168f92 |
|
@ -6,6 +6,7 @@ from .settings import *
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
@ -466,6 +467,19 @@ class EagleEyev3():
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_camera_by_id(self, esn):
|
||||||
|
found_camera = None
|
||||||
|
for camera in self.cameras:
|
||||||
|
if camera.id == esn:
|
||||||
|
found_camera = camera
|
||||||
|
break
|
||||||
|
|
||||||
|
if found_camera == None:
|
||||||
|
camera = Camera()
|
||||||
|
|
||||||
|
logging.debug(f"returning camera {camera} for search query {esn}")
|
||||||
|
return camera
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -484,13 +498,21 @@ class Device():
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
|
if 'connectionStatus' in self.status:
|
||||||
return self.status['connectionStatus']
|
return self.status['connectionStatus']
|
||||||
|
return None
|
||||||
|
|
||||||
def is_online(self):
|
def is_online(self):
|
||||||
|
if 'connectionStatus' in self.status:
|
||||||
return self.status['connectionStatus'] == "online"
|
return self.status['connectionStatus'] == "online"
|
||||||
|
return None
|
||||||
|
|
||||||
def is_offline(self):
|
def is_offline(self):
|
||||||
return self.status['connectionStatus'] == "deviceOffline" or self.status['connectionStatus'] == "bridgeOffline"
|
if 'connectionStatus' in self.status:
|
||||||
|
return self.status['connectionStatus'] == "deviceOffline" \
|
||||||
|
or self.status['connectionStatus'] == "bridgeOffline" \
|
||||||
|
or self.status['connectionStatus'] == "error"
|
||||||
|
return None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.is_online():
|
if self.is_online():
|
||||||
|
@ -525,6 +547,7 @@ class Camera(Device):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if start_timestamp == None or end_timestamp == None:
|
if start_timestamp == None or end_timestamp == None:
|
||||||
|
logging.debug(f"get_list_of_events called without timestamp")
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"response_http_status": None,
|
"response_http_status": None,
|
||||||
|
@ -538,7 +561,8 @@ class Camera(Device):
|
||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.get(url, headers=headers)
|
try:
|
||||||
|
response = requests.get(url, headers=headers, timeout=(3, 5))
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
|
|
||||||
logging.debug(f"{response.status_code} returned from {url} with {headers} and {response.text}")
|
logging.debug(f"{response.status_code} returned from {url} with {headers} and {response.text}")
|
||||||
|
@ -562,8 +586,56 @@ class Camera(Device):
|
||||||
else:
|
else:
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
logging.warn(f"timeout expired for {self.id} get_llist_of_events()")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"response_http_status": 0,
|
||||||
|
"data": None
|
||||||
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logging.warn(e)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"response_http_status": 0,
|
||||||
|
"data": None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": success,
|
"success": success,
|
||||||
"response_http_status": response.status_code,
|
"response_http_status": response.status_code,
|
||||||
"data": response_json
|
"data": response_json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_live_preview(self):
|
||||||
|
|
||||||
|
url = f"https://{self.user_base_url}/api/v3.0/media/liveImage.jpeg?deviceId={self.id}&type=preview"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {self.een_instance.access_token}",
|
||||||
|
"Accept": "image/jpeg"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=headers, timeout=(3, 5))
|
||||||
|
logging.info(f"{response.status_code} in get_live_preview")
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
logging.warn(f"timeout expired for {self.id} get_live_preview()")
|
||||||
|
response = None
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logging.warn(e)
|
||||||
|
response = None
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
73
app.py
73
app.py
|
@ -1,8 +1,10 @@
|
||||||
|
|
||||||
import json, requests
|
import json, requests
|
||||||
from flask import Flask, request, session, render_template, redirect, url_for
|
from flask import Flask, request, session, render_template, redirect, url_for, Response, send_file
|
||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
|
|
||||||
|
@ -46,8 +48,17 @@ def index():
|
||||||
logging.info(f"{check['success']} - check get_current_user")
|
logging.info(f"{check['success']} - check get_current_user")
|
||||||
|
|
||||||
|
|
||||||
|
#logging.info(een.cameras)
|
||||||
|
|
||||||
|
|
||||||
|
# this call could get expensive
|
||||||
|
een.get_list_of_cameras()
|
||||||
|
|
||||||
|
#logging.info(een.cameras)
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
"current_user": een.current_user
|
"current_user": een.current_user,
|
||||||
|
"cameras": een.cameras
|
||||||
}
|
}
|
||||||
|
|
||||||
return render_template('index.html', template_values=values)
|
return render_template('index.html', template_values=values)
|
||||||
|
@ -81,5 +92,63 @@ def logout():
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/cameras')
|
||||||
|
def cameras():
|
||||||
|
if 'een' in session:
|
||||||
|
een = session['een']
|
||||||
|
else:
|
||||||
|
een = EagleEyev3()
|
||||||
|
|
||||||
|
een.get_list_of_cameras()
|
||||||
|
|
||||||
|
values = {
|
||||||
|
"current_user": een.current_user,
|
||||||
|
"cameras": een.cameras
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template('cameras_partial.html', template_values=values)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/camera/<esn>/live_preview')
|
||||||
|
def camera_live_preivew(esn=None):
|
||||||
|
if 'een' in session:
|
||||||
|
een = session['een']
|
||||||
|
else:
|
||||||
|
een = EagleEyev3()
|
||||||
|
|
||||||
|
camera = een.get_camera_by_id(esn)
|
||||||
|
res = camera.get_live_preview()
|
||||||
|
|
||||||
|
if res:
|
||||||
|
return send_file(BytesIO(res.content), mimetype='image/jpeg')
|
||||||
|
else:
|
||||||
|
return send_file('static/placeholder.png')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/camera/<esn>')
|
||||||
|
def camera_detail(esn=None):
|
||||||
|
if 'een' in session:
|
||||||
|
een = session['een']
|
||||||
|
else:
|
||||||
|
een = EagleEyev3()
|
||||||
|
|
||||||
|
camera = een.get_camera_by_id(esn)
|
||||||
|
camera.get_list_of_events(end_timestamp=een.time_before(ts=een.time_now(), hours=0), \
|
||||||
|
start_timestamp=een.time_before(ts=een.time_now(), hours=6) )
|
||||||
|
|
||||||
|
values = {
|
||||||
|
"current_user": een.current_user,
|
||||||
|
"camera": camera,
|
||||||
|
"events": camera.events['status']
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template('camera_detail_partial.html', template_values=values)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host=een.server_host, port=een.server_port)
|
app.run(host=een.server_host, port=een.server_port)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
{% if "X-PJAX" not in request.headers %}
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
@ -10,7 +9,7 @@
|
||||||
|
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||||
{% block title %}
|
{% block title %}
|
||||||
<title>classifier.een.cloud</title>
|
<title>status.mcotton.space</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- Bootstrap core CSS -->
|
<!-- Bootstrap core CSS -->
|
||||||
|
@ -61,14 +60,11 @@
|
||||||
|
|
||||||
|
|
||||||
<main role="main">
|
<main role="main">
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% if "X-PJAX" not in request.headers %}
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="text-muted">
|
<footer class="text-muted">
|
||||||
|
@ -88,35 +84,14 @@
|
||||||
|
|
||||||
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
|
<script src="//cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
|
||||||
<script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js"></script>
|
<script src="https://cdn.datatables.net/1.10.19/js/dataTables.bootstrap4.min.js"></script>
|
||||||
<script src="/static/js/jquery.pjax.js"></script>
|
|
||||||
|
<script src="https://unpkg.com/htmx.org@1.9.2"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
|
||||||
$('#user_mode_toggle').on('click', function(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: '/api/v1/user_mode/toggle',
|
|
||||||
dataType: "text",
|
|
||||||
contentType : 'application/json',
|
|
||||||
success: function(data) {
|
|
||||||
//$('#labelModal-add').text('Saved');
|
|
||||||
//$('#labelModal').modal('hide');
|
|
||||||
location.reload();
|
|
||||||
},
|
|
||||||
error: function(data) {
|
|
||||||
console.log(data)
|
|
||||||
$('#labelModal-add').text('Failed');
|
|
||||||
//$('#labelModal').modal('hide');
|
|
||||||
//location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,4 +103,3 @@
|
||||||
{% endblock%}
|
{% endblock%}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{% endif %}
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h3>{{ template_values['camera'].name }}</h3>
|
||||||
|
<img src="/camera/{{ template_values['camera'].id }}/live_preview" style="width: 100%; max-height:360px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6" id="events_list">
|
||||||
|
<h3>Events</h3>
|
||||||
|
<ul>
|
||||||
|
{% if template_values['events'] %}
|
||||||
|
{% for event in template_values['events'] %}
|
||||||
|
<li>{{ event['data'][0]['newStatus']['connectionStatus'] }} <br> <small>{{ event['startTimestamp'] }}</small></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<li>No events in the last six hours</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
<div>
|
||||||
|
<!-- <button hx-get="/camera/{{ template_values['camera'].id }}/events" hx-trigger="click" hx-target="#event_list">refresh</button> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-md-3" id="camera_list_items">
|
||||||
|
<h3>Online</h3>
|
||||||
|
<ul>
|
||||||
|
{% for camera in template_values['cameras'] %}
|
||||||
|
{% if camera.is_online() %}
|
||||||
|
<li hx-get="/camera/{{ camera.id }}" hx-trigger="click" hx-target="#camera_detail"> {{ camera.name }}</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Offline</h3>
|
||||||
|
<ul>
|
||||||
|
{% for camera in template_values['cameras'] %}
|
||||||
|
{% if not camera.is_online() %}
|
||||||
|
<li hx-get="/camera/{{ camera.id }}" hx-trigger="click" hx-target="#camera_detail"> {{ camera.name }}</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<div class="col-md-6 offset-3">
|
||||||
|
<button hx-get="/cameras" hx-trigger="click" hx-target="#camera_list">refresh</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-9" id="camera_detail"></div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block style %}
|
{% block style %}
|
||||||
|
<style>
|
||||||
|
#camera_list_items {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#events_list {
|
||||||
|
max-height: 500px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block current_user %}
|
{% block current_user %}
|
||||||
|
@ -11,11 +23,11 @@
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 offset-3">
|
<div class="row" id="camera_list">
|
||||||
<h1>Hello {{ template_values['current_user']['firstName'] }} {{ template_values['current_user']['lastName'] }}</h1>
|
{% include 'cameras_partial.html' %}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue