🔹 Lovelace_gen - Add abilities to ui_lovelace.yaml

just put this in your file

# lovelace_gen

{#- # Macro for building a Dictionary #}
{%- macro buildmapping(indent, items, iterfunc) %}
{%- for attr, value in items.items() %}
{%- set line = indent ~ '- ' if loop.first else indent ~ '  ' %}
{%- if value is iterable and value is not string and value is not mapping %}
{{ line }}{{ attr }}:
{{- iterfunc(indent ~ '  ', value) }}
{%- elif value is mapping %}
{{ line }}{{ attr }}:
{{- buildmapping(indent+'  ', value, iterfunc) }}
{%- elif value %}
{{ line }}{{ attr }}: {{ value }}
{%- endif %}
{%- endfor %}
{%- endmacro %}

{#- # Macro for building a List #}
{%- macro builditerable(indent, values) %}
{%- for value in values %}
{%- if value is iterable and value is not string and value is not mapping %}
{{ indent ~ '- ' }}
{{- builditerable(indent+'  ', value) }}
{%- elif value is mapping %}
{{- buildmapping(indent, value, builditerable) }}
{%- elif value %}
{{ indent  ~ '- ' }}{{ value }}
{%- endif %}
{%- endfor %}
{%- endmacro %}

{#- # Macro for turning an object into yaml #}
{%- macro toyaml(indent, attr, value, first=False) %}
{%- set line = indent[:-2] ~ '- ' if first else indent %}
{%- if value is iterable and value is not string and value is not mapping %}
{%- if attr %}{{- line }}{{ attr }}:{%- endif %}
{{- builditerable(indent, value) }}
{%- elif value is mapping %}
{%- if attr %}{{- line }}{{ attr }}:{%- endif %}
{{- buildmapping(indent, value, builditerable) }}
{%- elif value %}
{{- line }}{{ attr }}: {{ value }}
{%- endif %}
{%- endmacro %}

- !include 
  - base.yaml
{{ toyaml('    ', 'user_view_config', _global.view_config_owner, True) }}

what about the base.yaml file? I assume the | fromjson is not required now.

Am I however unable to loop through the dictionary/yaml file:

  File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 1304, in render 
    self.environment.handle_exception()
  File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 925, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/config/hki-base/base.yaml", line 4, in top-level template code
    {% for name, data in user_view_config.items() %}
  File "/usr/local/lib/python3.9/site-packages/jinja2/utils.py", line 84, in from_obj
    if hasattr(obj, "jinja_pass_arg"):
jinja2.exceptions.UndefinedError: 'homeassistant.util.yaml.objects.NodeListClass object' has no attribute 'items'

base.yaml

# lovelace_gen

{% for name, data in view_config.items() %}
#Do stuff

I tried to look for the
homeassistant.util.yaml.objects.NodeListClass but with no luck

user_view_config is what’s passed in not view_config

also, what is actually in _global.view_config_owner

Ah right.
Even after I change there is the same(ish) error:

File “/usr/local/lib/python3.9/site-packages/jinja2/environment.py”, line 925, in handle_exception
raise rewrite_traceback_stack(source=source)
File “/config/hki-base/base.yaml”, line 3, in top-level template code
{% for name, data in user_view_config.items() %}
File “/usr/local/lib/python3.9/site-packages/jinja2/utils.py”, line 84, in from_obj
if hasattr(obj, “jinja_pass_arg”):
jinja2.exceptions.UndefinedError: ‘homeassistant.util.yaml.objects.NodeListClass object’ has no attribute ‘items’
2021-07-14 14:27:41 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [139670923827568] Error handling message: Unknown error

The global configs are parsed through a configuration.yaml file like this:


lovelace_gen: !include_dir_merge_named ../../hki-user/config/

lovelace:
  mode: storage
  dashboards:
    homekit-infused:
      mode: yaml
      title: Homekit Infused
      icon: mdi:home-assistant
      show_in_sidebar: true
      filename: "hki-base/homekit-infused.yaml"
      require_admin: false

Where the /config folder has a file name view_config.yaml
here are the first few lines:


view_config_owner:
  home:
    hki_menu: null
    icon: mdi:home
    menu:
      title: "Divis\xF5es"
    show_in_menu: false
    show_in_navbar: true
    title: Menu Principal
  room_1:
    devices:
    - entities:
      - entity: light.raceland
        name: Luz do escritorio
        type: rgb
      - entity: switch.raceland
        name: buton1
      - entity: switch.raceland_3
        name: Raceland
      title: "Luzes Escrit\xF3rio"
    icon: mdi:door
    show_in_favorites: 'true'
    title: "Escrit\xF3rio"

use this in your code

{% for name, data in view_config_owner[0].items() %}

(Sorry for the later answer).
With the last fix I was able to reload my HA without errors. However, the dashboard appears empty
Also I assume you meant

{% for name, data in user_view_config[0].items() %}

Not sure I can help you beyond that point, but yes that’s the code. Typically empty views means JS errors.

@thomasloven thanks for a great integration.
I just wanted to make sure, is there yet no way to avoid a full restart of HA? :slight_smile:

I am trying to use this code to pass a dictionary into an include file, but am having some challenges.

In its current, working state, my include file generates a custom:template-entity-row for the entities card, and looks like this:

entity: {{ entity_id }}
{% raw %}
type: custom:template-entity-row
state: "{{ 'Clear' if states(config.entity) == 'off' else 'Detected' }}"
secondary: >
  {{ '' if states('sensor.time') }}
  {% set rel = relative_time(as_datetime(states(state_attr(config.entity, 'lastchanged_sensor')))) %}
  {% if rel == None %}
    Never
  {% elif rel == '0 seconds' %}
    Now
  {% else %}
    {{ rel }} ago
  {% endif %}
{% endraw %}

It is invoked with this:

          - !include
            - ../components/motion-entity-row.yaml
            - entity_id: binary_sensor.motion_office

What I’d like to do is optionally pass additional configuration to template-entity-row, such as a hold_action (but could be anything else). I’m am trying to do this by passing in an options dictionary, like this:

          - !include
            - ../components/motion-entity-row.yaml
            - entity_id: binary_sensor.motion_office
              options:
                hold_action:
                  action: fire-dom-event
                  browser_mod:
                    command: popup
                    title: Office Motion
                    card:
                      type: entities
                      show_header_toggle: false
                      entities:
                        - entity: binary_sensor.motion_office_door
                          name: Door
                          secondary_info: last-changed
                        - entity: binary_sensor.motion_office_shelf
                          name: Shelf
                          secondary_info: last-changed

When trying to parse options in the include file, I have determined that it comes across as a bunch of OrderedDicts, which I need to convert back into yaml or json to render the Lovelace config correctly. A little debugging shows that options looks like:

OrderedDict([('hold_action', OrderedDict([('action', 'fire-dom-event'), ('browser_mod', OrderedDict([('command', 'popup'), ('title', 'Office Motion'), ('card', OrderedDict([('type', 'entities'), ('show_header_toggle', False), ('entities', [OrderedDict([('entity', 'binary_sensor.motion_office_door'), ('name', 'Door'), ('secondary_info', 'last-changed')]), OrderedDict([('entity', 'binary_sensor.motion_office_shelf'), ('name', 'Shelf'), ('secondary_info', 'last-changed')])])]))]))]))])

Long story long, I was hoping that the code that @petro shared could unwind those nested objects for me, but it spits out something like this (formatting/indents are missing due to my debugging method)

 - hold_action: - action: fire-dom-event browser_mod: - command: popup title: Office Motion card: - type: entities entities: - entity: binary_sensor.motion_office_door name: Door secondary_info: last-changed - entity: binary_sensor.motion_office_shelf name: Shelf secondary_info: last-changed

If you ignore the missing formatting, you can see that the first item in each OrderedDict is rendered as a list, which isn’t correct.

So, I don’t know where to go from here…tweaking @petro’s code, using some other unknown method for converting nested OrderedDicts to yaml, or just giving up on this approach altogether.

Any thoughts/help is much appreciated!

Well, it’s converting it properly based on what’s in the ordered dict. How are you parsing the options in the options file

Yeah, I guess I forget to step back and finish telling that part of the story…

To pull the options in, the updated include file now looks like this:

entity: {{ entity_id }}
{% raw %}
type: custom:template-entity-row
state: "{{ 'Clear' if states(config.entity) == 'off' else 'Detected' }}"
secondary: >
  {{ '' if states('sensor.time') }}
  {% set rel = relative_time(as_datetime(states(state_attr(config.entity, 'lastchanged_sensor')))) %}
  {% if rel == None %}
    Never
  {% elif rel == '0 seconds' %}
    Now
  {% else %}
    {{ rel }} ago
  {% endif %}
{% endraw %}
{{ options }}

which I was expecting to pull in the entire hold_action block. Instead, it throws an error:

while scanning a simple key in "/config/lovelace/components/motion-entity-row.yaml", line 19, column 1 could not find expected ':' in "/config/lovelace/components/motion-entity-row.yaml", line 19, column 488

I assume that’s because the object conversion is creating lists that weren’t intended to be there?

wrap the thing I wrote on that options

and if that doesn’t work

{% set options = options | to_json | from_json %}
{{ toyaml('  ', 'options', options) }}
entity: {{ entity_id }}
{% raw %}
type: custom:template-entity-row
state: "{{ 'Clear' if states(config.entity) == 'off' else 'Detected' }}"
secondary: >
  {{ '' if states('sensor.time') }}
  {% set rel = relative_time(as_datetime(states(state_attr(config.entity, 'lastchanged_sensor')))) %}
  {% if rel == None %}
    Never
  {% elif rel == '0 seconds' %}
    Now
  {% else %}
    {{ rel }} ago
  {% endif %}
{% endraw %}
{{ toyaml('', '', options, False) }}

results in

while parsing a block mapping in "/config/lovelace/components/motion-entity-row.yaml", line 4, column 1 expected <block end>, but found '-' in "/config/lovelace/components/motion-entity-row.yaml", line 20, column 1

If I drop the toyaml output into the secondary_info field for debugging, the output was:

- hold_action: - action: fire-dom-event browser_mod: - command: popup title: Office Motion card: - type: entities entities: - entity: binary_sensor.motion_office_door name: Door secondary_info: last-changed - entity: binary_sensor.motion_office_shelf name: Shelf secondary_info: last-changed

…which is really close, but has those extraneous list items that I mentioned earlier.

With the 2nd option,

entity: {{ entity_id }}
{% raw %}
type: custom:template-entity-row
state: "{{ 'Clear' if states(config.entity) == 'off' else 'Detected' }}"
secondary: >
  {{ '' if states('sensor.time') }}
  {% set rel = relative_time(as_datetime(states(state_attr(config.entity, 'lastchanged_sensor')))) %}
  {% if rel == None %}
    Never
  {% elif rel == '0 seconds' %}
    Now
  {% else %}
    {{ rel }} ago
  {% endif %}
{% endraw %}
{% set options = options | to_json | from_json %}
{{ toyaml('  ', 'options', options) }}

Throws this error in the HA log:

jinja2.exceptions.TemplateAssertionError: No filter named 'from_json'.

I managed to figure out a working solution for converting the OrderedDict back into YAML Lovelace configuration. I kept bumping into some sort of quirk where it wouldn’t allow me to apply the tojson filter directly to the OrderedDict object. But…it allowed me to apply the list filter to retrieve the keys in the OrderedDict…and for some reason it also allowed me to to apply tojson to their corresponding values.

So this approach works…

{% for key in options|list %}
{{ key }}: {{ options[key]|tojson }}
{% endfor %}

Technically, this doesn’t produce 100% ‘pure’ YAML format…the generated Lovelace config has YAML keys, and the corresponding values are Javascript objects. But that is totally valid from a YAML perspective, and it is generating the hold_action config that I was looking for.

@petro, thank you for your assistance and your sample code - it pushed me in the right direction!

Is there a way to reload the _global variables without a home assistant restart?

1 Like

so I have never used lovelace-gen really, and now am exploring if it can set the view icon. I added

# lovelace_gen

{% set my_icon = 'mdi:alert' %}

title: Presence
path: presence
icon: {{ my_icon }}

to a view, and yes, I did notice it change! however, reloading the page trying to use another icon renders the ‘Unknown Error’. A HA restart too btw, so there must be something wrong.

Schermafbeelding 2022-02-08 om 16.27.16

the reason I am trying to do this, is I want templates to take over control over the tab/view icon, and would love to do

{%- set my_icon = states('sensor.presence_icon') -%}

which all renders the same result, and string with the correct mdi: icon.

but now I can no even manage to get the most basic example in the repo to work, returning the same Unknown Error.

{% set my_lamp = "light.bed_light" %}

type: entities
entities:
 - {{ my_lamp }}

please help me get going?
fyi, I use my views as include, so have now only added # lovelace_gen to this view. Must it be added elsewhere too, maybe the main dashboard file which holds this view?
thanks!

update

Wait, I believe it had to do with the fact I also had commented bits and pieces in that view… deleted that, and now see the icon change upon refresh, and even the light in the example… :wink:

But, what I started out pursuing is still not working. the icon template. Cant this be done?

Inspector:

Update2:

guess this 🔹 Lovelace_gen - Add abilities to ui_lovelace.yaml - #53 by thomasloven means it cant be done. using states is not possible

Does this integration still works?
I would love to use it in my floorplan dashboard

Yes.

Does this card work with the “config-template-card”? I am trying to change the entity source and other parameters of a graph based on the value of an entity using an if statement in lovelace_gen.

The lovelace_gen acts differently if I replace the template with a raw string equivalent of what the template should resolve to.

- type: custom:config-template-card
  entities:
    - input_select.outside_temp_duration
  card:
    !include
      - "../template/outside_temp_graph.yaml"
      - entity: input_select.outside_temp_duration
        name: ${states["input_select.outside_temp_duration"].state}         

In the lovelace_gen file I try to do an if statement on the “name” (see below). It always equates to false using the template above, even if I know that the state of the input_select is 550d. However if I swap the template for a raw string with “550d” the if statement resolves true.

{% if name == '550d' %}