commit 73eabc84bd2f909779738ab51442828f702207c7
parent 4866667b7910b2e5e6b4287f2896145a2e704d76
Author: hooglo <[email protected]>
Date: Thu, 30 Jun 2022 11:49:19 +0200
Merge branch 'master' of https://github.com/friedelschoen/muizenval.tk
Diffstat:
18 files changed, 416 insertions(+), 395 deletions(-)
diff --git a/.vscode/arduino.json b/.vscode/arduino.json
@@ -1,6 +1,6 @@
{
"sketch": "client/client.ino",
"board": "SODAQ:samd:sodaq_sara",
- "port": "/dev/tty.usbmodem14201",
+ "port": "/dev/tty.usbmodem14101",
"output": "build"
}
\ No newline at end of file
diff --git a/client/client.ino b/client/client.ino
@@ -7,18 +7,20 @@
#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(LED_RED, OUTPUT);
- pinMode(LED_GREEN, OUTPUT);
- pinMode(LED_BLUE, OUTPUT);
+ pinMode(ledRed, OUTPUT);
+ pinMode(ledGreen, OUTPUT);
+ pinMode(ledBlue, OUTPUT);
pinMode(trapPin, INPUT_PULLUP);
- pinMode(BATVOLT_PIN, INPUT);
- pinMode(CHARGER_STATUS, INPUT);
+ pinMode(batteryPin, INPUT);
+ pinMode(chargerPin, INPUT);
config.open();
client.begin();
@@ -26,28 +28,27 @@ void setup() {
if (!config.valid)
config = config_default;
- client.request["token"] = config.token;
- client.request["domain"] = config.domain;
- while (!client.send(interface::METHOD_POST, "/api/hello")) {
+ do {
writeLED(COLOR_RED);
- delay(500);
- }
+ delay(2500);
+ client.request["token"] = config.token;
+ client.request["domain"] = config.domain;
+ } 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;
- json req;
- req["token"] = config.token;
- client.remote("set-token", req);
+ client.sendToken();
}
if (client.response.hasOwnProperty("domain"))
strcpy(config.domain, (const char*) client.response["domain"]), save = true;
-
if (save)
config.save();
-
Wire.begin();
delay(1000);
sodaq_gps.init(GPS_ENABLE);
@@ -64,7 +65,7 @@ void loop() {
int now = millis();
if (now - last > statusInterval * 1000) {
- if (sodaq_gps.scan(true, gpsTimeout * 1000)) {
+ if (scan && sodaq_gps.scan(next_scan, gpsTimeout * 1000)) {
client.request["latitude"] = sodaq_gps.getLat();
client.request["longitude"] = sodaq_gps.getLon();
client.request["accuracy"] = getAccuracy();
@@ -73,6 +74,7 @@ void loop() {
client.request["longitude"] = 0;
client.request["accuracy"] = 0;
}
+ scan = next_scan;
client.request["token"] = config.token;
client.request["battery"] = batteryVoltage();
@@ -80,14 +82,17 @@ void loop() {
client.request["charging"] = getCharging();
client.request["trap"] = getTrapStatus();
client.request["satellites"] = sodaq_gps.getNumberOfSatellites();
+ client.request["searching"] = scan;
- client.send(interface::METHOD_POST, "/api/update");
+ if (client.send(interface::METHOD_POST, "/api/update")) {
+ next_scan = (bool) client.response["location_search"];
+ }
last = now;
}
}
int batteryVoltage() {
- return batteryFactor * analogRead(BATVOLT_PIN);
+ return batteryFactor * analogRead(batteryPin);
}
double getAccuracy() { // -> 100% the best, 0% the worst
@@ -102,5 +107,5 @@ bool getTrapStatus() {
}
bool getCharging() {
- return digitalRead(CHARGER_STATUS);
+ return digitalRead(chargerPin);
}
\ No newline at end of file
diff --git a/client/include/config.h b/client/include/config.h
@@ -8,31 +8,38 @@
#define modemPowerPin SARA_ENABLE // modem power pin
#define modemEnablePin SARA_TX_ENABLE // modem enable pin
#define modemVoltagePin SARA_R4XX_TOGGLE // modem voltage pin
+#define batteryPin BAT_VOLT // messuring battery
+#define chargerPin CHARGER_STATUS // messuring charging
+#define ledRed LED_RED // rgb-led (red)
+#define ledGreen LED_GREEN // rgb-led (green)
+#define ledBlue LED_BLUE // rgb-led (blue)
#define trapPin 10 // pin of magnet-sensor
// -*- behaviour settings -*-
-#define remoteBaud 115200 // baud-rate of usb-serial
-#define modemBaud 115200 // baud-rate of modem-serial
-#define remoteForce true // do not try connect to modem
-#define remoteFirstTimeout 5.0 // seconds to wait for the first timeout
-#define remoteTimeout 1.0 // seconds to wait for remote to timeout
-#define lineBuffer 512 // buffer-size (bytes) to use to store lines
-#define commandTimeout 10.0 // seconds to cancel a command
-#define commandDelay 0.1 // delay after every command
-#define ignoreDelay 2.0 // seconds to wait if command is run with COMMAND_IGNORE
-#define commandDebug true // send debug information about command requests
-#define eventDebug true // print '+'-events
-#define lineDebug false // print each line to debug
-#define blockDebug true // print if command is blocking
-#define blinkInterval 0.25 // seconds to wait for blink
-#define gpsTimeout 15 // seconds to gps-timeout
-#define statusInterval 5 // send status every n seconds
-
-#define ADC_AREF 3.3f
-#define BATVOLT_R1 4.7f
-#define BATVOLT_R2 10.0f
-#define BATVOLT_PIN BAT_VOLT
-#define batteryFactor (0.978 * (BATVOLT_R1 / BATVOLT_R2 + 1) / ADC_AREF)
+#define remoteBaud 115200 // baud-rate of usb-serial
+#define modemBaud 115200 // baud-rate of modem-serial
+#define remoteForce true // do not try connect to modem
+#define lineBuffer 512 // buffer-size (bytes) to use to store lines
+#define commandDebug true // send debug information about command requests
+#define eventDebug true // print '+'-events
+#define lineDebug false // print each line to debug
+#define blockDebug true // print if command is blocking
+
+// -*- timing settings (seconds) -*-
+#define remoteFirstTimeout 5 // seconds to wait for the first timeout
+#define remoteTimeout 1 // seconds to wait for remote to timeout
+#define commandTimeout 10 // seconds to cancel a command
+#define commandDelay 0.1 // delay after every command
+#define ignoreDelay 2 // seconds to wait if command is run with COMMAND_IGNORE
+#define blinkInterval 0.25 // seconds to wait for blink
+#define gpsTimeout 15 // seconds to gps-timeout
+#define statusInterval 5 // send status every n seconds
+
+// -*- battery stuff -*-
+#define adcAREF 3.3
+#define batteryR1 4.7
+#define batteryR2 10.0
+#define batteryFactor (0.978 * (batteryR1 / batteryR2 + 1) / adcAREF)
struct configuration {
diff --git a/client/include/interface.h b/client/include/interface.h
@@ -45,6 +45,8 @@ struct interface {
int send(method method, const char* endpoint);
+ void sendToken();
+
command_status remote(const char* command, json params = nullptr, json& response = null_response, command_flags flags = COMMAND_NONE);
command_status modem(const char* request, char* response, command_flags flags = COMMAND_NONE);
diff --git a/client/interface.ino b/client/interface.ino
@@ -55,9 +55,7 @@ void interface::beginRemote() {
if (!usbSerial)
return;
- json req;
- req["token"] = config.token;
- remote("set_token", req, null_response, COMMAND_FORCE);
+ sendToken();
writeLED(COLOR_MAGENTA);
remoteReady = true;
@@ -87,16 +85,24 @@ int interface::send(interface::method method, const char* endpoint) {
request = nullptr;
response = cmd_response["body"];
return cmd_response["code"];
- } else {
+ } else if (modemReady) {
endRemote();
- if (!modemReady) {
- return 0;
- }
// modem
- return 1;
+ return 0;
+ } else {
+ endRemote();
+ writeLED(COLOR_RED);
+ return 0;
}
}
+void interface::sendToken() {
+ json req;
+ req["token"] = config.token;
+ remote("set_token", req, null_response, COMMAND_FORCE);
+}
+
+
interface::command_status interface::remote(const char* command, json params, json& response, command_flags flags) {
bool force = flags & COMMAND_FORCE;
@@ -127,6 +133,8 @@ interface::command_status interface::remote(const char* command, json params, js
}
interface::command_status interface::modem(const char* request, char* response, command_flags flags) {
+ return STATUS_NOT_READY;
+ /*
char line[lineBuffer];
size_t lineLen;
char buf;
@@ -146,7 +154,7 @@ interface::command_status interface::modem(const char* request, char* response,
modemSerial.write("\r\n");
- delay(commandDelay * 1000);
+ delay(commandDelay * 1000);*/
}
interface::command_status interface::modem(const char* request, command_flags flags) {
diff --git a/client/led.ino b/client/led.ino
@@ -1,6 +1,7 @@
+#include "include/config.h"
#include "include/led.h"
-static const bool colors[][3] = {
+static bool colors[][3] = {
[COLOR_NONE] = { 0, 0, 0 },
[COLOR_RED] = { 1, 0, 0 },
[COLOR_GREEN] = { 0, 1, 0 },
@@ -12,7 +13,7 @@ static const bool colors[][3] = {
};
void writeLED(color c) {
- digitalWrite(LED_RED, !colors[c][0]);
- digitalWrite(LED_GREEN, !colors[c][1]);
- digitalWrite(LED_BLUE, !colors[c][2]);
+ digitalWrite(ledRed, !colors[c][0]);
+ digitalWrite(ledGreen, !colors[c][1]);
+ digitalWrite(ledBlue, !colors[c][2]);
}
\ No newline at end of file
diff --git a/remote.py b/remote.py
@@ -14,8 +14,8 @@ import websockets
WEBSOCKET_PORT = 1612
+host, port = 'localhost', 5000
-client = HTTPConnection('localhost', 5000)
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)
@@ -48,8 +49,6 @@ async def websocket_handler(ws, _):
if await ws.recv() == 'token':
if token:
await ws.send(token)
- else:
- await ws.send(None)
await ws.close()
diff --git a/server/forms.py b/server/forms.py
@@ -88,9 +88,7 @@ class UpdateAccountForm(FlaskForm):
class UpdateTrapForm(FlaskForm):
- mac = StringField('MAC')
name = StringField('Naam')
- location = StringField('Locatie')
submit = SubmitField('Bewerken')
diff --git a/server/models.py b/server/models.py
@@ -1,5 +1,6 @@
-from datetime import datetime
-from typing import Any, Dict, Optional
+from datetime import datetime, timedelta
+from email.policy import default
+from typing import Optional
from flask_login import UserMixin
from .app import db, login_manager
@@ -27,51 +28,59 @@ class Trap(db.Model):
id: int = db.Column(db.Integer, primary_key=True)
token: str = db.Column(db.String(16), unique=True, nullable=False)
owner: Optional[int] = db.Column(db.Integer, db.ForeignKey('user.id'))
- name: Optional[str] = db.Column(db.Text)
+ owned_date: Optional[datetime] = db.Column(db.DateTime)
+ name: str = db.Column(db.Text, nullable=False, default='n/a')
- last_status: Optional[datetime] = db.Column(db.DateTime)
- caught: Optional[bool] = db.Column(db.Boolean)
- battery: Optional[int] = db.Column(db.Integer)
- charging: Optional[bool] = db.Column(db.Boolean)
- temperature: Optional[int] = db.Column(db.Integer)
+ last_status: datetime = db.Column(db.DateTime, nullable=False)
+ caught: bool = db.Column(db.Boolean, nullable=False, default=False)
+ battery: int = db.Column(db.Integer, nullable=False, default=0)
+ 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)
- location_acc: Optional[float] = db.Column(db.Float)
location_satellites: Optional[int] = db.Column(db.Integer)
def owner_class(self) -> Optional[User]:
return User.query.get(self.owner)
- def status_color(self) -> str:
- if self.caught:
- return '#f4a900'
- return 'currentColor'
+ def offline(self):
+ return datetime.now() - self.last_status > timedelta(hours=1)
- def dict(self) -> Dict[str, Any]:
- return {c.name: getattr(self, c.name) for c in self.__table__.columns}
-
- def to_json(self, token: bool = False):
+ def to_json(self):
owner = self.owner_class()
- owner_name = owner.name if owner else '{nobody}'
+ owner_name = owner.name if owner else 'n/a'
return dict(
id=self.id,
- name=self.name or '<code>unnamed</code>',
- status=self.status_color(),
- location=self.location_acc and self.location_acc > 0,
+ name=self.name,
+ offline=self.offline(),
+ locationSearch=self.location_search,
+ locationSearching=self.location_searching,
latitude=self.location_lat,
longitude=self.location_lon,
- accuracy=self.location_acc,
+ accuracy=round(self.location_acc, 1),
satellites=self.location_satellites,
activated=self.caught,
owner=owner_name,
battery=self.battery,
charging=self.charging,
temperature=self.temperature,
- byToken=token
+ lastStatus=self.last_status.strftime('%d-%m-%y %H:%M'),
+ ownedDate=self.owned_date.strftime(
+ '%d-%m-%y %H:%M') if self.owned_date else '-'
)
+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
@@ -1,4 +1,3 @@
-from datetime import datetime
from flask import flash, redirect, render_template, request, url_for
from flask_login import current_user, login_required, login_user, logout_user
from PIL import Image
@@ -9,16 +8,10 @@ from .models import Trap, User
import secrets
import os
-import random
-import string
current_user: User
-def validate_mac(mac):
- return len(mac) == 16 and all(c in string.hexdigits for c in mac)
-
-
""" index.html (home-page) route """
@@ -149,70 +142,7 @@ def account():
@app.route('/traps')
@login_required
def traps():
- if current_user.admin:
- # clean_traps()
- query = Trap.query.all()
- else:
- query = Trap.query.filter_by(owner=current_user.id)
-
- trap_json = [trap.dict() for trap in query]
-
- return render_template('trap.html', traps=query, trap_json=trap_json)
-
-
-"""
[email protected]('/traps/connect', methods=['POST', 'GET'])
-@login_required
-def trap_connect():
- form = ConnectTrapForm()
- if form.validate_on_submit() and form.code.data:
- trap = Trap.query.filter_by(mac=form.code.data.replace(':', '').replace(
- ' ', '').lower()).filter(Trap.connect_expired > datetime.utcnow()).first()
- if not trap:
- flash('Muizenval niet gevonden', 'danger')
- return redirect(url_for('trap_connect'))
-
- trap.owner = current_user.id
- trap.connect_expired = None
- trap.connect_code = None
- db.session.commit()
- flash('Muizenval toegevoegd!', 'success')
- return redirect(url_for('traps'))
-
- return render_template('connect.html', form=form)
-"""
-
-
[email protected]('/trap/<trap_id>/update', methods=['POST', 'GET'])
-@login_required
-def trap_update(trap_id):
- form = UpdateTrapForm()
- trap = Trap.query.filter_by(mac=trap_id).first()
- if form.validate_on_submit():
- trap.name = form.name.data
- print(form.location.data)
- if form.location.data:
- trap.location_lat, trap.location_lon = form.location.data.split(
- ' ', 2)
- 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.mac.data = trap.pretty_mac()
- form.name.data = trap.name
- return render_template('updatetrap.html', form=form, trap=trap)
-
-
[email protected]('/trap/<trap_id>/delete')
-@login_required
-def trap_delete(trap_id):
- trap = Trap.query.filter_by(mac=trap_id.lower()).first()
- db.session.delete(trap)
- db.session.commit()
-
- return redirect(url_for('traps'))
+ return render_template('trap.html')
@app.route('/contact')
diff --git a/server/site.db b/server/site.db
Binary files differ.
diff --git a/server/socket.py b/server/socket.py
@@ -1,16 +1,19 @@
-from datetime import datetime, timedelta
+from datetime import datetime
+import os
import random
from typing import Dict
from flask import request, jsonify
from flask_login import current_user
-from flask_socketio import emit, Namespace
+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
-sockets: Dict[int, Namespace] = {}
+sockets: Dict[int, str] = {}
+
+accuracy_min = 80
def make_token():
@@ -29,10 +32,15 @@ def register_trap():
token = make_token()
if not Trap.query.filter_by(token=token).first():
break
- trap = Trap(token=token)
+
+ trap = Trap(token=token, last_status=datetime.now())
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
@@ -50,52 +58,46 @@ def update_status():
if not trap:
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!')
+
+ trap.last_status = datetime.now()
trap.caught = req['trap']
trap.battery = req['battery']
trap.temperature = req['temperature']
trap.charging = req['charging']
- trap.location_lat = req['latitude']
- trap.location_lon = req['longitude']
- trap.location_acc = req['accuracy']
- trap.location_satellites = req['satellites']
+ trap.location_searching = req['searching']
+ if trap.location_search:
+ trap.location_satellites = req['satellites']
+ if req['accuracy'] != 0:
+ trap.location_acc = req['accuracy']
+ trap.location_lat = req['latitude']
+ trap.location_lon = req['longitude']
db.session.commit()
if trap.owner and trap.owner in sockets:
- sockets[trap.owner].emit('trap-change', trap.to_json())
-
- return jsonify(dict())
-
-
-"""@app.route("/api/search_connect", methods=['POST', 'GET'])
-def search_connect():
- if not request.json:
- return jsonify({"error": "invalid-json"})
- # if not validate_mac(request.json['mac']):
- # return jsonify({"error": "invalid-mac"})
-
- mac = request.json['mac'].lower()
-
- trap = Trap.query.filter_by(mac=mac).first()
- if not trap:
- trap = Trap(mac=mac)
- db.session.add(trap)
+ socket.emit('trap-change', trap.to_json(), to=sockets[trap.owner])
+ socket.emit('statistics', make_statistics(
+ trap.owner), to=sockets[trap.owner])
- code = ""
- while True:
- code = ''.join(random.choice(
- '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') for _ in range(5))
- if not Trap.query.filter_by(connect_code=code).first():
- break
+ return jsonify(dict(location_search=trap.location_search))
- trap.owner = None
- trap.connect_expired = datetime.utcnow() + timedelta(minutes=5)
- trap.connect_code = code
- db.session.commit()
+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 jsonify({"error": "ok"})
-"""
+ return months
@socket.on('connect')
@@ -103,18 +105,21 @@ def socket_connect():
if not current_user.is_authenticated:
return
- sockets[current_user.id] = request.namespace # type: ignore
+ sockets[current_user.id] = request.sid # type: ignore
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():
if not current_user.is_authenticated:
return
- del sockets[current_user.id]
+ if current_user.id in sockets:
+ del sockets[current_user.id]
@socket.on('token')
@@ -122,5 +127,58 @@ def socket_token(token):
if not token or not current_user.is_authenticated:
return
- for trap in Trap.query.filter_by(token=token):
- emit('trap-change', trap.to_json(True))
+ trap: Trap = Trap.query.filter_by(token=token).first()
+ if not trap or trap.owner == current_user.id:
+ return
+
+ trap.owner = current_user.id
+ trap.owned_date = datetime.now()
+ db.session.commit()
+
+ emit('trap-change', trap.to_json())
+
+
[email protected]('location-search')
+def socket_location(data):
+ if not data or not current_user.is_authenticated:
+ return
+
+ print(data['id'])
+ trap: Trap = Trap.query.get(data['id'])
+ if not trap or trap.owner != current_user.id:
+ return
+
+ trap.location_search = data['search']
+ db.session.commit()
+
+ emit('trap-change', trap.to_json())
+
+
[email protected]('delete')
+def socket_delete(data):
+ if not data or not current_user.is_authenticated:
+ return
+
+ print(data['id'])
+ trap: Trap = Trap.query.get(data['id'])
+ if not trap or trap.owner != current_user.id:
+ return
+
+ trap.owner = False
+ db.session.commit()
+
+
[email protected]('name')
+def socket_name(data):
+ if not data or not current_user.is_authenticated:
+ return
+
+ print(data['id'])
+ trap: Trap = Trap.query.get(data['id'])
+ if not trap or trap.owner != current_user.id:
+ return
+
+ trap.name = data['name']
+ db.session.commit()
+
+ emit('trap-change', trap.to_json())
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,5 +57,12 @@
border: #aaa solid 1px;
}
+#trap-chart {
+ height: 300px;
+ position: relative;
+}
-
+i {
+ padding-left: 10px;
+ padding-right: 10px;
+}
+\ No newline at end of file
diff --git a/server/static/trap.js b/server/static/trap.js
@@ -1,56 +1,140 @@
/*
-trap: {
- id: int,
- name: string,
- status: string (color),
- location: bool,
- latitude: float,
- longitude: float,
- accuracy: float,
- activated: bool,
- owner: string,
- battery: int (percent) | null,
- charging: bool,
- temperature: int,
- byToken: bool
+trap {
+ id: int
+ name: str?
+ status: str
+ offline: bool
+ locationSearch: bool
+ latitude: float?
+ longitude: float?
+ accuracy: float?
+ satellites: int?
+ activated: bool
+ owner: str
+ battery: int
+ charging: bool
+ temperature: bool
+ lastStatus: str
+ ownedDate: str
}
*/
+const errorDelay = 2500;
+
function addTrap(trap) {
var clone,
append = false;
if (traps[trap.id]) {
+ Object.assign(traps[trap.id], trap);
+
clone = traps[trap.id].element;
} else {
- clone = document.getElementById('trap-template').content.cloneNode(true);
+ traps[trap.id] = trap;
+
+ clone = document.getElementById('trap-template').content.cloneNode(true).querySelector('article');
+ traps[trap.id].element = clone;
+ traps[trap.id].updating = false;
+ clone.id = `trap-${trap.id}`;
append = true;
}
- traps[trap.id] = trap;
- traps[trap.id].element = clone;
-
- clone.id = `trap-${trap.id}`;
-
- clone.querySelector('a.link').href = `/trap/${trap.id}/update`;
- clone.querySelector('svg').fill = trap.status;
- clone.querySelector('span.name').innerHTML = trap.name;
- clone.querySelector('span.owner').innerHTML = trap.byToken ? `<strike>${trap.owner}</strike> <a href='#'>Register!</a>` : trap.owner;
- clone.querySelector('span.accuracy').innerHTML = trap.accuracy;
- clone.querySelector('span.battery').innerHTML = trap.battery;
- clone.querySelector('span.satellites').innerHTML = trap.satellites;
- clone.querySelector('span.charging').innerHTML = trap.charging ? 'yes' : 'no';
- clone.querySelector('span.temperature').innerHTML = trap.temperature;
-
- if (append) document.getElementById('trap-container').append(clone);
-
- if (trap.location) {
- traps[trap.id].marker = L.marker([trap.latitude, trap.longitude]).addTo(map).bindPopup(trap.name);
- map.fitBounds(
- Object.values(traps)
- .filter((x) => x.location)
- .map((x) => [x.latitude, x.longitude])
- );
+ if (!traps[trap.id].updating) {
+ var statusIcons = '',
+ statusString = '',
+ statusIcon,
+ batteryIcon,
+ tempIcon;
+
+ if (trap.offline) (statusIcon = 'moon'), (statusString += 'offline');
+ 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';
+ else if (trap.battery < 55) batteryIcon = 'battery-half';
+ else if (trap.battery < 80) batteryIcon = 'battery-three-quarters';
+ else if (trap.battery < 100) batteryIcon = 'battery-full';
+ else (batteryIcon = 'plug-circle-xmark'), (statusString += ', problemen met batterij');
+ statusIcons += `<i class='fas fa-${batteryIcon}'></i>`;
+
+ if (trap.temperature > 50) (tempIcon = 'temperature-high'), (statusString += ', oververhit');
+ else if (trap.temperature < -10) (tempIcon = 'temperature-low'), (statusString += ', onderkoeld');
+ if (tempIcon) statusIcons += `<i class='fas fa-${tempIcon}'></i>`;
+
+ 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 () {
+ var nameSpan = clone.querySelector('span.name'),
+ input = nameSpan.querySelector('input');
+ if (input) {
+ clone.querySelector('a.update').innerHTML = 'bewerken';
+ traps[trap.id].updating = false;
+ socket.emit('name', { id: trap.id, name: input.value });
+ } else {
+ nameSpan.innerHTML = `<input type="entry" value="${trap.name}" />`;
+ traps[trap.id].updating = true;
+ clone.querySelector('a.update').innerHTML = 'klaar';
+ }
+ };
+ clone.querySelector('a.delete').onclick = function () {
+ socket.emit('delete', { id: trap.id });
+ clone.remove();
+ delete traps[trap.id];
+ };
+ clone.querySelector('a.location').onclick = function () {
+ socket.emit('location-search', { id: trap.id, search: !trap.locationSearch });
+ };
+
+ clone.querySelector('span.location-button').innerHTML = trap.locationSearch ? 'locatie vastzetten' : 'locatie zoeken';
+ clone.querySelector('span.status-icons').innerHTML = statusIcons;
+ clone.querySelector('span.status').innerHTML = statusString;
+ clone.querySelector('span.name').innerHTML = trap.name;
+ clone.querySelector('span.owner').innerHTML = trap.owner;
+ clone.querySelector('span.accuracy').innerHTML = trap.accuracy;
+ clone.querySelector('span.satellites').innerHTML = trap.satellites;
+ clone.querySelector('span.temperature').innerHTML = trap.temperature;
+ clone.querySelector('span.last-status').innerHTML = trap.lastStatus;
+ clone.querySelector('span.owned-date').innerHTML = trap.ownedDate;
+ if (trap.battery < 100) {
+ clone.querySelector('p.battery').style.display = 'inherit';
+ clone.querySelector('span.battery').innerHTML = trap.battery;
+ } else {
+ clone.querySelector('p.battery').style.display = 'none';
+ }
+ if (trap.locationSearch) {
+ clone.querySelector('p.accuracy').style.display = 'inherit';
+ } else {
+ clone.querySelector('p.accuracy').style.display = 'none';
+ }
+ }
+
+ if (append) document.getElementById('trap-container').appendChild(clone);
+
+ if (trap.accuracy) {
+ if (traps[trap.id].marker) {
+ traps[trap.id].marker.setLatLng([trap.latitude, trap.longitude]);
+ } else {
+ traps[trap.id].marker = L.marker([trap.latitude, trap.longitude]).addTo(map).bindPopup(trap.name);
+ map.fitBounds(
+ Object.values(traps)
+ .filter((x) => x.accuracy)
+ .map((x) => [x.latitude, x.longitude])
+ );
+ }
+ } else if (traps[trap.id].marker) {
+ traps[trap.id].marker.remove();
+ traps[trap.id].marker = undefined;
}
}
@@ -60,16 +144,16 @@ function removeTrap(trap) {
delete traps[trap.id];
}
-const successDelay = 10000;
-const errorDelay = 2500;
+
function openWebSocket() {
let ws = new WebSocket('ws://localhost:1612/');
ws.addEventListener('open', () => ws.send('token'));
ws.addEventListener('message', (evt) => (token = evt.data));
ws.addEventListener('close', () => {
- socket.emit('token', token);
- if (token) remote = true;
- setTimeout(openWebSocket, successDelay);
+ if (token) {
+ socket.emit('token', token);
+ remote = true;
+ }
});
ws.addEventListener('error', () => {
token = null;
@@ -93,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
@@ -20,10 +20,12 @@
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
- <!-- Bootstrap CSS-->
+ <!-- Bootstrap CSS
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" rel="stylesheet">
- <link rel="stylesheet" href="style.css" />
- <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" />
+ <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" />-->
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css"
+ integrity="sha512-KfkfwYDsLkIlwQp6LFnl8zNdLGxu9YAA1QvwINks4PhcElQSvqcyVLLD9aMhXd13uQjoXtEKNosOWaZqXgel0g=="
+ crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- Google Font: Source Sans Pro, Source Code Pro -->
<link rel="preconnect" href="https://fonts.googleapis.com">
@@ -42,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 %}
@@ -73,20 +76,17 @@
</div>
<ul class="list-unstyled px-2">
- <li><a href="#" class="text-decoration-none px-3 py-2 d-block text-white"><i class="fas fa-home"></i>
- Home</a></li>
+ <li><a href="{{ url_for('index') }}" class="text-decoration-none px-3 py-2 d-block text-white"><i
+ class="fas fa-home"></i>Home</a></li>
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('traps') }}" class="text-decoration-none px-3 py-2 d-block text-white"><i
- class="far fa-tachometer-alt"></i> Dashboard</a></li>
- <li><a href="#" class="text-decoration-none px-3 py-2 d-block text-white"><i class="far fa-plug"></i>
- Koppel een
- val</a></li>
+ class="fas fa-chart-line"></i> Dashboard</a></li>
<li><a href="{{ url_for('contact') }}" class="text-decoration-none px-3 py-2 d-block text-white"><i
class="far fa-address-book"></i> Contact opnemen</a></li>
{% endif %}
<li><a href="#" class="text-decoration-none px-3 py-2 d-block text-white"><i
- class="far fa-map-marker-question"></i> Over ons</a></li>
+ class="far fa-clipboard"></i> Over ons</a></li>
</ul>
<hr class="h-color mx-2">
@@ -95,51 +95,17 @@
{% if current_user.is_authenticated %}
<li class=""><a href="{{ url_for('logout') }}"
- class="text-decoration-none px-3 py-2 d-block text-white"><i class="fas fa-sign-out"></i>
+ class="text-decoration-none px-3 py-2 d-block text-white"><i
+ class="fas fa-arrow-right-from-bracket"></i>
Uitloggen</a></li>
{% else %}
<li class=""><a href="{{ url_for('login') }}"
class="text-decoration-none px-3 py-2 d-block text-white"><i
- class="fas fa-sign-out"></i>Inloggen</a></li>
+ class="fas fa-arrow-right-to-bracket"></i>Inloggen</a></li>
<li class=""><a href="{{ url_for('register') }}"
- class="text-decoration-none px-3 py-2 d-block text-white"><i class="far fa-user"></i>
+ class="text-decoration-none px-3 py-2 d-block text-white"><i class="fas fa-square-pen"></i>
Registreren</a></li>
{% endif %}
- <!--=======
-=======
->>>>>>> cdc54f9efef31c60b062d347b41fb859b414092e
- <ul class="list-unstyled px-2">
- <li class=""><a href="{{ url_for('index') }}" class="text-decoration-none px-3 py-2 d-block"><i
- class="far fa-map-marker-question"></i>Home</a></li>
- {% if current_user.is_authenticated %}
- <li class=""><a href="{{ url_for('traps') }}" class="text-decoration-none px-3 py-2 d-block"><i
- class="far fa-tachometer-alt"></i>Dashboard</a></li>
- <li class=""><a href="#" class="text-decoration-none px-3 py-2 d-block"><i class="far fa-plug"></i>Koppel
- een val</a></li>
- <li class=""><a href="{{ url_for('contact') }}" class="text-decoration-none px-3 py-2 d-block"><i
- class="far fa-address-book"></i>Contact opnemen</a></li>
- {% endif %}
- <li class=""><a href="#" class="text-decoration-none px-3 py-2 d-block"><i
- class="far fa-map-marker-question"></i>Over ons</a></li>
- </ul>
- <hr class="h-color mx-2">
-
- <ul class="list-unstyled px-2">
- {% if current_user.is_authenticated %}
- <li class=""><a href="{{ url_for('account') }}" class="text-decoration-none px-3 py-2 d-block"><i
- class="far fa-cogs"></i>Instellingen</a></li>
- <li class=""><a href="{{ url_for('logout') }}" class="text-decoration-none px-3 py-2 d-block"><i
- class="fas fa-sign-out"></i>Uitloggen</a></li>
- {% else %}
- <li class=""><a href="{{ url_for('login') }}" class="text-decoration-none px-3 py-2 d-block"><i
- class="fas fa-sign-out"></i>Inloggen</a></li>
- <li class=""><a href="{{ url_for('register') }}" class="text-decoration-none px-3 py-2 d-block"><i
- class="fas fa-sign-out"></i>Registeren</a></li>
- {% endif %}
-<<<<<<< HEAD
->>>>>>> cdc54f9efef31c60b062d347b41fb859b414092e
-=======
->>>>>>> cdc54f9efef31c60b062d347b41fb859b414092e-->
</ul>
<hr class="h-color mx-2">
</div>
diff --git a/server/templates/trap.html b/server/templates/trap.html
@@ -7,57 +7,36 @@
<div id="trap-map"></div>
</div>
</article>
- {#}
- {% for trap in traps %}
<article class="media content-section">
<div class="media-body">
- <h3><a class="article-title" href="{{ url_for('trap_update', trap_id=trap.mac) }}">
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="{{ trap.status_color() }}"
- class="bi bi-circle-fill" viewBox="0 0 20 20">
- <circle cx="10" cy="10" r="10" />
- </svg>
- -
- {% if trap.name %}
- {{ trap.name }}
- {% else %}
- <code>[{{ trap.pretty_mac() }}]</code>
- {% endif %}
- </a>
- </h3>
- {% if trap.name %}
- <p>
- <code>[{{ trap.pretty_mac() }}]</code>
- </p>
- {% endif %}
- {% if trap.owner %}
- <b>
- van {{ trap.owner_class().name }}
- </b>
- {% endif %}
+ <h2 style="text-align:center;">activiteit in muizen per maand</h2>
+ <div id="trap-chart"></div>
</div>
</article>
- {% endfor %}
- {#}
</div>
<template id="trap-template">
<article class="media content-section">
<div class="media-body">
<h3><a class="article-title link">
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class="bi bi-circle-fill"
- viewBox="0 0 20 20">
- <circle cx="10" cy="10" r="10" />
- </svg>
- -
+ <span class="status-icons"></span>
+ |
<span class="name"></span>
</a>
</h3>
- <b>
- van <span class="owner"></span>
- </b>
- <p>Battery: <span class="battery"></span></p>
- <p>Charging: <span class="charging"></span></p>
- <p>Accuracy: <span class="accuracy"></span>% (<span class="satellites"></span> satellites)</p>
- <p>Temperature: <span class="temperature"></span>°C</p>
+ <p><i>
+ van <b><span class="owner"></span></b></span>
+ </i></p>
+ <p><b><span class="status"></span></b></p>
+ <p><i class="fas fa-face-smile"></i> geregistreerd sinds <span class="owned-date"></p>
+ <p><i class="fas fa-wave-square"></i> laaste update om <span class="last-status"></span></p>
+ <p class="accuracy"><i class="fas fa-location"></i> nauwkeurigheid: <span class="accuracy"></span>%
+ met <span class="satellites"></span> satelliet(en)</p>
+ <p><i class="fas fa-temperature-half"></i> temperatuur: <span class="temperature"></span>°C</p>
+ <p class="battery"><i class="fas fa-battery-half"></i> batterij: <span class="battery"></span>%</p>
+
+ <a class="btn btn-primary update" href="javascript:void(0)">bewerken</a>
+ <a class="btn btn-secondary location" href="javascript:void(0)"><span class="location-button"></span></a>
+ <a class="btn btn-danger delete" href="javascript:void(0)">verwijderen</a>
</div>
</article>
</template>
diff --git a/server/templates/updatetrap.html b/server/templates/updatetrap.html
@@ -8,19 +8,6 @@
<h1>{{ legend }}</h1>
</legend>
<div class="form-group">
- {{ form.mac.label(class="form-control-label") }}
- {% if form.mac.errors %}
- {{ form.mac(class="form-control form-control-lg is-invalid") }}
- <div class="invalid-feedback">
- {% for error in form.mac.errors %}
- <span>{{ error }}</span>
- {% endfor %}
- </div>
- {% else %}
- {{ form.mac(disabled=True, class="form-control form-control-lg") }}
- {% endif %}
- </div>
- <div class="form-group">
{{ form.name.label(class="form-control-label") }}
{% if form.name.errors %}
{{ form.name(class="form-control form-control-lg is-invalid") }}
@@ -33,60 +20,11 @@
{{ form.name(class="form-control form-control-lg") }}
{% endif %}
</div>
- <div class="form-group">
- {{ form.location.label(class="form-control-label") }}
- {% if form.location.errors %}
- {{ form.location(class="form-control form-control-lg is-invalid") }}
- <div class="invalid-feedback">
- {% for error in form.location.errors %}
- <span>{{ error }}</span>
- {% endfor %}
- </div>
- {% else %}
- {{ form.location(id='location-input', readonly=True, class="form-control form-control-lg") }}
- {% endif %}
- </div>
</fieldset>
- <p>
- <div id="trap-map"></div>
- </p>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
- <a class="btn btn btn-danger" href="{{ url_for('trap_delete', trap_id=trap.mac) }}">Verwijderen</a>
+ <a class="btn btn btn-danger" href="{{ url_for('trap_delete', trap_id=trap.id) }}">Verwijderen</a>
</div>
</form>
</div>
-<script type="text/javascript">
- var trap = {{ trap.dict() | tojson }};
-
- var map = L.map('trap-map').setView([52.283333, 5.666667], 7);
-
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
- attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
- }).addTo(map);
-
-
- let marker = null;
-
- function setMarker(locArg) {
- var loc = L.latLng(locArg);
- if (marker) {
- marker.setLatLng(loc);
- } else {
- marker = L.marker(loc).addTo(map);
- }
-
- document.getElementById('location-input').value = `${loc.lat} ${loc.lng}`;
- }
-
- if (trap.location_lat && trap.location_lon) {
- setMarker([trap.location_lat, trap.location_lon]);
- }
-
- function onMapClick(e) {
- setMarker(e.latlng);
- }
-
- map.on('click', onMapClick);
-</script>
{% endblock content %}
\ No newline at end of file