šŸ”¹ Lovelace_gen - Add abilities to ui_lovelace.yaml

This thread seems very quiet but in case anyone reads itā€¦

Is there a reason why this:

          - type: custom:multiple-entity-row
            entity: sensor.smartweather_average_rain_yesterday
            name: Smartweather
            state_header: Average
            entities:
              - entity: sensor.smartweather_1_rain_today
                name: {{ states('sensor.smartweather_1_rain_today') }}

gives me an error?
This is the relevant line but see below for whole error listing:

jinja2.exceptions.UndefinedError: 'states' is undefined

I know my example is fairly meaningless but it is reduced to the simplest I could think of.


FULL ERROR FOLLOWS HERE

Logger: homeassistant.components.websocket_api.http.connection
Source: lovelace/view_almanac.yaml:77
Integration: Home Assistant WebSocket API (documentation, issues)
First occurred: 12:40:26 (1 occurrences)
Last logged: 12:40:26

[139913098594000] Error handling message: Unknown error
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/decorators.py", line 25, in _handle_async_response
    await func(hass, connection, msg)
  File "/usr/src/homeassistant/homeassistant/components/lovelace/websocket.py", line 30, in send_with_error_handling
    result = await func(hass, connection, msg, config)
  File "/usr/src/homeassistant/homeassistant/components/lovelace/websocket.py", line 72, in websocket_lovelace_config
    return await config.async_load(msg["force"])
  File "/usr/src/homeassistant/homeassistant/components/lovelace/dashboard.py", line 188, in async_load
    is_updated, config = await self.hass.async_add_executor_job(
  File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/src/homeassistant/homeassistant/components/lovelace/dashboard.py", line 207, in _load_config
    config = load_yaml(self.path, Secrets(Path(self.hass.config.config_dir)))
  File "/usr/src/homeassistant/homeassistant/util/yaml/loader.py", line 113, in load_yaml
    return parse_yaml(conf_file, secrets)
  File "/usr/src/homeassistant/homeassistant/util/yaml/loader.py", line 125, in parse_yaml
    yaml.load(content, Loader=lambda stream: SafeLineLoader(stream, secrets))
  File "/usr/local/lib/python3.8/site-packages/yaml/__init__.py", line 114, in load
    return loader.get_single_data()
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 51, in get_single_data
    return self.construct_document(node)
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 55, in construct_document
    data = self.construct_object(node)
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 100, in construct_object
    data = constructor(self, node)
  File "/usr/src/homeassistant/homeassistant/util/yaml/loader.py", line 255, in _ordered_dict
    nodes = loader.construct_pairs(node)
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 155, in construct_pairs
    value = self.construct_object(value_node, deep=deep)
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 100, in construct_object
    data = constructor(self, node)
  File "/usr/src/homeassistant/homeassistant/util/yaml/loader.py", line 286, in _construct_seq
    (obj,) = loader.construct_yaml_seq(node)
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 408, in construct_yaml_seq
    data.extend(self.construct_sequence(node))
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 129, in construct_sequence
    return [self.construct_object(child, deep=deep)
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 129, in <listcomp>
    return [self.construct_object(child, deep=deep)
  File "/usr/local/lib/python3.8/site-packages/yaml/constructor.py", line 100, in construct_object
    data = constructor(self, node)
  File "/config/custom_components/lovelace_gen/__init__.py", line 54, in _include_yaml
    return loader._add_reference(load_yaml(fname, ldr.secrets, args=args), ldr, node)
  File "/config/custom_components/lovelace_gen/__init__.py", line 32, in load_yaml
    stream = io.StringIO(jinja.get_template(fname).render({**args, "_global": llgen_config}))
  File "/usr/local/lib/python3.8/site-packages/jinja2/environment.py", line 1304, in render
    self.environment.handle_exception()
  File "/usr/local/lib/python3.8/site-packages/jinja2/environment.py", line 925, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/config/lovelace/view_almanac.yaml", line 77, in top-level template code
    name: {{ states('sensor.smartweather_1_rain_today') }}
  File "/usr/local/lib/python3.8/site-packages/jinja2/utils.py", line 84, in from_obj
    if hasattr(obj, "jinja_pass_arg"):
jinja2.exceptions.UndefinedError: 'states' is undefined

donā€™t think the card supports templating at all? Not mentioned in the documentation optionsā€¦

I thought lovelace_gen allowed templating ā€˜everywhereā€™.
Maybe Iā€™m just a bit rusty though.

o heck, sorry, I didnā€™t realize this was the lovelace_gen topic. Never mind then, I donā€™t use that and wouldnā€™t knowā€¦ sorry again

I know this has been answered before but Iā€™m unable to pass a dictionary as value:
Here is my setup:

main package configuration.yaml

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

lovelace:
  mode: storage
  dashboards:
    homekit-infused:
      mode: yaml
      filename: "hki-base/homekit-infused.yaml"

homekit-infuse.yaml

# lovelace_gen

title: Home
background: 'center / cover no-repeat url("/local/images/wallpapers/blue-wood.png") fixed'
popup_cards: !include templates/popup-cards/popup-cards.yaml
views: 
  - !include base.yaml
  - user_view_config: {{ _global.view_config_owner | tojson }}

And finally base.yaml

# lovelace_gen

{% set view_config = user_view_config | fromjson %}

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

I checked on the logs and I think this bit is the more important:

  File "/config/hki-base/base.yaml", line 3, in top-level template code
    {% set view_config = user_view_config | fromjson %}
  File "/config/custom_components/lovelace_gen/__init__.py", line 16, in fromjson
    return json.loads(value)
  File "/usr/local/lib/python3.9/json/__init__.py", line 339, in loads
    raise TypeError(f'the JSON object must be str, bytes or bytearray, '
TypeError: the JSON object must be str, bytes or bytearray, not Undefined

This tells me probably the issues is in this line:
user_view_config: {{ _global.view_config_owner | tojson }}
Am I however unable to see why

if youā€™re passing in user_vew_config to base.yaml, then your format and indentation is wrong

  - !include 
    - base.yaml
    - user_view_config: {{ _global.view_config_owner | tojson }}

yeah that was my bad.

I modified it and in the dashboard the following message appears:

invalid key: "OrderedDict([('_global.view_config_owner | tojson', None)])" in "/config/hki-base/homekit-infused.yaml", line 24, column 0

And in the logs:
2021-07-14 12:14:21 ERROR (SyncWorker_4) [custom_components.lovelace_gen] invalid key: "OrderedDict([('_global.view_config_owner | tojson', None)])"

I have also tried to make another file (test.yaml) and change the condigurations like so:

##test.yaml

# lovelace_gen
- !include 
  - base.yaml
  - user_view_config: {{ _global.view_config_owner | tojson }}

----------------------------------------------------------------------------------------------------------------------------

##homekit-infuse.yaml

# lovelace_gen

views: !include test.yaml

which gave me the same error

is # lovelace_gen the first line in the file?

Yes. Here is the full yaml:

# lovelace_gen

#Main Lovelace Config

## Button Card Templates
button_card_templates: !include_dir_named templates/button/button-card-templates/


#Kiosk configuration
kiosk_mode:
  mobile_settings:
    hide_sidebar: true
    ignore_entity_settings: true
    custom_width: 768


## Base Configuration
title: Home
background: 'center / cover no-repeat url("/local/images/wallpapers/blue-wood.png") fixed'
popup_cards: !include templates/popup-cards/popup-cards.yaml
views: 
  - !include 
    - base.yaml
    - user_view_config: {{ _global.view_config_owner | tojson }}

(for base the file is too long, but here are the first few lines:


# lovelace_gen

{% set view_config = user_view_config | fromjson %}

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

- path: {{ name }}

It is interesting that removing # lovelace_gen as the first line does not change anything

Yes, because lovelace_gen isnā€™t running, which is why I asked.

when you say ā€œHere is the full yamlā€, which file are you referring to? Because lovelace gen cannot be placed into ui-lovelace.yaml

EDIT: I would assume that restriction is on all dashboards as well. Not just ui-lovelace.yaml.

FYI, read Important

is the ui-lovelace.yaml the file with the views? (the package Iā€™m using uses its own name convection). The file I showed you is called homekit-infused.yaml

Lets go one step back:
This the package file containing the configurations:


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

group:
  !include ../../hki-user/device_counters.yaml

frontend:
  themes: !include_dir_merge_named ../../hki-base/themes/

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

browser_mod:
  prefix: "browser_"

folder_watcher:
  - folder: /config/www/images/wallpapers
    patterns:
      - '*.jpg'
      - '*.png'
  - folder: /config/.storage
    patterns:
      - 'lovelace.*'

still doesnā€™t change anything, i believe the restriction would be on any dashboard. hki-base/homekit-infused.yaml would be your dashboard.

FYI, this is what my ā€˜dashboardā€™ looks like:

title: Home
views: !include lovelace/views/master.yaml
1 Like

Yes I imagined, I just was not sure where the restriction was.
Iā€™ve also tried to place view in another file but this raised another error
This is what I did

dashboard yaml (without #lovelace_gen in the first line)

title: Home
background: 'center / cover no-repeat url("/local/images/wallpapers/blue-wood.png") fixed'
popup_cards: !include templates/popup-cards/popup-cards.yaml
views: !include mytestview.yaml

and mytestview.yaml:

# lovelace_gen

- !include 
  - base.yaml
  - user_view_config: {{ _global.view_config_owner | tojson }}

Error:

File "/config/custom_components/lovelace_gen/__init__.py", line 54, in _include_yaml
    return loader._add_reference(load_yaml(fname, ldr.secrets, args=args), ldr, node)
  File "/config/custom_components/lovelace_gen/__init__.py", line 32, in load_yaml
    stream = io.StringIO(jinja.get_template(fname).render({**args, "_global": llgen_config}))
  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 3, in top-level template code
    {% set view_config = user_view_config | fromjson %}
  File "/config/custom_components/lovelace_gen/__init__.py", line 16, in fromjson
    return json.loads(value)
  File "/usr/local/lib/python3.9/json/__init__.py", line 339, in loads
    raise TypeError(f'the JSON object must be str, bytes or bytearray, '
TypeError: the JSON object must be str, bytes or bytearray, not OrderedDict

You may need to wrap it in quotes, iā€™ve never attempted to use an ordered dict with |to json. I usually convert everything to yaml when passing.

I use these functions in all lovelace gen files where I want to convert objects into formatted yaml. Works with all objects:

{#- # 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 %}

Then calling itā€¦

toyaml('    ', 'user_view_config', _global.view_config_owner, True)

The true at the end adds the dash. When False the dash is not included.

Ah, I think Iā€™m missing a step here. Iā€™m reading the dictionary from a yaml file. For some reasons I just assume the _global.view_config_owner would give a dictionary

well, just use the code above and it should work

Isnā€™t your code supposed to do the reverse? convert a dicionary into a yaml? I want the reverse, yaml to dictionary

No, my code takes an object, any object and turns it into yaml.

You may think it doesnā€™t do what you want, but it does :wink: