Script to find all ungrouped items

This code and @arsaboo’s version above (#71?) aren’t doing what you think. This is testing to see if the group is defined as a view, not if it is visible or not. For that you should be checking the visible attribute.

Since the switch turns the view false, it will essentially return the same value :slight_smile:

But, you are right, we should be checking for visible

Okay, having gone through your messages today, I think the only thing I need to do is add the ability to include spaces after commas, here’s the new version (the only change is on line 3, I’ve tried highlighting it, but it didn’t work) [I think in future I will try to reserve the first comment for the current version of the code]

def scan_for_new_entities(hass, logger, data):
  ignore = data.get("domains_to_ignore","zone,automation,script,zwave")
  domains_to_ignore=ignore.replace(" ","").split(",")
  target_group=data.get("target_group","group.catchall")
  show_as_view = data.get("show_as_view", True)
  show_if_empty = data.get("show_if_empty", False)
  min_items_to_show = data.get("min_items_to_show", 1)
  
  logger.info("ignoring {} domain(s)".format(len(domains_to_ignore)))
  logger.info("Targetting group {}".format(target_group))

  entity_ids=[]
  groups=[]
  
  for s in hass.states.all():
    state=hass.states.get(s.entity_id)
    domain = state.entity_id.split(".")[0]
    
    if (domain not in domains_to_ignore):
      if (domain != "group"):
        if (("hidden" not in state.attributes) or
             (state.attributes["hidden"] == False)):  
          entity_ids.append(state.entity_id)
      else:
        if (("view" not in state.attributes) or 
          (state.attributes["view"] == False)):
          entity_ids.append(state.entity_id)
        
    if (domain == "group") and (state.entity_id != target_group):
      groups.append(state.entity_id)

  logger.info("==== Entity count ====")
  logger.info("{0} entities".format(len(entity_ids)))
  logger.info("{0} groups".format(len(groups)))

  high_order=0
  for groupname in groups:
    group = hass.states.get(groupname)
    if int(group.attributes["order"]) > high_order:
      high_order=int(group.attributes["order"])
    for a in group.attributes["entity_id"]:
      if a in entity_ids:
        entity_ids.remove(a)

  attrs={}
  attrs["view"]=show_as_view

  if (len(entity_ids)) > min_items_to_show or show_if_empty:
    attrs["visible"]=True
  else:
    attrs["visible"]=False

  attrs["friendly_name"]="Ungrouped Items"
  attrs["icon"]= "mdi:magnify"
  attrs["view"]=show_as_view
  attrs["order"] = high_order
  entity_ids.insert(0,"script.scan_for_new_devices")
  attrs["entity_id"]=entity_ids

  hass.states.set("group.catchall", "new", attrs)

scan_for_new_entities(hass, logger, data)

So, I had to modify the original script for the group.remove to work. Turns out hass.states.set("group.catchall", "new", attrs) is not the optimal way of creating new group as it bypasses the group component (confirmed by Paulus). Instead, I used hass.services.call. This allows us to use all the group services. So, now you can enable/disable the group.catchall with a simple switch :slight_smile:

The updated python_script is available here and the switch.developer_mode is available here. Finally, the group is defined as

  developer:
    name: Developer
    view: false
    icon: mdi:developer-board
    entities:
      - group.catchall

Thanks for all the help!!

3 Likes

thanks to everyone who contributed to this very nice tool

Followed your instructions & as of now it works as I expected. Thanks!

And thanks to @NigelL for getting this whole ball rolling. Great tool!

hmm, no really sure. your script doesnt seem to update dynamically somehow, changing the ignored domains. If i do so in the original script, i can see the group appear/disappear, and change contents.

If I use yours, nothing changes at all…

maybe

the original line: entity_ids.insert(0,"script.scan_for_new_devices") you changed into entity_ids.insert(0, "python_script.scan_for_new_entities") has something to do with that?

about the removing of the group:
though appreciating core functionality being as it should be, why would you really care removing the group catchall, if you hide the containing group.developer already with your developer switch:

turn_off:
  - service: group.set
    data:
      object_id: developer
      view: false
      visible: false
  - service: group.remove
    data:
      object_id: catchall

setting group.developer view and visible to false, also obscures the group.catchall…
this is a bit like putting a lot of effort in controlling display of all individual switches in a group of switches, while you simply can switch-off/on the group itself with the group-control?

the one click that hides them all…:wink:

##########################################################################################
## Groups
##########################################################################################
# group.developer (containing all other developer groups) being set as view and visible
# False/True dynamically in Developer Mode
##########################################################################################
group:
  developer:
    name: Developer
    icon: mdi:remote-desktop
#    view: true
    entities:
      - group.catchall # if made on the fly by python_script
      - group.developer_tools
      - group.developer_links
      - group.developer_scripts

  developer_tools:
    name: Developer tools
    icon: mdi:toolbox
    control: hidden
    entities:
      - switch.mode_developer
      - script.catchall
      - input_select.log_level # package hassio_pi3_system

  developer_links:
    name: Developer links
    icon: mdi:web
    entities:
      - weblink.hassio_configurator
      - weblink.ide
      - weblink.terminal
      - weblink.nodered_flows

  developer_scripts:
    name: Developer scripts
    icon: mdi:script
    control: hidden
    entities:
      - script.reload_frontend
      - script.reload_python
      - script.restart_ha
      - script.rc_reboot_iungo
      - script.sc_reboot_iungo

was wondering if this python script could be transformed into doing another long desired but still missing feature: find grouped, but undeclared/non-existing entities. Orphans, if you want. They dont error out, and can hence pollute the yaml files.

We now can find groups or items we havent organized yet, how cool would it be if we could easily spot older groups or other entities belonging to sensors we don’t have any longer, or let’s say groups the aren’t there anymore, or are empty because of the containing entities which are gone or put aside.

Somehow I hope this script could be transformed into doing that, making it the perfect addition to the developers toolbox above.!

Cheers,
Marius

Well, the mechanics of what you’re asking for aren’t hard at all, the difficulty is in displaying the resulting set of items.

I have an idea how to do this, but really can’t say I like it all that much. I’ll start looking into it this evening.

cool, thanks, maybe just give it a go, and see the result in its most basic form. If necessary fine-tune afterwards? I am very curious/anxious if and how this could/would finally be realized.

I followed this method and it appears to be working as expected - thanks.

small issue I encounter is when rebooting the developer tab isnt persistent. I use an input select to set modes, and that uses restore state. which means no action is performed setting it in developer mode. Since the group.developer by default has view and visible: false, the rebooted state is in dev mode, without developer group in tabs.

Is this the case too in your setup?

Yes, I have set view: false for the developer tab in my case. I only want it to appear when needed. I use the switch.developer_mode to enable/disable the developer tab.

and if in this developer mode you restart the system, does it persist?

I haven’t tried that, but I suspect it will not given that group.developer is initialized with view: false. Given that the catchall group will be used only occasionally, I don’t want it to show up on start.

which is exactly the way I see it. still the dev mode would be switched on, since this how you left it before reboot, and the value_template would say it is off, since that’s the way the group.developer is initialized… confusing this is.

No…it will be off on restart, because it takes the state from the template.

1 Like

had to solve that differently because of usage of Modes in my setup not being boolean.

if it’s of any use for others, here’s the way I found it to work fine. and expandable, just adding services to the dev_on and dev_off scripts is simple as can be:

  - alias: 'set Dev Mode at startup'
    id: 'set Dev Mode at startup'
    initial_state: 'on'
    trigger:
      platform: homeassistant
      event: start
    condition:
      condition: template
      value_template: >
        {{ is_state('input_select.mode', 'Developer') }}
    action:
      - service: group.set
        data:
          object_id: developer
          view: true
          visible: true

  - alias: 'set Dev groups'
    id: 'set Dev groups'
    initial_state: 'on'
    trigger:
      platform: state
      entity_id: input_select.mode
    condition: []
    action:
      service_template: >
        {% if is_state('input_select.mode', 'Developer') %}
          script.dev_on
        {% else %}
          script.dev_off
        {%endif%}

I have 2 questions on the script in its latest version:

what does this line do and why is it now python_script, while in the original script is was simply script.?
entity_ids.insert(0, "python_script.scan_for_new_entities") and why doesn’t it make any difference switching between that ??

  • secondly: I have several scripts, regular, that are not found by the python script, and I havent ignored them: ignore = data.get("domains_to_ignore", "scene, zwave")

is that an error of kinds? isnt it supposed to catch these too?

I don’t think we need that line…there is no such entity.

On my system, that’s this script :slight_smile: