Merged changed to routing

This commit is contained in:
Viktor Søndergaard 2019-04-06 23:31:43 +02:00
commit 4634409738
21 changed files with 207 additions and 123 deletions

View File

@ -1,14 +1,3 @@
<AbsoluteLayout> <GridLayout>
<ns-side-drawer></ns-side-drawer> <page-router-outlet></page-router-outlet>
</GridLayout>
<ns-locationButton (tap)=onLocationTap($event)></ns-locationButton>
<StackLayout class="float-btn-container">
<ns-my-button (tap)=onTap($event) text="Nightr"></ns-my-button>
</StackLayout>
<StackLayout>
<ns-locationButton></ns-locationButton>
<ns-camera-button></ns-camera-button>
</StackLayout>
</AbsoluteLayout>

View File

@ -1,65 +1,8 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import * as dialogs from "tns-core-modules/ui/dialogs";
import { TouchGestureEventData, GestureEventData } from 'tns-core-modules/ui/gestures'
import { Location } from "nativescript-geolocation";
import { MyHttpPostService } from './services/my-http-post-service'
import { MyGeoLocationService} from './services/my-geo-location.service';
import { MyBatteryInfoService } from './services/my-battery-info.service';
import { MyCameraService } from './services/my-camera-service'
@Component({ @Component({
selector: "ns-app", selector: "ns-app",
moduleId: module.id, moduleId: module.id,
templateUrl: "./app.component.html", templateUrl: "./app.component.html",
styleUrls: ['./app.component.css'],
providers: [MyHttpPostService]
}) })
export class AppComponent { export class AppComponent {}
returnMessage: string = "";
myReturnJSON: JSON;
locationData: Location;
myPicture: String;
image: any;
constructor(private myHttpPostSerivce: MyHttpPostService,
private geoLocationService: MyGeoLocationService,
private batterInfoService: MyBatteryInfoService,
private cameraService: MyCameraService) { }
public onTap(args: GestureEventData): Promise<void> {
return this.cameraService.takePicture().
then(picture => {
this.image = JSON.stringify(picture);
//console.log('this is picture in json', JSON.stringify(picture));
this.getLocation();
})
}
public getLocation(): any {
this.geoLocationService.getLocation().then(location => {
this.locationData = location;
//console.log('this is locationData', this.locationData);
this.submit();
}).catch(error => {
});
}
public onLocationTap(args: GestureEventData): any {
//console.log('This should be batterinfo', this.batterInfoService.getPowerPercent());
}
public submit(): void {
this.makePostRequest();
}
private makePostRequest(): void {
this.myHttpPostSerivce
.postData({ position: this.locationData, image: this.image })
.subscribe(res => {
//console.log('This is res', res);
this.myReturnJSON = (<any>res).json.data.username;
//console.log('THis is myreturnJSON', this.myReturnJSON);
});
}
}

View File

@ -1,11 +1,14 @@
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { AppComponent } from "./app.component"; import { AppComponent } from "./app.component";
import { HomePageComponent } from "./home-page/home-page.component";
import { MyButtonComponent } from './component/my-button/my-button.component'; import { MyButtonComponent } from './component/my-button/my-button.component';
import { NativeScriptHttpClientModule } from "nativescript-angular/http-client"; import { NativeScriptHttpClientModule } from "nativescript-angular/http-client";
import { MyLocationButtonComponent } from './component/locationButton/locationButton.component'; import { MyLocationButtonComponent } from './component/locationButton/locationButton.component';
import { CameraButtonComponent } from './component/camera-button/camera-button.component'; import { CameraButtonComponent } from './component/camera-button/camera-button.component';
import { ResultPageComponent } from './result-page/result-page.component';
// Uncomment and add to NgModule imports if you need to use two-way binding // Uncomment and add to NgModule imports if you need to use two-way binding
// import { NativeScriptFormsModule } from "nativescript-angular/forms"; // import { NativeScriptFormsModule } from "nativescript-angular/forms";
@ -18,6 +21,12 @@ import { CameraButtonComponent } from './component/camera-button/camera-button.c
AppComponent AppComponent
], ],
imports: [ imports: [
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot([
{ path: "", redirectTo: "/home-page", pathMatch: "full" },
{ path: "home-page", component: HomePageComponent},
{ path: "result-page", component: ResultPageComponent}
]),
NativeScriptModule, NativeScriptModule,
NativeScriptHttpClientModule, NativeScriptHttpClientModule,
], ],
@ -26,6 +35,8 @@ import { CameraButtonComponent } from './component/camera-button/camera-button.c
MyLocationButtonComponent, MyLocationButtonComponent,
MyButtonComponent, MyButtonComponent,
CameraButtonComponent, CameraButtonComponent,
ResultPageComponent,
HomePageComponent,
], ],
providers: [], providers: [],
schemas: [ schemas: [

View File

@ -0,0 +1,5 @@
.float-btn-container
{
margin-top: 35%;
margin-left: 20%;
}

View File

@ -0,0 +1,14 @@
<ActionBar title="Home" class="action-bar"></ActionBar>
<ScrollView class="page">
<AbsoluteLayout>
<StackLayout class="float-btn-container">
<ns-my-button (tap)=onTap($event) text="Nightr"></ns-my-button>
</StackLayout>
<StackLayout>
<ns-locationButton></ns-locationButton>
<ns-camera-button></ns-camera-button>
<Button class="btn btn-primary" text="Result page" [nsRouterLink]="['/result-page']"></Button>
</StackLayout>
</AbsoluteLayout>
</ScrollView>

View File

@ -0,0 +1,49 @@
import { Component, OnInit } from "@angular/core";
import * as dialogs from "tns-core-modules/ui/dialogs";
import { MyHttpPostService } from '../services/my-http-post-service'
import { RouterExtensions } from "nativescript-angular/router";
import { TouchGestureEventData, GestureEventData } from 'tns-core-modules/ui/gestures'
import { isEnabled, enableLocationRequest, getCurrentLocation, watchLocation, distance, clearWatch } from "nativescript-geolocation";
@Component({
selector: "home-page",
moduleId: module.id,
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.css'],
providers: [MyHttpPostService]
})
export class HomePageComponent implements OnInit {
public user: string = "";
public pass: string = "";
returnMessage: string = "";
constructor(private myHttpPostSerivce: MyHttpPostService, private routerExtensions: RouterExtensions) { }
public onTap(args: GestureEventData): any {
this.routerExtensions.navigateByUrl("/result-page");
this.submit();
dialogs.confirm("Should be result").then(result => {
console.log("Dialog result: " + result);
});
}
public submit(): void {
this.makePostRequest();
}
private makePostRequest(): void {
console.log('Reached makepostRequest');
this.myHttpPostSerivce
.postData({ username: this.user, password: this.pass })
.subscribe(res => {
console.log('This is res', res);
this.returnMessage = (<any>res).json.data.username;
});
}
ngOnInit(): void {
}
goBack(): void {
this.routerExtensions.back();
}
}

View File

@ -0,0 +1 @@
/* Add mobile styles for the component here. */

View File

@ -0,0 +1,7 @@
<ActionBar title="Result" class="action-bar"></ActionBar>
<ScrollView class="page">
<AbsoluteLayout>
<Button class="btn btn-primary" text="Home" [nsRouterLink]="['/home-page']"></Button>
</AbsoluteLayout>
</ScrollView>

View File

@ -0,0 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { RouterExtensions } from 'nativescript-angular/router';
@Component({
selector: 'result-page',
templateUrl: './result-page.component.html',
styleUrls: ['./result-page.component.css'],
moduleId: module.id,
})
export class ResultPageComponent implements OnInit {
constructor(private routerExtensions: RouterExtensions) { }
ngOnInit(): void {
}
goBack(): void {
this.routerExtensions.back();
}
}

View File

@ -10,7 +10,7 @@ from typing import List
import requests_cache import requests_cache
from flask import Flask, jsonify, logging, request from flask import Flask, jsonify, logging, request
from .strategies import miloStrats, iss, cars_in_traffic, tide_strat, upstairs_neighbour, bing from .strategies import miloStrats, iss, cars_in_traffic, tide_strat, upstairs_neighbour, bing, battery
from .util import Context from .util import Context
app = Flask(__name__) app = Flask(__name__)
@ -30,6 +30,7 @@ strategies = {
"tide": tide_strat.is_tide, "tide": tide_strat.is_tide,
"upstairs_neighbour": upstairs_neighbour.check_games, "upstairs_neighbour": upstairs_neighbour.check_games,
"bing": bing.clock, "bing": bing.clock,
"battery_level": battery.battery_level,
} }
@ -78,14 +79,13 @@ def probabilities():
prediction["night"] = not prediction["night"] prediction["night"] = not prediction["night"]
# Calculate contributions of predictions # Calculate contributions of predictions
consensus_weight_sum = sum(p["weight"] for p in predictions if p["night"] == night) weight_sum = sum(p["weight"] for p in predictions)
for prediction in predictions: for prediction in predictions:
# If this prediction agrees with the consensus it contributed prediction["contribution"] = prediction["weight"] / weight_sum
if prediction["night"] == night:
prediction["contribution"] = prediction["weight"] / consensus_weight_sum
else:
prediction["contribution"] = 0.0
# If this prediction disagrees with the consensus it contributed negatively
if prediction["night"] != night:
prediction["contribution"] *= -1
return jsonify({ return jsonify({
"predictions": predictions, "predictions": predictions,
"weighted_probabilities_mean": mean, "weighted_probabilities_mean": mean,

View File

@ -0,0 +1,21 @@
from ..util import Context, Prediction
def battery_level(context: Context) -> Prediction:
"""
If the battery is low, it's probably bedtime soon.
"""
p = Prediction()
if context.battery > 60:
p.reasons.append("Battery level's good, so it's probably still early in the day.")
elif context.battery > 30:
p.reasons.append("Battery level's getting low, so it's probably around dinnertime.")
elif context.battery > 10:
p.reasons.append("Your phone is dying, so it's bedtime soon?")
else:
p.reasons.append("Your phone's practically dead, so it's probably around four in the morning.")
p.probability = 1 - (context.battery / 100) # night is inverse proportional to battery level
return p

View File

@ -11,7 +11,7 @@ def clock(context: Context) -> Prediction:
It's nighttime if Bing says it's daytime. It's nighttime if Bing says it's daytime.
""" """
p = Prediction() p = Prediction()
p.weight = 0.5 p.weight = 0.02
headers = { headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'} 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'}
@ -22,12 +22,12 @@ def clock(context: Context) -> Prediction:
time = datetime.strptime(time_str, "%H:%M") time = datetime.strptime(time_str, "%H:%M")
night = time.hour < 6 or time.hour >= 22 night = time.hour < 6 or time.hour >= 22
time_description = "" if night else "daytime" time_description = "nighttime" if night else "daytime"
time_description_oppersite = "daytime" if night else "nighttime" time_description_oppersite = "daytime" if night else "nighttime"
p.reasons.append(f"Bing says its {time_description}.") p.reasons.append(f"Bing says its {time_description}.")
p.reasons.append(f"We don't really trust it.") p.reasons.append(f"But we don't really trust it (who does?).")
p.reasons.append(f"Let's guess its {time_description_oppersite}.") p.reasons.append(f"Let's guess it's {time_description_oppersite}.")
p.probability = 1 - p.probability p.probability = 1 - p.probability

View File

@ -27,13 +27,13 @@ def cars_in_traffic(context: Context) -> Prediction:
diff = day_avr - night_avr diff = day_avr - night_avr
if curr_avg >= day_avr: if curr_avg >= day_avr:
p.reasons.append(f"Because {curr_avg} cars are driving around Aarhus right now and {day_avr} is the expected number for daytime") p.reasons.append(f"Because {curr_avg:.1f} cars are driving around Aarhus right now and {day_avr:.1f} is the expected number for daytime")
p.probability = 0.0 p.probability = 0.0
elif curr_avg <= night_avr: elif curr_avg <= night_avr:
p.reasons.append(f"Because {curr_avg} cars are driving around Aarhus right now and {night_avr} is the expected number for nighttime") p.reasons.append(f"Because {curr_avg:.1f} cars are driving around Aarhus right now and {night_avr:.1f} is the expected number for nighttime")
p.probability = 1.0 p.probability = 1.0
else: else:
p.reasons.append(f"Because average for daytime is {day_avr} and average for nighttime is {night_avr}, but the current average is {curr_avg}") p.reasons.append(f"Because average for daytime is {day_avr:.1f} and average for nighttime is {night_avr:.1f}, but the current average is {curr_avg:.1f}")
res = 1 - curr_avg / diff res = 1 - curr_avg / diff
p.probability = res p.probability = res

View File

@ -1,5 +1,4 @@
import itertools import itertools
import logging
from datetime import datetime from datetime import datetime
from math import pi, sqrt, sin, cos, atan2 from math import pi, sqrt, sin, cos, atan2
@ -14,7 +13,7 @@ tf = TimezoneFinder(in_memory=True)
def night_on_iss(context: Context) -> Prediction: def night_on_iss(context: Context) -> Prediction:
""" """
It is night if it is night on the ISS and it is currently orbiting above us. It is night if it is night on the ISS and it is currently orbiting above us. http://www.isstracker.com/
""" """
p = Prediction() p = Prediction()

View File

@ -17,7 +17,7 @@ def is_restaurant_open(name, open, close) -> Prediction:
soup = BeautifulSoup(r.content, features='html5lib') soup = BeautifulSoup(r.content, features='html5lib')
listing_groups = soup.find_all('div', {'class': 'listing-group'}) listing_groups = soup.find_all('div', {'class': 'listing-group'})
p.reasons.append("Hopefully we are not banned from Just-eat ..") #p.reasons.append("Hopefully we are not banned from Just-eat ..")
nice_group = None nice_group = None
for x in listing_groups: for x in listing_groups:
@ -32,10 +32,12 @@ def is_restaurant_open(name, open, close) -> Prediction:
all_listings = nice_group.find_all('a', {'class': 'mediaElement'}) all_listings = nice_group.find_all('a', {'class': 'mediaElement'})
if any(name in x['href'] for x in all_listings): if any(name in x['href'] for x in all_listings):
p.reasons.append(f"{name} is currently open. We conclude from this, that there is {1 / 11}% chance of it being night outside!") p.reasons.append(f"Our favorite pizza place, {name}, is currently open.")
p.reasons.append(f"We conclude from this, that there is {1 / 11}% chance of it being night outside")
p.probability = 1 / 11 p.probability = 1 / 11
else: else:
p.reasons.append(f"{name} is not open. We can conclude from this, that there is {1 - (1/11)}% chance of it currently being night outside! ") p.reasons.append(f"Our favorite pizza place, {name}, is closed.")
p.reasons.append(f"We can conclude from this, that there is {1 - (1/11)}% chance of it currently being night outside!")
p.probability = 1 - (1 / 11) p.probability = 1 - (1 / 11)
return p return p

View File

@ -11,15 +11,18 @@ def camImgStrat(context : Context) -> Prediction:
The contents of the camera image The contents of the camera image
""" """
img = context.image img = context.image
average = img.mean() average = float(img.mean())
p = Prediction() p = Prediction()
p.weight = 0.7 p.weight = 1.0
if average < 100:
p.probability = 1.0 p.probability = 1 - round((average/255),3)
p.reasons.append('Image was dark') if average < 128:
p.weight = round(1 - (average/255), 3)
p.reasons.append('Camera image was dark, so the sun has probably set.')
else: else:
p.reasons.append('Image was light') p.weight = round(average / 255, 3)
p.probability = 0.0 p.reasons.append('Camera image was light, so the sun is still shining.')
return p return p
@ -34,10 +37,10 @@ def australiaStrat(context : Context) -> Prediction:
if hour > 22 or hour < 6: if hour > 22 or hour < 6:
p.probability = 0.0 p.probability = 0.0
p.reasons.append('It\'s night-time in Australia') p.reasons.append('It\'s night-time in Australia, so it must be day-time here.')
else: else:
p.probability = 1.0 p.probability = 1.0
p.reasons.append('It\'s day-time in Australia') p.reasons.append('It\'s day-time in Australia, so it must be night-time here.')
return p return p
@ -46,6 +49,7 @@ def tv2newsStrat(context : Context) -> Prediction:
The number of articles releases in the last few hours on TV2.dk The number of articles releases in the last few hours on TV2.dk
""" """
r = requests.get('http://mpx.services.tv2.dk/api/latest') r = requests.get('http://mpx.services.tv2.dk/api/latest')
data = r.json() data = r.json()
publish_dates = [(x['pubDate'])//1000 for x in data][:10] publish_dates = [(x['pubDate'])//1000 for x in data][:10]
delta_times = [] delta_times = []

Binary file not shown.

View File

@ -6,10 +6,11 @@ import json
import numpy as np import numpy as np
from server.nightr.strategies.strat_utils import write_json from .strat_utils import write_json
from ..util import Context, Prediction
def find_data(time): def write_data(time):
write_json("https://portal.opendata.dk/api/3/action/datastore_search?resource_id=2a82a145-0195-4081-a13c-b0e587e9b89c", "parking_aarhus", time) write_json("https://portal.opendata.dk/api/3/action/datastore_search?resource_id=2a82a145-0195-4081-a13c-b0e587e9b89c", "parking_aarhus", time)
def load_data(): def load_data():
@ -18,7 +19,7 @@ def load_data():
Y = [] Y = []
for filename in glob.glob("parking_aarhus*"): for filename in glob.glob("parking_aarhus*"):
p_class = '2330' in filename p_class = '2235' in filename
with open(filename) as file: with open(filename) as file:
data = json.load(file) data = json.load(file)
@ -32,13 +33,26 @@ def load_data():
def train(): def train():
X, Y = load_data() X, Y = load_data()
classifier = svm.SVC(C=10, gamma=0.01, probability=True) classifier = svm.SVC(gamma=0.01, probability=True)
classifier.fit(X, Y) classifier.fit(X, Y)
joblib.dump(classifier, "nightness_classifier.pkl") joblib.dump(classifier, "nightness_classifier.pkl")
def predict(X): def predict(X):
classifier = joblib.load("nightness_classifier.pkl") classifier = joblib.load("nightness_classifier.pkl")
prob = classifier.predict_proba(X) prob = classifier.predict_proba(np.array(X).reshape(1, -1))
return prob[0, 1] return prob[0, 1]
train()
def perform_svm_pred(context: Context) -> Prediction:
p = Prediction()
data = requests.get('https://portal.opendata.dk/api/3/action/datastore_search?resource_id=2a82a145-0195-4081-a13c-b0e587e9b89c')
records = data.json()['result']['records']
X = [house['vehicleCount'] / house['totalSpaces'] for house in records]
X = [min(x, 1) for x in X]
p.reasons.append("We only have two data points")
p.reasons.append("Our only two data points have 11 dimensions")
p.reasons.append("We are using a SVM")
p.probability = predict(X)
return p

View File

@ -19,10 +19,9 @@ def is_tide(context: Context) -> Prediction:
month, cur_year_total_cars, last_year_total_cars = determine_month() month, cur_year_total_cars, last_year_total_cars = determine_month()
month = int(month) month = int(month)
p.reasons.append(f"Because the month is f{calendar.month_name[month]}") p.reasons.append(f"The month is {calendar.month_name[month]}")
p.reasons.append(f"Because the number of cars having driven on the Storbæltsbro is f{cur_year_total_cars}") p.reasons.append(f"The number of cars having driven on the Storbæltsbro is {cur_year_total_cars}, in the current year")
p.reasons.append(f"And because the number of cars having driven over it in the last year is f{last_year_total_cars}") p.reasons.append(f"The number of cars having driven over it in the last year is {last_year_total_cars}, thus the frequency is: {last_year_total_cars / cur_year_total_cars}")
tide_data = requests.get('https://www.dmi.dk/fileadmin/user_upload/Bruger_upload/Tidevand/2019/Aarhus.t.txt') tide_data = requests.get('https://www.dmi.dk/fileadmin/user_upload/Bruger_upload/Tidevand/2019/Aarhus.t.txt')
@ -47,27 +46,27 @@ def is_tide(context: Context) -> Prediction:
average_delta = timedelta(seconds=average_inc) average_delta = timedelta(seconds=average_inc)
if last_match[1] < 0 and last_match[1] < current_water_level: # Increasing if last_match[1] < 0 and last_match[1] <= current_water_level: # Increasing
time = last_match time = last_match
while time[1] != current_water_level: while time[1] != current_water_level:
time[0] += average_delta time[0] += average_delta
time[1] += 1 time[1] += 1
elif last_match[1] < 0 and last_match[1] > current_water_level: elif last_match[1] < 0 and last_match[1] >= current_water_level:
time = last_match time = last_match
while time[1] != current_water_level: while time[1] != current_water_level:
time[0] += average_delta time[0] += average_delta
time[1] -= 1 time[1] -= 1
elif last_match[1] > 0 and last_match[1] > current_water_level: # Decreasing elif last_match[1] > 0 and last_match[1] >= current_water_level: # Decreasing
time = last_match time = last_match
while time[1] != current_water_level: while time[1] != current_water_level:
time[0] += average_delta time[0] += average_delta
time[1] -= 1 time[1] -= 1
elif last_match[1] > 0 and last_match[1] < current_water_level: elif last_match[1] > 0 and last_match[1] <= current_water_level:
time = last_match time = last_match
while time[1] != current_water_level: while time[1] != current_water_level:
@ -78,9 +77,9 @@ def is_tide(context: Context) -> Prediction:
moments.append(time[0]) moments.append(time[0])
night = sum([1 for x in moments if 6 >= x.hour or x.hour >= 22]) night = sum([1 for x in moments if 6 >= x.hour or x.hour >= 22])
p.reasons.append(f"The water level is currently at {current_water_level}")
p.reasons.append(f"The number of times the water is at the current level at nighttime is: {night}, compared to the total amount of times in {calendar.month_name[month]}, being {len(moments)}")
p.reasons.append(f"And because the number of times the water is at the current level at nighttime is: {night}, compared to the total amount of times in {calendar.month_name[month]}, being {len(moments)}") p.probability = 1 - (night / len(moments))
p.probability = night / len(moments)
return p return p

View File

@ -1,12 +1,17 @@
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from datetime import datetime from datetime import datetime, timedelta
from ..util import Prediction, Context from ..util import Prediction, Context
last_update = datetime.min
def update(): def update():
global last_update
now = datetime.utcnow()
if (now - timedelta(minutes=5)) > last_update:
requests.post('https://euw.op.gg/summoner/ajax/renew.json/', data={'summonerId': 34009256}) requests.post('https://euw.op.gg/summoner/ajax/renew.json/', data={'summonerId': 34009256})
last_update = now
def check_games(context: Context) -> Prediction: def check_games(context: Context) -> Prediction:
@ -29,12 +34,12 @@ def check_games(context: Context) -> Prediction:
last_game_in_hours = (((datetime.now() - last_played_game).seconds)/60/60) last_game_in_hours = (((datetime.now() - last_played_game).seconds)/60/60)
if last_game_in_hours < 2: if last_game_in_hours < 2:
p.reasons.append("Alexanders upstairs neighbour is currently playing league") p.reasons.append("Alexander's upstairs neighbour is currently playing league")
p.probability = 0.8 p.probability = 0.8
else: else:
last_game_in_hours = min(24.0, last_game_in_hours) last_game_in_hours = min(24.0, last_game_in_hours)
p.reasons.append(f"Alexanders upstairs neighbour has not played league for {last_game_in_hours} hours!") p.reasons.append(f"Alexanders upstairs neighbour has not played league for {last_game_in_hours:.2f} hours!")
p.probability = 1 - (last_game_in_hours / 24) p.probability = 1 - (last_game_in_hours / 24)
return p return p

View File

@ -9,8 +9,8 @@ import numpy as np
@dataclass @dataclass
class Context: class Context:
battery: int = 100 battery: int = 55
position: Dict[str, float] = field(default_factory=lambda: {'latitude': 53.0, 'longitude': 9.0}) position: Dict[str, float] = field(default_factory=lambda: {'latitude': 53.0, 'longitude': 9.0}) # Denmark somewhere
image: np.ndarray = None image: np.ndarray = None
# App settings # App settings