flockompass/main.py

390 lines
13 KiB
Python
Raw Normal View History

2020-07-17 02:12:36 +00:00
from plyer import gps
from kivy.app import App
2020-07-16 22:42:33 +00:00
from kivy.clock import Clock
from kivy.core.window import Window
2020-08-11 21:59:55 +00:00
from kivy.properties import DictProperty, NumericProperty, StringProperty
2020-07-16 18:43:28 +00:00
from kivy.clock import mainthread
from kivy.utils import platform
2020-08-11 04:50:25 +00:00
from kivy_garden.mapview import MapView
from kivy.uix.screenmanager import ScreenManager, Screen, RiseInTransition, SlideTransition
2020-08-28 17:22:30 +00:00
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
2020-07-17 00:17:49 +00:00
from kivy.vector import Vector
from kivy.animation import Animation
2020-08-11 05:27:57 +00:00
from math import atan2, sin, cos, degrees, floor
2020-09-16 02:01:54 +00:00
import requests
2020-08-11 21:59:55 +00:00
from geopy.distance import geodesic
from os import path
import pickle
2020-09-16 02:01:54 +00:00
FLOCK_SERVER="http://192.168.0.8:5000"
2020-08-28 17:22:30 +00:00
class IconButton(ButtonBehavior, Image):
pass
2020-08-04 22:20:08 +00:00
class MapScreen(Screen):
pass
2020-08-05 02:52:20 +00:00
2020-07-17 00:17:49 +00:00
class CompassScreen(Screen):
def __init__(self, **kwargs):
super(CompassScreen, self).__init__(**kwargs)
Window.bind(on_keyboard=self.android_back_click)
2020-08-30 20:02:01 +00:00
def android_back_click(self, window, key, *largs):
2020-08-30 20:02:01 +00:00
if key == 27 and self.parent is not None:
self.parent.current='map'
2020-08-30 20:02:01 +00:00
return True
2020-08-30 19:18:01 +00:00
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
Window.bind(on_keyboard=self.android_back_click)
def android_back_click(self, window, key, *largs):
if key == 27 and self.parent is not None:
self.parent.transition = SlideTransition(direction='left')
self.parent.current='map'
2020-08-30 20:02:01 +00:00
return True
2020-08-30 19:18:01 +00:00
class FlockompassApp(App):
gps_data = DictProperty()
session_data = DictProperty()
2020-08-11 04:50:25 +00:00
needle_angle = NumericProperty(0)
2020-08-11 05:27:57 +00:00
fbearing = NumericProperty(0)
dbearing = NumericProperty(0)
2020-08-28 04:40:20 +00:00
dest_distance = StringProperty("")
2020-08-30 20:02:01 +00:00
dashboard_dest = StringProperty("")
dashboard_flock = StringProperty("")
2020-08-11 21:59:55 +00:00
2020-09-03 18:38:41 +00:00
theme = DictProperty(
{'needle': "assets/needle_dark.png",
'to_map': "assets/to_map_dark.png",
'bgcolor': "#336645"})
def dump(self, dt):
print(dt, self.gps_data, self.session_data)
def read_settings(self):
app_folder = os.path.dirname(os.path.abspath(__file__))
pickle_path = path.join(app_folder, 'settings.pickle')
if path.isfile(pickle_path):
with open(pickle_path, 'rb') as f:
settings = pickle.load(f)
else:
settings = {'settings_smoothing': 0.88,
2020-09-03 19:08:34 +00:00
'settings_compass_update': 0.777,
'settings_theme': "dark"
}
with open(pickle_path, 'wb') as f:
pickle.dump(settings, f)
self.session_data.update(settings)
2020-09-03 19:08:34 +00:00
if self.session_data['settings_theme'] == "dark":
self.set_dark_theme(True)
else:
self.set_dark_theme(False)
def save_settings(self):
app_folder = os.path.dirname(os.path.abspath(__file__))
pickle_path = path.join(app_folder, 'settings.pickle')
settings = {k: self.session_data[k]
for k in self.session_data
if k.startswith('settings_')}
with open(pickle_path, 'wb') as f:
pickle.dump(settings, f)
2020-08-30 20:22:19 +00:00
def set_smoothing(self):
self.session_data['settings_smoothing'] = self.ss.ids.slider_smoothing.value
self.save_settings()
2020-08-30 20:22:19 +00:00
2020-08-30 20:41:04 +00:00
def set_compass_update(self):
self.session_data['settings_compass_update'] = self.ss.ids.slider_compass_update.value
2020-08-30 20:41:04 +00:00
self.compass_disable()
self.compass_enable()
self.save_settings()
2020-08-30 20:41:04 +00:00
def set_destination(self):
2020-08-11 04:50:25 +00:00
self.compass_enable()
self.session_data['dest_lat'] = self.ms.ids.centermark.lat
self.session_data['dest_lon'] = self.ms.ids.centermark.lon
self.ms.ids.mapview.center_on(self.session_data['dest_lat'],
self.session_data['dest_lon'])
2020-07-17 00:17:49 +00:00
2020-09-03 18:38:41 +00:00
def set_dark_theme(self, active):
if active:
self.theme = {'needle': "assets/needle_dark.png",
'to_map': "assets/to_map_dark.png",
2020-09-03 19:08:34 +00:00
'bgcolor': "#336645",
'fgcolor': "#f3e8d2"
}
self.session_data['settings_theme'] = 'dark'
2020-09-03 18:38:41 +00:00
else:
self.theme = {'needle': "assets/needle_bright.png",
'to_map': "assets/to_map_bright.png",
2020-09-03 19:08:34 +00:00
'bgcolor': "#f3e8d2",
'fgcolor': "#346645"
}
self.session_data['settings_theme'] = 'bright'
self.save_settings()
2020-09-03 18:38:41 +00:00
def request_android_permissions(self):
from android.permissions import request_permissions, Permission
2020-08-05 02:52:20 +00:00
def callback(permissions, results):
if all([res for res in results]):
2020-08-30 20:22:19 +00:00
print("All permissions granted.")
else:
2020-08-30 20:22:19 +00:00
print("Some permissions refused.", results)
2020-08-05 02:52:20 +00:00
request_permissions([Permission.ACCESS_COARSE_LOCATION,
Permission.ACCESS_FINE_LOCATION],
callback)
2020-08-05 02:52:20 +00:00
def gps_start(self, minTime, minDistance):
2020-07-17 00:17:49 +00:00
gps.start(minTime, minDistance)
2020-08-05 02:52:20 +00:00
def gps_stop(self):
2020-07-17 00:17:49 +00:00
gps.stop()
2020-07-16 18:43:28 +00:00
@mainthread
def on_location(self, **kwargs):
self.gps_data = kwargs
2020-08-11 21:59:55 +00:00
self.dump(1)
2020-08-28 04:40:20 +00:00
if ('dest_lat' not in self.session_data
and
'dest_lon' not in self.session_data):
self.session_data['dest_lat'] = self.gps_data['lat']
self.session_data['dest_lon'] = self.gps_data['lon']
2020-07-17 02:12:36 +00:00
self.ms.ids.mapview.center_on(self.session_data['dest_lat'],
self.session_data['dest_lon'])
2020-08-11 05:27:57 +00:00
else:
2020-08-28 23:56:32 +00:00
dest_distance = geodesic(
2020-08-11 21:59:55 +00:00
(self.gps_data['lat'], self.gps_data['lon']),
(self.session_data['dest_lat'], self.session_data['dest_lon'])).kilometers
2020-08-28 23:56:32 +00:00
if dest_distance < 1:
dest_distance *= 1000
dest_distance = "%i metros" % dest_distance
else:
dest_distance = "%0.1f km" % dest_distance
2020-08-30 20:02:01 +00:00
2020-08-28 23:56:32 +00:00
self.dest_distance = "Destino: %s\nVel: %0.1f km/h" % (dest_distance,
self.gps_data['speed'] * 1.609344)
2020-08-28 23:56:32 +00:00
if 'flock_lat' in self.session_data:
flock_distance = geodesic(
(self.gps_data['lat'], self.gps_data['lon']),
(self.session_data['flock_lat'], self.session_data['flock_lon'])).kilometers
if flock_distance < 1:
flock_distance *= 1000
flock_distance = "%i metros" % flock_distance
else:
flock_distance = "%0.1f km" % flock_distance
self.dashboard_flock = "Flock: %s\nVel: %0.1f km/h" % (flock_distance,
self.session_data['flock_avg_speed'] * 1.609344)
else:
self.dashboard_flock = "no flocks"
2020-09-16 02:01:54 +00:00
self.session_data.update({'speed': self.gps_data['speed'] * 1.609344,
'bearing': self.gps_data['bearing'],
'lat': self.gps_data['lat'],
'lon': self.gps_data['lon']})
2020-08-28 04:40:20 +00:00
2020-08-11 16:31:25 +00:00
def center_map_on_gps(self):
self.ms.ids.mapview.center_on(self.gps_data['lat'],
self.gps_data['lon'])
2020-08-11 04:50:25 +00:00
def get_field(self, dt):
2020-08-11 16:31:25 +00:00
"""
get magnetic field for compass
"""
needle_angle = 0
2020-08-11 04:50:25 +00:00
if self.cs.facade.field != (None, None, None):
x, y, z = self.cs.facade.field
2020-08-28 04:40:20 +00:00
smoothingFactor = self.session_data.get('settings_smoothing', 0.88)
2020-08-11 17:24:50 +00:00
angle = atan2(y, x)
self.last_angle = smoothingFactor * self.last_angle + (1 - smoothingFactor) * angle
2020-08-11 04:50:25 +00:00
2020-08-11 18:12:10 +00:00
needle_angle = degrees(self.last_angle) - 90
2020-08-28 04:40:20 +00:00
2020-08-11 04:50:25 +00:00
# fix animation transition around the unit circle
if (self.needle_angle % 360) - needle_angle > 180:
needle_angle += 360
elif (self.needle_angle % 360) - needle_angle < -180:
needle_angle -= 360
# add the number of revolutions to the result
needle_angle += 360 * floor(self.needle_angle / 360.)
# animate the needle
if self.cs._anim:
self.cs._anim.stop(self)
2020-08-11 05:27:57 +00:00
self.cs._anim = Animation(needle_angle=needle_angle,
2020-08-12 01:37:56 +00:00
d=0.1,
2020-08-11 17:24:50 +00:00
t='out_quad')
2020-08-12 01:37:56 +00:00
lat1 = self.gps_data['lat']
lon1 = self.gps_data['lon']
lat2 = self.session_data['dest_lat']
lon2 = self.session_data['dest_lon']
2020-09-16 02:01:54 +00:00
dbearing = atan2(sin(lon2 - lon1) * cos(lat2),
2020-08-12 01:37:56 +00:00
cos(lat1) * sin(lat2)
- sin(lat1) * cos(lat2) * cos(lon2-lon1))
2020-09-16 02:01:54 +00:00
dbearing = self.needle_angle - degrees(dbearing)
2020-08-28 04:40:20 +00:00
2020-09-16 02:01:54 +00:00
self.cs._anim &= Animation(dbearing=dbearing,
2020-08-12 01:37:56 +00:00
d=0.1,
t='out_quad')
2020-08-28 04:40:20 +00:00
if 'flock_lat' in self.session_data:
self.cs.ids.to_flock.source = 'assets/needle_to_flock.png'
lat2 = self.session_data['flock_lat']
lon2 = self.session_data['flock_lon']
fbearing = atan2(sin(lon2 - lon1) * cos(lat2),
cos(lat1) * sin(lat2)
- sin(lat1) * cos(lat2) * cos(lon2-lon1))
fbearing = self.needle_angle - degrees(fbearing)
self.cs._anim &= Animation(fbearing=fbearing,
d=0.1,
t='out_quad')
else:
self.cs.ids.to_flock.source = 'assets/pivot.png'
2020-08-11 04:50:25 +00:00
self.cs._anim.start(self)
2020-08-11 04:50:25 +00:00
def compass_enable(self):
self.cs.facade.enable()
2020-08-30 20:41:04 +00:00
Clock.schedule_interval(self.get_field,
1.0 - self.session_data.get('settings_compass_update', 0.777))
2020-08-11 04:50:25 +00:00
def compass_disable(self):
self.cs.facade.disable()
Clock.unschedule(self.get_field)
def on_pause(self):
self.compass_disable()
self.gps_stop()
return True
def on_resume(self):
self.compass_enable()
self.gps_start(1000, 0)
2020-09-16 02:01:54 +00:00
def flock_server_register(self):
try:
register_url = "{server}/register/?dest_lon={dest_lon}&dest_lat={dest_lat}"
2020-09-16 02:01:54 +00:00
r = requests.get(register_url.format(server=FLOCK_SERVER,
dest_lon=self.session_data['dest_lon'],
dest_lat=self.session_data['dest_lat']))
out = r.json()
2020-09-16 02:01:54 +00:00
self.session_data['bike_id'] = out['bike_id']
except:
self.session_data.pop('bike_id', None)
2020-09-16 02:01:54 +00:00
def flock_server_update(self):
try:
update_url = "{server}/update/{bike_id}/?lat={lat}&lon={lon}&speed={speed}&bearing={bearing}"
req = requests.get(update_url.format(server=FLOCK_SERVER,
bike_id=self.session_data['bike_id'],
speed=self.session_data['speed'],
bearing=self.session_data['bearing'],
lat=self.session_data['lat'],
lon=self.session_data['lon']))
resp = req.json()
print('server update ¡¡¡¡¡¡¡¡', req, resp)
self.session_data.update(resp)
except:
self.session_data.pop('bike_id', None)
self.session_data.pop('speed', None)
self.session_data.pop('bearing', None)
2020-09-16 02:01:54 +00:00
def flock(self, dt):
if 'bike_id' in self.session_data:
self.flock_server_update()
else:
self.flock_server_register()
def build(self):
2020-08-04 22:20:08 +00:00
# start GPS
2020-08-05 02:52:20 +00:00
try:
gps.configure(on_location=self.on_location)
2020-08-05 02:52:20 +00:00
except NotImplementedError:
import traceback
traceback.print_exc()
print('GPS is not implemented for your platform')
2020-07-17 00:17:49 +00:00
if platform == "android":
self.request_android_permissions()
self.gps_start(1000, 0)
# setup app screens
screen_manager = ScreenManager(transition=RiseInTransition())
2020-09-03 18:38:41 +00:00
# map screen
self.ms = MapScreen(name='map')
screen_manager.add_widget(self.ms)
2020-09-03 18:38:41 +00:00
# settings screen
2020-08-30 19:18:01 +00:00
self.ss = SettingsScreen(name='settings')
screen_manager.add_widget(self.ss)
2020-08-30 20:02:01 +00:00
2020-09-03 18:38:41 +00:00
# compass screen
2020-08-11 17:24:50 +00:00
# smoothing vars for compass
self.last_angle = 0
2020-08-28 04:40:20 +00:00
2020-09-03 18:38:41 +00:00
self.cs = CompassScreen(name='compass')
self.cs._anim = None
self.cs._anim1 = None
screen_manager.add_widget(self.cs)
2020-09-16 02:01:54 +00:00
Clock.schedule_interval(self.flock, 20.0)
return screen_manager
if __name__ == '__main__':
app = FlockompassApp()
app.read_settings()
app.run()