uvjim / linksys_velop

Home Assistant integration for the Linksys Velop
MIT License
50 stars 7 forks source link

Enhancement - Device Details Card #340

Closed mschingel closed 1 year ago

mschingel commented 1 year ago

Hello,

I will start by saying I know that this request is a stretch! You mentioned multiple times you are focusing your time on a different dashboard for the Velops which I respect. However I use your existing examples in the readme as that's all I have to go by and with your recent update to the services I know we can have the ability to rename devices. Could you add that function to the Device Detail Card? Also if its not to much to ask, the ability to then reserve the DHCP IP for it?

This is my use case: Your cards fit perfectly for my Portrait monitor so I enjoy the layout you originally took.

image

uvjim commented 1 year ago

So, the good news for you is that I've also reworked the device details as well. In the version I have locally I have the ability to pause/resume Internet access, rename and delete if need be.

I'll have to have a look at how easy it would be to port that into this card for you.

RE: DHCP reservations. The library currently doesn't support that so that isn't possible at the moment. In fact I don't use the DHCP server on the Velop so I'd need to have a look at how it's done.

uvjim commented 1 year ago

Quick question that should make my life easier... Do you know how to use custom button card templates?

mschingel commented 1 year ago

Yes I know a little bit, and it doesnt seem too difficult as long as I know what to put where and possibly why. Styling seems hard for me but yea I can use it and figure it out.

mschingel commented 1 year ago

RE: DHCP reservations. The library currently doesn't support that so that isn't possible at the moment. In fact I don't use the DHCP server on the Velop so I'd need to have a look at how it's done.

I probably shouldn't use the DHCP on the velop either, so do you bridge your velops to a router behind the mesh? I am considering doing that but I got 100+ ip assignments to consider and don't know how my Velops will handle bridge mode. For the MX4200 that is a troublesome router to begin with... I will research doing a DHCP server separate. I recently added tplinks loadbalancer behind my router to support 2 wans for internet.

uvjim commented 1 year ago

In which case hopefully this will work for you...

Device Details ```yaml type: custom:auto-entities card: type: vertical-stack card_param: cards filter: include: - entity_id: /select\.(velop_)*mesh_devices/ options: type: entities title: Device Details entities: - this.entity_id - type: attribute entity: this.entity_id attribute: unique_id name: ID tap_action: action: none - type: attribute entity: this.entity_id attribute: manufacturer name: Manufacturer tap_action: action: none - type: attribute entity: this.entity_id attribute: model name: Model tap_action: action: none - type: attribute entity: this.entity_id attribute: description name: Description tap_action: action: none - type: attribute entity: this.entity_id attribute: parent_name name: Parent Node tap_action: action: none - type: attribute entity: this.entity_id attribute: serial name: Serial tap_action: action: none - type: attribute entity: this.entity_id attribute: operating_system name: Operating System tap_action: action: none - type: custom:template-entity-row entity: this.entity_id name: Connected state: |- {% if states(config.entity) == 'unknown' %} — {% else %} {{ "Yes" if state_attr(config.entity, "status") is eq true else "No" }} {% endif %} tap_action: action: none - type: custom:template-entity-row entity: this.entity_id name: Signal Strength state: >- {% set connected_adapters = state_attr(config.entity, 'connected_adapters') %} {% set signal_strength = connected_adapters | first if connected_adapters else [] %} {% if 'signal_strength' in signal_strength %} {% if signal_strength.signal_strength %} {{ signal_strength.signal_strength }} ({{ signal_strength.rssi}}dBm) {% else %} — {% endif %} {% else %} — {% endif %} tap_action: action: none - type: custom:template-entity-row entity: this.entity_id name: IP state: >- {% set connected_adapters = state_attr(config.entity, 'connected_adapters') %} {% if connected_adapters %} {{ (connected_adapters | first).ip }} {% else %} — {% endif %} tap_action: action: none - type: custom:template-entity-row entity: this.entity_id name: MAC state: >- {% set connected_adapters = state_attr(config.entity, 'connected_adapters') %} {% if connected_adapters %} {{ (connected_adapters | first).mac }} {% else %} — {% endif %} tap_action: action: none - type: custom:template-entity-row entity: this.entity_id name: Guest Network state: >- {% set connected_adapters = state_attr(config.entity, 'connected_adapters') %} {% if connected_adapters %} {{ "Yes" if (connected_adapters | first).guest_network else "No" }} {% else %} — {% endif %} tap_action: action: none - type: custom:state-switch entity: >- {% set schedules = state_attr("this.entity_id", "parental_control_schedule") %} {% if schedules is not none and (schedules | map(attribute="blocked_internet_access") | list | length) %} display {% endif %} default: default states: default: type: custom:template-entity-row name: Blocked Internet Access state: — tap_action: action: none display: type: markdown card_mod: style: .: | ha-card { border: 0; box-shadow: none; } ha-markdown$: > table { width: 100%; border-collapse: collapse; } p { margin-bottom: 0px; } tbody tr:nth-child(2n+1) { background-color: var(--table-row-background-color); } tbody tr td { padding: 4px 0px; } tbody tr td:first-of-type { padding-left: 20px; } content: >- Blocked Internet Access | | | |---|---:| {% set schedules = state_attr("this.entity_id", "parental_control_schedule") %} {% if schedules is not none and (schedules | map(attribute="blocked_internet_access") | list | length) %} {% for day in schedules.blocked_internet_access -%} |{{ day | title }}|{{ schedules.blocked_internet_access[day] | join(", ") }}| {% endfor -%} {% endif %} - type: custom:state-switch entity: >- {% set sites = state_attr("this.entity_id", "parental_control_schedule") %} {% if sites is not none and (sites | map(attribute="blocked_sites") | reject | list | length) %} display {% endif %} default: default states: default: type: custom:template-entity-row name: Blocked Sites state: — tap_action: action: none display: type: markdown card_mod: style: .: | ha-card { border: 0; box-shadow: none; } ha-markdown$: > table { width: 100%; border-collapse: collapse; } p { margin-bottom: 0px; } tbody tr:nth-child(2n+1) { background-color: var(--table-row-background-color); } tbody tr td { padding: 4px 0px; } tbody tr td:first-of-type { padding-left: 20px; } content: >- Blocked Sites | | |:---| {% set sites = state_attr("this.entity_id", "parental_control_schedule") %} {% if sites is not none and (sites | map(attribute="blocked_sites") | list | length) %} {%- for site in sites.blocked_sites -%} |{{ site }}| {% endfor -%} {% endif %} - type: conditional conditions: - entity: this.entity_id state_not: unknown row: type: section - type: custom:state-switch entity: template template: >- {{ iif(not is_state("this.entity_id", "unknown"), iif(state_attr("this.entity_id", "status"), "connected", "not_connected"), "") }} states: connected: type: custom:layout-card layout_type: custom:grid-layout layout: grid-template-columns: 1fr 1fr margin: 0px padding: 12px 0px 0px place-items: center cards: - type: custom:button-card entity: this.entity_id template: - linksys_velop_select_buttons - linksys_velop_select_button_internet_access - type: custom:button-card entity: this.entity_id template: - linksys_velop_select_buttons - linksys_velop_select_button_rename not_connected: type: custom:layout-card layout_type: custom:grid-layout layout: grid-template-columns: 1fr 1fr 1fr margin: 0px padding: 12px 0px 0px place-items: center cards: - type: custom:button-card entity: this.entity_id template: - linksys_velop_select_buttons - linksys_velop_select_button_internet_access - type: custom:button-card entity: this.entity_id template: - linksys_velop_select_buttons - linksys_velop_select_button_rename - type: custom:button-card template: - linksys_velop_mesh - linksys_velop_select_buttons entity: this.entity_id name: Delete tap_action: action: call-service confirmation: text: >- [[[ return 'Remove "' + entity.state + '" from the device list?' ]]] service: linksys_velop.delete_device service_data: device: '[[[ return entity.attributes.unique_id ]]]' mesh: '[[[ return variables.mesh ]]]' card_mod: style: .: | #states > div:first-of-type { margin-bottom: 20px; } hui-select-entity-row: $: hui-generic-entity-row: $: | state-badge { display: none; } state-badge + div.info { margin-left: 0px; } hui-attribute-row: $: hui-generic-entity-row: $: | state-badge { display: none; } state-badge + div.info { margin-left: 0px; } template-entity-row: $: | #wrapper { min-height: auto !important; } state-badge { display: none; } state-badge + div.info { margin-left: 0px; } state-switch: $: template-entity-row: $: | #wrapper { min-height: auto !important; } state-badge { display: none; } state-badge + div.info { margin-left: 0px; } hui-markdown-card: $: .: | ha-card { border-radius: 0px; box-shadow: none; } ha-markdown { padding: 0px !important; } ```

You'll need to add these to the following to the custom:button-card templates area - see here

edit: I missed a template - sorry.

Button Card Templates ```yaml linksys_velop_mesh: variables: mesh: | [[[ if (entity) { let config_entry_id = hass.entities[entity.entity_id].config_entry_id let mesh_device = Object.entries(hass.devices).filter(([device_id, device_details]) => device_details.config_entries.indexOf(config_entry_id) != -1 && device_details.model.startsWith("pyvelop")) if (mesh_device.length) { return mesh_device[0][0] } } ]]] linksys_velop_select_button_internet_access: variables: paused: | [[[ if (entity.attributes.parental_control_schedule && entity.attributes.parental_control_schedule.blocked_internet_access) { return true } return false ]]] name: '[[[ return ((variables.paused) ? "Resume" : "Pause") + " Internet" ]]]' template: linksys_velop_mesh tap_action: action: call-service confirmation: text: >- [[[ return `Are you sure you want to ${((variables.paused) ? "resume" : "pause")} access to the Internet for ${entity.attributes.name}?` ]]] service: linksys_velop.device_internet_access service_data: device: '[[[ return entity.attributes.unique_id ]]]' mesh: '[[[ return variables.mesh ]]]' pause: '[[[ return !variables.paused ]]]' linksys_velop_select_button_rename: name: Rename template: linksys_velop_mesh tap_action: action: call-service service: linksys_velop.rename_device service_data: device: '[[[ return entity.attributes.unique_id ]]]' mesh: '[[[ return variables.mesh ]]]' new_name: | [[[ let new_name = prompt("Enter the new name.", entity.attributes.name) return new_name || entity.attributes.name ]]] linksys_velop_select_buttons: show_icon: false show_state: false styles: card: - background-color: >- var(--ha-chip-background-color, rgba(var(--rgb-primary-text-color), 0.15)) - border: 0 - height: 32px - padding: 0 12px - width: fit-content name: - color: rgb(var(--rgb-primary-color)) - font-size: 0.875rem - font-weight: 500 - text-transform: var(--mdc-typography-button-text-transform, uppercase) ```
uvjim commented 1 year ago

I probably shouldn't use the DHCP on the velop either, so do you bridge your velops to a router behind the mesh? I am considering doing that but I got 100+ ip assignments to consider and don't know how my Velops will handle bridge mode. For the MX4200 that is a troublesome router to begin with... I will research doing a DHCP server separate. I recently added tplinks loadbalancer behind my router to support 2 wans for internet.

I'm in the UK and use my ISP provided router in modem only mode and the Velop to provide the entire Mesh. DHCP is provided by a Pi-Hole on the network. I have a few services on the network where I reserver the IP on the Pi-Hole (for legacy reasons). However, Pi-Hole doesn't really need reservations as devices get the same IP address each time due to both the lease time and the fact that the Pi-Hole uses an algorithm to determine the served IP rather than just using the next available address.

mschingel commented 1 year ago

Button Card Templates

Help me out, where would I enter the Template data? image Someplace there, i just pasted it in and it still gave me errors

uvjim commented 1 year ago

So in Raw configuration editor you'll need a section called button_card_templates: if you don't already have one, create it at the top of all the text. Then paste the templates in from the previous message, making sure they are indented correctly. As an example mine looks like the following screenshot: -

image

mschingel commented 1 year ago

Thanks I had template: your code.

udpated with button_card_templates: and i see the buttons now. Looks good! Thanks!

uvjim commented 1 year ago

I won't update the README with this as it's a little more intricate than I wanted for the examples. Hopefully it'll just work for you and you'll be able to maintain it if changes are needed.

mschingel commented 1 year ago

As I learn more and more I am certain this will make due. Thanks a bunch!

uvjim commented 1 year ago

No problem. I'll close this off now.

mschingel commented 1 year ago

@uvjim

Anyway by chance you can help me fix this: linksys_velop_mesh: variables: mesh: | [[[ if (entity) { let config_entry_id = hass.entities[entity.entity_id].config_entry_id let mesh_device = Object.entries(hass.devices).filter(([device_id, device_details]) => device_details.config_entries.indexOf(config_entry_id) != -1 && device_details.model.startsWith("pyvelop")) if (mesh_device.length) { return mesh_device[0][0] } } ]]]

I think its broken since the latest update... I cannot figure out what is wrong with the Mesh part. I am getting a Key error when I rename , delete, or pause internet. I know this was fixed in the Node cards but I tried to remake the Mesh part with the code you had but failed.

I tried to place this Object.keys(this.hass.devices).filter(key => this.hass.devices[key].config_entries[0] == config_entry_id && this.hass.devices[key].model.startsWith("pyvelop")) after let mesh_device =

Edit: I Can get it work by editing the mesh code at the break by replacing it with the key from the Developers Service feature found in the yaml there. But I assume fixing the code is the correct way so that others can enjoy these features.