def set_group(self, id, name=None, source_id=None, zones=None, mute=None, vol_delta=None): """Configures an existing group parameters will be used to configure each sone in the group's zones all parameters besides the group id, @id, are optional Args: id: group id (a guid) name: group name source_id: group source zones: zones that belong to the group mute: group mute setting (muted=True) vol_delta: volume adjustment to apply to each zone [-79,79] Returns: 'None' on success, otherwise error (dict) """ _, g = self.get_group(id) if g is None: return utils.error( 'set group failed, group {} not found'.format(id)) if type(zones) is str: try: zones = eval(zones) except Exception as e: return utils.error( 'failed to configure group, error parsing zones: {}'. format(e)) try: name, _ = utils.updated_val(name, g['name']) zones, _ = utils.updated_val(zones, g['zones']) vol_delta, vol_updated = utils.updated_val(vol_delta, g['vol_delta']) if vol_updated: vol_change = vol_delta - g['vol_delta'] else: vol_change = 0 except Exception as e: return utils.error( 'failed to configure group, error getting current state: {}'. format(e)) g['name'] = name g['zones'] = zones for z in [self.status['zones'][zone] for zone in zones]: if vol_change != 0: # TODO: make this use volume delta adjustment, for now its a fixed group volume vol = vol_delta # vol = z['vol'] + vol_change else: vol = None self.set_zone(z['id'], None, source_id, mute, vol) g['vol_delta'] = vol_delta # update the group stats self._update_groups()
def set_source(self, sid: int, update: models.SourceUpdate, force_update: bool = False, internal: bool = False) -> ApiResponse: """Modifes the configuration of one of the 4 system sources Args: id (int): source id [0,3] update: changes to source force_update: bool, update source even if no changes have been made (for hw startup) internal: called by a higher-level ctrl function: Returns: 'None' on success, otherwise error (dict) """ idx, src = utils.find(self.status.sources, sid) if idx is not None and src is not None: name, _ = utils.updated_val(update.name, src.name) input_, input_updated = utils.updated_val(update.input, src.input) try: # update the name src.name = str(name) if input_updated or force_update: # shutdown old stream old_stream = self.get_stream(src) if old_stream: old_stream.disconnect() # start new stream last_input = src.input src.input = input_ # reconfigure the input so get_stream knows which stream to get stream = self.get_stream(src) if stream: # update the streams last connected source to have no input, since we have stolen its input if stream.src is not None and stream.src != idx: other_src = self.status.sources[stream.src] print('stealing {} from source {}'.format(stream.name, other_src.name)) other_src.input = '' stream.disconnect() stream.connect(idx) rt_needs_update = self._is_digital(input_) != self._is_digital(last_input) if rt_needs_update or force_update: # get the current underlying type of each of the sources, for configuration of the runtime src_cfg = [self._is_digital(self.status.sources[s].input) for s in range(4)] # update this source src_cfg[idx] = self._is_digital(input_) if not self._rt.update_sources(src_cfg): return ApiResponse.error('failed to set source') self._update_src_info(src) # synchronize the source's info if not internal: self.mark_changes() return ApiResponse.ok() except Exception as exc: return ApiResponse.error('failed to set source: ' + str(exc)) else: return ApiResponse.error('failed to set source: index {} out of bounds'.format(idx))
def set_group(self, gid, update: models.GroupUpdate, internal: bool = False) -> ApiResponse: """Configures an existing group parameters will be used to configure each sone in the group's zones all parameters besides the group id, @id, are optional Args: gid: group id (a guid) update: changes to group internal: called by a higher-level ctrl function Returns: 'None' on success, otherwise error (dict) """ _, group = utils.find(self.status.groups, gid) if group is None: return ApiResponse.error('set group failed, group {} not found'.format(gid)) name, _ = utils.updated_val(update.name, group.name) zones, _ = utils.updated_val(update.zones, group.zones) vol_delta, vol_updated = utils.updated_val(update.vol_delta, group.vol_delta) if vol_updated and (group.vol_delta is not None and vol_delta is not None): vol_change = vol_delta - group.vol_delta else: vol_change = 0 group.name = name group.zones = zones # update each of the member zones zone_update = models.ZoneUpdate(source_id=update.source_id, mute=update.mute) if vol_change != 0: # TODO: make this use volume delta adjustment, for now its a fixed group volume zone_update.vol = vol_delta # vol = z.vol + vol_change for zone in [self.status.zones[zone] for zone in zones]: self.set_zone(zone.id, zone_update, internal=True) # save the volume group.vol_delta = vol_delta if not internal: # update the group stats self._update_groups() self.mark_changes() return ApiResponse.ok()
def set_zone(self, id, name=None, source_id=None, mute=None, vol=None, disabled=None, force_update=False): """Configures a zone Args: id (int): any valid zone [0,p*6-1] (6 zones per preamp) name(str): friendly name for the zone, ie "bathroom" or "kitchen 1" source_id (int): source to connect to [0,4] mute (bool): mute the zone regardless of set volume vol (int): attenuation [-79,0] 0 is max volume, -79 is min volume disabled (bool): disable zone, for when the zone is not connected to any speakers and not in use force_update: bool, update source even if no changes have been made (for hw startup) Returns: 'None' on success, otherwise error (dict) """ idx = None for i, s in enumerate(self.status['zones']): if s['id'] == int(id): idx = i if idx is not None: try: z = self.status['zones'][idx] # TODO: use updated? value name, _ = utils.updated_val(name, z['name']) source_id, update_source_id = utils.updated_val( source_id, z['source_id']) mute, update_mutes = utils.updated_val(mute, z['mute']) vol, update_vol = utils.updated_val(vol, z['vol']) disabled, _ = utils.updated_val(disabled, z['disabled']) except Exception as e: return utils.error( 'failed to set zone, error getting current state: {}'. format(e)) try: sid = utils.parse_int(source_id, [0, 1, 2, 3, 4]) vol = utils.parse_int( vol, range(-79, 79) ) # hold additional state for group delta volume adjustments, output volume will be saturated to 0dB zones = self.status['zones'] # update non hw state z['name'] = name z['disabled'] = disabled # TODO: figure out an order of operations here, like does mute need to be done before changing sources? if update_source_id or force_update: zone_sources = [zone['source_id'] for zone in zones] zone_sources[idx] = sid if self._rt.update_zone_sources(idx, zone_sources): z['source_id'] = sid else: return utils.error( 'set zone failed: unable to update zone source') if update_mutes or force_update: mutes = [zone['mute'] for zone in zones] mutes[idx] = mute if self._rt.update_zone_mutes(idx, mutes): z['mute'] = mute else: return utils.error( 'set zone failed: unable to update zone mute') if update_vol or force_update: real_vol = utils.clamp(vol, -79, 0) if self._rt.update_zone_vol(idx, real_vol): z['vol'] = vol else: return utils.error( 'set zone failed: unable to update zone volume') # update the group stats (individual zone volumes, sources, and mute configuration can effect a group) self._update_groups() return None except Exception as e: return utils.error('set zone: ' + str(e)) else: return utils.error('set zone: index {} out of bounds'.format(idx))
def set_source(self, id, name=None, input=None, force_update=False): """Modifes the configuration of one of the 4 system sources Args: id (int): source id [0,3] name (str): user friendly source name, ie. "cd player" or "stream 1" input: method of audio input ('local', 'stream=ID') force_update: bool, update source even if no changes have been made (for hw startup) Returns: 'None' on success, otherwise error (dict) """ idx = None for i, s in enumerate(self.status['sources']): if s['id'] == id: idx = i if idx is not None: try: src = self.status['sources'][idx] name, _ = utils.updated_val(name, src['name']) input, input_updated = utils.updated_val(input, src['input']) except Exception as e: return utils.error( 'failed to set source, error getting current state: {}'. format(e)) try: # update the name src['name'] = str(name) if input_updated or force_update: # shutdown old stream old_stream = self.get_stream(src['input']) if old_stream: old_stream.disconnect() # start new stream stream = self.get_stream(input) if stream: # update the streams last connected source to have no input, since we have stolen its input if stream.src is not None and stream.src != idx: other_src = self.status['sources'][stream.src] print('stealing {} from source {}'.format( stream.name, other_src['name'])) other_src['input'] = '' else: print('stream.src={} idx={}'.format( stream.src, idx)) stream.disconnect() stream.connect(idx) rt_needs_update = self._is_digital( input) != self._is_digital(src['input']) if rt_needs_update or force_update: # get the current underlying type of each of the sources, for configuration of the runtime src_cfg = [ self._is_digital( self.status['sources'][s]['input']) for s in range(4) ] # update this source src_cfg[idx] = self._is_digital(input) if self._rt.update_sources(src_cfg): # update the status src['input'] = input return None else: return utils.error('failed to set source') else: src['input'] = input except Exception as e: return utils.error('failed to set source: ' + str(e)) else: return utils.error( 'failed to set source: index {} out of bounds'.format(idx))
def set_zone(self, zid, update: models.ZoneUpdate, force_update: bool = False, internal: bool = False) -> ApiResponse: """Reconfigures a zone Args: id: any valid zone [0,p*6-1] (6 zones per preamp) update: changes to zone force_update: update source even if no changes have been made (for hw startup) internal: called by a higher-level ctrl function Returns: ApiResponse """ idx, zone = utils.find(self.status.zones, zid) if idx is not None and zone is not None: name, _ = utils.updated_val(update.name, zone.name) source_id, update_source_id = utils.updated_val(update.source_id, zone.source_id) mute, update_mutes = utils.updated_val(update.mute, zone.mute) vol, update_vol = utils.updated_val(update.vol, zone.vol) disabled, _ = utils.updated_val(update.disabled, zone.disabled) try: sid = utils.parse_int(source_id, [0, 1, 2, 3]) vol = utils.parse_int(vol, range(-79, 79)) # hold additional state for group delta volume adjustments, output volume will be saturated to 0dB zones = self.status.zones # update non hw state zone.name = name zone.disabled = disabled if update_source_id or force_update: zone_sources = [zone.source_id for zone in zones] zone_sources[idx] = sid if self._rt.update_zone_sources(idx, zone_sources): zone.source_id = sid else: return ApiResponse.error('set zone failed: unable to update zone source') def set_mute(): mutes = [zone.mute for zone in zones] mutes[idx] = mute if self._rt.update_zone_mutes(idx, mutes): zone.mute = mute else: raise Exception('set zone failed: unable to update zone mute') def set_vol(): real_vol = utils.clamp(vol, -79, 0) if self._rt.update_zone_vol(idx, real_vol): zone.vol = vol else: raise Exception('set zone failed: unable to update zone volume') # To avoid potential unwanted loud output: # If muting, mute before setting volumes # If un-muting, set desired volume first try: if force_update or (update_mutes and update_vol): if mute: set_mute() set_vol() else: set_vol() set_mute() elif update_vol: set_vol() elif update_mutes: set_mute() except Exception as exc: return ApiResponse.error(str(exc)) if not internal: # update the group stats (individual zone volumes, sources, and mute configuration can effect a group) self._update_groups() self.mark_changes() return ApiResponse.ok() except Exception as exc: return ApiResponse.error('set zone: ' + str(exc)) else: return ApiResponse.error('set zone: index {} out of bounds'.format(idx))