text: #!/usr/bin/env python
  import os
  import sys
  import time
  import importlib
  import subprocess
  import signal
  import traceback
  import usb1
  from multiprocessing import Process
  from common.services import service_list
  
  import zmq
  
  from setproctitle import setproctitle
  
  from selfdrive.swaglog import cloudlog
  import selfdrive.messaging as messaging
  from selfdrive.thermal import read_thermal
  from selfdrive.registration import register
  
  import common.crash
  
  from selfdrive.loggerd.config import ROOT
  
  # comment out anything you don't want to run
  managed_processes = {
    "uploader": "selfdrive.loggerd.uploader",
    "controlsd": "selfdrive.controls.controlsd",
    "radard": "selfdrive.controls.radard",
    "calibrationd": "selfdrive.calibrationd.calibrationd",
    "loggerd": "selfdrive.loggerd.loggerd",
    "logmessaged": "selfdrive.logmessaged",
    "logcatd": ("logcatd", ["./logcatd"]),
    "boardd": ("boardd", ["./boardd"]),   # switch to c++ boardd
    "ui": ("ui", ["./ui"]),
    "visiond": ("visiond", ["./visiond"]),
    "sensord": ("sensord", ["./sensord"]), }
  
  running = {}
  
  # due to qualcomm kernel bugs SIGKILLing visiond sometimes causes page table corruption
  unkillable_processes = ['visiond']
  
  # processes to end with SIGINT instead of SIGTERM
  interrupt_processes = ['loggerd']
  
  car_started_processes = ['controlsd', 'loggerd', 'sensord', 'radard', 'calibrationd', 'visiond']
  
  
  # ****************** process management functions ******************
  def launcher(proc, gctx):
    try:
      # import the process
      mod = importlib.import_module(proc)
  
      # rename the process
      setproctitle(proc)
  
      # exec the process
      mod.main(gctx)
    except KeyboardInterrupt:
      cloudlog.info("child %s got ctrl-c" % proc)
    except Exception:
      # can't install the crash handler becuase sys.excepthook doesn't play nice
      # with threads, so catch it here.
      common.crash.capture_exception()
      raise
  
  def nativelauncher(pargs, cwd):
    # exec the process
    os.chdir(cwd)
  
    # because when extracted from pex zips permissions get lost -_-
    os.chmod(pargs[0], 0o700)
  
    os.execvp(pargs[0], pargs)
  
  def start_managed_process(name):
    if name in running or name not in managed_processes:
      return
    proc = managed_processes[name]
    if isinstance(proc, basestring):
      cloudlog.info("starting python %s" % proc)
      running[name] = Process(name=name, target=launcher, args=(proc, gctx))
    else:
      pdir, pargs = proc
      cwd = os.path.dirname(os.path.realpath(__file__))
      if pdir is not None:
        cwd = os.path.join(cwd, pdir)
      cloudlog.info("starting process %s" % name)
      running[name] = Process(name=name, target=nativelauncher, args=(pargs, cwd))
    running[name].start()
  
  def kill_managed_process(name):
    if name not in running or name not in managed_processes:
      return
    cloudlog.info("killing %s" % name)
  
    if name in interrupt_processes:
      os.kill(running[name].pid, signal.SIGINT)
    else:
      running[name].terminate()
  
  
    # give it 5 seconds to die
    running[name].join(5.0)
    if running[name].exitcode is None:
      if name in unkillable_processes:
        cloudlog.critical("unkillable process %s failed to exit! rebooting in 15 if it doesn't die" % name)
        running[name].join(15.0)
        if running[name].exitcode is None:
          cloudlog.critical("FORCE REBOOTING PHONE!")
          os.system("date > /sdcard/unkillable_reboot")
          os.system("reboot")
          raise RuntimeError
      else:
        cloudlog.info("killing %s with SIGKILL" % name)
        os.kill(running[name].pid, signal.SIGKILL)
        running[name].join()
  
    cloudlog.info("%s is dead with %d" % (name, running[name].exitcode))
    del running[name]
  
  def cleanup_all_processes(signal, frame):
    cloudlog.info("caught ctrl-c %s %s" % (signal, frame))
    for name in running.keys():
      kill_managed_process(name)
    sys.exit(0)
  
  # ****************** run loop ******************
  
  def manager_init():
    global gctx
  
    reg_res = register()
    if reg_res:
      dongle_id, dongle_secret = reg_res
    else:
      raise Exception("server registration failed")
  
    # set dongle id
    cloudlog.info("dongle id is " + dongle_id)
    os.environ['DONGLE_ID'] = dongle_id
    os.environ['DONGLE_SECRET'] = dongle_secret
  
    cloudlog.bind_global(dongle_id=dongle_id)
    common.crash.bind_user(dongle_id=dongle_id)
  
    # set gctx
    gctx = {
      "calibration": {
        "initial_homography": [1.15728010e+00, -4.69379619e-02, 7.46450623e+01,
                               7.99253014e-02, 1.06372458e+00, 5.77762553e+01,
                               9.35543519e-05, -1.65429898e-04, 9.98062699e-01]
      }
    }
  
  def manager_thread():
    # now loop
    context = zmq.Context()
    thermal_sock = messaging.pub_sock(context, service_list['thermal'].port)
    health_sock = messaging.sub_sock(context, service_list['health'].port)
  
    version = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "common", "version.h")).read().split('"')[1]
  
    cloudlog.info("manager start %s" % version)
    cloudlog.info(dict(os.environ))
  
    start_managed_process("logmessaged")
    start_managed_process("logcatd")
    start_managed_process("uploader")
    start_managed_process("ui")
  
    if os.getenv("NOBOARD") is None:
      # *** wait for the board ***
      wait_for_device()
  
    # flash the device
    if os.getenv("NOPROG") is None:
      boarddir = os.path.dirname(os.path.abspath(__file__))+"/../board/"
      os.system("cd %s && make" % boarddir)
  
    start_managed_process("boardd")
  
    if os.getenv("STARTALL") is not None:
      for p in car_started_processes:
        start_managed_process(p)
  
    logger_dead = False
  
    count = 0
  
    # set 5 second timeout on health socket
    # 5x slower than expected
    health_sock.RCVTIMEO = 5000
  
    while 1:
      # get health of board, log this in "thermal"
      td = messaging.recv_sock(health_sock, wait=True)
      print td
  
      # replace thermald
      msg = read_thermal()
  
      # loggerd is gated based on free space
      statvfs = os.statvfs(ROOT)
      avail = (statvfs.f_bavail * 1.0)/statvfs.f_blocks
  
      # thermal message now also includes free space
      msg.thermal.freeSpace = avail
      with open("/sys/class/power_supply/battery/capacity") as f:
        msg.thermal.batteryPercent = int(f.read())
      with open("/sys/class/power_supply/battery/status") as f:
        msg.thermal.batteryStatus = f.read().strip()
      thermal_sock.send(msg.to_bytes())
      print msg
  
      # TODO: add car battery voltage check
      max_temp = max(msg.thermal.cpu0, msg.thermal.cpu1,
                     msg.thermal.cpu2, msg.thermal.cpu3) / 10.0
  
      # uploader is gated based on the phone temperature
      if max_temp > 85.0:
        cloudlog.info("over temp: %r", max_temp)
        kill_managed_process("uploader")
      elif max_temp < 70.0:
        start_managed_process("uploader")
  
      if avail < 0.05:
        logger_dead = True
  
      # start constellation of processes when the car starts
      if not os.getenv("STARTALL"):
        # with 2% left, we killall, otherwise the phone is bricked
        if td is not None and td.health.started and avail > 0.02:
          for p in car_started_processes:
            if p == "loggerd" and logger_dead:
              kill_managed_process(p)
            else:
              start_managed_process(p)
        else:
          logger_dead = False
          for p in car_started_processes:
            kill_managed_process(p)
  
        # shutdown if the battery gets lower than 10%, we aren't running, and we are discharging
        if msg.thermal.batteryPercent < 5 and msg.thermal.batteryStatus == "Discharging":
          os.system('LD_LIBRARY_PATH="" svc power shutdown')
  
      # check the status of all processes, did any of them die?
      for p in running:
        cloudlog.debug("   running %s %s" % (p, running[p]))
  
      # report to server once per minute
      if (count%60) == 0:
        cloudlog.event("STATUS_PACKET",
          running=running.keys(),
          count=count,
          health=(td.to_dict() if td else None),
          thermal=msg.to_dict(),
          version=version)
  
      count += 1
  
  
  # optional, build the c++ binaries and preimport the python for speed
  def manager_prepare():
    for p in managed_processes:
      proc = managed_processes[p]
      if isinstance(proc, basestring):
        # import this python
        cloudlog.info("preimporting %s" % proc)
        importlib.import_module(proc)
      else:
        # build this process
        cloudlog.info("building %s" % (proc,))
        try:
          subprocess.check_call(["make", "-j4"], cwd=proc[0])
        except subprocess.CalledProcessError:
          # make clean if the build failed
          cloudlog.info("building %s failed, make clean" % (proc, ))
          subprocess.check_call(["make", "clean"], cwd=proc[0])
          subprocess.check_call(["make", "-j4"], cwd=proc[0])
  
  def wait_for_device():
    while 1:
      try:
        context = usb1.USBContext()
        for device in context.getDeviceList(skip_on_error=True):
          if (device.getVendorID() == 0xbbaa and device.getProductID() == 0xddcc) or \
             (device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11):
            handle = device.open()
            handle.claimInterface(0)
            cloudlog.info("found board")
            handle.close()
            return
      except Exception as e:
        print "exception", e,
      print "waiting..."
      time.sleep(1)
  
  def main():
    if os.getenv("NOLOG") is not None:
      del managed_processes['loggerd']
    if os.getenv("NOUPLOAD") is not None:
      del managed_processes['uploader']
    if os.getenv("NOVISION") is not None:
      del managed_processes['visiond']
    if os.getenv("NOBOARD") is not None:
      del managed_processes['boardd']
    if os.getenv("LEAN") is not None:
      del managed_processes['uploader']
      del managed_processes['loggerd']
      del managed_processes['logmessaged']
      del managed_processes['logcatd']
    if os.getenv("NOCONTROL") is not None:
      del managed_processes['controlsd']
      del managed_processes['radard']
  
    manager_init()
    manager_prepare()
    
    if os.getenv("PREPAREONLY") is not None:
      sys.exit(0)
  
    try:
      manager_thread()
    except Exception:
      traceback.print_exc()
      common.crash.capture_exception()
    finally:
      cleanup_all_processes(None, None)
  
  if __name__ == "__main__":
    main()
  