Affirmatech / MeshSense

MeshSense directly connects to your Meshtastic node via Bluetooth or WiFi and continuously provides information to assess the health of your mesh network.
https://affirmatech.com/meshsense
GNU General Public License v3.0
40 stars 7 forks source link

display reported channel utilization % #10

Open mblauser opened 1 month ago

mblauser commented 1 month ago

Proposed Enhancement: will display reported channel utilization% (rounded to int) for each node. Placement to the right of any reported SNR and RSSI

Users may find each node's respective reported channel utilization useful in identifying problem areas on the mesh. For now, I think the best placement for this is to the right of any potential reported SNR and RSSI data. The box will only display if deviceMetrics?.channelUtilization exists, displaying only an INT value.

Eventually we could have a toggle in settings to display this or not. We could also later add a colorized bar below the value once defined thresholds have been set. (0-14 green, 15-24 yellow, 25+ red ?)

proposed rough illustration: image

*** Please give feedback or correct the html class used here. I used w-10 but perhaps w-8 or some other value is more appropriate?

Soltares commented 1 month ago

Very cool!! Testing on my node here appears as below. I wonder if it would take up less space if we had it on the second row somewhere? "ch" seems to occasionally spill out of the div, but if we were to remove it, it would be difficult to distinguish between battery and ch util. Another consideration is whether or not to include it in "smallMode" (left arrow in UI).

I'm happy to take the pull when you are happy and we can adjust down the line as we see it in action.

image

Soltares commented 1 month ago

I was playing around a little with a sidebar display of channel util. It can show percentage on mouseover. I'm flexible, just some ideas!

image

        <!-- Channel Utilization % -->
        {#if node.deviceMetrics?.channelUtilization}
          <div
            title="{(node.deviceMetrics?.channelUtilization).toFixed(0)}% Channel Utilization"
            class="absolute w-1.5 rounded bottom-1 top-1 right-0.5 overflow-hidden border border-white/20 flex flex-col"
          >
            <div class="grow"></div>
            <div class="bg-green-500" style="height: {(node.deviceMetrics?.channelUtilization).toFixed(0)}%;"></div>
          </div>
          {/if}
mblauser commented 1 month ago

I like the concept of the vertical bar representation with mouse-over description. Organizationally I am hesitant to move the box to the 2nd row bc I believe it should remain grouped with SNR and RSSI. This vertical colorized bar should fit nicely in both smallMode and expanded mode.

Is this the proper way to introduce a local variable in JavaScript? {let $thisNodeChUasINT = (node.deviceMetrics?.channelUtilization).toFixed(0);} <div title="{$thisNodeChUasINT}% Channel Utilization" class=...>

Then we can colorize that vertical bar using these thresholds: {$thisNodeChUasINT < 15 ? 'green' : $thisNodeChUasINT < 25 ? 'yellow' : 'red'}

Perhaps we should reduce the scale of the vertical bar to only show 0-50 instead of 0-100; or artificially double the visually displayed value. In practice, the bar will not ever come close to 100% as any mesh tends to fall apart above 40%; in which case seeing a solid red bar w 50 being the upper maximum representation makes sense.

Maybe later we can add into the settings user-defined thresholds for personal preference.

mblauser commented 1 month ago

This is a reminder to myself to rewrite using Math.floor() [approx 4x faster operation] instead of toFixed(0) Math.floor(node?.deviceMetrics?.channelUtilization)

mblauser commented 1 month ago

within the CSS, I do not see a color definition for .bg-yellow-500 nor .bg-orange-500

I'd like to use these thresholds: under 15% = green under 25% = yellow under 35% = an attention-getting orange 35% and above = attention-getting red

do you have a recommendation for adding .bg-yellow-500 and .bg-orange-500?

Additionally, I've visually emphasized the 0-50% scale of the vertical bar. Any channelUtilization values over 50 will still represent 100% of the vertical bar.

I've updated the draft pull request with refactored code, but haven't yet been able to get the project compiled on my local macos workstation to test this code myself. Please test.

Soltares commented 1 month ago

Unfortunately the #let convention does not work in Svelte:

{#let channelUtilizationINT = Math.floor(node.deviceMetrics.channelUtilization)}

This problem would probably be best solved using a separate Svelte component. Example below as lib/ChannelUtilization.svelte:

<script lang="ts">
  import type { NodeInfo } from 'api/src/vars'
  export let node: NodeInfo

  let channelUtilizationINT = Math.floor(node.deviceMetrics?.channelUtilization || 0)
  let scaledHeight = Math.min(channelUtilizationINT * 2, 100)
  let colorClass = channelUtilizationINT < 15 ? 'green' : channelUtilizationINT < 25 ? 'yellow' : channelUtilizationINT < 35 ? 'orange' : 'red'
</script>

{#if node?.deviceMetrics?.channelUtilization}
  <div
    title="{channelUtilizationINT}% Observed Channel Utilization"
    class="absolute w-1.5 rounded bottom-1 top-1 right-0.5 overflow-hidden border border-white/20 flex flex-col"
    aria-label="Observed Channel Utilization: {channelUtilizationINT}%"
    aria-valuemin="0"
    aria-valuemax="50"
    aria-valuenow={channelUtilizationINT}
  >
    <div class="grow"></div>
    <div
      class:bg-green-500={colorClass === 'green'}
      class:bg-yellow-500={colorClass === 'yellow'}
      class:bg-orange-500={colorClass === 'orange'}
      class:bg-red-500={colorClass === 'red'}
      style="height: {scaledHeight}%;"
    ></div>
  </div>
{/if}

Then this component can be included in Nodes.svelte:

<script>
import ChannelUtilization from './lib/ChannelUtilization.svelte'
</script>
...
<!-- Channel Utilization % -->
<ChannelUtilization {node} />

Also add a relative class to line 85:

      <div
        class:ring-1={node.hopsAway == 0}
        class="relative bg-blue-300/10 rounded px-1 py-0.5 flex flex-col gap-0.5 {node.num == $myNodeNum ? 'bg-gradient-to-r ' : ''}  {Date.now() - node.lastHeard * 1000 < 3.6e6 ? '' : 'grayscale'}"
      >

I artificially increased channel utilization in my local environment to test the colors and they look great!

image

I will find some time to update the README with some documentation on running the code locally as it is a bit unique in setup. Thanks!

Soltares commented 1 month ago

Pushed Affirmatech:patch-2 with separate component code

mblauser commented 1 month ago

I appreciate the explanation. Thank you. I agree w your suggestion of using a separate Svelte component; keeps it clean and reduces injected code in Nodes.svelte

Is it possible/advisable to instead use reactive declarations for these local variables within lib/ChannelUtilization.svelte?

<script lang="ts">
  import type { NodeInfo } from 'api/src/vars';
  export let node: NodeInfo;

  $: channelUtilizationINT = node?.deviceMetrics?.channelUtilization 
    ? Math.floor(node.deviceMetrics.channelUtilization) 
    : null;

  $: scaledHeight = channelUtilizationINT !== null 
    ? Math.min(channelUtilizationINT * 2, 100) 
    : 0;

  $: colorClass = channelUtilizationINT !== null
    ? (channelUtilizationINT < 15 ? 'green' : 
       channelUtilizationINT < 25 ? 'yellow' : 
       channelUtilizationINT < 35 ? 'orange' : 
       'red')
    : 'grayscale'; // default color when channelUtilizationINT is null
</script>

{#if channelUtilizationINT !== null}
  <div
    title="{channelUtilizationINT}% Observed Channel Utilization"
    class="absolute w-1.5 rounded bottom-1 top-1 right-0.5 overflow-hidden border border-white/20 flex flex-col"
    aria-label="Observed Channel Utilization: {channelUtilizationINT}%"
    aria-valuemin="0"
    aria-valuemax="50"
    aria-valuenow={channelUtilizationINT}
  >
    <div class="grow"></div>
    <div
      class:bg-green-500={colorClass === 'green'}
      class:bg-yellow-500={colorClass === 'yellow'}
      class:bg-orange-500={colorClass === 'orange'}
      class:bg-red-500={colorClass === 'red'}
      class:grayscale={colorClass === 'grayscale'}
      style="height: {scaledHeight}%;"
    ></div>
  </div>
{/if}

Should we display an empty grayscale vertical bar when channelUtilization is not reported? It might look better aligned if additional vertical bars are added later. Please check the grayscale implementation above.

Would you please explain the intention of adding the suggested relative class:

      <div
        class:ring-1={node.hopsAway == 0}
        class="relative bg-blue-300/10 rounded px-1 py-0.5 flex flex-col gap-0.5 {node.num == $myNodeNum ? 'bg-gradient-to-r ' : ''}  {Date.now() - node.lastHeard * 1000 < 3.6e6 ? '' : 'grayscale'}"
      >
Soltares commented 1 month ago

Ah yes, I believe you are correct regarding the reactive declarations. Great catch!

Regarding relative -- I was using absolute positioning to put the bar on the right side, but to keep it constrained within the node's box (and not the node list) a relative was needed. After pondering it, this may not be the cleanest approach, however, as we need to manually pad space to make the bar fit.. Perhaps a parent flex div would be more appropriate, but would need some experimentation.

I hope to have details on setting up local development soon. Real life has been busy lately, but hope to catch up with all this soon.

Soltares commented 1 month ago

The README has been updated with setting up a local development environment. If you need any additional information just let me know. Thanks!

mblauser commented 1 month ago

Thank you. I was successful setting up a dev environment on MacOS based on your README instructions. Have not found time yet to experiment with the UI/UX code.