Implemented background telemetry

This commit is contained in:
Mark Qvist 2023-12-03 02:08:04 +01:00
parent ecf13e0041
commit 19cacd0e86
5 changed files with 179 additions and 42 deletions

View File

@ -10,7 +10,7 @@ source.exclude_patterns = app_storage/*,venv/*,Makefile,./Makefil*,requirements,
version.regex = __version__ = ['"](.*)['"] version.regex = __version__ = ['"](.*)['"]
version.filename = %(source.dir)s/main.py version.filename = %(source.dir)s/main.py
android.numeric_version = 20231114 android.numeric_version = 20231203
# Cryptography recipe is currently broken, using RNS-internal crypto for now # Cryptography recipe is currently broken, using RNS-internal crypto for now
requirements = kivy==2.2.1,libbz2,pillow,qrcode==7.3.1,usb4a,usbserial4a requirements = kivy==2.2.1,libbz2,pillow,qrcode==7.3.1,usb4a,usbserial4a
@ -25,7 +25,8 @@ android.presplash_color = #00000000
orientation = portrait orientation = portrait
fullscreen = 0 fullscreen = 0
android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_CONNECT,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE android.permissions = INTERNET,POST_NOTIFICATIONS,WAKE_LOCK,FOREGROUND_SERVICE,CHANGE_WIFI_MULTICAST_STATE,BLUETOOTH_CONNECT,ACCESS_NETWORK_STATE,ACCESS_FINE_LOCATION,ACCESS_COARSE_LOCATION,MANAGE_EXTERNAL_STORAGE,ACCESS_BACKGROUND_LOCATION
android.api = 30 android.api = 30
android.minapi = 24 android.minapi = 24
android.ndk = 25b android.ndk = 25b

View File

@ -1,6 +1,6 @@
__debug_build__ = False __debug_build__ = False
__disable_shaders__ = False __disable_shaders__ = False
__version__ = "0.7.6" __version__ = "0.7.7"
__variant__ = "beta" __variant__ = "beta"
import sys import sys

View File

@ -30,8 +30,7 @@ if RNS.vendor.platformutils.get_platform() == "android":
from android import python_act from android import python_act
android_api_version = autoclass('android.os.Build$VERSION').SDK_INT android_api_version = autoclass('android.os.Build$VERSION').SDK_INT
Context = autoclass('android.content.Context')
Intent = autoclass('android.content.Intent') Intent = autoclass('android.content.Intent')
BitmapFactory = autoclass('android.graphics.BitmapFactory') BitmapFactory = autoclass('android.graphics.BitmapFactory')
Icon = autoclass("android.graphics.drawable.Icon") Icon = autoclass("android.graphics.drawable.Icon")
@ -60,6 +59,7 @@ class SidebandService():
0x0483: [0x5740], # ST CDC 0x0483: [0x5740], # ST CDC
0x2E8A: [0x0005, 0x000A], # Raspberry Pi Pico 0x2E8A: [0x0005, 0x000A], # Raspberry Pi Pico
} }
def android_notification(self, title="", content="", ticker="", group=None, context_id=None): def android_notification(self, title="", content="", ticker="", group=None, context_id=None):
if android_api_version < 26: if android_api_version < 26:
return return
@ -121,12 +121,78 @@ class SidebandService():
built_notification = notification.build() built_notification = notification.build()
self.notification_service.notify(0, built_notification) self.notification_service.notify(0, built_notification)
def check_permission(self, permission):
if RNS.vendor.platformutils.is_android():
try:
result = self.android_service.checkSelfPermission("android.permission."+permission)
if result == 0:
return True
except Exception as e:
RNS.log("Error while checking permission: "+str(e), RNS.LOG_ERROR)
return False
else:
return False
def background_location_allowed(self):
if not RNS.vendor.platformutils.is_android():
return False
perms = ["ACCESS_FINE_LOCATION","ACCESS_COARSE_LOCATION","ACCESS_BACKGROUND_LOCATION"]
for perm in perms:
if not self.check_permission(perm):
return False
return True
def android_location_callback(self, **kwargs):
self._raw_gps = kwargs
self._last_gps_update = time.time()
def should_update_location(self):
if self.sideband.config["telemetry_enabled"] and self.sideband.config["telemetry_s_location"] and self.background_location_allowed():
return True
else:
return False
def update_location_provider(self):
if RNS.vendor.platformutils.is_android():
if self.should_update_location():
if not self._gps_started:
RNS.log("Starting service location provider", RNS.LOG_DEBUG)
if self.gps == None:
from plyer import gps
self.gps = gps
self.gps.configure(on_location=self.android_location_callback)
self.gps.start(minTime=self._gps_stale_time, minDistance=self._gps_min_distance)
self._gps_started = True
else:
if self._gps_started:
RNS.log("Stopping service location provider", RNS.LOG_DEBUG)
if self.gps != None:
self.gps.stop()
self._gps_started = False
self._raw_gps = None
def get_location(self):
return self._last_gps_update, self._raw_gps
def __init__(self): def __init__(self):
self.argument = environ.get('PYTHON_SERVICE_ARGUMENT', '') self.argument = environ.get('PYTHON_SERVICE_ARGUMENT', '')
self.app_dir = self.argument self.app_dir = self.argument
self.multicast_lock = None self.multicast_lock = None
self.wake_lock = None self.wake_lock = None
self.should_run = False self.should_run = False
self.gps = None
self._gps_started = False
self._gps_stale_time = 299
self._gps_min_distance = 25
self._raw_gps = None
self.android_service = None self.android_service = None
self.app_context = None self.app_context = None
@ -149,9 +215,7 @@ class SidebandService():
self.discover_usb_devices() self.discover_usb_devices()
self.sideband = SidebandCore(self, is_service=True, android_app_dir=self.app_dir, verbose=__debug_build__) self.sideband = SidebandCore(self, is_service=True, android_app_dir=self.app_dir, verbose=__debug_build__, owner_service=self, service_context=self.android_service)
self.sideband.service_context = self.android_service
self.sideband.owner_service = self
if self.sideband.config["debug"]: if self.sideband.config["debug"]:
Logger.setLevel(LOG_LEVELS["debug"]) Logger.setLevel(LOG_LEVELS["debug"])
@ -161,6 +225,7 @@ class SidebandService():
if RNS.vendor.platformutils.is_android(): if RNS.vendor.platformutils.is_android():
RNS.log("Discovered USB devices: "+str(self.usb_devices), RNS.LOG_EXTREME) RNS.log("Discovered USB devices: "+str(self.usb_devices), RNS.LOG_EXTREME)
self.update_location_provider()
def discover_usb_devices(self): def discover_usb_devices(self):

View File

@ -90,6 +90,7 @@ class SidebandCore():
PERIODIC_JOBS_INTERVAL = 60 PERIODIC_JOBS_INTERVAL = 60
PERIODIC_SYNC_RETRY = 360 PERIODIC_SYNC_RETRY = 360
TELEMETRY_INTERVAL = 60 TELEMETRY_INTERVAL = 60
SERVICE_TELEMETRY_INTERVAL = 300
IF_CHANGE_ANNOUNCE_MIN_INTERVAL = 6 # In seconds IF_CHANGE_ANNOUNCE_MIN_INTERVAL = 6 # In seconds
AUTO_ANNOUNCE_RANDOM_MIN = 90 # In minutes AUTO_ANNOUNCE_RANDOM_MIN = 90 # In minutes
@ -103,7 +104,7 @@ class SidebandCore():
# stream logger # stream logger
self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter) self.log_announce(destination_hash, app_data, dest_type=SidebandCore.aspect_filter)
def __init__(self, owner_app, is_service=False, is_client=False, android_app_dir=None, verbose=False): def __init__(self, owner_app, is_service=False, is_client=False, android_app_dir=None, verbose=False, owner_service=None, service_context=None):
self.is_service = is_service self.is_service = is_service
self.is_client = is_client self.is_client = is_client
self.db = None self.db = None
@ -132,6 +133,8 @@ class SidebandCore():
self.state_lock = Lock() self.state_lock = Lock()
self.rpc_connection = None self.rpc_connection = None
self.service_stopped = False self.service_stopped = False
self.service_context = service_context
self.owner_service = owner_service
self.app_dir = plyer.storagepath.get_home_dir()+"/.config/sideband" self.app_dir = plyer.storagepath.get_home_dir()+"/.config/sideband"
self.cache_dir = self.app_dir+"/cache" self.cache_dir = self.app_dir+"/cache"
@ -2244,6 +2247,23 @@ class SidebandCore():
threading.Thread(target=telemetry_job, daemon=True).start() threading.Thread(target=telemetry_job, daemon=True).start()
def run_service_telemetry(self):
if not self.telemetry_running:
self.telemetry_running = True
def telemetry_job():
while self.telemetry_running:
try:
self.update_telemetry()
except Exception as e:
import traceback
exception_info = "".join(traceback.TracebackException.from_exception(e).format())
RNS.log(f"An {str(type(e))} occurred while updating service telemetry: {str(e)}", RNS.LOG_ERROR)
RNS.log(exception_info, RNS.LOG_ERROR)
time.sleep(SidebandCore.SERVICE_TELEMETRY_INTERVAL)
threading.Thread(target=telemetry_job, daemon=True).start()
def stop_telemetry(self): def stop_telemetry(self):
self.telemetry_running = False self.telemetry_running = False
self.telemeter.stop_all() self.telemeter.stop_all()
@ -2298,7 +2318,10 @@ class SidebandCore():
def update_telemeter_config(self): def update_telemeter_config(self):
if self.config["telemetry_enabled"] == True: if self.config["telemetry_enabled"] == True:
if self.telemeter == None: if self.telemeter == None:
self.telemeter = Telemeter() if self.service_context == None:
self.telemeter = Telemeter()
else:
self.telemeter = Telemeter(android_context=self.service_context, service=True, location_provider=self.owner_service)
sensors = ["location", "information", "battery", "pressure", "temperature", "humidity", "magnetic_field", "ambient_light", "gravity", "angular_velocity", "acceleration", "proximity"] sensors = ["location", "information", "battery", "pressure", "temperature", "humidity", "magnetic_field", "ambient_light", "gravity", "angular_velocity", "acceleration", "proximity"]
for sensor in sensors: for sensor in sensors:
@ -2561,6 +2584,8 @@ class SidebandCore():
if self.is_service or self.is_standalone: if self.is_service or self.is_standalone:
while True: while True:
time.sleep(SidebandCore.PERIODIC_JOBS_INTERVAL) time.sleep(SidebandCore.PERIODIC_JOBS_INTERVAL)
self.owner_service.update_location_provider()
if self.config["lxmf_periodic_sync"] == True: if self.config["lxmf_periodic_sync"] == True:
if self.getpersistent("lxmf.lastsync") == None: if self.getpersistent("lxmf.lastsync") == None:
self.setpersistent("lxmf.lastsync", time.time()) self.setpersistent("lxmf.lastsync", time.time())
@ -2660,6 +2685,8 @@ class SidebandCore():
if self.is_standalone or self.is_client: if self.is_standalone or self.is_client:
if self.config["telemetry_enabled"]: if self.config["telemetry_enabled"]:
self.run_telemetry() self.run_telemetry()
elif self.is_service:
self.run_service_telemetry()
def __add_localinterface(self, delay=None): def __add_localinterface(self, delay=None):
self.interface_local_adding = True self.interface_local_adding = True

View File

@ -40,7 +40,7 @@ class Telemeter():
RNS.log("An error occurred while unpacking telemetry. The contained exception was: "+str(e), RNS.LOG_ERROR) RNS.log("An error occurred while unpacking telemetry. The contained exception was: "+str(e), RNS.LOG_ERROR)
return None return None
def __init__(self, from_packed=False): def __init__(self, from_packed=False, android_context=None, service=False, location_provider=None):
self.sids = { self.sids = {
Sensor.SID_TIME: Time, Sensor.SID_TIME: Time,
Sensor.SID_RECEIVED: Received, Sensor.SID_RECEIVED: Received,
@ -80,10 +80,15 @@ class Telemeter():
if not self.from_packed: if not self.from_packed:
self.enable("time") self.enable("time")
self.location_provider = location_provider
self.android_context = android_context
self.service = service
def synthesize(self, sensor): def synthesize(self, sensor):
if sensor in self.available: if sensor in self.available:
if not sensor in self.sensors: if not sensor in self.sensors:
self.sensors[sensor] = self.sids[self.available[sensor]]() self.sensors[sensor] = self.sids[self.available[sensor]]()
self.sensors[sensor]._telemeter = self
self.sensors[sensor].active = True self.sensors[sensor].active = True
self.sensors[sensor].synthesized = True self.sensors[sensor].synthesized = True
@ -92,6 +97,7 @@ class Telemeter():
if sensor in self.available: if sensor in self.available:
if not sensor in self.sensors: if not sensor in self.sensors:
self.sensors[sensor] = self.sids[self.available[sensor]]() self.sensors[sensor] = self.sids[self.available[sensor]]()
self.sensors[sensor]._telemeter = self
if not self.sensors[sensor].active: if not self.sensors[sensor].active:
self.sensors[sensor].start() self.sensors[sensor].start()
@ -150,6 +156,28 @@ class Telemeter():
return rendered return rendered
def check_permission(self, permission):
if RNS.vendor.platformutils.is_android():
if self.android_context != None:
try:
result = self.android_context.checkSelfPermission("android.permission."+permission)
if result == 0:
return True
except Exception as e:
RNS.log("Error while checking permission: "+str(e), RNS.LOG_ERROR)
return False
else:
from android.permissions import check_permission
return check_permission("android.permission."+permission)
else:
return False
class Sensor(): class Sensor():
SID_NONE = 0x00 SID_NONE = 0x00
SID_TIME = 0x01 SID_TIME = 0x01
@ -169,6 +197,7 @@ class Sensor():
SID_RECEIVED = 0x10 SID_RECEIVED = 0x10
def __init__(self, sid = None, stale_time = None): def __init__(self, sid = None, stale_time = None):
self._telemeter = None
self._sid = sid or Sensor.SID_NONE self._sid = sid or Sensor.SID_NONE
self._stale_time = stale_time self._stale_time = stale_time
self._data = None self._data = None
@ -238,6 +267,13 @@ class Sensor():
def render(self, relative_to=None): def render(self, relative_to=None):
return None return None
def check_permission(self, permission):
if self._telemeter != None:
return self._telemeter.check_permission(permission)
else:
from android.permissions import check_permission
return check_permission("android.permission."+permission)
class Time(Sensor): class Time(Sensor):
SID = Sensor.SID_TIME SID = Sensor.SID_TIME
STALE_TIME = 0.1 STALE_TIME = 0.1
@ -587,6 +623,7 @@ class Location(Sensor):
self._last_update = None self._last_update = None
self._min_distance = Location.MIN_DISTANCE self._min_distance = Location.MIN_DISTANCE
self._accuracy_target = Location.ACCURACY_TARGET self._accuracy_target = Location.ACCURACY_TARGET
self._query_method = None
self.latitude = None self.latitude = None
self.longitude = None self.longitude = None
@ -619,12 +656,15 @@ class Location(Sensor):
def setup_sensor(self): def setup_sensor(self):
if RNS.vendor.platformutils.is_android(): if RNS.vendor.platformutils.is_android():
from android.permissions import request_permissions, check_permission
if check_permission("android.permission.ACCESS_COARSE_LOCATION") and check_permission("android.permission.ACCESS_FINE_LOCATION"): if self._telemeter and self._telemeter.service:
self.gps.configure(on_location=self.android_location_callback) self._location_provider = self._telemeter.location_provider
self.gps.start(minTime=self._stale_time, minDistance=self._min_distance) else:
if self.check_permission("ACCESS_COARSE_LOCATION") and self.check_permission("ACCESS_FINE_LOCATION"):
self.gps.configure(on_location=self.android_location_callback)
self.gps.start(minTime=self._stale_time, minDistance=self._min_distance)
self._location_provider = self
self.update_data() self.update_data()
def teardown_sensor(self): def teardown_sensor(self):
@ -669,33 +709,37 @@ class Location(Sensor):
} }
elif RNS.vendor.platformutils.is_android(): elif RNS.vendor.platformutils.is_android():
if "lat" in self._raw: if self._location_provider != None:
self.latitude = self._raw["lat"] if self._location_provider != self:
if "lon" in self._raw: self._last_update, self._raw = self._location_provider.get_location()
self.longitude = self._raw["lon"]
if "altitude" in self._raw:
self.altitude = self._raw["altitude"]
if "speed" in self._raw:
self.speed = self._raw["speed"]
if self.speed < 0:
self.speed = 0
if "bearing" in self._raw:
self.bearing = self._raw["bearing"]
if "accuracy" in self._raw:
self.accuracy = self._raw["accuracy"]
if self.accuracy < 0:
self.accuracy = 0
if self.accuracy != None and self.accuracy <= self._accuracy_target: if "lat" in self._raw:
self.data = { self.latitude = self._raw["lat"]
"latitude": round(self.latitude, 6), if "lon" in self._raw:
"longitude": round(self.longitude, 6), self.longitude = self._raw["lon"]
"altitude": round(self.altitude, 2), if "altitude" in self._raw:
"speed": round(self.speed, 2), self.altitude = self._raw["altitude"]
"bearing": round(self.bearing, 2), if "speed" in self._raw:
"accuracy": round(self.accuracy, 2), self.speed = self._raw["speed"]
"last_update": int(self._last_update), if self.speed < 0:
} self.speed = 0
if "bearing" in self._raw:
self.bearing = self._raw["bearing"]
if "accuracy" in self._raw:
self.accuracy = self._raw["accuracy"]
if self.accuracy < 0:
self.accuracy = 0
if self.accuracy != None and self.accuracy <= self._accuracy_target:
self.data = {
"latitude": round(self.latitude, 6),
"longitude": round(self.longitude, 6),
"altitude": round(self.altitude, 2),
"speed": round(self.speed, 2),
"bearing": round(self.bearing, 2),
"accuracy": round(self.accuracy, 2),
"last_update": int(self._last_update),
}
except: except:
self.data = None self.data = None