Ejemplo n.º 1
0
    def __init__(self,
                 _rt=rt.Mock(),
                 mock_streams=True,
                 config_file='saved_state.json'):
        self._rt = _rt
        self._mock_hw = type(_rt) is rt.Mock
        self._mock_streams = mock_streams
        """Intitializes the mock system to to base configuration """
        # test open the config file, this will throw an exception if there are issues writing to the file
        with open(
                config_file, 'a'
        ):  # use append more to make sure we have read and write permissions, but won't overrite the file
            pass
        self.config_file = config_file
        self.backup_config_file = config_file + '.bak'
        self.config_file_valid = True  # initially we assume the config file is valid
        # try to load the config file or its backup
        config_paths = [self.config_file, self.backup_config_file]
        errors = []
        loaded_config = False
        for cfg_path in config_paths:
            try:
                if os.path.exists(cfg_path):
                    with open(cfg_path, 'r') as cfg:
                        self.status = json.load(cfg)
                    loaded_config = True
                    break
                else:
                    errors.append(
                        'config file "{}" does not exist'.format(cfg_path))
            except Exception as e:
                self.config_file_valid = False  # mark the config file as invalid so we don't try to back it up
                errors.append('error loading config file: {}'.format(e))

        if not loaded_config:
            print(errors[0])
            print('using default config')
            self.status = deepcopy(
                self._DEFAULT_CONFIG
            )  # only make a copy of the default config so we can make changes to it
            self.save()
        # configure all streams into a known state
        self.streams = {}
        for stream in self.status['streams']:
            self.streams[stream['id']] = streams.build_stream(
                stream, self._mock_streams)
        # configure all sources so that they are in a known state
        for src in self.status['sources']:
            self.set_source(src['id'], input=src['input'], force_update=True)
        # configure all of the zones so that they are in a known state
        #   we mute all zones on startup to keep audio from playing immediately at startup
        for z in self.status['zones']:
            # TODO: disbale zones that are not found
            self.set_zone(z['id'],
                          source_id=z['source_id'],
                          mute=True,
                          vol=z['vol'],
                          force_update=True)
        # configure all of the groups (some fields may need to be updated)
        self._update_groups()
Ejemplo n.º 2
0
def create_app(mock_ctrl=False,
               mock_streams=False,
               config_file='config/house.json'):
    if mock_ctrl:
        app.api = ctrl.Api(rt.Mock(),
                           mock_streams=mock_streams,
                           config_file=config_file)
    else:
        app.api = ctrl.Api(rt.Rpi(),
                           mock_streams=mock_streams,
                           config_file=config_file)
    return app
Ejemplo n.º 3
0
def create_app(mock=False, config_file='config/house.json'):
    if mock:
        app.api = ctrl.Api(rt.Mock(), config_file)
    else:
        app.api = ctrl.Api(rt.Rpi(), config_file)
    return app
Ejemplo n.º 4
0
  def reinit(self, settings: models.AppSettings = models.AppSettings(), change_notifier: Optional[Callable[[models.Status], None]] = None, config: Optional[models.Status] = None):
    """ Initialize or Reinitialize the controller

    Intitializes the system to to base configuration """
    self._change_notifier = change_notifier
    self._mock_hw = settings.mock_ctrl
    self._mock_streams = settings.mock_streams
    self._save_timer = None
    self._delay_saves = settings.delay_saves
    self._settings = settings

    # Create firmware interface. If one already exists delete then re-init.
    if self._initialized:
      # we need to make sure to mute every zone before resetting the fw
      zones_update = models.MultiZoneUpdate(zones=[z.id for z in self.status.zones], update=models.ZoneUpdate(mute=True))
      self.set_zones(zones_update, force_update=True, internal=True)
      try:
        del self._rt # remove the low level hardware connection
      except AttributeError:
        pass
    self._rt = rt.Mock() if settings.mock_ctrl else rt.Rpi() # reset the fw

    # test open the config file, this will throw an exception if there are issues writing to the file
    with open(settings.config_file, 'a'): # use append more to make sure we have read and write permissions, but won't overrite the file
      pass
    self.config_file = settings.config_file
    self.backup_config_file = settings.config_file + '.bak'
    self.config_file_valid = True # initially we assume the config file is valid
    errors = []
    if config:
      self.status = config
      loaded_config = True
    else:
      # try to load the config file or its backup
      config_paths = [self.config_file, self.backup_config_file]
      loaded_config = False
      for cfg_path in config_paths:
        try:
          if os.path.exists(cfg_path):
            self.status = models.Status.parse_file(cfg_path)
            loaded_config = True
            break
          errors.append('config file "{}" does not exist'.format(cfg_path))
        except Exception as exc:
          self.config_file_valid = False # mark the config file as invalid so we don't try to back it up
          errors.append('error loading config file: {}'.format(exc))

    if not loaded_config:
      print(errors[0])
      print('using default config')
      self.status = models.Status.parse_obj(self.DEFAULT_CONFIG)
      self.save()

    self.status.info = models.Info(
      mock_ctrl=self._mock_hw,
      mock_streams=self._mock_streams,
      config_file=self.config_file,
      version=utils.detect_version()
    )

    # TODO: detect missing sources

    # detect missing zones
    if self._mock_hw:
      # only allow 6 zones when mocked to simplify testing
      # add more if needed by specifying them in the config
      potential_zones = range(6)
    else:
      potential_zones = range(rt.MAX_ZONES)
    added_zone = False
    for zid in potential_zones:
      _, zone = utils.find(self.status.zones, zid)
      if zone is None and self._rt.exists(zid):
        added_zone = True
        self.status.zones.append(models.Zone(id=zid, name=f'Zone {zid+1}'))
    # save new config if zones were added
    if added_zone:
      self.save()

    # configure all streams into a known state
    self.streams: Dict[int, amplipi.streams.AnyStream] = {}
    failed_streams: List[int] = []
    for stream in self.status.streams:
      if stream.id:
        try:
          self.streams[stream.id] = amplipi.streams.build_stream(stream, self._mock_streams)
        except Exception as exc:
          print(f"Failed to create '{stream.name}' stream: {exc}")
          failed_streams.append(stream.id)
    # only keep the successful streams, this fixes a common problem of loading a stream that doesn't exist in the current developement
    # [:] does an in-place modification to the list suggested by https://stackoverflow.com/a/1208792/1110730
    self.status.streams[:] = [s for s in self.status.streams if s.id not in failed_streams]

    # configure all sources so that they are in a known state
    for src in self.status.sources:
      if src.id is not None:
        update = models.SourceUpdate(input=src.input)
        self.set_source(src.id, update, force_update=True, internal=True)
    # configure all of the zones so that they are in a known state
    #   we mute all zones on startup to keep audio from playing immediately at startup
    for zone in self.status.zones:
      # TODO: disable zones that are not found
      # we likely need an additional field for this, maybe auto-disabled?
      zone_update = models.ZoneUpdate(source_id=zone.source_id, mute=True, vol=zone.vol)
      self.set_zone(zone.id, zone_update, force_update=True, internal=True)
    # configure all of the groups (some fields may need to be updated)
    self._update_groups()