commit 3b78d8a97dbdba460830a43a2d3ab79b3cea7799
parent e1971798d76d55094ef250c1643d6bdf2d18ee2d
Author: Friedel Schön <[email protected]>
Date: Thu, 30 Jun 2022 11:43:23 +0200
statistics
Diffstat:
11 files changed, 93 insertions(+), 56 deletions(-)
diff --git a/client/client.ino b/client/client.ino
@@ -7,11 +7,13 @@
#include <Sodaq_UBlox_GPS.h>
#include <Wire.h>
-interface client;
-Sodaq_LSM303AGR accel;
+static interface client;
+static Sodaq_LSM303AGR accel;
+static bool next_scan, scan;
void (*reset)() = 0;
+
void setup() {
pinMode(ledRed, OUTPUT);
pinMode(ledGreen, OUTPUT);
@@ -34,6 +36,8 @@ void setup() {
} while (!client.send(interface::METHOD_POST, "/api/hello"));
writeLED(COLOR_WHITE);
+ next_scan = scan = (bool) client.request["location_search"];
+
bool save = false;
if (client.response.hasOwnProperty("token")) {
strcpy(config.token, (const char*) client.response["token"]), save = true;
@@ -57,9 +61,8 @@ void setup() {
}
void loop() {
- static int last = 0;
- static bool next_scan = true, scan = true;
- int now = millis();
+ static int last = 0;
+ int now = millis();
if (now - last > statusInterval * 1000) {
if (scan && sodaq_gps.scan(next_scan, gpsTimeout * 1000)) {
@@ -79,6 +82,7 @@ void loop() {
client.request["charging"] = getCharging();
client.request["trap"] = getTrapStatus();
client.request["satellites"] = sodaq_gps.getNumberOfSatellites();
+ client.request["searching"] = scan;
if (client.send(interface::METHOD_POST, "/api/update")) {
next_scan = (bool) client.response["location_search"];
diff --git a/remote.py b/remote.py
@@ -14,8 +14,8 @@ import websockets
WEBSOCKET_PORT = 1612
+host, port = 'localhost', 5000
-client = HTTPConnection('muizenval.tk', 80)
remote = Remote(115200)
token: Optional[str] = None
@@ -32,6 +32,7 @@ def send_http(params):
print(body)
+ client = HTTPConnection(host, port)
client.request(method, endpoint, json.dumps(body))
res = client.getresponse()
response = json.load(res)
diff --git a/server/models.py b/server/models.py
@@ -37,6 +37,8 @@ class Trap(db.Model):
charging: bool = db.Column(db.Boolean, nullable=False, default=False)
temperature: int = db.Column(db.Integer, nullable=False, default=0)
location_search: bool = db.Column(db.Boolean, nullable=False, default=True)
+ location_searching: bool = db.Column(
+ db.Boolean, nullable=False, default=True)
location_acc: float = db.Column(db.Float, nullable=False, default=0)
location_lat: Optional[float] = db.Column(db.Float)
location_lon: Optional[float] = db.Column(db.Float)
@@ -57,6 +59,7 @@ class Trap(db.Model):
name=self.name,
offline=self.offline(),
locationSearch=self.location_search,
+ locationSearching=self.location_searching,
latitude=self.location_lat,
longitude=self.location_lon,
accuracy=round(self.location_acc, 1),
@@ -72,6 +75,12 @@ class Trap(db.Model):
)
+class Statistic(db.Model):
+ id: int = db.Column(db.Integer, primary_key=True)
+ user: int = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
+ date: datetime = db.Column(db.DateTime, nullable=False)
+
+
@login_manager.user_loader
def load_user(user_id: int) -> User:
return User.query.get(user_id)
diff --git a/server/routes.py b/server/routes.py
@@ -145,53 +145,6 @@ def traps():
return render_template('trap.html')
-"""
[email protected]('/trap/<int:trap_id>/update', methods=['POST', 'GET'])
-@login_required
-def trap_update(trap_id: int):
- form = UpdateTrapForm()
- trap = Trap.query.get(trap_id)
- if not trap or trap.owner != current_user.id:
- flash('Val is niet van u', 'error')
- return redirect(url_for('traps'))
- if form.validate_on_submit():
- trap.name = form.name.data
- db.session.commit()
- return redirect(url_for('traps'))
- elif not trap:
- flash('Muizenval niet gevonden', 'danger')
- return redirect(url_for('traps'))
- elif request.method == 'GET':
- form.name.data = trap.name
- return render_template('updatetrap.html', form=form, trap=trap)
-
-
[email protected]('/trap/<int:trap_id>/delete')
-@login_required
-def trap_delete(trap_id: int):
- trap: Trap = Trap.query.get(trap_id)
- if not trap or trap.owner != current_user.id:
- flash('Val is niet van u', 'error')
- else:
- trap.owner = None
- db.session.commit()
-
- return redirect(url_for('traps'))
-
-
[email protected]('/trap/<int:trap_id>/location/<int:search>')
-@login_required
-def trap_location_search(trap_id: int, search: int):
- trap: Trap = Trap.query.get(trap_id)
- if not trap or trap.owner != current_user.id:
- flash('Val is niet van u', 'error')
- else:
- trap.location_search = bool(search)
- db.session.commit()
- return redirect(url_for('traps'))
-"""
-
-
@app.route('/contact')
@login_required
def contact():
diff --git a/server/site.db b/server/site.db
Binary files differ.
diff --git a/server/socket.py b/server/socket.py
@@ -7,7 +7,7 @@ from flask_login import current_user
from flask_socketio import emit
from .app import app, db, socket, domain
-from .models import Trap, User
+from .models import Statistic, Trap, User
current_user: User
@@ -37,6 +37,10 @@ def register_trap():
db.session.add(trap)
db.session.commit()
res['token'] = token
+ else:
+ trap: Trap = Trap.query.filter_by(token=req['token']).first()
+
+ res['location_search'] = trap.location_search
if 'domain' not in req or req['domain'] != domain:
res['domain'] = domain
@@ -55,6 +59,9 @@ def update_status():
return jsonify(dict(error='invalid-token'))
if not trap.caught and req['trap']:
+ if trap.owner:
+ stc = Statistic(user=trap.owner, date=datetime.now())
+ db.session.add(stc)
# os.system(
# f"echo -e -s \"Je muizenval '{trap.name}' heeft iets gevangen!\\n\\nGroetjes uw Team Benni!\" | mailx -s 'Muizenval werd geactiveerd' {trap.owner_class().email}") # type: ignore
print('Email sent!')
@@ -64,6 +71,7 @@ def update_status():
trap.battery = req['battery']
trap.temperature = req['temperature']
trap.charging = req['charging']
+ trap.location_searching = req['searching']
if trap.location_search:
trap.location_satellites = req['satellites']
if req['accuracy'] != 0:
@@ -75,10 +83,23 @@ def update_status():
if trap.owner and trap.owner in sockets:
socket.emit('trap-change', trap.to_json(), to=sockets[trap.owner])
+ socket.emit('statistics', make_statistics(
+ trap.owner), to=sockets[trap.owner])
return jsonify(dict(location_search=trap.location_search))
+def make_statistics(user: int):
+ year = datetime.now().year
+ months = [0] * 12
+ stc: Statistic
+ for stc in Statistic.query.filter_by(user=user):
+ if stc.date.year == year:
+ months[stc.date.month-1] += 1
+
+ return months
+
+
@socket.on('connect')
def socket_connect():
if not current_user.is_authenticated:
@@ -89,6 +110,8 @@ def socket_connect():
for trap in Trap.query.filter_by(owner=current_user.id):
emit('trap-change', trap.to_json())
+ emit('statistics', make_statistics(current_user.id))
+
@socket.on('disconnect')
def socket_disconnect():
diff --git a/server/static/main.css b/server/static/main.css
@@ -1,3 +1,7 @@
+body {
+ background-color: #efefef;
+}
+
#side_nav{
background: #000;
list-style-type: none;
@@ -53,6 +57,11 @@
border: #aaa solid 1px;
}
+#trap-chart {
+ height: 300px;
+ position: relative;
+}
+
i {
padding-left: 10px;
padding-right: 10px;
diff --git a/server/static/trap.js b/server/static/trap.js
@@ -50,8 +50,12 @@ function addTrap(trap) {
else if (trap.activated) (statusIcon = 'circle-exclamation'), (statusString += 'geactiveerd');
else (statusIcon = 'clock'), (statusString += 'wachtend');
statusIcons += `<i class='fas fa-${statusIcon}'></i>`;
+ clone.style.background = '#ffffff';
if (!trap.offline) {
+ if (trap.activated) {
+ clone.style.background = '#e8dcca';
+ }
if (trap.charging) (batteryIcon = 'plug-circle-bolt'), (statusString += ', aan het opladen');
else if (trap.battery == 0) batteryIcon = 'battery-empty';
else if (trap.battery < 30) batteryIcon = 'battery-quarter';
@@ -65,7 +69,9 @@ function addTrap(trap) {
else if (trap.temperature < -10) (tempIcon = 'temperature-low'), (statusString += ', onderkoeld');
if (tempIcon) statusIcons += `<i class='fas fa-${tempIcon}'></i>`;
- if (trap.locationSearch) (statusIcons += '<i class="fas fa-satellite"></i>'), (statusString += ', zoekt naar locatie');
+ if (trap.locationSearching) (statusIcons += '<i class="fas fa-satellite"></i>'), (statusString += ', zoekt naar locatie');
+ } else {
+ clone.style.background = '#eeeeee';
}
clone.querySelector('a.update').onclick = function () {
@@ -171,5 +177,30 @@ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
socket.on('trap-change', addTrap);
socket.on('trap-remove', removeTrap);
+socket.on('statistics', function (months) {
+ var chart = new CanvasJS.Chart('trap-chart', {
+ data: [
+ {
+ // Change type to "doughnut", "line", "splineArea", etc.
+ type: 'column',
+ dataPoints: [
+ { label: 'Januari', y: months[0] },
+ { label: 'Februari', y: months[1] },
+ { label: 'Maart', y: months[2] },
+ { label: 'April', y: months[3] },
+ { label: 'Mei', y: months[4] },
+ { label: 'Juni', y: months[5] },
+ { label: 'Juli', y: months[6] },
+ { label: 'Augustus', y: months[7] },
+ { label: 'September', y: months[8] },
+ { label: 'October', y: months[9] },
+ { label: 'November', y: months[10] },
+ { label: 'December', y: months[11] },
+ ],
+ },
+ ],
+ });
+ chart.render();
+});
openWebSocket();
diff --git a/server/templates/contact.html b/server/templates/contact.html
@@ -3,7 +3,7 @@
{% with contact = current_user.contact_class() %}
<article class="media content-section">
<div class="media-body">
- <h2>Uw contactgegevens</h2>
+ <h2>Contactgegevens Service Punt</h2>
{% if contact %}
<p>
<b>{{ contact.name }}</b>
diff --git a/server/templates/layout.html b/server/templates/layout.html
@@ -44,6 +44,7 @@
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
crossorigin=""></script>
+ <script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>
<script type="text/javascript" charset="utf-8">
{% if user_token %}
diff --git a/server/templates/trap.html b/server/templates/trap.html
@@ -7,6 +7,12 @@
<div id="trap-map"></div>
</div>
</article>
+ <article class="media content-section">
+ <div class="media-body">
+ <h2 style="text-align:center;">activiteit in muizen per maand</h2>
+ <div id="trap-chart"></div>
+ </div>
+ </article>
</div>
<template id="trap-template">
<article class="media content-section">