Hardware abstraction class (#2080)

* hardware abstraction class

* De Morgan

* Rename pc hardware class

* Fix sound card in controlsd

* Pc get sim info

* fix hardware in test

* two more

* No more random imei on android

* no randomness on android

* Need to return something that looks like imei for registration to work

* Return proper network strength

* Unused import

* Bug fixes + gpsd is only android
old-commit-hash: c7152d5419
commatwo_master
Willem Melching 5 years ago committed by GitHub
parent 93926b0cd2
commit 16fe1bb2ad
  1. 124
      common/android.py
  2. 10
      common/basedir.py
  3. 50
      common/hardware.py
  4. 34
      common/hardware_base.py
  5. 2
      common/realtime.py
  6. 1
      release/files_common
  7. 20
      selfdrive/athena/athenad.py
  8. 2
      selfdrive/boardd/tests/test_boardd_loopback.py
  9. 4
      selfdrive/controls/controlsd.py
  10. 4
      selfdrive/crash.py
  11. 31
      selfdrive/loggerd/uploader.py
  12. 15
      selfdrive/manager.py
  13. 7
      selfdrive/registration.py
  14. 9
      selfdrive/test/helpers.py
  15. 3
      selfdrive/test/process_replay/camera_replay.py
  16. 4
      selfdrive/test/test_sounds.py
  17. 7
      selfdrive/thermald/thermald.py
  18. 14
      tools/lib/auth_config.py

@ -1,78 +1,32 @@
import os
import binascii
import itertools
import os
import re
import struct
import subprocess
import random
from cereal import log
from common.hardware_base import HardwareBase
NetworkType = log.ThermalData.NetworkType
NetworkStrength = log.ThermalData.NetworkStrength
ANDROID = os.path.isfile('/EON')
def get_sound_card_online():
return (os.path.isfile('/proc/asound/card0/state') and
open('/proc/asound/card0/state').read().strip() == 'ONLINE')
def getprop(key):
if not ANDROID:
return ""
return subprocess.check_output(["getprop", key], encoding='utf8').strip()
def get_imei(slot):
slot = str(slot)
if slot not in ("0", "1"):
raise ValueError("SIM slot must be 0 or 1")
ret = parse_service_call_string(service_call(["iphonesubinfo", "3" , "i32", str(slot)]))
if not ret:
# allow non android to be identified differently
ret = "%015d" % random.randint(0, 1 << 32)
return ret
def get_serial():
ret = getprop("ro.serialno")
if ret == "":
ret = "cccccccc"
return ret
def get_subscriber_info():
ret = parse_service_call_string(service_call(["iphonesubinfo", "7"]))
if ret is None or len(ret) < 8:
return ""
return ret
def reboot(reason=None):
if reason is None:
reason_args = ["null"]
else:
reason_args = ["s16", reason]
subprocess.check_output([
"service", "call", "power", "16", # IPowerManager.reboot
"i32", "0", # no confirmation,
*reason_args,
"i32", "1" # wait
])
def service_call(call):
if not ANDROID:
return None
ret = subprocess.check_output(["service", "call", *call], encoding='utf8').strip()
if 'Parcel' not in ret:
return None
return parse_service_call_bytes(ret)
def parse_service_call_unpack(r, fmt):
try:
return struct.unpack(fmt, r)[0]
except Exception:
return None
def parse_service_call_string(r):
try:
r = r[8:] # Cut off length field
@ -89,6 +43,7 @@ def parse_service_call_string(r):
except Exception:
return None
def parse_service_call_bytes(ret):
try:
r = b""
@ -98,10 +53,69 @@ def parse_service_call_bytes(ret):
except Exception:
return None
def get_network_type():
if not ANDROID:
return NetworkType.none
def getprop(key):
return subprocess.check_output(["getprop", key], encoding='utf8').strip()
class Android(HardwareBase):
def get_sound_card_online(self):
return (os.path.isfile('/proc/asound/card0/state') and
open('/proc/asound/card0/state').read().strip() == 'ONLINE')
def get_imei(self, slot):
slot = str(slot)
if slot not in ("0", "1"):
raise ValueError("SIM slot must be 0 or 1")
return parse_service_call_string(service_call(["iphonesubinfo", "3", "i32", str(slot)]))
def get_serial(self):
ret = getprop("ro.serialno")
if ret == "":
ret = "cccccccc"
return ret
def get_subscriber_info(self):
ret = parse_service_call_string(service_call(["iphonesubinfo", "7"]))
if ret is None or len(ret) < 8:
return ""
return ret
def reboot(self, reason=None):
# e.g. reason="recovery" to go into recover mode
if reason is None:
reason_args = ["null"]
else:
reason_args = ["s16", reason]
subprocess.check_output([
"service", "call", "power", "16", # IPowerManager.reboot
"i32", "0", # no confirmation,
*reason_args,
"i32", "1" # wait
])
def get_sim_info(self):
# Used for athena
# TODO: build using methods from this class
sim_state = getprop("gsm.sim.state").split(",")
network_type = getprop("gsm.network.type").split(',')
mcc_mnc = getprop("gsm.sim.operator.numeric") or None
sim_id = parse_service_call_string(service_call(['iphonesubinfo', '11']))
cell_data_state = parse_service_call_unpack(service_call(['phone', '46']), ">q")
cell_data_connected = (cell_data_state == 2)
return {
'sim_id': sim_id,
'mcc_mnc': mcc_mnc,
'network_type': network_type,
'sim_state': sim_state,
'data_connected': cell_data_connected
}
def get_network_type(self):
wifi_check = parse_service_call_string(service_call(["connectivity", "2"]))
if wifi_check is None:
return NetworkType.none
@ -134,7 +148,7 @@ def get_network_type():
}
return cell_networks.get(cell_check, NetworkType.none)
def get_network_strength(network_type):
def get_network_strength(self, network_type):
network_strength = NetworkStrength.unknown
# from SignalStrength.java

@ -1,10 +1,10 @@
import os
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))
from common.android import ANDROID
if ANDROID:
PERSIST = "/persist"
PARAMS = "/data/params"
else:
from common.hardware import PC
if PC:
PERSIST = os.path.join(BASEDIR, "persist")
PARAMS = os.path.join(BASEDIR, "persist", "params")
else:
PERSIST = "/persist"
PARAMS = "/data/params"

@ -1,4 +1,54 @@
import os
import random
from typing import cast
from cereal import log
from common.android import Android
from common.hardware_base import HardwareBase
EON = os.path.isfile('/EON')
TICI = os.path.isfile('/TICI')
PC = not (EON or TICI)
ANDROID = EON
NetworkType = log.ThermalData.NetworkType
NetworkStrength = log.ThermalData.NetworkStrength
class Pc(HardwareBase):
def get_sound_card_online(self):
return True
def get_imei(self, slot):
return "%015d" % random.randint(0, 1 << 32)
def get_serial(self):
return "cccccccc"
def get_subscriber_info(self):
return ""
def reboot(self, reason=None):
print("REBOOT!")
def get_network_type(self):
return NetworkType.none
def get_sim_info(self):
return {
'sim_id': '',
'mcc_mnc': None,
'network_type': ["Unknown"],
'sim_state': ["ABSENT"],
'data_connected': False
}
def get_network_strength(self, network_type):
return NetworkStrength.unknown
if EON:
HARDWARE = cast(HardwareBase, Android())
else:
HARDWARE = cast(HardwareBase, Pc())

@ -0,0 +1,34 @@
from abc import abstractmethod
class HardwareBase:
@abstractmethod
def get_sound_card_online(self):
pass
@abstractmethod
def get_imei(self, slot):
pass
@abstractmethod
def get_serial(self):
pass
@abstractmethod
def get_subscriber_info(self):
pass
@abstractmethod
def reboot(self, reason=None):
pass
@abstractmethod
def get_network_type(self):
pass
@abstractmethod
def get_sim_info(self):
pass
@abstractmethod
def get_network_strength(self, network_type):
pass

@ -6,7 +6,7 @@ import subprocess
import multiprocessing
from cffi import FFI
from common.android import ANDROID
from common.hardware import ANDROID
from common.common_pyx import sec_since_boot # pylint: disable=no-name-in-module, import-error

@ -19,6 +19,7 @@ common/.gitignore
common/__init__.py
common/android.py
common/hardware.py
common/hardware_base.py
common/gpio.py
common/realtime.py
common/clock.pyx

@ -20,7 +20,7 @@ from websocket import ABNF, WebSocketTimeoutException, create_connection
import cereal.messaging as messaging
from cereal.services import service_list
from common import android
from common.hardware import HARDWARE
from common.api import Api
from common.basedir import PERSIST
from common.params import Params
@ -132,7 +132,7 @@ def reboot():
def do_reboot():
time.sleep(2)
android.reboot()
HARDWARE.reboot()
threading.Thread(target=do_reboot).start()
@ -218,21 +218,7 @@ def getSshAuthorizedKeys():
@dispatcher.add_method
def getSimInfo():
sim_state = android.getprop("gsm.sim.state").split(",")
network_type = android.getprop("gsm.network.type").split(',')
mcc_mnc = android.getprop("gsm.sim.operator.numeric") or None
sim_id = android.parse_service_call_string(android.service_call(['iphonesubinfo', '11']))
cell_data_state = android.parse_service_call_unpack(android.service_call(['phone', '46']), ">q")
cell_data_connected = (cell_data_state == 2)
return {
'sim_id': sim_id,
'mcc_mnc': mcc_mnc,
'network_type': network_type,
'sim_state': sim_state,
'data_connected': cell_data_connected
}
return HARDWARE.get_sim_info()
@dispatcher.add_method

@ -8,7 +8,7 @@ from functools import wraps
import cereal.messaging as messaging
from cereal import car
from common.basedir import PARAMS
from common.android import ANDROID
from common.hardware import ANDROID
from common.params import Params
from common.spinner import Spinner
from panda import Panda

@ -2,7 +2,7 @@
import os
import gc
from cereal import car, log
from common.android import ANDROID, get_sound_card_online
from common.hardware import HARDWARE
from common.numpy_fast import clip
from common.realtime import sec_since_boot, set_realtime_priority, set_core_affinity, Ratekeeper, DT_CTRL
from common.profiler import Profiler
@ -79,7 +79,7 @@ class Controls:
internet_needed or not openpilot_enabled_toggle
# detect sound card presence and ensure successful init
sounds_available = not ANDROID or get_sound_card_online()
sounds_available = HARDWARE.get_sound_card_online()
car_recognized = self.CP.carName != 'mock'
# If stock camera is disconnected, we loaded car controls and it's not dashcam mode

@ -6,9 +6,9 @@ import capnp
from selfdrive.version import version, dirty
from selfdrive.swaglog import cloudlog
from common.android import ANDROID
from common.hardware import PC
if os.getenv("NOLOG") or os.getenv("NOCRASH") or not ANDROID:
if os.getenv("NOLOG") or os.getenv("NOCRASH") or PC:
def capture_exception(*args, **kwargs):
pass

@ -1,24 +1,26 @@
#!/usr/bin/env python3
import ctypes
import inspect
import json
import os
import random
import re
import subprocess
import threading
import time
import json
import random
import ctypes
import inspect
import requests
import traceback
import threading
import subprocess
from selfdrive.swaglog import cloudlog
from selfdrive.loggerd.config import ROOT
import requests
from common import android
from common.params import Params
from cereal import log
from common.hardware import HARDWARE
from common.api import Api
from common.params import Params
from common.xattr import getxattr, setxattr
from selfdrive.loggerd.config import ROOT
from selfdrive.swaglog import cloudlog
NetworkType = log.ThermalData.NetworkType
UPLOAD_ATTR_NAME = 'user.upload'
UPLOAD_ATTR_VALUE = b'1'
@ -69,13 +71,8 @@ def clear_locks(root):
cloudlog.exception("clear_locks failed")
def is_on_wifi():
# ConnectivityManager.getActiveNetworkInfo()
try:
# TODO: figure out why the android service call sometimes dies with SIGUSR2 (signal from MSGQ)
result = android.parse_service_call_string(android.service_call(["connectivity", "2"]))
if result is None:
return True
return 'WIFI' in result
return HARDWARE.get_network_type() == NetworkType.wifi
except Exception:
cloudlog.exception("is_on_wifi failed")
return False

@ -14,7 +14,7 @@ from selfdrive.swaglog import cloudlog, add_logentries_handler
from common.basedir import BASEDIR, PARAMS
from common.android import ANDROID
from common.hardware import HARDWARE, ANDROID, PC
WEBCAM = os.getenv("WEBCAM") is not None
sys.path.append(os.path.join(BASEDIR, "pyextra"))
os.environ['BASEDIR'] = BASEDIR
@ -156,7 +156,6 @@ from selfdrive.registration import register
from selfdrive.version import version, dirty
from selfdrive.loggerd.config import ROOT
from selfdrive.launcher import launcher
from common import android
from common.apk import update_apks, pm_apply_packages, start_offroad
ThermalStatus = cereal.log.ThermalData.ThermalStatus
@ -253,13 +252,18 @@ if WEBCAM:
'dmonitoringmodeld',
]
if ANDROID:
if not PC:
car_started_processes += [
'sensord',
'gpsd',
'dmonitoringmodeld',
]
if ANDROID:
car_started_processes += [
'gpsd',
]
def register_managed_process(name, desc, car_started=False):
global managed_processes, car_started_processes, persistent_processes
print("registering %s" % name)
@ -365,6 +369,7 @@ def kill_managed_process(name):
join_process(running[name], 15)
if running[name].exitcode is None:
cloudlog.critical("unkillable process %s failed to die!" % name)
# TODO: Use method from HARDWARE
if ANDROID:
cloudlog.critical("FORCE REBOOTING PHONE!")
os.system("date >> /sdcard/unkillable_reboot")
@ -536,7 +541,7 @@ def uninstall():
with open('/cache/recovery/command', 'w') as f:
f.write('--wipe_data\n')
# IPowerManager.reboot(confirm=false, reason="recovery", wait=true)
android.reboot(reason="recovery")
HARDWARE.reboot(reason="recovery")
def main():
os.environ['PARAMS_PATH'] = PARAMS

@ -4,12 +4,13 @@ import json
from datetime import datetime, timedelta
from selfdrive.swaglog import cloudlog
from selfdrive.version import version, terms_version, training_version, get_git_commit, get_git_branch, get_git_remote
from common.android import get_imei, get_serial, get_subscriber_info
from common.hardware import HARDWARE
from common.api import api_get
from common.params import Params
from common.file_helpers import mkdirs_exists_ok
from common.basedir import PERSIST
def register():
params = Params()
params.put("Version", version)
@ -19,7 +20,7 @@ def register():
params.put("GitCommit", get_git_commit(default=""))
params.put("GitBranch", get_git_branch(default=""))
params.put("GitRemote", get_git_remote(default=""))
params.put("SubscriberInfo", get_subscriber_info())
params.put("SubscriberInfo", HARDWARE.get_subscriber_info())
# create a key for auth
# your private key is kept on your device persist partition and never sent to our servers
@ -50,7 +51,7 @@ def register():
try:
cloudlog.info("getting pilotauth")
resp = api_get("v2/pilotauth/", method='POST', timeout=15,
imei=get_imei(0), imei2=get_imei(1), serial=get_serial(), public_key=public_key, register_token=register_token)
imei=HARDWARE.get_imei(0), imei2=HARDWARE.get_imei(1), serial=HARDWARE.get_serial(), public_key=public_key, register_token=register_token)
dongleauth = json.loads(resp.text)
dongle_id = dongleauth["dongle_id"]

@ -3,7 +3,7 @@ import subprocess
from functools import wraps
from nose.tools import nottest
from common.android import ANDROID
from common.hardware import PC
from common.apk import update_apks, start_offroad, pm_apply_packages, android_packages
from common.params import Params
from selfdrive.version import training_version, terms_version
@ -19,10 +19,10 @@ def set_params_enabled():
params.put("CompletedTrainingVersion", training_version)
def phone_only(x):
if ANDROID:
return x
else:
if PC:
return nottest(x)
else:
return x
def with_processes(processes, init_time=0):
def wrapper(func):
@ -68,4 +68,3 @@ def with_apks():
assert apk_is_not_running, package
return wrap
return wrapper

@ -5,7 +5,7 @@ import time
from typing import Any
from tqdm import tqdm
from common.android import ANDROID
from common.hardware import ANDROID
os.environ['CI'] = "1"
if ANDROID:
os.environ['QCOM_REPLAY'] = "1"
@ -101,4 +101,3 @@ if __name__ == "__main__":
f.write(diff2)
sys.exit(int(failed))

@ -5,7 +5,7 @@ import subprocess
from cereal import log, car
import cereal.messaging as messaging
from selfdrive.test.helpers import phone_only, with_processes
from common.android import get_sound_card_online
from common.hardware import HARDWARE
from common.realtime import DT_CTRL
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
@ -30,7 +30,7 @@ def get_total_writes():
@phone_only
def test_sound_card_init():
assert get_sound_card_online()
assert HARDWARE.get_sound_card_online()
@phone_only

@ -9,9 +9,8 @@ from smbus2 import SMBus
import cereal.messaging as messaging
from cereal import log
from common.android import get_network_strength, get_network_type
from common.filter_simple import FirstOrderFilter
from common.hardware import EON, TICI
from common.hardware import EON, HARDWARE, TICI
from common.numpy_fast import clip, interp
from common.params import Params, put_nonblocking
from common.realtime import DT_TRML, sec_since_boot
@ -241,8 +240,8 @@ def thermald_thread():
# get_network_type is an expensive call. update every 10s
if (count % int(10. / DT_TRML)) == 0:
try:
network_type = get_network_type()
network_strength = get_network_strength(network_type)
network_type = HARDWARE.get_network_type()
network_strength = HARDWARE.get_network_strength(network_type)
except Exception:
cloudlog.exception("Error getting network status")

@ -1,17 +1,21 @@
import json
import os
from common.android import ANDROID
from common.hardware import PC
from common.file_helpers import mkdirs_exists_ok
class MissingAuthConfigError(Exception):
pass
if ANDROID:
CONFIG_DIR = "/tmp/.comma"
else:
if PC:
CONFIG_DIR = os.path.expanduser('~/.comma')
else:
CONFIG_DIR = "/tmp/.comma"
mkdirs_exists_ok(CONFIG_DIR)
def get_token():
try:
with open(os.path.join(CONFIG_DIR, 'auth.json')) as f:
@ -20,9 +24,11 @@ def get_token():
except Exception:
raise MissingAuthConfigError('Authenticate with tools/lib/auth.py')
def set_token(token):
with open(os.path.join(CONFIG_DIR, 'auth.json'), 'w') as f:
json.dump({'access_token': token}, f)
def clear_token():
os.unlink(os.path.join(CONFIG_DIR, 'auth.json'))

Loading…
Cancel
Save