Programmer's block on saving List data in pluginProps

Posted on
Tue Nov 03, 2020 1:05 pm
FlyingDiver offline
User avatar
Posts: 5351
Joined: Jun 07, 2014
Location: Southwest Florida, USA

Programmer's block on saving List data in pluginProps

I've written code similar to this a dozen times. But not exactly like this. And I can't seem to get what I want to work. But it's so basic, I'm sure someone else has done it.

I have a device definition (Devices.xml) that has a menu to select from a number of items (other Indigo devices, actually) a list of items already selected, and a couple buttons (add, delete).
Code: Select all
    <Device type="sensor" id="area">
        <Name>Occupancy Area</Name>
        <ConfigUI>           
            <Field id="sensorDevice" type="menu">
               <Label>Sensor Device to Add:</Label>
               <List class="indigo.devices" filter="indigo.sensor"/>
            </Field>
            <Field id="addDevice" type="button">
                <Label/>
                <Title>Add Device</Title>
                <CallbackMethod>addDevice</CallbackMethod>
            </Field>
            <Field id="sensorListLabel" type="label" fontColor="darkgray">
                <Label>This is the list of sensor devices included in this area.</Label>
            </Field>
            <Field id="sensorDeviceList" type="list" rows="15">
                <Label>Included devices:</Label>
                <List class="self" method="sensorDeviceList" dynamicReload="true"/>
            </Field>
            <Field id="deleteDevices" type="button">
                <Label/>
                <Title>Delete Devices</Title>
                <CallbackMethod>deleteDevices</CallbackMethod>
            </Field>
       </ConfigUI>
    </Device>


The items selected must be persistent, so they get saved in the pluginProps. Which should happen in pluginProps['sensorDeviceList']. right?

The menu popup works fine. And I'm ignoring the deleteDevices callback for now. I just can't seem to get addDevice to work. I should just be able to add the device selected in the sensorDevice popup to the sensorDeviceList, shouldn't I?

Code: Select all
    def addDevice(self, valuesDict, typeId=None, devId=None):
        self.logger.debug(u"addDevice called, devId={}, typeId={}, valuesDict = {}".format(devId, typeId, valuesDict))
       
        valuesDict['sensorDeviceList'].append(devId)
       
        self.logger.debug(u"addDevice returning, valuesDict = {}".format(valuesDict))
        return valuesDict


Code: Select all
    def sensorDeviceList(self, filter="", valuesDict=None, typeId="", targetId=0):
        self.logger.debug(u"sensorDeviceList called with filter: {}  typeId: {}  targetId: {} valuesDict: {}".format(filter, typeId, targetId, valuesDict))

        if not targetId:        # no device specified
            self.logger.debug(u"sensorDeviceList returning, no targetId")
            return []
           
        # get the saved list of sensor IDs
        sensorList = valuesDict['sensorDeviceList']
        if not sensorList:
            self.logger.debug(u"sensorDeviceList returning, no sensorDeviceList")
            return []

        self.logger.debug(u"sensorDeviceList sensorList = {}".format(sensorList))
        sensors = [
            (sensorID, indigo.devices[sensorID].name)
            for sensorID in sensorList
        ]
        return sensors


Like I said, I'm probably forgetting something incredibly basic, and just not seeing it. These UI callbacks always give me grief. Sigh.

joe (aka FlyingDiver)
my plugins: http://forums.indigodomo.com/viewforum.php?f=177

Posted on
Tue Nov 03, 2020 3:00 pm
matt (support) offline
Site Admin
User avatar
Posts: 20569
Joined: Jan 27, 2003
Location: Texas

Re: Programmer's block on saving List data in pluginProps

Take a look at the Example Device - Custom plugin in the SDK:

Code: Select all
   <!-- This example shows how one can create more complex configuration
   UI. In this case multiple UI controls are used so the user can add
   (or delete) devices to a scene list. See the "Overview of scene devices"
   comment in the plugin.py file for more details.
   -->
   <Device type="custom" id="sceneExample">
      <Name>Scene</Name>
      <ConfigUI>
         <Field id="sourceDeviceMenu" type="menu">
            <Label>Device to add to scene:</Label>
            <List class="self" method="sourceDevices" dynamicReload="true"/>
         </Field>
         <Field id="addDevice" type="button">
            <Label/>
            <Title>Add Device</Title>
            <CallbackMethod>addDevice</CallbackMethod>
         </Field>
         <Field id="sep1" type="separator"/>
         <Field id="memberDeviceList" type="list">
            <Label>Devices in scene:</Label>
            <List class="self" method="memberDevices" dynamicReload="true"/>
         </Field>
         <Field id="deleteDevices" type="button">
            <Label/>
            <Title>Delete Devices</Title>
            <CallbackMethod>deleteDevices</CallbackMethod>
         </Field>
         <Field id="memberDevices" type="textfield" hidden="yes">
            <Label/>
         </Field>
      </ConfigUI>
   </Device>

Code: Select all
   ########################################
   # Buttons and dynamic list methods defined for the scenes custom device
   #
   # Overview of scene devices:
   #   Scene devices are custom devices that will contain multiple devices.
   #    We implement this custom device by storing a comma-delimited list of
   #   device IDs which is manipulated by clicking Add and Delete buttons
   #   in the device config dialog. There are two dynamic list controls in
   #   the dialog:
   #      1) one popup button control on which the user selects a device
   #         to add then clicks the Add Device button.
   #      2) one list control which shows all the devices that have already
   #         been added to the scene and in which the user can select devices
   #         and click the Delete Devices button
   #   There is a hidden field "memberDevices" that stores a comma-delimited
   #   list of device ids for each member of the scene. The addDevice and
   #   deleteDevices methods will take the selections from the respective
   #   dynamic lists and do the right thing with the list.
   #   Finally, there are the two methods that build the dynamic lists.
   #   The method that builds the source list will inspect the "memberDevices"
   #   field and won't include those devices in the source list (so the user
   #   won't be confused by seeing a device that's already in the member list
   #   in the source list). The method that builds the member list of course
   #   uses "memberDevices" to build the list.
   #
   #   One other thing that should be done probably - in the deviceStartComm
   #   method (or the appropriate CRUD methods if you're using them instead)
   #   you should check the IDs to make sure they're still around and if not
   #   remove them from the device id list.
   #
   #   The device id list property ("memberDevices") could, of course, be
   #   formatted in some other way besides a comma-delimited list of ids
   #   if you need to store more information. You could, for instance, store
   #   some kind of formatted text like JSON or XML that had much more
   #   information.

   ####################
   # This is the method that's called by the Add Device button in the scene
   # device config UI.
   ####################
   def addDevice(self, valuesDict, typeId, devId):
      self.debugLog(u"addDevice called")
      # just making sure that they have selected a device in the source
      # list - it shouldn't be possible not to but it's safer
      if "sourceDeviceMenu" in valuesDict:
         # Get the device ID of the selected device
         deviceId = valuesDict["sourceDeviceMenu"]
         if deviceId == "":
            return
         # Get the list of devices that have already been added to the "scene"
         # If the key doesn't exist then return an empty string indicating
         # no devices have yet been added. "memberDevices" is a hidden text
         # field in the dialog that holds a comma-delimited list of device
         # ids, one for each of the devices in the scene.
         selectedDevicesString = valuesDict.get("memberDevices","")
         self.debugLog(u"adding device: %s to %s" % (deviceId, selectedDevicesString))
         # If no devices have been added then just set the selected device string to
         # the device id of the device they selected in the popup
         if selectedDevicesString == "":
            selectedDevicesString = deviceId
         # Otherwise append it to the end separated by a comma
         else:
            selectedDevicesString += "," + str(deviceId)
         # Set the device string back to the hidden text field that contains the
         # list of device ids that are in the scene
         valuesDict["memberDevices"] = selectedDevicesString
         self.debugLog(u"valuesDict = " + str(valuesDict))
         # Delete the selections on both dynamic lists since we don't
         # want to preserve those across dialog runs
         if "memberDeviceList" in valuesDict:
            del valuesDict["memberDeviceList"]
         if "sourceDeviceMenu" in valuesDict:
            del valuesDict["sourceDeviceMenu"]
         # return the new dict
         return valuesDict

   ####################
   # This is the method that's called by the Delete Device button in the scene
   # device config UI.
   ####################
   def deleteDevices(self, valuesDict, typeId, devId):
      self.debugLog(u"deleteDevices called")
      if "memberDevices" in valuesDict:
         # Get the list of devices that are already in the scene
         devicesInScene = valuesDict.get("memberDevices","").split(",")
         # Get the devices they've selected in the list that they want
         # to remove
         selectedDevices = valuesDict.get("memberDeviceList", [])
         # Loop through the devices to be deleted list and remove them
         for deviceId in selectedDevices:
            self.debugLog(u"remove deviceId: " + deviceId)
            if deviceId in devicesInScene:
               devicesInScene.remove(deviceId)
         # Set the "memberDevices" field back to the new list which
         # has the devices deleted from it.
         valuesDict["memberDevices"] = ",".join(devicesInScene)
         # Delete the selections on both dynamic lists since we don't
         # want to preserve those across dialog runs
         if "memberDeviceList" in valuesDict:
            del valuesDict["memberDeviceList"]
         if "sourceDeviceMenu" in valuesDict:
            del valuesDict["sourceDeviceMenu"]
         return valuesDict

   ####################
   # This is the method that's called to build the source device list. Note
   # that valuesDict is read-only so any changes you make to it will be discarded.
   ####################
   def sourceDevices(self, filter="", valuesDict=None, typeId="", targetId=0):
      self.debugLog(u"sourceDevices called with filter: %s  typeId: %s  targetId: %s" % (filter, typeId, str(targetId)))
      returnList = list()
      # if valuesDict doesn't exist yet - if this is a brand new device
      # then we just create an empty dict so the rest of the logic will
      # work correctly. Many other ways to skin that particular cat.
      if not valuesDict:
         valuesDict = {}
      # Get the member device id list, loop over all devices, and if the device
      # id isn't in the member list then include it in the source list.
      deviceList = valuesDict.get("memberDevices","").split(",")
      for devId in indigo.devices.iterkeys():
         if str(devId) not in deviceList:
            returnList.append((str(devId),indigo.devices.get(devId).name))
      return returnList

   ####################
   # This is the method that's called to build the member device list. Note
   # that valuesDict is read-only so any changes you make to it will be discarded.
   ####################
   def memberDevices(self, filter="", valuesDict=None, typeId="", targetId=0):
      self.debugLog(u"memberDevices called with filter: %s  typeId: %s  targetId: %s" % (filter, typeId, str(targetId)))
      returnList = list()
      # valuesDict may be empty or None if it's a brand new device
      if valuesDict and "memberDevices" in valuesDict:
         # Get the list of devices
         deviceListString = valuesDict["memberDevices"]
         self.debugLog(u"memberDeviceString: " + deviceListString)
         deviceList = deviceListString.split(",")
         # Iterate over the list and if the device exists (it could have been
         # deleted) then add it to the list.
         for devId in deviceList:
            if int(devId) in indigo.devices:
               returnList.append((devId, indigo.devices[int(devId)].name))
      return returnList

   ########################################
   def validateDeviceConfigUi(self, valuesDict, typeId, devId):
      # If the typeId is "scene", we want to clear the selections on both
      # dynamic lists so that they're not stored since we really don't
      # care about those.
      self.debugLog(u"validateDeviceConfigUi: typeId: %s  devId: %s" % (typeId, str(devId)))
      if typeId == "scene":
         if "memberDeviceList" in valuesDict:
            valuesDict["memberDeviceList"] = ""
         if "sourceDeviceMenu" in valuesDict:
            valuesDict["sourceDeviceMenu"] = ""
      return (True, valuesDict)

Image

Posted on
Tue Nov 03, 2020 3:01 pm
FlyingDiver offline
User avatar
Posts: 5351
Joined: Jun 07, 2014
Location: Southwest Florida, USA

Re: Programmer's block on saving List data in pluginProps

D'oh, that's probably the one place I didn't look. ;)

joe (aka FlyingDiver)
my plugins: http://forums.indigodomo.com/viewforum.php?f=177

Posted on
Tue Nov 03, 2020 5:22 pm
FlyingDiver offline
User avatar
Posts: 5351
Joined: Jun 07, 2014
Location: Southwest Florida, USA

Re: Programmer's block on saving List data in pluginProps

OK, got it working. For some reason I thought I would be able to store an actual List in the pluginProps, rather than a string.

joe (aka FlyingDiver)
my plugins: http://forums.indigodomo.com/viewforum.php?f=177

Page 1 of 1

Who is online

Users browsing this forum: No registered users and 1 guest