Custom Integration: Linksys Velop

First off - just found this over the weekend and THANK YOU! I spent the weekend (as an HA newbie) getting this up and running, but I think I have it all in place, including the cards, and it’s great, so thank you for taking the time to make this available.

I’m having one issue: while I can expand my “offline devices” list, when I try to expand my “online devices” list, nothing shows up. The card changes - the downward-pointing triangle flips to point up - but nothing appears.

I copied all of your card YAML directly from https://github.com/uvjim/linksys_velop - has something recently changed that might be causing this one function not to work?

Thank you!
-Jude

Not from the UI. You can modify the config entry in the relevant JSON file and reload if you’re that way inclined. Although, removing and re-adding the integration really should t be an issue unless you renamed some entities as they’ll come back with the same names.

Seems to work for me here still. Do you have a number next to online devices?

Also, what does the attribute of sensor.velop_mesh_online_devices show?

Yes - I see the number 44 in the card, and that’s also the current value of : sensor.velop_mesh_online_devices

It’s not a big deal, but it’s weird. I’ll poke and play a bit and see if I can figure it out, but I copied your card yaml exactly.

jtf

A bit of a teaser for something I’m working on…
The Android app now includes signal strength for the Velop nodes and also an indication for the signal strength for devices.

The Linksys application explains this as follows: -

I’ve updated the pyvelop library to expose this information and am currently working through getting the information into HA.

The sensors are currently there (see below), but I’m trying to work out the best way of getting all of the information available.

2 Likes

2022.8.1b0 is out: -

  • remove unnecessary domains key from hacs.json
  • add loggers to manifest
  • bump pyvelop version to 2022.7.2
  • Add Backhaul Sensor for Nodes
  • linting fixes
  • bump pyvelop version to 2022.8.1
  • correctly set entity_domain
  • make icon dependent on connection type
  • update card3
  • use async_forward_entry_setups if available

2022.8.1b1 is out: -

  • bump pyvelop to 2022.8.2
  • update card for select entity

2022.8.1b2 is out: -

  • fix regression in service handler from linting fixes

2022.8.1b3 is out: -

  • adopt new naming convention for HASS 2022.8.0
  • update cards 2 and 3 to reflect naming changes
  • update select entity example ui
  • account for SOURCE_TYPE_* deprecation

2022.8.1b4 is out: -

  • fix template errors for disconnected devices
  • update image and pre-reqs for select entity card
  • guard against missing properties in card 2

2022.8.1b5 is out: -

  • bump pyvelop to 2022.8.3
  • ensure all device properties are available
  • update card 2 YAML to refelect IPv4 or IPv6

2022.8.1 is now out. It should be available in HACS soon.

Breaking Change

If you were using the backhaul information from the Parent sensor, this has now been moved into it’s own sensor. The backhaul sensor is documented in the README, but it uses the reported RSSI value in dBm as it’s state and the attributes now hold the additional backhaul information. Card3 in the example UI has been update to reflect this.

Additional Information

Naming conventions - In HASS 2022.7.0 an entity property was introduced that determined whether the new naming convention should be used. This integration uses the existence of that property to determine how to name the entities.

As I’m sure you’re aware the default for the integration has been Velop Mesh: <identifier> for mesh entities and Velop <node_name>: <identifier> for node entities.

If you are using HASS versions prior to 2022.7.0 then this will still be the case. Any HASS version later than that will use the format detailed here and here.

If you have an existing installation of the integration your entity_ids will not change (I’ve tested that) if new entities are added in the future the naming of those entity_ids will be derived from the new entity naming convention.

No issues should be seen at all for new installations.

The example UIs in the README have been updated to be dynamic between the two conventions. However, they do not take into account a mixture of conventions, i.e. a new entity being added as per the detail above.

1 Like

@judetf did you ever figure this out? I’m having the exact same issue (i.e. offline devices can expand, but online doesn’t). I’m also not seeing images for card 3’s nodes (image not found icon).

@uvjim first, this add-on is amazing. I got HACS just to use it, and very impressed. One QQ: I opted to use the non-beta when prompted in HACS. Is the YAML on GitHub tailored to the beta versions? (e.g. 2022.8.1b3 seems like the latest, but I’m on 2022.6.2)

Thanks for the kind words. The YAML you see on GitHub in the README will depend on which branch/tag you have selected - the releases are shown as tags and the main branch is essentially live development branch (after my local testing).

RE: the dropdown not showing anything. I’d be interested to know if anything appears in the HASS logs, but also in the developer tools of the browser. Navigate to the dashboard you’re using the cards on, clear the console log of the developer tools and refresh the page. That should give me an idea of what’s happening (combined with potential logs from HASS).

RE: images for the nodes… There’s a post higher up about that I think… However, the path defaults to /local/velop_nodes/<model_number>.png. You can change the path as detailed here. If you change the path then you’ll also need to enable the sensor for the node so the card can read it.

Hope that makes sense.

@LearningHASlowly:

  1. No, still having the issue, but I will dive into the HASS logs now for @uvjim to see what it shows
  2. The images: you have to find and upload your own images - they don’t come as part of the package. So google each model type, find an image, save it as a .png file, NOTE THAT THE FILENAME IS CAPS-SENSITIVE, and save it to the specified directory, and they should appear immediately.

I don’t see anything potentially relevant in the HASS logs although I may not know what/how to look? Here’s what I’m seeing in the console log after a refresh:
image

Hmm… Can we check what’s in the HASS Developer Tools?
It should look something like this…

The card tries to build the contents based on that info. This is mine at the moment.

Hopefully you can see they tally up.

Could you test using the YAML in the main repository and also that available in the tag version that matches the integration you have installed, that would be great.

1 Like

Ah, got it (still learning HA - sorry). Yes, everything is showing in the Developer Tools:

@uvjim, I apologize tremendously, but I’m not sure what you are asking me to do when you say " test using the YAML in the main repository and also that available in the tag version that matches the integration you have installed." I’m happy to do whatever you need me to do, and I hate when I can’t follow instructions, but I can’t figure out what that means or what to test. :frowning:

No problem. First thing to try is to grab the latest code for the card from here. There were updates to this over the last few days. The updates relate to the beta release but I see no reason that they should fail for older releases.

If the still doesn’t work, could you try the the code here - this assumes that you have 2022.6.2 of the integration installed.

Let’s stick to those things for now.

Again check logs in Home Assistant (Settings → System → Logs) and the browser developer tools (clearing the console before refresh - not sure which browser your using but forcing the cache to invalidate might help, normally CTRL+R or CTRL+F5 if on Windows).

Also, could you post a screenshot of what you’re seeing (or not seeing as the case actually is).

Okay, I tried the new code and it didn’t fix things. (I had been using the code from the second link you send - yes, I’m on 2022.6.2). However, when I did a hard-reload of the page after installing the new code, I happened to have the Developer Tools open in my browser when I clicked on the ‘expand’ arrow and this error popped up:
image

Screenshots of what I actually see are pretty basic. Here’s what it looks like before I expand the list:
image

And here’s what it looks like after I click the down-arrow to try and expand it:
image

Nothing is showing up in the System → Logs at all…

Last note/comment: I greatly appreciate your efforts here. Thank you for taking the time. However, I’ll also note that solving this is not critical for me. Since your code seems to be working for everyone else, I’m more than happy to live without this one feature, when the rest of what you put together is so awesome.

Hmmm… I can work with that template error… I have a feeling I have that solved and may not have pushed the changes yet… Gimme a mo…

Right, could you try this in your card please? If this works, or at least gets us closer we’ll probably need to find out why you seem to have a device online but it doesn’t have a connection type - that’s a problem for another day though (and is possibly solved by rebooting the mesh).

Card2 YAML Test
type: custom:auto-entities
card:
  type: vertical-stack
card_param: cards
filter:
  include:
    - entity_id: /binary_sensor.(velop_)*mesh_wan_status/
      options:
        type: entities
        card_mod:
          style:
            .: |
              #states { padding-top: 0px; }
            fold-entity-row:
              $:
                template-entity-row:
                  $: |
                    state-badge { display: none; }
                    state-badge + div { margin-left: 8px !important; }
                    .info.pointer { font-weight: 500; }
                    .state { margin-right: 10px; }
        entities:
          - type: custom:button-card
            entity: this.entity_id
            show_name: false
            icon: hass:web
            tap_action:
              action: none
            custom_fields:
              attr_dns_servers: '[[[ return (entity != undefined) ? entity.attributes.dns : ""]]]'
              attr_public_ip: '[[[ return (entity != undefined) ? entity.attributes.ip : "" ]]]'
              attr_speedtest_latest: |
                [[[
                  let entity_speedtest_id = 'sensor.' + this._config.entity.split('.')[1].split('_').slice(0, -2).join('_') + '_speedtest_latest'
                  let entity_speedtest = states[entity_speedtest_id]
                  if (entity_speedtest != undefined && entity_speedtest.state != 'unknown') {
                    let d = new Date(entity_speedtest.state)
                    return "As at: " + d.toLocaleString()
                  } else {
                    return "No Speedtest results"
                  }
                ]]]
              attr_speedtest_details: |
                [[[
                  let round2 = (num) => Math.round(num * 100) / 100
                  let spacing_internal = 5
                  let spacing_external = 30
                  let icon_size = 22
                  let entity_speedtest_id = 'sensor.' + this._config.entity.split('.')[1].split('_').slice(0, -2).join('_') + '_speedtest_latest'
                  let entity_speedtest = states[entity_speedtest_id]
                  if (entity_speedtest != undefined && entity_speedtest.state != 'unknown') {
                    let latency = entity_speedtest.attributes.latency
                    let download_bandwidth = round2(entity_speedtest.attributes.download_bandwidth / 1000)
                    let upload_bandwidth = round2(entity_speedtest.attributes.upload_bandwidth / 1000)

                    return `<span style="margin-right: ${spacing_external}px;">
                              <ha-icon icon="hass:swap-horizontal" style="width: ${icon_size}px;"></ha-icon>
                              <span>${latency}ms</span>
                            </span>
                            <span style="margin-right: ${spacing_external}px;">
                              <ha-icon icon="hass:cloud-download-outline" style="width: ${icon_size}px;"></ha-icon>
                              <span>${download_bandwidth} Mbps</span>
                            </span>
                            <span>
                              <ha-icon icon="hass:cloud-upload-outline" style="width: ${icon_size}px;"></ha-icon>
                              <span>${upload_bandwidth} Mbps</span>
                            </span>
                            `
                  }
                ]]]
            state:
              - value: 'on'
                color: darkcyan
              - value: 'off'
                color: darkred
            styles:
              card:
                - box-shadow: none
                - padding: 16px 8px
              grid:
                - grid-template-areas: >-
                    "attr_dns_servers . attr_public_ip" "i i i"
                    "attr_speedtest_details attr_speedtest_details
                    attr_speedtest_details" "attr_speedtest_latest
                    attr_speedtest_latest attr_speedtest_latest"
                - grid-template-rows: 5% 1fr 15% 5%
                - grid-template-columns: 1fr min-content 1fr
              custom_fields:
                attr_dns_servers:
                  - justify-self: self-start
                attr_public_ip:
                  - justify-self: self-end
            extra_styles: >
              div[id^="attr_"] { font-size: smaller; color:
              var(--disabled-text-color);

              }

              div[id^="attr_speedtest_"] { margin-top: 10px; }

              #attr_public_ip::before { content: 'Public IP: ' }

              #attr_dns_servers::before { content: 'DNS: ' }
          - type: conditional
            conditions:
              - entity: binary_sensor.velop_mesh_speedtest_status
                state: 'on'
            row:
              type: section
          - type: conditional
            conditions:
              - entity: binary_sensor.mesh_speedtest_status
                state: 'on'
            row:
              type: section
          - type: custom:template-entity-row
            card_mod:
              style: >
                state-badge { display: none; } state-badge + div.info {
                margin-left: 8px !important; margin-right: 8px; text-align:
                center; }
            condition: >-
              {{ is_state('binary_sensor.velop_mesh_speedtest_status', 'on') or
              is_state('binary_sensor.mesh_speedtest_status', 'on')}}
            name: >-
              {% set entity_status = "this.entity_id" | replace("wan_status",
              "speedtest_status") %}  {{ state_attr(entity_status, 'status') }}
            tap_action:
              action: none
          - type: section
          - type: custom:auto-entities
            card:
              type: horizontal-stack
            card_param: cards
            sort:
              method: friendly_name
            filter:
              include:
                - entity_id: /^(button|switch)\.(velop_)*mesh_/
                  options:
                    type: custom:button-card
                    hold_action:
                      action: more-info
                    tap_action:
                      action: >-
                        [[[ return (entity.entity_id.startsWith("button")) ?
                        "call-service" : "toggle" ]]]
                      service: >-
                        [[[ return (entity.entity_id.startsWith("button")) ?
                        "button.press" : undefined ]]]
                      service_data:
                        entity_id: entity
                    name: |-
                      [[[
                        let friendly_name = entity.attributes.friendly_name.replace(/Velop|Mesh|:/gi, "").trim()
                        let idx = friendly_name.lastIndexOf(" ");
                        let ret = friendly_name.substring(0, idx) + "<br />" + friendly_name.substring(idx + 1)
                        return ret
                      ]]]
                    styles:
                      card:
                        - box-shadow: none
                        - margin-bottom: 3px
                      icon:
                        - animation: |-
                            [[[
                              let ret
                              let speedtest_entity = states["binary_sensor.velop_mesh_speedtest_status"] || states["binary_sensor.mesh_speedtest_status"]
                              if (entity.entity_id.match(/button\.(velop_)*mesh_start_speedtest/) && speedtest_entity.state == "on") {
                                ret = "rotating 2s linear infinite"
                              }
                              return ret
                            ]]]
                        - color: |-
                            [[[
                              let ret
                              let col_on = "darkcyan"
                              let col_off = "var(--primary-text-color)"
                              ret = (entity.state == "on") ? col_on : col_off
                              if (entity.entity_id.match(/button\.(velop_)*mesh_start_speedtest/)) {
                                let speedtest_entity = states["binary_sensor.velop_mesh_speedtest_status"] || states["binary_sensor.mesh_speedtest_status"]
                                ret = (speedtest_entity.state == "on") ? col_on : col_off
                              }
                              return ret
                            ]]]
                      name:
                        - font-size: smaller
                        - color: |-
                            [[[
                              let ret
                              let col_on = "darkcyan"
                              let col_off = "var(--primary-text-color)"
                              ret = (entity.state == "on") ? col_on : col_off
                              if (entity.entity_id.match(/button\.(velop_)*mesh_start_speedtest/)) {
                                let speedtest_entity = states["binary_sensor.velop_mesh_speedtest_status"] || states["binary_sensor.mesh_speedtest_status"]
                                ret = (speedtest_entity.state == "on") ? col_on : col_off
                              }
                              return ret
                            ]]]
          - type: section
          - type: custom:fold-entity-row
            padding: 0
            head:
              type: custom:template-entity-row
              entity: this.entity_id
              name: Online Devices
              state: >-
                {% set devices_entity = "sensor." ~
                "this.entity_id".split(".")[1] | replace("wan_status",
                "online_devices") %} {{ states(devices_entity) }}
              tap_action:
                action: fire-dom-event
                fold_row: true
              card_mod:
                style: |
                  .info.pointer { font-weight: 500; }
                  .state { margin-right: 10px; }
            entities:
              - type: custom:hui-element
                card_type: markdown
                card_mod:
                  style:
                    .: |
                      ha-card { border-radius: 0px; box-shadow: none; }
                      ha-markdown { padding: 16px 0px 0px !important; }
                    ha-markdown$: >
                      table { width: 100%; border-collapse: separate;
                      border-spacing: 0px; }

                      tbody tr:nth-child(2n+1) { background-color:
                      var(--table-row-background-color); }

                      thead tr th, tbody tr td { padding: 4px 10px; }
                content: >
                  {% set devices_entity = "sensor." ~
                  "this.entity_id".split(".")[1] | replace("wan_status",
                  "online_devices") %} {% set devices =
                  state_attr(devices_entity, 'devices') %} | # | Name | IP |
                  Type |

                  |:---:|:---|---:|:---:| {%- for device in devices -%}
                    {%- set device_ip = device.ip -%}
                    {%- set connection_type = device.connection | lower if "connection" in device else "" -%}
                    {%- set guest_network = device.guest_network -%}
                    {%- if connection_type == "wired" -%}
                      {%- set connection_icon = "ethernet" -%}
                    {% elif connection_type == "wireless" -%}
                      {%- set connection_icon = "wifi" -%}
                    {% elif connection_type == "unknown" -%}
                      {%- set connection_icon = "help" -%}
                    {% else -%}
                      {%- set connection_icon = "" -%}
                    {%- endif %}
                  {{ "| {} | {}{} | {} | {} |".format(loop.index, device.name,
                  '&nbsp;<ha-icon icon="hass:account-multiple"></ha-icon>' if
                  guest_network else '', device_ip, '<ha-icon icon="hass:' ~
                  connection_icon ~ '"></ha-icon>') }}
                    {%- endfor %}
          - type: custom:fold-entity-row
            padding: 0
            head:
              type: custom:template-entity-row
              entity: this.entity_id
              name: Offline Devices
              state: >-
                {% set devices_entity = "sensor." ~
                "this.entity_id".split(".")[1] | replace("wan_status",
                "offline_devices") %} {{ states(devices_entity) }}
              tap_action:
                action: fire-dom-event
                fold_row: true
              card_mod:
                style: |
                  .info.pointer { font-weight: 500; }
                  .state { margin-right: 10px; }
            entities:
              - type: custom:hui-element
                card_type: markdown
                card_mod:
                  style:
                    .: |
                      ha-card { border-radius: 0px; box-shadow: none; }
                      ha-markdown { padding: 16px 0px 0px !important; }
                    ha-markdown$: >
                      table { width: 100%; border-collapse: separate;
                      border-spacing: 0px; }

                      tbody tr:nth-child(2n+1) { background-color:
                      var(--table-row-background-color); }

                      thead tr th, tbody tr td { padding: 4px 10px; }
                content: >
                  {% set devices_entity = "sensor." ~
                  "this.entity_id".split(".")[1] | replace("wan_status",
                  "offline_devices") %} {% set devices =
                  state_attr(devices_entity, 'devices') %} | # | Name |

                  |:---:|:---|

                  {% for device in devices %} {{ "| {} | {}
                  |".format(loop.index, device.name) }}

                  {% endfor %}
          - type: custom:auto-entities
            card:
              type: custom:fold-entity-row
              head:
                type: section
                label: Storage
              padding: 0
            show_empty: false
            filter:
              include:
                - entity_id: /sensor.(velop_)*mesh_available_storage/
                  options:
                    type: custom:hui-element
                    card_type: markdown
                    card_mod:
                      style:
                        .: |
                          ha-card { border-radius: 0px; box-shadow: none; }
                          ha-markdown { padding: 16px 0px 0px !important; }
                        ha-markdown$: >
                          table { width: 100%; border-collapse: separate;
                          border-spacing: 0px; }

                          tbody tr:nth-child(2n+1) { background-color:
                          var(--table-row-background-color); }

                          thead tr th, tbody tr td { padding: 4px 10px; }
                    content: >
                      {% set storage_entity = "sensor." ~
                      "this.entity_id".split(".")[1] | replace("wan_status",
                      "available_storage") %} {% set partitions =
                      state_attr(storage_entity, 'partitions') %} | Host | Label
                      | %age used

                      |:---:|:---:|:---:|

                      {% for partition in partitions %} {{ "| {} | {} | {}
                      |".format(partition.ip, partition.label,
                      partition.used_percent) }}

                      {% endfor %}

I’d love to say that did it, but still no love after replacing the card code, restarting HA, and hard-reloading the dashboard page. Here’s the Developer log now:
image

I will restart my mesh as soon as the wife is done with her work calls! :slight_smile:

Don’t reboot just yet.

I’m traveling at the moment, but will update again probably tomorrow.
It’s just an attribute error so I’ll check the attribute exists betore using it. That way you won’t see the error.

Also, on the Mesh device there’s an option to download diagnostics. Could you message that over to me please? I’d like to see if I can work out which device is causing this. Probably best to do that before rebooting anything as it’ll likely clear up after a reboot.

Cheers.

Awesome. I just uploaded the diagnostics to a new GitHub issue. Let me know if you need anything else!

I just tried the code you sent for Card 2 and that fixed it immediately! Thank you!

Brilliant. I’ll push the updates for the card soon then.

1 Like