Device Maintenance Monitor - Smart maintenance monitor

Device Maintenance Monitor: Custom Component for Home Assistant

I am very happy to inroduce my first HA custo component!

Overview - What and Why?

Maintaining the various devices in our homes can be challenging, especially when their usage varies greatly.
The Device Maintenance Monitor custom component for Home Assistant aims to solve this by providing a tailored maintenance reminder system based on actual device usage rather than just fixed intervals.
This is particularly useful for devices like air conditioners that have different usage patterns across different rooms. For instance, in my home, the bedroom AC runs for about 10 hours a day, while the office AC runs only 2-3 hours a week. The standard three-month cleaning schedule often resulted in the bedroom AC being overdue for maintenance while the office AC was still in pristine condition.
This component ensures that each device gets the attention it needs based on how much it is actually used.

Features & Entities

Features

  • Runtime-Based Reminders: Reminders based on the total runtime of the device (e.g., every X hours of operation).
  • Turn-On Count-Based Reminders: Reminders based on how many times the device has been turned on (e.g., every X on/off cycles).
  • Fixed Interval Reminders: Traditional reminders based on fixed time intervals (e.g., every X days/months).

Entities Created

  1. Binary Sensor: Indicates whether maintenance is needed.

    Attributes:

    • Last maintenance date
    • Runtime duration since last maintenance (if runtime method chosen)
    • Turn-on count since last maintenance (if count method chosen)
    • Estimated maintenance date (based on usage since last maintenance)
  2. Button Entity: Used to reset the maintenance needed status (indicates maintenance done).

Services

  • Reset Maintenance Service: Resets the maintenance needed status.

How to Install

  1. HACS Installation:
    • Ensure you have HACS installed in your Home Assistant.
    • Add the Device Maintenance Monitor repository to HACS.
    • Install the integration via HACS.
  2. Manual Installation:
    • Download the component from the GitHub repository.
    • Place the downloaded files in the custom_components/device_maintenance_monitor/ directory within your Home Assistant configuration folder.
    • Restart Home Assistant.
    • Configure the integration via the Home Assistant UI.

Future Enhancements

We are continuously working to improve the Device Maintenance Monitor component. Here are some ideas for future enhancements:

  • Adding a minimum and maximum fixed interval for the runtime method (e.g., every X hours of operation, but at max every Y days/months).
  • Introducing methods for tracking increasing measurements (e.g., water meters).
  • Utilizing machine learning to predict estimated maintenance dates more accurately, reducing fluctuations.
  • Adding dedicated sensors instead of attributes for more granular tracking.
  • Supporting state-based runtime adjustments (similar to the PowerCalc component).
  • Implementing templates to determine whether the current state should be counted towards usage.

We encourage you to open feature requests on the GitHub repository and comment on existing requests to help prioritize the features you’d like to see implemented.

Thank you for your support and contributions to making Home Assistant an even more powerful and versatile smart home platform!

4 Likes

Really good idea, thank you!

1 Like

Nice work! Slightly annoyed (in a good way :wink: ) that my handywork from a month ago could have been way easier done using this. I edited it to reference your great contribution:

Now I need to revisit my work to integrate your solution, and throw away a good part of it :rofl: The tasks bit to put reminders in Google remains.

1 Like

[Release] Device Maintenance Monitor v0.1.0: New Features & Bug Fixes

Hello Home Assistant Community,

I’m excited to announce the release of version 0.1.0 of the Device Maintenance Monitor custom component! This update brings several new features, enhancements, and important bug fixes that improve the overall functionality and user experience.

:rocket: New Features

  • Dedicated Sensors for Granular Tracking
    We’ve introduced dedicated sensors for tracking the inner state of the logic, in addition to the existing binary sensor attributes. This allows for more granular monitoring and control.
  • Support for Fixed Interval Sensors Without Linked Devices
    You can now configure fixed interval sensors that operate independently of any linked device. This is perfect for tracking maintenance tasks that don’t have a specific device associated with them.
  • State Templates for Custom Usage Tracking
    Implemented templates allow you to determine whether the current state of a device should be counted towards its usage. This gives you greater flexibility in how you track and manage device maintenance.
  • Reset Maintenance Service
    A new reset_maintenance service has been added, allowing you to reset maintenance data and start a new tracking period with ease.
  • Minimum and Maximum Fixed Intervals for Runtime Method
    You can now set minimum and maximum intervals for the runtime method, ensuring that reminders are both timely and not overly frequent.

:bug: Bug Fixes

  • Configuration Entry Name Update
    Fixed an issue where the configuration entry name wouldn’t update after changing the name of the integration in the configuration.
  • Statistics Reset Issue
    Resolved a problem where statistics would reset when updating configuration or reloading the integration.

This release is a significant step forward, and I’m grateful for the community’s feedback and contributions. Please update to the latest version and let me know how these changes improve your Home Assistant experience!

As always, your feedback is welcome. Feel free to open any issues or feature requests on the GitHub repository.

Thank you for your support!

1 Like

I had been trying to build something like this with custom sensors and automations, but failed due to complete lak of coding-knowhow.

Amazing t see someone building a soultion to this. thanks so much!

I am trying to set this up for various things in my home such as to remind me to clean the combustion chamber of a pellet heating after every 20 hours of burning as well as remind me of changing a UV-Water sterilization bulb after 365 days of use.

As a complete code-idiot I am quite lost how this should work and feel like there are a few things missing to make this really great. But maybe its just me being stupid:

  • I understand the “Minimum Interval” and the “Maximum Interval” - but what is the “Interval” and why does it only accept hours/min/sec instead of days as well, like the other two?

  • The integration creates 3 entities of wich one is the reset button, one is the “maintenance needed” and one is the “predicted maintenance date”. However “maintenance needed” again has an attribute that is named “predicted maintenance date” as well. Is this just a duplicate or is there a difference?

  • The entity “maintenance needed” has attributes “Last maintenance date” and “Last reset date”. What is the difference here?

  • I think it would be great if all attributes would just be exposed as individual entities, this would make using them much easier.

  • The runtime counter appears to be based on its own counting and does not rely on the recoder/history of HA. If that is true, that would be a great pity since this means that anything that happened before installing the Integration does not count against the maintenance time.

  • It would be great if the Integration provided a percentage-entity that shows the “healt” of the appliance (counting down from 100% after the reset towards 0% when “Maximum Interval” is reached) as well as a entity for “runtime left” or “usage cycels left”. Would make it much easier to see and estimate upcoming maintenance and would make calculations a lot more transparent.

  • Could anyone please provide examples of how the “is_on_template” should look like? As a non-coder I am completely lost as to how to track a state that is not one of the provided options on/off. In my case the relevant stae would be “burning”.

  • The Integrations takes tha name of the device/entity it tracks to name its new entities, instead of the name of the integration entries and then even adds them to said device! This makes them almost impossible to find. E.g. my integration entry “UV Lamp Maintenance” relies on a Shelly called “Shelly Water treatment”. Instead of naming the reset entity “UV Lamp Maintenance reset” the integration created a entity called “Shelly Water treatment reset” an associated it with said Shelly device. Made it seem like the button would reset the Shelly device, instead of the maintenance counter. Very confusing!

  • It would be great if one could choose what device (or if at all) one wants to associate the new entities with instead of biding them to whatever device is used to track the state.

  • For the UV bulb I created an Integration entry with 350 days “Minimum Interval” and 365 days “Maximum Interval”. Left the “Interval” at 0:00:00 since iIdidn know what it means. Now the “Runtime Duration” attribute is telling me it has since run about 9000 seconds, so about 2.5 hours. Yet the estimated date of amintenance is 18 days from now, instead of in one year. What am I doing wrong?

If anyone interested, I’ve used it to track may bigger house chores. It uses auto-entities to display it with some custom button card, that add day counting and coloring (green >10 days, yellow <10 days, red<1 day) depending on chore timeout. Also when pressed, it opens more-info for binary sensor that’s allow to reset the task.

type: custom:auto-entities
filter:
  include:
    - integration: device_maintenance_monitor
      domain: binary_sensor
      options:
        type: custom:button-card
        template: card_device_maintenance
        variables:
          card_force_background_color: true
  exclude: []
sort:
  method: attribute
  attribute: predicted_maintenance_date
card:
  type: vertical-stack
  title: Najbliższe działania serwisowe
card_param: cards
grid_options:
  columns: full
  rows: auto

And the custom button card - you need to put it on top of the dashboard yaml config with Raw Configuration Editor:

button_card_templates:
  card_device_maintenance:
    variables:
      var_entity: '[[[ return entity.entity_id ]]]'
      var_icon_on: mdi:alert
      var_icon_off: mdi:check-circle
      var_icon_warning: mdi:alert-circle
    type: custom:button-card
    entity: '[[[ return variables.var_entity ]]]'
    name: >
      [[[ const ent = variables.var_entity || ""; const id =
      ent.replace(/^binary_sensor\./,""); const key = id.split("_maint")[0];
      const friendly = states["sensor." + key +
      "_predicted_maintenance_date"]?.attributes?.friendly_name; return friendly
      || "Maintenance"; ]]]
    tap_action:
      action: more-info
      entity: >
        [[[ const ent = variables.var_entity || ""; const key =
        ent.replace(/^binary_sensor\./,"").split("_maint")[0]; return "button."
        + key + "_reset_maintenance"; ]]]
    show_state: false
    show_label: true
    layout: icon_name_state
    styles:
      card:
        - border-radius: 16px
        - padding: 12px
        - font-size: 14px
      grid:
        - grid-template-areas: '"i n" "i l"'
        - grid-template-columns: 64px 1fr
        - grid-template-rows: auto auto
        - column-gap: 12px
      name:
        - justify-self: start
        - text-align: left
      label:
        - justify-self: start
        - text-align: left
      img_cell:
        - align-self: center
        - justify-self: center
        - width: 56px
        - height: 56px
      icon:
        - width: 56px
        - height: 56px
        - color: |
            [[[ 
              const ent = variables.var_entity || ""; 
              const e = states[ent];
              if (!e) return "#4caf50";
              const key = ent.replace(/^binary_sensor\./,"").split("_maint")[0];
              const predicted = e?.attributes?.predicted_maintenance_date || states["sensor."+key+"_predicted_maintenance_date"]?.state;
              if (!predicted || isNaN(Date.parse(predicted))) return e.state === "on" ? "#e53935" : "#4caf50";
              const diffDays = Math.ceil((new Date(predicted) - new Date())/(1000*60*60*24));
              if (e.state === "on") return "#e53935";
              if (diffDays <= 10) return "#f1c40f";
              return "#4caf50";
            ]]]
    state:
      - value: 'on'
        icon: '[[[ return variables.var_icon_on ]]]'
      - value: 'off'
        icon: >-
          [[[  const ent = variables.var_entity || "";  const e = states[ent];
          if (!e) return variables.var_icon_off; const key =
          ent.replace(/^binary_sensor\./,"").split("_maint")[0]; const predicted
          = e?.attributes?.predicted_maintenance_date ||
          states["sensor."+key+"_predicted_maintenance_date"]?.state; if
          (!predicted || isNaN(Date.parse(predicted))) return
          variables.var_icon_off; const diffDays = Math.ceil((new
          Date(predicted) - new Date())/(1000*60*60*24)); return diffDays <= 10
          ? variables.var_icon_warning : variables.var_icon_off; ]]]
    label: |
      [[[ 
        const ent = variables.var_entity || ""; 
        const e = states[ent];
        if (!e) return "-";
        const key = ent.replace(/^binary_sensor\./,"").split("_maint")[0];
        const last = e.attributes.last_maintenance_date || "-";
        const reset = e.attributes.last_reset_date || "-";
        let predicted = states["sensor."+key+"_predicted_maintenance_date"]?.state || "-";
        let daysText = "";
        let color = "#4caf50";
        if (predicted && predicted !== "-" && !isNaN(Date.parse(predicted))) {
          const diffDays = Math.ceil((new Date(predicted) - new Date())/(1000*60*60*24));
          daysText = " (" + diffDays + " days)";
          if (diffDays <= 0) color = "#e53935";
          else if (diffDays <= 10) color = "#f1c40f";
          else color = "#4caf50";
        }
        let predDisplay = predicted;
        if (predicted && !isNaN(Date.parse(predicted))) predDisplay = new Date(predicted).toLocaleDateString('pl-PL');
        return "<div><div style='font-size:16px;font-weight:700;color:" + color + "'>Next: " + predDisplay + daysText + "</div><div style='opacity:0.6;margin-top:6px;font-size:12px'>Last: " + last + " | Reset: " + reset + "</div></div>";
      ]]]