示例#1
0
  def build_cntrl(self):
    ''' Builds the top bar of the GUI.

    Initializes all the general tkinter control widgets
    '''
    tk.Label(self.cntrl_frame, text = 'Select\nUniverse:').pack(side = tk.LEFT)
    self.universe_menu = RDMMenu(self.cntrl_frame, 'No Universes Available',
                                  'Select Universe')
    self.universe_menu.pack(side = tk.LEFT)
    discover_button = tk.Button(self.cntrl_frame, text = 'Discover', 
                                command = self.discover)
    discover_button.pack(side = tk.LEFT)
    self.device_menu = RDMMenu(self.cntrl_frame, "No Devices", "Select Device")
    self.device_menu.pack(side = tk.LEFT)
    self.id_state = tk.IntVar(self.root)
    self.id_state.set(0)
    self.id_box = tk.Checkbutton(self.cntrl_frame, text = 'Identify', 
                                 variable = self.id_state, 
                                 command = self.identify)
    self.id_box.pack(side = tk.LEFT)
    self.auto_disc = tk.BooleanVar(self.root)
    self.auto_disc.set(False)
    self.auto_disc_box = tk.Checkbutton(self.cntrl_frame, 
                                        text = 'Automatic\nDiscovery',
                                        variable = self.auto_disc, 
                                        command = self.discover)
    self.auto_disc_box.pack(side = tk.LEFT)
示例#2
0
  def _init_dmx(self):
    """
    initalize the widgets for the dmx tab
    """
    # Text Variables
    self.dmx_footprint = tk.StringVar(self.dmx_tab)
    self.dmx_start_address = tk.StringVar(self.dmx_tab)
    self.slot_required = tk.StringVar(self.dmx_tab)
    self.personality_name = tk.StringVar(self.dmx_tab)
    self.slot_number = tk.StringVar(self.dmx_tab)
    self.slot_name = tk.StringVar(self.dmx_tab)
    self.slot_type = tk.StringVar(self.dmx_tab)
    self.slot_label_id = tk.StringVar(self.dmx_tab)
    self.default_slot_value = tk.StringVar(self.dmx_tab)
    # Widgets
    self.start_address_entry = tk.Entry(
        self.dmx_tab, textvariable = self.dmx_start_address)
    self.start_address_button = tk.Button(self.dmx_tab, 
        text = 'Set Start Address', 
        command = self.set_start_address)
    # validatecommand make sure between 1 and 512
    self.dmx_personality_menu = RDMMenu(
        self.dmx_tab, "Personality description not supported.", "")
    self.slot_menu = RDMMenu(
        self.dmx_tab, "No slot description.", "Choose Slot")

    self.objects["DMX512_SETUP"] = [
        tk.Label(self.dmx_tab, text = "DMX Footprint:"),
        tk.Label(self.dmx_tab, textvariable = self.dmx_footprint),
        tk.Label(self.dmx_tab, text = "DMX Start Address:"),
        self.start_address_entry,
        tk.Label(self.dmx_tab, text = ""),
        self.start_address_button,
        tk.Label(self.dmx_tab, text = "Current Personality:"),
        self.dmx_personality_menu,
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_required),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.personality_name),
        tk.Label(self.dmx_tab, text = "Slot Info:"),
        self.slot_menu,
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_type),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_label_id),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_name),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.default_slot_value),
    ]
示例#3
0
  def _init_sensor(self):
    """
    initalize the widgets for the sensor tab
    """
    # Text Variable
    self.sensor_type = tk.StringVar(self.sensor_tab)
    self.sensor_unit = tk.StringVar(self.sensor_tab)
    self.sensor_prefix = tk.StringVar(self.sensor_tab)
    self.sensor_range = tk.StringVar(self.sensor_tab)
    self.normal_range = tk.StringVar(self.sensor_tab)
    self.supports_recording = tk.StringVar(self.sensor_tab)
    self.supports_lowest_highest = tk.StringVar(self.sensor_tab)
    self.sensor_name = tk.StringVar(self.sensor_tab)
    self.sensor_number = tk.StringVar(self.sensor_tab)
    self.present_value = tk.StringVar(self.sensor_tab)
    self.lowest = tk.StringVar(self.sensor_tab)
    self.highest = tk.StringVar(self.sensor_tab)
    self.recorded = tk.StringVar(self.sensor_tab)
    # Widgets
    self.sensor_menu = RDMMenu(
        self.sensor_tab, "Sensor information not provided.", "Choose Sensor")
    self.record_sensor_button = tk.Button(
        self.sensor_tab, text="Record Sensor", state=tk.DISABLED)
    self.clear_sensor_button = tk.Button(
        self.sensor_tab, text='Clear Sensor', state=tk.DISABLED)
    self.refresh_sensor_button = tk.Button(
        self.sensor_tab, text = 'Refresh', state=tk.DISABLED)

    self.objects["SENSORS"] = [
        tk.Label(self.sensor_tab, text = "Choose Sensor"),
        self.sensor_menu,
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_type),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_unit),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_prefix),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_range),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.normal_range),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.supports_recording),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.supports_lowest_highest),                              
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.present_value),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.lowest),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.highest),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.recorded),
        tk.Label(self.sensor_tab, text = ""),
        self.record_sensor_button,
        tk.Label(self.sensor_tab, text = ""),
        self.clear_sensor_button,
        tk.Label(self.sensor_tab, text = ""),
        self.refresh_sensor_button
    ]
示例#4
0
 def _init_config(self):
   """
   initalize the widgets for the config tab
   """
   # Variables
   self.display_invert = tk.StringVar(self.config_tab)
   self.display_level = tk.IntVar(self.config_tab)
   self.pan_invert = tk.BooleanVar(self.config_tab)
   self.tilt_invert = tk.BooleanVar(self.config_tab)
   self.pan_tilt_swap = tk.BooleanVar(self.config_tab)
   self.real_time_clock = tk.StringVar(self.config_tab)
   # Widgets
   self.language_menu = RDMMenu(
       self.config_tab, "Languages not supported.", "")
   self.display_invert_menu = tk.OptionMenu(self.config_tab,
                                           self.display_invert, 
                                           *PIDDict.DISPLAY_INVERT.values(),
                                           command = self._set_display_invert)
   self.display_level_menu = tk.Scale(
       self.config_tab, 
       from_ = 0, 
       to = 255, 
       variable=self.display_level,
       orient=tk.HORIZONTAL, 
       command = self._controller.set_display_level,
       length = 255, 
       state = tk.DISABLED, 
       tickinterval = 255)
   self.pan_invert_button = tk.Checkbutton(self.config_tab,
                                           variable = self.pan_invert,
                                           command = self._set_pan_invert,
                                           state = tk.DISABLED)
   self.tilt_invert_button = tk.Checkbutton(self.config_tab,
                                            variable = self.tilt_invert,
                                            command = self._set_tilt_invert,
                                            state = tk.DISABLED)
   self.pan_tilt_swap_button = tk.Checkbutton(
       self.config_tab,
       variable = self.pan_tilt_swap,
       command = self._set_pan_tilt_swap,
       state = tk.DISABLED
   )
   self.objects["CONFIGURATION"] = [
       tk.Label(self.config_tab, text = "Device Language:"),
       self.language_menu,
       tk.Label(self.config_tab, text = "Display Invert:"),
       self.display_invert_menu,
       tk.Label(self.config_tab, text = "Display Level:"),
       self.display_level_menu,
       tk.Label(self.config_tab, text = "Pan Invert:"),
       self.pan_invert_button,
       tk.Label(self.config_tab, text = "Tilt Invert:"),
       self.tilt_invert_button,
       tk.Label(self.config_tab, text = "Pan Tilt Swap"),
       self.pan_tilt_swap_button,
       tk.Label(self.config_tab, text = "Real Time Clock"),
       tk.Label(self.config_tab, textvariable = self.real_time_clock) 
   ]
示例#5
0
  def _init_setting(self):
    """
    initalize the widgets for the setting tab
    """
    # sText Varibles
    self.device_hours = tk.StringVar(self.setting_tab)
    self.lamp_hours = tk.StringVar(self.setting_tab)
    self.lamp_strikes = tk.StringVar(self.setting_tab)
    self.lamp_state = tk.StringVar(self.setting_tab)
    self.lamp_on_mode = tk.StringVar(self.setting_tab)
    self.device_power_cycles = tk.StringVar(self.setting_tab)
    self.power_state = tk.StringVar(self.setting_tab)
    # Widgets
    self.lamp_state_menu = RDMMenu(self.setting_tab,
                                   'Lamp state not supported.',
                                   '')
    self.lamp_on_mode_menu = RDMMenu(self.setting_tab,
                                     'Lamp on mode not supported',
                                     '')
    self.power_state_menu = RDMMenu(self.setting_tab,
                                    'Power state not supported.',
                                    '')

    self.objects["POWER_LAMP_SETTINGS"] = [
        tk.Label(self.setting_tab, text = "Device Hours:"),
        tk.Label(self.setting_tab, textvariable = self.device_hours),
        tk.Label(self.setting_tab, text = "Device Power Cycles:"),
        tk.Label(self.setting_tab, textvariable = self.device_power_cycles),
        tk.Label(self.setting_tab, text = "Lamp Hours:"),
        tk.Label(self.setting_tab, textvariable = self.lamp_hours),
        tk.Label(self.setting_tab, text = "Lamp Strikes:"),
        tk.Label(self.setting_tab, textvariable = self.lamp_strikes),
        tk.Label(self.setting_tab, text = "Lamp State:"),
        self.lamp_state_menu,
        tk.Label(self.setting_tab, text = "Lamp On Mode:"),
        self.lamp_on_mode_menu,
        tk.Label(self.setting_tab, text = "Power State:"),
        self.power_state_menu
    ]
示例#6
0
class DisplayApp(object):
  ''' Creates the GUI for sending and receiving RDM messages through the
      ola thread. 
  '''
  def __init__(self, width, height):
    ''' initializes the GUI and the ola thread
    
    Args:
      width: the int value of the width of the tkinter window
      height: the int value of the height of the tkinter window
    '''
    # ================== Initialize the tk root window =========================
    self._controller = Controller(self)
    self.root = tk.Tk()
    self.init_dx = width
    self.init_dy = height
    self.root.geometry('%dx%d+50+30'%(self.init_dx, self.init_dy))
    self.root.title('RDM user interface version: 1.0')
    self.root.maxsize(1600, 900)
    self.root.lift()
    self.root.update_idletasks()
    # ================== Initialize Variables and Ola Thread ===================
    self.universe = tk.IntVar(self.root)
    self.universe_dict = {}
    self._cur_uid = None
    self._uid_dict = {}
    self._pid_store = PidStore.GetStore()
    self.ola_thread = olathread.OLAThread(self._pid_store)
    self.ola_thread.start()
    self.build_frames()
    self.build_cntrl()
    self._notebook = notebook.RDMNotebook(self.root, self._controller)
    # ================== Call Fetch Universes ==================================
    self.fetch_universes(self.fetch_universes_complete)

    print 'currently in thread: %d' % threading.currentThread().ident
    time.sleep(1)
    print 'back from sleep'


  def build_frames(self):
    ''' builds the two tkinter frames that are used as parents for the
       tkinter widgets that both control and display the RDM messages.
    '''
    self.cntrl_frame = tk.PanedWindow(self.root)
    self.cntrl_frame.pack(side = tk.TOP, padx = 1, pady = 1, fill = tk.Y)
    self.info_frame_1 = tk.PanedWindow(self.root)
    self.info_frame_1.pack(side = tk.TOP, padx = 1, pady = 2, fill = tk.Y)
    
  def build_cntrl(self):
    ''' Builds the top bar of the GUI.

    Initializes all the general tkinter control widgets
    '''
    tk.Label(self.cntrl_frame, text = 'Select\nUniverse:').pack(side = tk.LEFT)
    self.universe_menu = RDMMenu(self.cntrl_frame, 'No Universes Available',
                                  'Select Universe')
    self.universe_menu.pack(side = tk.LEFT)
    discover_button = tk.Button(self.cntrl_frame, text = 'Discover', 
                                command = self.discover)
    discover_button.pack(side = tk.LEFT)
    self.device_menu = RDMMenu(self.cntrl_frame, "No Devices", "Select Device")
    self.device_menu.pack(side = tk.LEFT)
    self.id_state = tk.IntVar(self.root)
    self.id_state.set(0)
    self.id_box = tk.Checkbutton(self.cntrl_frame, text = 'Identify', 
                                 variable = self.id_state, 
                                 command = self.identify)
    self.id_box.pack(side = tk.LEFT)
    self.auto_disc = tk.BooleanVar(self.root)
    self.auto_disc.set(False)
    self.auto_disc_box = tk.Checkbutton(self.cntrl_frame, 
                                        text = 'Automatic\nDiscovery',
                                        variable = self.auto_disc, 
                                        command = self.discover)
    self.auto_disc_box.pack(side = tk.LEFT)

  def device_selected(self, uid):
    ''' called when a new device is chosen from dev_menu.

      Args: 
        uid: the uid of the newly selected device
    '''
    if uid == self._cur_uid:
      print 'Already Selected'
      return
    self._cur_uid = uid
    pid_key = 'DEVICE_LABEL'
    self.ola_thread.rdm_get(self.universe.get(), uid, 0, 'IDENTIFY_DEVICE', 
                  lambda b, s, uid = uid:self._get_identify_complete(uid, b, s))
    data = self._uid_dict[uid]
    flow = controlflow.RDMControlFlow(
        self.universe.get(), uid, [
            actions.GetSupportedParams(data, self.ola_thread.rdm_get),
            actions.GetDeviceInfo(data, self.ola_thread.rdm_get)
        ],
        self._device_changed_complete)
    flow.run()

  def _device_changed_complete(self):
    '''called once the control flow created in device_selected completes
    '''
    self._uid_dict[self._cur_uid]['PARAM_NAMES'] = set()
    for pid_key in self._uid_dict[self._cur_uid]['SUPPORTED_PARAMETERS']:
      pid = self._pid_store.GetPid(pid_key)
      if pid is not None:
        self._uid_dict[self._cur_uid]['PARAM_NAMES'].add(pid.name)
    print 'Device selected: %s' % self._uid_dict[self._cur_uid]
    self._notebook.update()

  def _get_identify_complete(self, uid, succeeded, value):
    ''' Callback for rdm_get in device_selected.

        Sets the checkbox's state to that of the currently selected device
    '''
    if succeeded: 
      self.id_state.set(value['identify_state'])

  def fetch_universes(self, callback):
    self.ola_thread.fetch_universes(self.fetch_universes_complete)

  def fetch_universes_complete(self, succeeded, universes):

    if succeeded:
      for universe in universes:
        self.universe_dict[universe.id] = UniverseObj(universe.id, universe.name)
        self.universe_menu.add_item(universe.name, 
          lambda i = universe.id: self._set_universe(i))
    else:
      print 'could not find active universe'

  def _set_universe(self, universe_id):
    ' sets the int var self.universe to the value of i '
    print universe_id
    self.universe.set(universe_id)
    self.device_menu.clear_menu()
    if not self.universe_dict[universe_id].discovery_run:
      self.discover()
    else:
      for uid in self.universe_dict[universe_id].uids:
        self._add_device_to_menu(uid)

  def discover(self):
    ' runs discovery for the current universe. '
    universe_id = self.universe.get()
    self.ola_thread.run_discovery(universe_id, 
            lambda status, uids:self._upon_discover(status, uids, universe_id))
    if self.auto_disc.get():
      self.ola_thread.add_event(5000, self.discover)
    else: 
      print 'Automatic discovery is off.'
  
  def _upon_discover(self, status, uids, universe_id):
    ' callback for client.runRDMDiscovery. '
    if self.universe.get() != universe_id:
      return
    self.universe_dict[universe_id].set_uids(uids)
    if not uids:
      self.device_menu.clear_menu()
    for uid in uids:
      if uid not in self._uid_dict.keys():
        self._uid_dict[uid] = {}
        self.ola_thread.rdm_get(self.universe.get(), uid, 0, 'DEVICE_LABEL', 
                             lambda b, s, uid = uid:self._add_device(uid, b, s),
                             [])

  def identify(self):
    ''' Command is called by id_box.

        sets the value of the device's identify field based on the value of 
        id_box.
    '''
    if self._cur_uid is None:
      return
    self.ola_thread.rdm_set(self.universe.get(), self._cur_uid, 0, 
              'IDENTIFY_DEVICE', 
              lambda b, s, uid = self._cur_uid
        :self._set_identify_complete(uid, b, s), 
              [self.id_state.get()])

  def _add_device(self, uid, error, data):
    ''' callback for the rdm_get in upon_discover.
        populates self.device_menu
    '''
    if error is None:
      self._uid_dict.setdefault(uid, {})['DEVICE_LABEL'] = data['label']
    else:
      self._uid_dict.setdefault(uid, {})['DEVICE_LABEL'] = ''
    self._add_device_to_menu(uid)

  def _set_identify_complete(self, uid, succeded, value):
    ''' callback for the rdm_set in identify. '''
    print 'identify %s' % value
    self._uid_dict[self._cur_uid] = value

  # ============================================================================
  # ============================ RDM Gets ======================================

  def get_basic_information(self):
    ''' creates and calls the action flow for retrieving information for the
        first tab of the notebook. 

        Triggered by: the control flow class, originally from the notebook
        initialization and "Device Information" tab selection.
    '''
    if self._cur_uid is None:
      print 'you need to select a device'
      return
    data = self._uid_dict[self._cur_uid]
    flow = controlflow.RDMControlFlow(
                  self.universe.get(), 
                  self._cur_uid, 
                  [
                  actions.GetProductDetailIds(data, self.ola_thread.rdm_get),
                  actions.GetDeviceModel(data, self.ola_thread.rdm_get),
                  actions.GetManufacturerLabel(data, self.ola_thread.rdm_get),
                  actions.GetFactoryDefaults(data, self.ola_thread.rdm_get),
                  actions.GetSoftwareVersion(data, self.ola_thread.rdm_get),
                  actions.GetBootSoftwareLabel(data, self.ola_thread.rdm_get),
                  actions.GetBootSoftwareVersion(data, self.ola_thread.rdm_get)
                  ],
                  self.update_basic_information)
    flow.run()

  def get_dmx_information(self):
    ''' creates and calls the action flow for retrieving information for the
        second tab of the notebook. 

        Triggered by: the control flow class, originally from the notebook
        initialization and "DMX512 Information" tab selection.
    '''

    if self._cur_uid is None:
      print 'you need to select a device.'
      return
    dmx_actions = []
    data = self._uid_dict[self._cur_uid]
    dmx_actions.append(actions.GetDmxPersonality(data, self.ola_thread.rdm_get))
    for i in xrange(data['DEVICE_INFO']['personality_count']):
      dmx_actions.append(actions.GetPersonalityDescription(
                                                  data, 
                                                  self.ola_thread.rdm_get, 
                                                  [i + 1]))
    dmx_actions.append(actions.GetStartAddress(data, self.ola_thread.rdm_get))
    dmx_actions.append(actions.GetSlotInfo(data, self.ola_thread.rdm_get))
    print 'dmx_footprint: %d' % data['DEVICE_INFO']['dmx_footprint']
    for i in xrange(data['DEVICE_INFO']['dmx_footprint']):
      dmx_actions.append(actions.GetSlotDescription(
                                                  data, 
                                                  self.ola_thread.rdm_get,
                                                  [i]))
    dmx_actions.append(
        actions.GetDefaultSlotValue(data, self.ola_thread.rdm_get))
    print i
    flow = controlflow.RDMControlFlow(
                self.universe.get(),
                self._cur_uid,
                dmx_actions,
                self.update_dmx_information)
    flow.run()

  def get_sensor_definitions(self):
    ''' gets the sensor definition for each sensor in the sensor count
    '''
    if self._cur_uid is None:
      return
    sensor_actions = []
    data = self._uid_dict[self._cur_uid]
    for i in xrange(data['DEVICE_INFO']['sensor_count']):
      sensor_actions.append(actions.GetSensorDefinition(data, 
                                                        self.ola_thread.rdm_get,
                                                        [i]))
                        
    flow = controlflow.RDMControlFlow(
                  self.universe.get(),
                  self._cur_uid,
                  sensor_actions,
                  self.update_sensor_information)
    flow.run()

  def get_sensor_value(self, sensor_number):
    """ gets the sensor value for the currently selected sensor

      Args: sensor_number: the number associated with the currently selected 
            sensor. 

      call originates in render_sensor_information in the notebook class.
    """
    if self._cur_uid is None:
      return
    sensor_actions = []
    data = self._uid_dict[self._cur_uid]
    sensor_actions = [actions.GetSensorValue(data,
                                             self.ola_thread.rdm_get,
                                             [sensor_number])]
                        
    flow = controlflow.RDMControlFlow(
                  self.universe.get(),
                  self._cur_uid,
                  sensor_actions,
                  lambda: self.display_sensor_data(sensor_number))
    flow.run()

  def get_setting_information(self):
    ''' creates and calls the action flow for retrieving information for the
        forth tab of the notebook. 

        Triggered by: the control flow class, originally from the notebook
        initialization and "Power and Lamp Settings" tab selection.
    '''
    if self._cur_uid is None:
      print 'you need to select a device'
      return
    data = self._uid_dict[self._cur_uid]
    flow = controlflow.RDMControlFlow(
                  self.universe.get(), 
                  self._cur_uid, 
                  [
                  actions.GetDeviceHours(data, self.ola_thread.rdm_get),
                  actions.GetLampHours(data, self.ola_thread.rdm_get),
                  actions.GetLampState(data, self.ola_thread.rdm_get),
                  actions.GetLampStrikes(data, self.ola_thread.rdm_get),
                  actions.GetLampOnMode(data, self.ola_thread.rdm_get),
                  actions.GetPowerCycles(data, self.ola_thread.rdm_get),
                  actions.GetPowerState(data, self.ola_thread.rdm_get),
                  ],
                  self.update_setting_information)
    flow.run()

  def get_config_information(self):
    ''' creates and calls the action flow for retrieving information for the
        fifth tab of the notebook. 

        Triggered by: the control flow class, originally from the notebook
        initialization and "Configure" tab selection.
    '''
    if self._cur_uid is None:
      print 'you need to select a device'
      return
    data = self._uid_dict[self._cur_uid]
    flow = controlflow.RDMControlFlow(
                  self.universe.get(), 
                  self._cur_uid, 
                  [
                  actions.GetLanguageCapabilities(data, self.ola_thread.rdm_get),
                  actions.GetLanguage(data, self.ola_thread.rdm_get),
                  actions.GetDisplayInvert(data, self.ola_thread.rdm_get),
                  actions.GetDisplayLevel(data, self.ola_thread.rdm_get),
                  actions.GetPanInvert(data, self.ola_thread.rdm_get),
                  actions.GetTiltInvert(data, self.ola_thread.rdm_get),
                  actions.GetPanTiltSwap(data, self.ola_thread.rdm_get),
                  actions.GetRealTimeClock(data, self.ola_thread.rdm_get)
                  ],
                  self.update_config_information)
    flow.run()
  
  # ============================ Notebook Updates ==============================
  # The following methods call into the notebook class and popluate the 
  # associted tab with RDM information

  def update_basic_information(self):
    # print "uid_dict: %s" % self._uid_dict[self._cur_uid]
    self._notebook.render_basic_information(self._uid_dict[self._cur_uid])

  def update_dmx_information(self):
    self._notebook.render_dmx_information(self._uid_dict[self._cur_uid])

  def update_setting_information(self):
    self._notebook.render_setting_information(self._uid_dict[self._cur_uid])

  def update_config_information(self):
    self._notebook.render_config_information(self._uid_dict[self._cur_uid])

  # the sensor tab is slightly different that those above. The first method
  # populates (update_sensor_information) the RDM menu on the sensor tab and the
  # second method displays the values being recorded/sensed by the sensor device 
  def update_sensor_information(self):
    self._notebook.render_sensor_information(self._uid_dict[self._cur_uid])

  def display_sensor_data(self,sensor_number):
    self._notebook.display_sensor_data(
        self._uid_dict[self._cur_uid],sensor_number)

  # ============================ RDM Sets ======================================
  def set_start_address(self, start_address):
    ''' RDM set call for DMX_START_ADDRESS
        call originates from the start address button in the notebook class

        Args:
          start_address: INT 16 bit; the DMX address that is set as the start
              address.
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._set_address_complete(start_address, b, s)
    self.ola_thread.rdm_set(
        self.universe.get(), uid, 0, 'DMX_START_ADDRESS', callback, 
        [start_address])

  def _set_address_complete(self, start_address, error, data):
    ''' Callback method from DMX_START_ADDRESS RDM set.
        
        Args:
          start_address: INT 16 bit; the new DMX start address of the RDM
              device
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    print 'set start address callback'
    if error is None:
      pid_dict = self._uid_dict[self._cur_uid]
      pid_dict['DEVICE_INFO']['dmx_start_address'] = start_address
      pid_dict['DMX_START_ADDRESS'] = start_address
      print 'DMX start address set to %s' % start_address
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_dmx_personality(self, personality):
    ''' RDM set call for DMX_PERSONALITY
        call originates from an optionMenu in the DMX tab in the notebook class

        Args:
          personaility: INT 8 bit; the new personality for the current device
    '''
    print "setting DMX persaonality..."
    if self._cur_uid is None:
      return
    data = self._uid_dict[self._cur_uid]
    flow_actions = [actions.SetDMXPersonality(data, self.ola_thread.rdm_set,
                                              [personality])]
    flow = controlflow.RDMControlFlow(
                self.universe.get(),
                self._cur_uid,
                flow_actions,
                lambda: self._get_slot_info(personality, None, data))
    flow.run()

  def _get_slot_info(self, personality, error, data):
    '''
        Args:
          personality: INT 8 bit; the personality of the current device
          error: the error returned if the previous ola call fails, otherwise
              this will be None
          data: 16 bit slot number, data to be passed to the ola thread
    '''
    print 'getting slot info...'
    if error is None:
      data = self._uid_dict[self._cur_uid]
      flow_actions = []
      for slot in xrange(self._uid_dict[self._cur_uid]
          ['DMX_PERSONALITY_DESCRIPTION']
          [personality]['slots_required']):
        flow_actions.append(actions.GetSlotDescription(
            data, self.ola_thread.rdm_get, [slot]))
        flow_actions.append(actions.GetSlotInfo(data, self.ola_thread.rdm_get))
        flow_actions.append(actions.GetDefaultSlotValue(
            data, self.ola_thread.rdm_get))
      flow = controlflow.RDMControlFlow(
                self.universe.get(),
                self._cur_uid,
                flow_actions,
                lambda: self._set_dmx_personality_complete(None, data))
      flow.run()
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def _set_dmx_personality_complete(self, error, data):
    ''' Callback method from DMX_PERSONALITY RDM set.
        
        Args:
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    #print self._uid_dict[self._cur_uid]
    if error is None:
      self._notebook.set_dmx_personality_complete(self._uid_dict[self._cur_uid])
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)

  def set_display_level(self, level):
    ''' RDM set call for DISPLAY_LEVEL
        call originates from a scale in the config tab of the notebook class

        Args:
          level: INT 8 bit; the new display level for the current device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._display_level_complete(uid, level, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'DISPLAY_LEVEL',
                              callback,
                              [level])

  def _display_level_complete(self, uid, level, error, data):
    ''' Callback method from DISPLAY_LEVEL RDM set.
        
        Args:
          uid: The uid of the current uid
          level: INT 8 bit, the new display level of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['DISPLAY_LEVEL'] = level
      self._notebook.set_display_level_complete(level)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_lamp_state(self, state):
    ''' RDM set call for LAMP_STATE
        call originates from an optionMenu in the lamp/power settings tab of the 
        notebook class

        Args:
          state: INT [0, 3]; the new lamp state for the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._set_lamp_state_complete(uid, state, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'LAMP_STATE',
                              callback,
                              [state])

  def _set_lamp_state_complete(self, uid, state, error, data):
    '''Callback method from LAMP_STATE RDM set.

        Args:
          uid: The uid of the RDM device
          state: INT [0, 3]; the new lamp state of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['LAMP_STATE'] = state
      self._notebook.set_lamp_state_complete(state)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_lamp_on_mode(self, mode):
    ''' RDM set call for LAMP_ON_MODE
        call originates from an optionMenu in the lamp/power settings tab of the 
        notebook class

        Args:
          mode: INT [0, 3]; the new lamp on mode for the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._set_lamp_on_mode_complete(uid, mode, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'LAMP_ON_MODE',
                              callback,
                              [mode])
    
  def _set_lamp_on_mode_complete(self, uid, mode, error, data):
    '''Callback method from LAMP_ON_MODE RDM set.
        Args:
          uid: The uid of the RDM device
          mode: INT [0, 3]; the new lamp on mode of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['LAMP_ON_MODE'] = mode
      self._notebook.set_lamp_on_mode_complete(mode)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_power_state(self, state):
    ''' RDM set call for POWER_STATE
        call originates from an optionMenu in the lamp/power settings tab of the 
        notebook class

        Args:
          state: INT {[0,2], 255}; the new power state for the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._set_power_state_complete(uid, state, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'POWER_STATE',
                              callback,
                              [state])
    
  def _set_power_state_complete(self, uid, state, error, data):
    '''Callback method from POWER_STATE RDM set.
        Args:
          uid: The uid of the RDM device
          state: INT {[0,2], 255}; the new power state of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['POWER_STATE'] = state
      self._notebook.set_power_state_complete(state)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_language(self, language):
    ''' RDM set call for LANGUAGE
        call originates from an optionMenu in the config tab of the notebook
        class

        Args:
          language: STRING length 2; the new language for the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._language_complete(uid, language, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'LANGUAGE',
                              callback,
                              [language])

  def _language_complete(self, uid, language, error, data):
    '''Callback method from LANGUAGE RDM set.
        Args:
          uid: The uid of the RDM device
          language: STRING length 2; the new language of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['LANGUAGE'] = language
      self._notebook.set_language_complete(language)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_display_invert(self, invert):
    ''' RDM set call for DISPLAY_INVERT
        call originates from an optionMenu in the config tab of the notebook
        class

        Args:
          invert: INT [0,2]; the new invert state for the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._display_invert_complete(uid, invert, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'DISPLAY_INVERT',
                              callback,
                              [invert])

  def _display_invert_complete(self, uid, invert, error, data):
    '''Callback method from DISPLAY_INVERT RDM set.
        Args:
          uid: The uid of the RDM device
          invert: INT [0,2]; the new invert state of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
                 otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['DISPLAY_INVERT'] = invert
      self._notebook.set_display_invert_complete(invert)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_pan_invert(self, invert):
    ''' RDM set call for PAN_INVERT
        call originates from a checkbox in the config tab of the notebook
        class

        Args:
          invert: BOOL; the new pan invert state for the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._pan_invert_complete(uid, invert, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'PAN_INVERT',
                              callback,
                              [invert])

  def _pan_invert_complete(self, uid, invert, error, data):
    '''Callback method from PAN_INVERT RDM set.
        Args:
          uid: The uid of the RDM device
          invert: BOOL; the new pan invert state of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
                 otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['PAN_INVERT'] = invert
      self._notebook.set_pan_invert_complete(invert)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_tilt_invert(self, invert):
    ''' RDM set call for TILT_INVERT
        call originates from a checkbox in the config tab of the notebook
        class

        Args:
          invert: BOOL; the new tilt invert state for the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._tilt_invert_complete(uid, invert, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                              uid,
                              0,
                              'TILT_INVERT',
                              callback,
                              [invert])

  def _tilt_invert_complete(self, uid, invert, error, data):
    '''Callback method from TILT_INVERT RDM set.
        Args:
          uid: The uid of the RDM device
          invert: BOOL; the new tilt invert state of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
                 otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['TILT_INVERT'] = invert
      self._notebook.set_tilt_invertComplete(invert)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def set_pan_tilt_swap(self, swap):
    ''' RDM set call for PAN_TILT_SWAP
        call originates from a checkbox in the config tab of the notebook
        class

        Args:
          swap: BOOL; the new pan/tilt swap state of the RDM device
    '''
    if self._cur_uid is None:
      return
    uid = self._cur_uid
    callback = lambda b, s: self._pan_tilt_swap_complete(uid, swap, b, s)
    self.ola_thread.rdm_set(self.universe.get(),
                            uid,
                            0,
                            'PAN_TILT_SWAP',
                            callback,
                            [swap])

  def _pan_tilt_swap_complete(self, uid, swap, error, data):
    '''Callback method from PAN_TILT_SWAP RDM set.
        Args:
          uid: The uid of the RDM device
          swap: BOOL; the new pan/tilt swap state of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
                 otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      self._uid_dict[uid]['PAN_TILT_SWAP'] = swap
      self._notebook.set_pan_tilt_swap_complete(swap)
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def record_sensor(self, sensor_number):
    ''' RDM set call for RECORD_SENSORS
        call originates from a button in the sensor tab of the notebook
        class

        Args:
          sensor_number: INT; the number of the sensor whose values are being
              recorded
    '''
    self.ola_thread.rdm_set(self.universe.get(),
                            self._cur_uid,
                            0,
                            'RECORD_SENSORS',
                            lambda b, s: self._record_sensor_complete(b, s),
                            [sensor_number])
  
  def _record_sensor_complete(self, error, data):
    ''' Callback method from RECORD_SENSORS RDM set.
        
        Args:
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      pass
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()

  def clear_sensor(self, sensor_number):
    ''' clears the values of the RDM sensor device

        Args:
          sensor_number: INT; the number of the sensor whose values are being
              cleared
    '''
    sensor_actions = []
    data = self._uid_dict[self._cur_uid]
    sensor_actions.append(actions.SetSensorValue(
        data, self.ola_thread.rdm_set, [sensor_number]))
    sensor_actions.append(actions.GetSensorValue(
        data, self.ola_thread.rdm_get, [sensor_number]))
    sensor_actions.append(actions.GetSensorDefinition(
        data, self.ola_thread.rdm_get, [sensor_number]))
    flow = controlflow.RDMControlFlow(
                  self.universe.get(),
                  self._cur_uid,
                  sensor_actions,
                  lambda: self.display_sensor_data(sensor_number))
    flow.run()

  # def clear_sensor_complete(self, error, data):
  #   ''' Callback method from CLEA RDM set.
        
  #       Args:
  #         error: If the RDM set fails, this will be the error returned by OLA
  #             otherwise this value will be None 
  #         data: Not Present
  #   '''
  #   if error is None:
  #     pass
  #   else:
  #     d = RDMDialog(self.root, error)
  #     self.root.wait_window(d.top)
  #     self._notebook.update()

  # ================================ Callbacks =================================


  def set_device_label(self, label):
    ''' RDM set call for DEVICE_LABEL
        call originates from a button in the device information tab of the
        notebook class

        Args:
          label: STRING maxsize 32; the new device label for the RDM device
    '''
    uid = self._cur_uid
    callback = (lambda b, s: self.set_device_label_complete(uid, label, b, s))
    self.ola_thread.rdm_set(self.universe.get(), 
                              uid,
                              0, 
                              'DEVICE_LABEL',
                              callback,
                              [label]
                              )

  def set_device_label_complete(self, uid, label, error, data):
    ''' Callback method from DEVICE_LABEL RDM set.

        Args:
          uid: The uid of the RDM device
          label: STRING maxsize 32; the new device label of the RDM device
          error: If the RDM set fails, this will be the error returned by OLA
              otherwise this value will be None 
          data: Not Present
    '''
    if error is None:
      index = self._uid_dict[self._cur_uid]['index']
      self._uid_dict[self._cur_uid]['DEVICE_LABEL'] = label
      self.device_menu.entryconfigure(index, label = '%s (%s)'%(
                  self._uid_dict[uid]['DEVICE_LABEL'], uid))
    else:
      d = RDMDialog(self.root, error)
      self.root.wait_window(d.top)
      self._notebook.update()
    # store the results in the uid dict
    self.root.update_idletasks()
    self._notebook.update()

  def _add_device_to_menu(self, uid):
    ''' adds a device to the device menu at the top of the GUI

        Args:
          uid: 48-bit Unique ID (UID) consisting of a 16-bit Manufacturer ID 
              and a 32-bit Device ID
    '''
    label = self._uid_dict[uid]['DEVICE_LABEL']
    if label == '':
      menu_label = '%s' % uid
    else:
      menu_label = '%s (%s)' % (label, uid)
    index = self.device_menu.add_item(menu_label, lambda:self.device_selected(uid))
    self._uid_dict[uid]['index'] = index

  def main(self):
    print 'Entering main loop'
    self.root.mainloop()
示例#7
0
class RDMNotebook(object):
  def __init__(self, root, controller, width=800, height=500, side=tk.TOP):
    """ Builds the ttk.Notebook """
    self.root = root
    self._controller = controller
    self.init_dx = width
    self.init_dy = height
    self.side = side
    self.objects = {}
    self.pid_location_dict = {}
    self._notebook = ttk.Notebook(self.root, name="nb", height=height,
                                  width=width)
    # self._notebook.config(state = tk.DISABLED)
    self._notebook.bind('<<NotebookTabChanged>>', self._tab_changed)
    self.populate_defaults()

  def populate_defaults(self):
    """ creates the default frames. """
    self.info_tab = self._create_tab("info_tab", "Device Information")
    self._init_info()
    self.dmx_tab = self._create_tab("dmx_tab", "DMX512 Setup")
    self._init_dmx()
    self.sensor_tab = self._create_tab("sensor_tab", "Sensors")
    self._init_sensor()
    self.setting_tab = self._create_tab("setting_tab", 
                                        "Power and Lamp Settings")
    self._init_setting()
    self.config_tab = self._create_tab("config_tab", "Configuration")
    self._init_config()
    tabs = ["PRODUCT_INFO", "DMX512_SETUP", "SENSORS",
            "POWER_LAMP_SETTINGS", "CONFIGURATION"]
    for tab in tabs:
      self._grid_info(self.objects[tab])
    self._notebook.pack(side = self.side)

# ==============================================================================
# ============================ Tab Inits =======================================
# ==============================================================================

  def _init_info(self):
    """
    initalize the widgets for the info tab
    """
    # Text Variables:
    self.protocol_version = tk.StringVar(self.info_tab)
    self.device_model = tk.StringVar(self.info_tab)
    self.product_category = tk.StringVar(self.info_tab)
    self.software_version = tk.StringVar(self.info_tab)
    self.sub_device_count = tk.StringVar(self.info_tab)
    self.product_detail_ids = tk.StringVar(self.info_tab)
    self.manufacturer_label = tk.StringVar(self.info_tab)
    self.device_label = tk.StringVar(self.info_tab)
    self.boot_software = tk.StringVar(self.info_tab)
    # Widgets:
    #device label entry boc value should be capped at 32
    self.factory_defaults = tk.BooleanVar(self.info_tab)
    self.factory_defaults_button = tk.Checkbutton(
        self.info_tab, variable = self.factory_defaults)
    self.device_label_button = tk.Button(self.info_tab, 
                                         text = "Update Device Label",
                                         command = self.device_label_set)
    self.objects["PRODUCT_INFO"] = [
        tk.Label(self.info_tab, text = "RDM Protocol Version"),
        tk.Label(self.info_tab, textvariable = self.protocol_version),
        tk.Label(self.info_tab, text = "Device Model"),
        tk.Label(self.info_tab, textvariable = self.device_model),
        tk.Label(self.info_tab, text = "Product Category:"),
        tk.Label(self.info_tab, textvariable = self.product_category),
        tk.Label(self.info_tab, text = "Software Version:"),
        tk.Label(self.info_tab, textvariable = self.software_version),
        tk.Label(self.info_tab, text = "Product Details:"),
        tk.Label(self.info_tab, textvariable = self.product_detail_ids),
        tk.Label(self.info_tab, text = "Sub-Device Count"),
        tk.Label(self.info_tab, textvariable = self.sub_device_count),
        tk.Label(self.info_tab, text = "Manufacturer:"),
        tk.Label(self.info_tab, textvariable = self.manufacturer_label),
        tk.Label(self.info_tab, text = "Device Label:"),
        tk.Entry(self.info_tab, textvariable = self.device_label),
        tk.Label(self.info_tab, text = "Factory Defaults:"),
        tk.Checkbutton(self.info_tab, variable = self.factory_defaults),
        tk.Label(self.info_tab, text = "Boot Software Version:"),
        tk.Label(self.info_tab, textvariable = self.boot_software),
        self.device_label_button,
        tk.Label(self.info_tab, text = ""),
    ]

  def _init_dmx(self):
    """
    initalize the widgets for the dmx tab
    """
    # Text Variables
    self.dmx_footprint = tk.StringVar(self.dmx_tab)
    self.dmx_start_address = tk.StringVar(self.dmx_tab)
    self.slot_required = tk.StringVar(self.dmx_tab)
    self.personality_name = tk.StringVar(self.dmx_tab)
    self.slot_number = tk.StringVar(self.dmx_tab)
    self.slot_name = tk.StringVar(self.dmx_tab)
    self.slot_type = tk.StringVar(self.dmx_tab)
    self.slot_label_id = tk.StringVar(self.dmx_tab)
    self.default_slot_value = tk.StringVar(self.dmx_tab)
    # Widgets
    self.start_address_entry = tk.Entry(
        self.dmx_tab, textvariable = self.dmx_start_address)
    self.start_address_button = tk.Button(self.dmx_tab, 
        text = 'Set Start Address', 
        command = self.set_start_address)
    # validatecommand make sure between 1 and 512
    self.dmx_personality_menu = RDMMenu(
        self.dmx_tab, "Personality description not supported.", "")
    self.slot_menu = RDMMenu(
        self.dmx_tab, "No slot description.", "Choose Slot")

    self.objects["DMX512_SETUP"] = [
        tk.Label(self.dmx_tab, text = "DMX Footprint:"),
        tk.Label(self.dmx_tab, textvariable = self.dmx_footprint),
        tk.Label(self.dmx_tab, text = "DMX Start Address:"),
        self.start_address_entry,
        tk.Label(self.dmx_tab, text = ""),
        self.start_address_button,
        tk.Label(self.dmx_tab, text = "Current Personality:"),
        self.dmx_personality_menu,
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_required),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.personality_name),
        tk.Label(self.dmx_tab, text = "Slot Info:"),
        self.slot_menu,
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_type),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_label_id),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.slot_name),
        tk.Label(self.dmx_tab, text = ""),
        tk.Label(self.dmx_tab, textvariable = self.default_slot_value),
    ]

  def _init_sensor(self):
    """
    initalize the widgets for the sensor tab
    """
    # Text Variable
    self.sensor_type = tk.StringVar(self.sensor_tab)
    self.sensor_unit = tk.StringVar(self.sensor_tab)
    self.sensor_prefix = tk.StringVar(self.sensor_tab)
    self.sensor_range = tk.StringVar(self.sensor_tab)
    self.normal_range = tk.StringVar(self.sensor_tab)
    self.supports_recording = tk.StringVar(self.sensor_tab)
    self.supports_lowest_highest = tk.StringVar(self.sensor_tab)
    self.sensor_name = tk.StringVar(self.sensor_tab)
    self.sensor_number = tk.StringVar(self.sensor_tab)
    self.present_value = tk.StringVar(self.sensor_tab)
    self.lowest = tk.StringVar(self.sensor_tab)
    self.highest = tk.StringVar(self.sensor_tab)
    self.recorded = tk.StringVar(self.sensor_tab)
    # Widgets
    self.sensor_menu = RDMMenu(
        self.sensor_tab, "Sensor information not provided.", "Choose Sensor")
    self.record_sensor_button = tk.Button(
        self.sensor_tab, text="Record Sensor", state=tk.DISABLED)
    self.clear_sensor_button = tk.Button(
        self.sensor_tab, text='Clear Sensor', state=tk.DISABLED)
    self.refresh_sensor_button = tk.Button(
        self.sensor_tab, text = 'Refresh', state=tk.DISABLED)

    self.objects["SENSORS"] = [
        tk.Label(self.sensor_tab, text = "Choose Sensor"),
        self.sensor_menu,
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_type),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_unit),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_prefix),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.sensor_range),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.normal_range),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.supports_recording),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.supports_lowest_highest),                              
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.present_value),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.lowest),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.highest),
        tk.Label(self.sensor_tab, text = ""),
        tk.Label(self.sensor_tab, textvariable = self.recorded),
        tk.Label(self.sensor_tab, text = ""),
        self.record_sensor_button,
        tk.Label(self.sensor_tab, text = ""),
        self.clear_sensor_button,
        tk.Label(self.sensor_tab, text = ""),
        self.refresh_sensor_button
    ]

  def _init_setting(self):
    """
    initalize the widgets for the setting tab
    """
    # sText Varibles
    self.device_hours = tk.StringVar(self.setting_tab)
    self.lamp_hours = tk.StringVar(self.setting_tab)
    self.lamp_strikes = tk.StringVar(self.setting_tab)
    self.lamp_state = tk.StringVar(self.setting_tab)
    self.lamp_on_mode = tk.StringVar(self.setting_tab)
    self.device_power_cycles = tk.StringVar(self.setting_tab)
    self.power_state = tk.StringVar(self.setting_tab)
    # Widgets
    self.lamp_state_menu = RDMMenu(self.setting_tab,
                                   'Lamp state not supported.',
                                   '')
    self.lamp_on_mode_menu = RDMMenu(self.setting_tab,
                                     'Lamp on mode not supported',
                                     '')
    self.power_state_menu = RDMMenu(self.setting_tab,
                                    'Power state not supported.',
                                    '')

    self.objects["POWER_LAMP_SETTINGS"] = [
        tk.Label(self.setting_tab, text = "Device Hours:"),
        tk.Label(self.setting_tab, textvariable = self.device_hours),
        tk.Label(self.setting_tab, text = "Device Power Cycles:"),
        tk.Label(self.setting_tab, textvariable = self.device_power_cycles),
        tk.Label(self.setting_tab, text = "Lamp Hours:"),
        tk.Label(self.setting_tab, textvariable = self.lamp_hours),
        tk.Label(self.setting_tab, text = "Lamp Strikes:"),
        tk.Label(self.setting_tab, textvariable = self.lamp_strikes),
        tk.Label(self.setting_tab, text = "Lamp State:"),
        self.lamp_state_menu,
        tk.Label(self.setting_tab, text = "Lamp On Mode:"),
        self.lamp_on_mode_menu,
        tk.Label(self.setting_tab, text = "Power State:"),
        self.power_state_menu
    ]

  def _init_config(self):
    """
    initalize the widgets for the config tab
    """
    # Variables
    self.display_invert = tk.StringVar(self.config_tab)
    self.display_level = tk.IntVar(self.config_tab)
    self.pan_invert = tk.BooleanVar(self.config_tab)
    self.tilt_invert = tk.BooleanVar(self.config_tab)
    self.pan_tilt_swap = tk.BooleanVar(self.config_tab)
    self.real_time_clock = tk.StringVar(self.config_tab)
    # Widgets
    self.language_menu = RDMMenu(
        self.config_tab, "Languages not supported.", "")
    self.display_invert_menu = tk.OptionMenu(self.config_tab,
                                            self.display_invert, 
                                            *PIDDict.DISPLAY_INVERT.values(),
                                            command = self._set_display_invert)
    self.display_level_menu = tk.Scale(
        self.config_tab, 
        from_ = 0, 
        to = 255, 
        variable=self.display_level,
        orient=tk.HORIZONTAL, 
        command = self._controller.set_display_level,
        length = 255, 
        state = tk.DISABLED, 
        tickinterval = 255)
    self.pan_invert_button = tk.Checkbutton(self.config_tab,
                                            variable = self.pan_invert,
                                            command = self._set_pan_invert,
                                            state = tk.DISABLED)
    self.tilt_invert_button = tk.Checkbutton(self.config_tab,
                                             variable = self.tilt_invert,
                                             command = self._set_tilt_invert,
                                             state = tk.DISABLED)
    self.pan_tilt_swap_button = tk.Checkbutton(
        self.config_tab,
        variable = self.pan_tilt_swap,
        command = self._set_pan_tilt_swap,
        state = tk.DISABLED
    )
    self.objects["CONFIGURATION"] = [
        tk.Label(self.config_tab, text = "Device Language:"),
        self.language_menu,
        tk.Label(self.config_tab, text = "Display Invert:"),
        self.display_invert_menu,
        tk.Label(self.config_tab, text = "Display Level:"),
        self.display_level_menu,
        tk.Label(self.config_tab, text = "Pan Invert:"),
        self.pan_invert_button,
        tk.Label(self.config_tab, text = "Tilt Invert:"),
        self.tilt_invert_button,
        tk.Label(self.config_tab, text = "Pan Tilt Swap"),
        self.pan_tilt_swap_button,
        tk.Label(self.config_tab, text = "Real Time Clock"),
        tk.Label(self.config_tab, textvariable = self.real_time_clock) 
    ]


  # ============================================================================
  # ============================== Update Tabs =================================
  # ============================================================================

  def update(self):
    '''
    using the value of the current tab (self._notebook.index('current')), calls 
    the control for obtaining the information for that tab.
    '''
    index = self._notebook.index('current')
    print 'The selected tab changed to %d' % index
    if index == 0:
      self._controller.get_basic_information()
    elif index == 1:
      self._controller.get_dmx_information()
    elif index == 2:
      self._controller.get_sensor_definitions()
    elif index == 3:
      self._controller.get_setting_information()
    elif index == 4:
      self._controller.get_config_information()

  # ========================= Information Rendering ============================

  def render_basic_information(self, param_dict):
    '''
    Uses the data in param_dict to display the DMX information for the device
    Called when the tab is selected, or the device changed.
    Ultimate callback function for control flow, 'get_basic_information'.

    Args:
      param_dict: dictionary of pids for the current uid. In the form:
                                                                    {PID: data}

          NOTE: data may be in the form of a int, string or dict and is treated
              differently in each case.
    '''
    device_info = param_dict["DEVICE_INFO"]
    self.protocol_version.set(
        "Version %d.%d" % (
        device_info["protocol_major"], 
        device_info["protocol_minor"]))
    self.device_model.set(param_dict["DEVICE_INFO"]["device_model"])
    self.device_model.set("%s (%d)" % (
                          param_dict.get("DEVICE_MODEL_DESCRIPTION", 'N/A'),
                          device_info["device_model"]))
    index = device_info["product_category"]
    self.product_category.set(RDMConstants.PRODUCT_CATEGORY_TO_NAME.get(index, 
        "").replace("_"," "))
    self.sub_device_count.set(param_dict["DEVICE_INFO"]["sub_device_count"])

    self.software_version.set("%s (%d)" % (
                              param_dict.get("SOFTWARE_VERSION_LABEL", "N/A"),
                              device_info["software_version"]))
    self.sub_device_count.set(device_info["sub_device_count"])
    if "PRODUCT_DETAIL_ID_LIST" in param_dict:
      ids = param_dict["PRODUCT_DETAIL_ID_LIST"]
      names = ', '.join(RDMConstants.PRODUCT_DETAIL_IDS_TO_NAME[id]
                        for id in ids).replace("_", " ")
      self.product_detail_ids.set(names)
    self.manufacturer_label.set(param_dict.get("MANUFACTURER_LABEL", "N/A"))
    self.device_label.set(param_dict.get("DEVICE_LABEL", "N/A"))
    self.factory_defaults.set(param_dict.get("FACTORY_DEFAULTS", "N/A"))
    boot_software = 'N/A'
    boot_software_version = param_dict.get('BOOT_SOFTWARE_VERSION')
    boot_software_label = param_dict.get('BOOT_SOFTWARE_LABEL')
    if boot_software_version and boot_software_label:
      boot_software = '%s (%d)' % (boot_software_label, boot_software_version)
    elif boot_software_label:
      boot_software =  boot_software_label
    elif boot_software_version:
      boot_software =  boot_software_version
    self.boot_software.set(boot_software)

  def render_dmx_information(self, param_dict):
    '''
    Uses the data in param_dict to display the DMX information for the device
    Called when the tab is selected, or the device changed.
    Ultimate callback function for control flow, 'get_dmx_information'.

    Args:
      param_dict: dictionary of pids for the current uid. In the form:
                                                                    {PID: data}

          NOTE: data may be in the form of a int, string or dict and is treated
              differently in each case.
    '''
    print "param_dict: %s" % param_dict
    device_info = param_dict["DEVICE_INFO"]
    self.dmx_personality_menu.clear_menu()
    self.slot_menu.clear_menu()
    self._display_personality_decription('N/A', 'N/A')
    if "DMX_PERSONALITY_DESCRIPTION" in param_dict:
      personalities = param_dict["DMX_PERSONALITY_DESCRIPTION"]
      for personality in personalities.iteritems():
        print personality
        self.dmx_personality_menu.add_item(
            self._get_personality_string(personality[1]),
            lambda i = personality[0]:self._controller.set_dmx_personality(i))
      personality_id = device_info['current_personality']
      self.dmx_personality_menu.set(self._get_personality_string(
          personalities[personality_id]))
      s = personalities[personality_id]['slots_required']
      p = personality_id
      self._display_personality_decription(s, p)
    self.dmx_footprint.set(param_dict["DEVICE_INFO"]["dmx_footprint"])
    start_address = param_dict["DEVICE_INFO"]["dmx_start_address"]
    if start_address == 0xffff:
      self.dmx_start_address.set('N/A')
      self.start_address_entry.config(state=tk.DISABLED)
      self.start_address_button.config(state=tk.DISABLED)
    else:
      self.dmx_start_address.set(start_address)
      self.start_address_entry.config(state=tk.NORMAL)
      self.start_address_button.config(state=tk.NORMAL)
    if "SLOT_INFO" in param_dict:
      for index in xrange(param_dict["DEVICE_INFO"]["dmx_footprint"]):
        self.slot_menu.add_item(
            "Slot Number %d" % index,
            lambda i = index:self._display_slot_info(i, param_dict))

  def render_sensor_information(self, param_dict):
    '''
    Uses the data in param_dict to display the DMX information for the device
    Called when the tab is selected, or the device changed.
    Ultimate callback function for control flow, 'get_sensor_information'.

    Args:
      param_dict: dictionary of pids for the current uid. In the form:
                                                                    {PID: data}

          NOTE: data may be in the form of a int, string or dict and is treated
              differently in each case.
    '''
    self.sensor_type.set('')
    self.sensor_unit.set('')
    self.sensor_prefix.set('')
    self.sensor_range.set('')
    self.normal_range.set('')
    self.supports_recording.set('')
    self.present_value.set('')
    self.lowest.set('')
    self.highest.set('')
    self.recorded.set('')
    sensor_info = {}
    self.sensor_menu.clear_menu()
    for index, sensor in param_dict.get('SENSOR_DEFINITION', {}).iteritems():
      self.sensor_menu.add_item('%s' % sensor['name'],
                                lambda i=index: self._populate_sensor_tab(i))
   

  def render_setting_information(self, param_dict):
    '''
    Uses the data in param_dict to display the DMX information for the device
    Called when the tab is selected, or the device changed.
    Ultimate callback function for control flow, 'GetSettingsInformation'.

    Args:
      param_dict: dictionary of pids for the current uid. In the form:
                                                                    {PID: data}

          NOTE: data may be in the form of a int, string or dict and is treated
              differently in each case.
    '''
    print "PARAM_DICT: %s" % param_dict
    self.device_hours.set(param_dict.get('DEVICE_HOURS', 'N/A'))
    self.lamp_hours.set(param_dict.get('LAMP_HOURS', 'N/A'))
    self.device_power_cycles.set(param_dict.get("DEVICE_POWER_CYCLES", "N/A"))
    self.lamp_strikes.set(param_dict.get('LAMP_STRIKES', 'N/A'))
    self.lamp_state_menu.config(state = tk.DISABLED)
    self.lamp_on_mode_menu.config(state = tk.DISABLED)
    self.power_state_menu.config(state = tk.DISABLED)

    if 'LAMP_STATE' in param_dict:
      self.lamp_state_menu.config(state = tk.NORMAL)
      for key, value in PIDDict.LAMP_STATE.iteritems():
        self.lamp_state_menu.add_item(
            value, lambda k=key: self._controller.set_lamp_state(k))
      self.lamp_state_menu.set(PIDDict.LAMP_STATE[param_dict['LAMP_STATE']])

    if 'LAMP_ON_MODE' in param_dict:
      self.lamp_on_mode_menu.config(state = tk.NORMAL)
      for key, value in PIDDict.LAMP_ON_MODE.iteritems():
        self.lamp_on_mode_menu.add_item(value, 
                                        lambda k=key: self._set_lamp_on_mode(k))
      self.lamp_on_mode_menu.set(
          PIDDict.LAMP_ON_MODE[param_dict['LAMP_ON_MODE']])

    if 'POWER_STATE' in param_dict:
      self.power_state_menu.config(state = tk.NORMAL)
      for key, value in PIDDict.POWER_STATE.iteritems():
        self.power_state_menu.add_item(value, 
                                       lambda k=key: self._set_power_state(k))
      self.power_state_menu.set(PIDDict.POWER_STATE[param_dict['POWER_STATE']])

  def render_config_information(self, param_dict):
    '''
    Uses the data in param_dict to display the DMX information for the device
    Called when the tab is selected, or the device changed.
    Ultimate callback function for control flow, 'get_config_information'.

    Args:
      param_dict: dictionary of pids for the current uid. In the form:
                                                                    {PID: data}

          NOTE: data may be in the form of a int, string or dict and is treated
              differently in each case.
    '''
    self.language_menu.clear_menu()
    if 'LANGUAGE_CAPABILITIES' in param_dict:
      self.language_menu.config(state = tk.NORMAL)
      for value in param_dict['LANGUAGE_CAPABILITIES']:
        language = value['language']
        self.language_menu.add_item(language, 
                                    lambda l = language: self._set_language(l))
    self.language_menu.set(param_dict.get('LANGUAGE', 'N/A'))

    if "DISPLAY_LEVEL" in param_dict:
      self.display_level.set(param_dict['DISPLAY_LEVEL'])
      self.display_level_menu.config(state = tk.NORMAL)
    else:
      self.display_level.set(0)
      self.display_level_menu.config(state = tk.DISABLED)

    if 'DISPLAY_INVERT' in param_dict:
      display_invert = PIDDict.DISPLAY_INVERT [param_dict['DISPLAY_INVERT']]
      self.display_invert.set(display_invert)
      self.display_invert_menu.config(state = tk.NORMAL)
    else:
      self.display_invert.set('N/A')
      self.display_invert_menu.config(state = tk.DISABLED)

    if 'PAN_INVERT' in param_dict:
      self.pan_invert.set(param_dict['PAN_INVERT'])
      self.pan_invert_button.config(state = tk.NORMAL)
    else:
      self.pan_invert.set(False)
      self.pan_invert_button.config(state = tk.DISABLED)

    if 'TILT_INVERT' in param_dict:
      self.tilt_invert.set(param_dict['TILT_INVERT'])
      self.tilt_invert_button.config(state = tk.NORMAL)
    else:
      self.tilt_invert.set(False)
      self.tilt_invert_button.config(state = tk.DISABLED)

    if 'PAN_TILT_SWAP' in param_dict:
      self.pan_tilt_swap.set(param_dict['PAN_TILT_SWAP'])
      self.pan_tilt_swap_button.config(state = tk.NORMAL)
    else:
      self.pan_tilt_swap.set(False)
      self.pan_tilt_swap_button.config(state = tk.DISABLED)

    if 'REAL_TIME_CLOCK' in param_dict:
      clock = param_dict['REAL_TIME_CLOCK']
      self.real_time_clock.set("%d:%d:%d %d/%d/%d" % (clock['hour'],
                                                      clock['minute'],
                                                      clock['second'],
                                                      clock['day'],
                                                      clock['month'],
                                                      clock['year']
                                                      ))

  # ============================================================================
  # ============================ RDM Set Methods ===============================
  # ============================================================================
  def device_label_set(self):
    """
    calls the control flow for setting the device's label to a new value, using
    the value of self.device_label
    """
    self._controller.set_device_label(self.device_label.get())

  def set_start_address(self):
    '''
    start of control flow for setting the dmx_start_address of a device.
    '''
    try:
      start_address = int(self.dmx_start_address.get())
    except ValueError:
      d = RDMDialog(self.root, "Start address must be an int between 1 and 512")
      self.root.wait_window(d.top)
      self._notebook.update()
      return
    if start_address > 512 or start_address < 1:
      d = RDMDialog(self.root, "Start address must be between 1 and 512")    
      self.root.wait_window(d.top)
      self._notebook.update()
      return
    self._controller.set_start_address(start_address)

  def _set_lamp_state(self, state):
    '''
    Internal Function, first function in the 'SetLampState' control flow.

    Args:
      state: int, see PIDDict.LAMP_STATE for state name.
    '''
    self._controller.set_lamp_state(state)

  def set_lamp_state_complete(self, state):
    '''
    Ultimate callback for, 'SetLampState' control flow.

    Args:
      state: int, see PIDDict.LAMP_STATE for state name.
    '''
    if self.lamp_state_menu.get() != PIDDict.LAMP_STATE[state]:
      self.lamp_state_menu.set(PIDDict.LAMP_STATE[state])

  def _set_lamp_on_mode(self, mode):
    '''
    Internal Function, first function in the 'SetLampOnMode' control flow.

    Args:
      mode: int, see PIDDict.LAMP_ON_MODE for mode names.
    '''
    self._controller.set_lamp_on_mode(mode)

  def set_lamp_on_mode_complete(self, mode):
    '''
    Ulitmate callback for 'SetLampOnMode' control flow

    Args:
      mode: int, see PIDDict.LAMP_ON_MODE for mode names.
    '''
    if self.lamp_on_mode_menu.get() != PIDDict.LAMP_ON_MODE[mode]:
      self.lamp_on_mode_menu.set(PIDDict.LAMP_ON_MODE[mode])

  def _set_power_state(self, state):
    '''
    Internal Function, first function in the 'SetPowerState' control flow.

    Args:
      state: int, see PIDDict.LAMP_STATE for state name.
    '''
    self._controller.set_power_state(state)

  def set_power_state_complete(self, state):
    ''' 
    '''
    if self.power_state_menu.get() != PIDDict.POWER_STATE[state]:
      self.power_state_menu.set(PIDDict.POWER_STATE[state])

  def set_dmx_personality_complete(self, param_dict):
    print param_dict
    personality = param_dict['DEVICE_INFO']['current_personality']
    slots_required = param_dict.get("DMX_PERSONALITY_DESCRIPTION", 
                                    {})[personality].get(
                                    "slots_required", 
                                    "N/A")
    self._display_personality_decription(slots_required, personality)
    self.slot_menu.clear_menu()
    self.slot_name.set('')
    self.slot_type.set('')
    self.slot_label_id.set('')
    self.default_slot_value.set('')
    if 'SLOT_INFO' or 'SLOT_DESCRIPTION' or 'DEFAULT_SLOT_VALUE' in param_dict['PARAM_NAMES']:
      for index in xrange(slots_required):
        self.slot_menu.add_item('Slot %d' % index, 
                              lambda i=index: self._display_slot_info(i, param_dict))
    return

  def _set_display_invert(self, invert):
    '''
    Internal Function, first function in the 'SetDisplayInvert' control flow.

    Args:
      state: int, see PIDDict.DISPLAY_INVERT for state name.
    '''
    self._controller.set_display_invert(invert)
    # self._controller.SetDisplayInvert(self.display_invert.get())

  def set_display_invert_complete(self, invert):
    if self.display_invert.get() != invert:
      self.display_invert.set(invert)

  def _set_pan_invert(self):
    '''
    Internal Function, first function in the 'SetPanInvert' control flow.

    Args:
      state: boolean, true: inverted, false: normal.
    '''
    self._controller.set_pan_invert(self.pan_invert.get())

  def set_pan_invert_complete(self, invert):
    if self.pan_invert.get() != invert:
      self.pan_invert.set(invert)

  def _set_tilt_invert(self):
    '''
    Internal Function, first function in the 'SetTiltInvert' control flow.

    Args:
      state: boolean, true: inverted, false: normal.
    '''
    self._controller.set_tilt_invert(self.tilt_invert.get())

  def set_tilt_invert_complete(self, invert):
    if self.tilt_invert.get() != invert:
      self.tilt_invert.set(invert)

  def _set_pan_tilt_swap(self):
    """ Swaps the pan and tilt in the device.

    Calls set_pan_tilt_swap in the controller. The final callback for this
    control flow is set_pan_tilt_swap_complete (below).
    """
    self._controller.set_pan_tilt_swap(self.pan_tilt_swap.get())

  def set_pan_tilt_swap_complete(self, swap):
    """ Sets the value of the pan-tilt-swap Checkbutton

    Only sets the value of the Checkbutton if the current value is does not
    match the value passed into the method.

    Args:
      swap: boolean value
        True, when pan and tilt are swapped
        False, when they are unswapped, or normal
    """
    if self.pan_tilt_swap.get() != swap:
      self.pan_tilt_swap.set(swap)

  def _set_language(self, language):
    self._controller.set_language(language)

  def set_language_complete(self, language):
    if self.language_menu.get() != language:
      self.language_menu.set(language)

  def record_sensor(self, sensor_number):
    self._controller.record_sensor(sensor_number)

  def clear_sensor(self, sensor_number):
    self._controller.clear_sensor(sensor_number)

  def set_display_level_complete(self, level):
    if level != self.display_level:
      self.display_level.set(level)
    return

  # ============================================================================
  # ========================== Internal Methods ================================
  # ============================================================================

  def _tab_changed(self, event):
    '''
    Method bound to tab change event, calls self.update
    '''
    # Note that this will be called when the program starts
    self.update()

  def _grid_info(self, obj_list):
    """
    places the widgets subject to change upon completion of controlflows
    """
    obj_list.reverse()
    for r in xrange((len(obj_list) + 1) / 2):
      for c in xrange(2):
        obj_list.pop().grid(row=r, column=c)

  def _create_tab(self, tab_name, tab_label=None):
    """ Creates a tab. 

        will want to have all the options allowed by the ttk notebook widget to
        be args for this method

        Args:
          tab_name: string, cannot begin with a capital letter
          pid_list: list of strings, 
          tab_label: string that will be displayed on the tab, default set to 
            None, and tab_name will be on the tab

        Returns:
          tab: the Frame
    """
    if tab_label is None:
      tab_label = tab_name
    tab = tk.Frame(self._notebook, name=tab_name)
    self._notebook.add(tab, text=tab_label)
    return tab

  def _display_slot_info(self, slot_number, param_dict):
    """
    """
    if param_dict['SLOT_INFO'][slot_number]['slot_type'] != 0:
      self.slot_label_id.set('Primary Slot Index: %d' % param_dict.get(
          'SLOT_INFO', {})[slot_number].get('slot_label_id', "N/A"))
    else:
      label_id = RDMConstants.SLOT_DEFINITION_TO_NAME[
        param_dict.get('SLOT_INFO', {})[slot_number].get('slot_label_id', "N/A")
        ].replace('_', ' ')
      self.slot_label_id.set('Slot Label: %s' % label_id)
    slot_name = param_dict.get('SLOT_DESCRIPTION', {}).get('slot_number')
    if slot_name is not None:
      self.slot_name.set('Name: %s' % slot_name)
    else:
      if param_dict['SLOT_INFO'][slot_number]['slot_type'] == 0:
        self.slot_name.set('Description: %s' % PIDDict.SLOT_ID_DEFINITIONS.get(
            param_dict['SLOT_INFO'][slot_number]['slot_label_id'], 'N/A'))
      else:
          self.slot_name.set('Name: N/A')
    type_name = RDMConstants.SLOT_TYPE_TO_NAME[
        param_dict.get('SLOT_INFO', {})[slot_number].get('slot_type', "N/A")
        ].replace('_', ' ')
    self.slot_type.set('Type: %s' % type_name)
    if 'DEFAULT_SLOT_VALUE' in param_dict:
      print param_dict['DEFAULT_SLOT_VALUE']
    self.default_slot_value.set('Default Slot Value: %d' %
                          param_dict.get('DEFAULT_SLOT_VALUE', {})[slot_number])

  def _display_personality_decription(self, slots_required, personality):
    """ Changes the displayed DMX personality information

    Called after a device's DMX personality has been changed. Changes the values
    for both the personality name and the number of DMX slots required.

    Args:
      slots_required: the number of DMX slots required for the new DMX 
        personality
      personality: the personality ID for the new DMX personality assigned to
        the device
    """
    self.slot_required.set("Slots Required: %s" % slots_required)
    self.personality_name.set("Personality ID: %s" % personality)

  def _get_personality_string(self, personality):
    """ 
    """
    return '%s (%d)' % (personality['name'], personality['slots_required'])

  def _populate_sensor_tab(self, sensor_number):
    self._controller.get_sensor_value(sensor_number)

  def display_sensor_data(self, param_dict, sensor_number):
    self.record_sensor_button.config(
        command = lambda: self.record_sensor(sensor_number), state = tk.NORMAL)
    self.clear_sensor_button.config(
        command = lambda: self.clear_sensor(sensor_number), state = tk.NORMAL)
    self.refresh_sensor_button.config(
        command = lambda: self._populate_sensor_tab(sensor_number), 
        state = tk.NORMAL)
    definition = param_dict['SENSOR_DEFINITION'][sensor_number]
    TYPE = RDMConstants.SENSOR_TYPE_TO_NAME[definition['type']].replace("_", " ")
    UNIT = RDMConstants.UNIT_TO_NAME[definition['unit']].replace("_", " ")
    PREFIX = RDMConstants.PREFIX_TO_NAME[definition['prefix']].replace("_", " ")
    self.sensor_type.set('Type: %s' % TYPE)
    self.sensor_unit.set('Unit: %s' % UNIT)
    self.sensor_prefix.set('Prefix: %s' % PREFIX)
    self.sensor_range.set('Range: %d - %d' % (definition['range_min'], 
                                               definition['range_max']))
    self.normal_range.set(
        'Normal Range: %d - %d' % 
        (definition['normal_min'], definition['normal_max']))
    self.supports_recording.set(
        'Supports Recording: %s' %
        bool(PIDDict.RECORDED_SUPPORTED & definition['supports_recording']))
    self.supports_lowest_highest.set('Supports Lowest/Highest: %s' %
        bool(PIDDict.LOWEST_HIGHEST_SUPPORTED & definition['supports_recording']))

    if 'SENSOR_VALUE' in param_dict:
      value = param_dict['SENSOR_VALUE'][sensor_number]
      self.present_value.set('Value: %d' % value['present_value'])
      self.lowest.set('Lowest Value: %d' % value['lowest'])
      self.highest.set('Highest Value: %d' % value['highest'])
      self.recorded.set('Recorded Value: %d' % value['recorded'])