Monitor your HDD SMART STATUS

First of all, thanks to all the folks that contributed to this topic. I came here looking to monitoring my JOBD’s S.M.A.R.T. data and you people inspired me.

The first issue I wanted to solve was the exit code. @kiwijunglist suggested using this smartctl command:

/usr/sbin/smartctl --info --all --json --nocheck standby /dev/$arg

I have a sick disk in my media server and noticed this was always returning 0. After a bit of investigation I found this command lacked a a single character (“x”), as evidenced by the following on my media server:

root@media:/tmp# smartctl --all /dev/sdf >/dev/null ; echo $?
0
root@media:/tmp# smartctl --xall /dev/sdf >/dev/null ; echo $?
68

The -x/--xall flag enables the extended exit status and allows us to decode the non-zero exit bits smartctl(8).

Using the bash script example from the smartctl(8) man page, I updated the awake script with the following payload data:

NOTE: My naming scheme is:

  • JSON smartctl_$HOSTNAME_$DEV_json
  • scripts and sensors smartctl_$HOSTNAME_$DEV
  • smartctl_media_sda_json in this example.
{# Extended Status Bits #}
{% for index in range(0,7) -%}
  {% if states('sensor.smartctl_media_sda_json') | int | bitwise_and(2 ** index) > 0 -%}
    {% set smartctl_exit =  {
        0:"command line did not parse",
        1:"open failed or low-power mode",
        2:"checksum error",
        3:"DISK FAILING",
        4:"PRE-FAIL",
        5:"DISK OK but PRE-FAIL",
        6:"Logged ERRORs",
        7:"Self-Test ERRORs"
    } -%}
    "exit_bit_{{ index }}": "{{ smartctl_exit[index] }}",
  {% else -%}
    "exit_bit_{{ index }}": 0,
  {% endif -%}
{% endfor -%}
{# END Extended Status Bits #}
"smartctl_man_page": "https://www.smartmontools.org/browser/trunk/smartmontools/smartctl.8.in"

I’m tying to dig myself out of this rabbit hole, but every time I solve one problem I create another one :slight_smile:. I’m still tweaking this config, but thought I’d share my current HA S.M.A.R.T. solution.

The following is a complete config for the /dev/sdf disk on my media host. I have several disks in this server, so I just edit the sda.yaml file in /config/packages/smartctl/media and run a for loop to copy them into place for the other disks and then a quick sed -i -e "s/sda/$DEV/g" ${DEV}.yaml. I included the script I’m using to ssh into the remote host and to grab the smartcl JSON data, and the crontab entry I have set on my HA server.

You’ll have to excuse the mess, this is still a Work In Progress. I’m trying to make some sense of the logs (among other things).

homeassistant:
  customize:
    sensor.smartctl_media_sdf:
      friendly_name: Media /dev/sdf
      icon: mdi:harddisk

sensor:
  - platform: mqtt
    name: smartctl_media_sdf
    state_topic: 'smartctl/media/sdf/state'
    json_attributes_topic: 'smartctl/media/sdf/attributes'

#################################################################################################################################
# crontab entry (user: pi)
# Run once an hour
# * */1 * * * for DEV in sda sdb sdc sdd sde sdf nvme0n1p2; do /home/pi/docker/homeassistant/bin/get-smartctl-json.sh media "${DEV}"; done
#
#################################################################################################################################
#
######################
# get-smartctl-json.sh
######################
# #!/usr/bin/env bash
#
# SSHKEY='~/.ssh/smart'
# RHOST="$1"
# RUSER=smart
# DEV="$2"
# RCOMMAND="sudo /usr/sbin/smartctl --info --xall --json --nocheck standby /dev/$DEV"
# BASEDIR="/home/pi/docker/homeassistant/config/smartctl"
# [ -z "$RHOST" -o -z "$DEV" ] && { echo "$0 hostname device"; exit 1; }
# ssh -i $SSHKEY ${RUSER}@${RHOST} "$RCOMMAND" > "${BASEDIR}/${RHOST}/${DEV}.json"
#
#################################################################################################################################

  - platform: command_line
    name: smartctl_media_sdf_json
    command: "/bin/cat /config/smartctl/media/sdf.json"
    value_template: "{{ value_json.smartctl.exit_status }}"
    json_attributes:
      - smartctl
      - device
      - model_name
      - serial_number
      - user_capacity
      - smart_status
      - ata_smart_attributes
      - temperature
      - firmware_version
      - ata_smart_self_test_log
      - ata_smart_error_log
    scan_interval: 30

automation:
  - alias: "smartctl_media_sdf"
    trigger:
      - platform: state
        entity_id: sensor.smartctl_media_sdf_json
      - platform: homeassistant
        event: start
    action:
      service_template: >-
        {% if states('sensor.smartctl_media_sdf_json') | int | bitwise_and(2 ** 1) > 0 %} {# exit_bit_1 = sleep, see smartctl (8) #}
          script.smartctl_media_sdf_sleep
        {% else %}
          script.smartctl_media_sdf_awake
        {% endif %}

script:
  smartctl_media_sdf_awake:
    sequence:
      - service: mqtt.publish
        data:
          topic: "smartctl/media/sdf/state"
          # figure out how to define all the bitwise settings
          payload: >-
            {% if states('sensor.smartctl_media_sdf_json') | int > 0 %}
              Sick
            {% else -%}
              Healthy
            {% endif -%}
          retain: true
      - service: mqtt.publish
        data_template:
          topic: "smartctl/media/sdf/attributes"
          # IF YOU HAVE PROBLEMS WITH THE SENSOR YOU CAN COPY+PASTE THE PAYLOAD INTO HOME ASSISTANT TEMPLATE EDITOR
          payload: >-
            {
              "last updated": "{{ states('sensor.date_time') }}",
              "model name": "{{ state_attr('sensor.smartctl_media_sdf_json','model_name') | string }}",
              "serial_number": "{{ state_attr('sensor.smartctl_media_sdf_json','serial_number') | string }}",
              "firmware_version": "{{ state_attr('sensor.smartctl_media_sdf_json','firmware_version') | string }}",
              "device": "{{ state_attr('sensor.smartctl_media_sdf_json','device').name | string }}",
              "device_type": "{{ state_attr('sensor.smartctl_media_sdf_json','device').type | string }}",
              "device_protocol": "{{ state_attr('sensor.smartctl_media_sdf_json','device').protocol | string }}",
              "size": "{{ (state_attr('sensor.smartctl_media_sdf_json','user_capacity').bytes / 1000000000000) | round(2)}} TB",
              "temperature": "{{ state_attr('sensor.smartctl_media_sdf_json','temperature').current | int }}",
              "smart_status":
               {% if states.sensor.smartctl_media_sdf_json.attributes.smart_status.passed  -%}
                 "Healthy",
               {% else -%}
                 "Sick",
               {% endif -%}

              {# ATA SMART Error Log #}
              {% if 'table' in state_attr('sensor.smartctl_media_sdf_json','ata_smart_error_log').extended.keys() -%}
                {% for attr in state_attr('sensor.smartctl_media_sdf_json','ata_smart_error_log').extended.table -%}
                  "error_log_number_{{ attr.error_number }}":"{{ attr.error_description }}",
                {% endfor -%}
              {% else -%}
                "ata_smart_error_log":"Not Found",
              {% endif -%}
              {# END ATA SMART Error Log #}

              {# ATA Self Test Log #}
              {%- for attr in state_attr('sensor.smartctl_media_sdf_json','ata_smart_self_test_log') -%}
                {%- if attr == "standard" -%}
                  {%- if 'table' in state_attr('sensor.smartctl_media_sdf_json','ata_smart_self_test_log').standard.keys() -%}
                    {%- for log in state_attr('sensor.smartctl_media_sdf_json','ata_smart_self_test_log').standard.table -%}
                      "self_test_{{ loop.index }}": "{{ log.type.string }}, {{ log.status.string }} @ {{ log.lifetime_hours }} hrs",
                    {%- endfor -%}
                  {%- endif -%}
                {%- endif -%}
                {%- if attr == "extended" -%}
                  {%- if 'table' in state_attr('sensor.smartctl_media_sdf_json','ata_smart_self_test_log').extended.keys() -%}
                    {%- for log in state_attr('sensor.smartctl_media_sdf_json','ata_smart_self_test_log').extended.table -%}
                    "self_test_{{ loop.index }}": "{{ log.type.string }}, {{ log.status.string }} @ {{ log.lifetime_hours }} hrs",
                    {%- endfor -%}
                  {%- endif -%}
                {%- endif -%}
              {%- endfor -%}
              {# END ATA Self Test Log #}

              {# END ATA SMART Error Log #}
              {# ATA Attributes #}
              {% if state_attr('sensor.smartctl_media_sdf_json','ata_smart_attributes').table -%}
                {% for attr in state_attr('sensor.smartctl_media_sdf_json','ata_smart_attributes').table -%}
                  {%- if attr.id -%}
                    "ID_{{ attr.id }}_{{ attr.name }}":"{{ attr.raw.value | int }}",
                  {% else -%}
                    "DEBUG_{{ attr }}":"You should never see this.",
                  {% endif -%}
                {% endfor -%}
              {% else -%}
                "attributes_table":"Not Found",
              {% endif -%}
              {# END ATA Attributes #}
              "smartctl_id_info": "https://www.backblaze.com/blog/what-smart-stats-indicate-hard-drive-failures/",

              {# Extended Status Bits #}
              {% for index in range(0,7) -%}
                {% if states('sensor.smartctl_media_sdf_json') | int | bitwise_and(2 ** index) > 0 -%}
                  {% set smartctl_exit =  {
                      0:"command line did not parse",
                      1:"open failed or low-power mode",
                      2:"checksum error",
                      3:"DISK FAILING",
                      4:"PRE-FAIL",
                      5:"DISK OK but PRE-FAIL",
                      6:"Logged ERRORs",
                      7:"Self-Test ERRORs"
                  } -%}
                  "exit_bit_{{ index }}": "{{ smartctl_exit[index] }}",
                {% else -%}
                  "exit_bit_{{ index }}": 0,
                {% endif -%}
              {% endfor -%}
              {# END Extended Status Bits #}
              "smartctl_man_page": "https://www.smartmontools.org/browser/trunk/smartmontools/smartctl.8.in"
            }
          retain: true

  smartctl_media_sdf_sleep:
    sequence:
      - service: mqtt.publish
        data:
          topic: "smartctl/media/sdf/state"
          payload: "Sleep"
          retain: true

And what post is complete with a couple of obligatory screenshots?

  • HACS card custom:auto-entities

2 Likes

Very nice and detailed topic but it requires a lot of reading for a not-so skilled linux user. Before I start reading and implementing this solution can anyone tell me if all of this will be included in a HAOS backup? If not it doesn’t me much sense to use for me.

I was hoping it might inspire someone who knows how to program to turn it into a component.

I wrote it but I have no programming skills I just know some yaml.

Hi. Very interesting what you’ve done. I succeeded in copying most, if not everything. But, I have a hard time figuring out how you made the ‘details-card’. I figure you have a tap_action to open another card when clicking an entity - but I’m unsure how you do it. Could you help me?

@smathev I can try… The “details-card” is just a screenshot of clicking on one of the entries and viewing the “attributes”.

@SpencerButler - Yeah, I see that now :slight_smile: Thanks for getting back to me, and thanks for the helpful scripting you’ve done! :slight_smile:

1 Like

@SwerveShot It certainly should be included in your backup, unless you have some strange custom setup.

That being said, if you wanted to use something with a cron job, the crontab is most likely not going to be in your backup. The location of the crontab (to backup) would depend on where you decide to put it. For example, in my docker container, the homeassistant user doesn’t even have a working cron (the root user does though).

ha:~$ id
uid=1000(homeassistant) gid=1000(homeassistant) groups=1000(homeassistant),1000(homeassistant)
ha:~$ crontab -l
crontab: must be suid to work properly
ha:~$ hass --version
2022.2.8

So I would put the crontab entry on the host server. That file to backup would be in /var/spool/cron/crontabs/$USER on most Linux OS’s.

1 Like

Hello everyone,
thanks to all who provided this really awesome implementation to monitor the SMART status - works like a charm.

However, I am trying to push this even a bit further. I would like to monitor certain SMART attributes (e.g. Raw_Read_Error_Rate) in more detail. The goal is:

  • to have an attribute displayed as a graph (history graph card)
  • if the value changes, send a notification

The idea I have is to put the attribute value into a sensor and proceed from there. The thing is: the SMART-JSON file provides the “ata_smart_attributes” as a table. I am struggling to extract the Raw_Read_Error_Rate (in my case: id=1, raw.value) and transfer it into (value_template) of said sensor:

- platform: template
    sensors: 
      raw_error_rate:
        value_template: ???  

Does anyone have an idea how to parse through the table and extract a attribute into a sensor? Or is there even a better way to reach the goal?

After some playing with all information provided in this thread I finally managed it.

Step1: Create mqtt sensor for a specific SMART value you are interesetd in:

sensor:
# SMART ID = 1
  - platform: mqtt
    name: hdd_sdc_raw_read_error_rate
    state_topic: 'smartctl/sdc/raw_read_error_rate'
    unit_of_measurement: ""

Step 2: When the HDD is awake and smartctl has created an updated JSON, let the script (provided by user @kiwijunglist) update the new sensor as well. In this example SMART ID = 1 is used (Raw Read Error Rate)

script:
  smartctl_sdc_awake:
    sequence:
# put the given script sequence from above here - removed for better overview
# and our own for the new sensor:
    - service: mqtt.publish
      data:
        topic: "smartctl/sdc/raw_read_error_rate"
        payload: "{% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sdc_json','ata_smart_attributes').table %}{%- if i.id == 1 %}{% set ns.found = true %}{{ i.raw.value }}{% else %}{% endif -%}{% endfor %}{% if not ns.found %}not available{% endif %}"
        retain: true

Now every time the script is executed (when the HDD is awake) the attributes and our new sensor will be updated.

Step 3: Create a card which shows the SMART values which you are interested in to have a immediate and detailed overview of your HDD:

Hope this helps someone here as well.

1 Like

first of all, thanks for the good idea/start, but I don’t have a MQTT Borker running (and I didn’t want to set one up just for this). So I modified it to use a template sensor:

1. crontab job

* * * * * for arg in sd{a..d}; do /usr/sbin/smartctl --info --all --json --nocheck standby /dev/$arg > $dir_for_json_files/$arg.json; done

2. configuration.yaml

...
homeassistant:
  packages: !include_dir_named packages
  
  whitelist_external_dirs:
    - /config/smartctl
...

3. packages/sd*.yaml

homeassistant:
  customize:
    sensor.smartctl_sd*:
      friendly_name: RAID5-sd*
      icon: mdi:harddisk

sensor:
  - platform: command_line
    name: smartctl_sd*_json
    command: "/bin/cat /config/smartctl/sd*.json"
    value_template: "{{ value_json.smartctl.exit_status }}"
    json_attributes:
      - smartctl
      - device
      - user_capacity
      - smart_status
      - power_on_time
      - power_cycle_count
      - temperature
      - ata_smart_attributes
    scan_interval: 60

template:
  - sensor:
    - name: smartctl_sd*
      unit_of_measurement: "°C"
      state: "{{ state_attr('sensor.smartctl_sd*_json','temperature').current | int }}"
      attributes:
        last updated: "{{ states('sensor.date_time') }}"
        device: "{{ state_attr('sensor.smartctl_sd*_json','device').name | string }}"
        device_type: "{{ state_attr('sensor.smartctl_sd*_json','device').type | string }}"
        device_protocol: "{{ state_attr('sensor.smartctl_sd*_json','device').protocol | string }}"
        size: "{{ (state_attr('sensor.smartctl_sd*_json','user_capacity').bytes / 1000000000000) | round(2)}} TB"
        smart_status: >
           {% if states.sensor.smartctl_sd*_json.attributes.smart_status.passed  -%}
             "Healthy",
           {% else -%}
             "Sick",
           {% endif -%}
        power on time (hrs): "{{ state_attr('sensor.smartctl_sd*_json','power_on_time').hours | int }}"
        power cycle count: "{{ state_attr('sensor.smartctl_sd*_json','power_cycle_count') | int }}"
        start stop count: >
          {% set ns = namespace(found=false) %}
          {% for i in state_attr('sensor.smartctl_sd*_json','ata_smart_attributes').table %}
            {%- if i.id == 4 %}
              {% set ns.found = true %}
              "{{ i.raw.value }}"
            {% else %}
            {% endif -%}
          {% endfor %}
          {% if not ns.found %}
            "not available"
          {% endif %}
        smartctl_id_info: "https://www.backblaze.com/blog/what-smart-stats-indicate-hard-drive-failures/"
        smartctl_man_page: "https://www.smartmontools.org/browser/trunk/smartmontools/smartctl.8.in"

NOTE
you can add as many attributes as you want but those where the attributes I wanted.
Also change the * to the devise letter you want.

1 Like

Where I add corntab job in home assistant os?

if you can login with ssh (or directly with screen, mouse and keyboard) you can use

sudo crontab -e 

and add it to the files that opens for editing
also remember to specify how often you want to run the crontab (I use https://crontab.guru to get the right format)

A little bit confused. Why do we need to whitelist a dir if it’s inside the config directory of HA?
/home/mike/.docker/config/homeassistant/smartctl/

In my case it’s

xxx/Docker/Hass/Config/ and this is where the configuration file is located. But seems not working

@CedericN Mind sharing the info?

@moskovskiy82 sorry for the late reply. but I’m not exactly sure why the whitelist_external_dirs is needed it is where the json files get put in by the contab job (in my case /config/smartctl) and to be able to get the template sensor to read those files I needed to add it to whitelist_external_dirs.
also the command in the sensor need to point to the json file.
I had some problems with directly putting the JSON files in the config directory that’s why I added a sub directory
Hope this helps

I like your example but this will also be my first packages so I encounter some challenges. One of them I hope you already tackled because it recently changed in newer versions of Home Assistant:

I will try to solved “Command Line YAML configuration has moved” myself but if you have already an updated version I would welcome it. Apart from T&E (Trial & Error) if prefer off course C&P (cut & paste) :smiley:

Update:
Reverted back to the original MQTT example which in principle worked out of the box but had some annoying 'syntax ‘errors’ all caused by my use of a newer version of HA, to name a few:

  • “whitelist_external_dirs:” changed to “allowlist_external_dirs:” (however I am not sure we need this for this example because it is a subfolder of config and that is already part of HA, in other words it just works without it).
  • Not really required (yet) swapping places of mqtt and sensor like this:
mqtt:
  sensor:
#sensor:
#  - platform: mqtt
  • “service_template” (inside automation) becomes error-less when I changed it in just “service” (this might be related to issue 1886 and error should be ignored a documentation search does not mention service_template anymore however in HA_version 2023.12.3 it is accepted again but expect only until …)

All thanks for the examples it was very useful.

Hi! I want to monitor disk idle/sleep on my Proxmox. HA is running in VM. Any idea how to do so? I am using HAOS so I believe if I mount any drive which would have mentioned json to HAOS VM, next update would overvrite fstab. So perhaps any solution using webhook from proxmox or send the values from proxmox via MQTT? I am quite noob :slight_smile:

I am running on Proxmox and HA is in VM I would like to send status (whether disk sleeping or on) but most secure way which I think is wehbook. Anybody tried that approach?

Hi there!

Thanks to everyone for the provided info.
I also made an attempt getting everything working on my own environment, but I took the route of working with systemd timers & made the automations and scripts from within the UI.
Managed to get rid of all the error messages like “Command Line YAML configuration has moved” or " Configuring Command Line sensor using YAML has moved".

Note, i’m using Home Assistant Docker 2024.1.2 on a bare metal Debian host.

Here’s is my config:

Use the provided script in this topic and create te following systemd service

[Unit]

Description=Smartctl service for polling hdd states

[Service]

Type=Simple

ExecStart=/opt/smartctl.sh

Create a timer

[Unit]

Description=Smartctl timer for polling hdd states

[Timer]

Unit=smartctl.service

OnCalendar=*-*-* *:*:00

Persistent=true

[Install]

WantedBy=timers.target

Reload systemd manager

systemctl daemon-reload

Start your service, enable and start the timer

systemctl start smartctl.service
systemctl enable smartctl.timer
systemctl start smartctl.timer

You can check if the timer is working with the following command:

systemctl status smartctl.timer

Create the scripts:

alias: smartctl_sda_awake
sequence:
  - service: mqtt.publish
    data:
      topic: smartctl/sda/state
      payload: Awake
      retain: true
  - service: mqtt.publish
    data_template:
      topic: smartctl/sda/attributes
      payload: |-
        {
          "model name": "{{ state_attr('sensor.smartctl_sda_json','model_name') | string }}",
          "device": "{{ state_attr('sensor.smartctl_sda_json','device').name | string }}",
          "size": "{{ (state_attr('sensor.smartctl_sda_json','user_capacity').bytes / 1000000000000) | round(2)}} TB",
          "temperature": "{{ state_attr('sensor.smartctl_sda_json','temperature').current }}",
          "smart status": "{% if states.sensor.smartctl_sda_json.attributes.smart_status.passed  %} Healthy {% else %} Failed {% endif %}",
          "power on time (hrs)": {% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 9 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          "power cycle count": {% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 12 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          "start stop count":{% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 4 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          "SMART5": {% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 5 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          "SMART187": {% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 187 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          "SMART188": {% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 188 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          "SMART197": {% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 197 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          "SMART198": {% set ns = namespace(found=false) %}{% for i in state_attr('sensor.smartctl_sda_json','ata_smart_attributes').table %}{%- if i.id == 198 %}{% set ns.found = true %}"{{ i.raw.value }}"{% else %}{% endif -%}{% endfor %}{% if not ns.found %}"not available"{% endif %},
          {%- for i in state_attr('sensor.smartctl_sda_json','ata_smart_self_test_log') -%}
            {%- if i == "standard" -%}
              {%- for x in state_attr('sensor.smartctl_sda_json','ata_smart_self_test_log').standard.table %}
          "TEST {{ loop.index }}": "{{ x.type.string }}, {{ x.status.string }} @ {{x.lifetime_hours }} hrs",
              {%- endfor -%}
            {%- endif -%}
            {%- if i == "extended" -%}
              {%- for x in state_attr('sensor.smartctl_sda_json','ata_smart_self_test_log').extended.table %}
              "TEST {{ loop.index }}": "{{ x.type.string }}, {{ x.status.string }} @ {{x.lifetime_hours }} hrs",
              {%- endfor -%}
            {%- endif -%}
          {%- endfor %}
          "SMART Key": "5=Reallocated_Sector_Ct 187=Reported_Uncorrect 188=Command_Timeout 197=Current_Pending_Sector 198=Offline_Uncorrectable",
          "SMART Ref": "https://www.backblaze.com/blog/what-smart-stats-indicate-hard-drive-failures/"
        }          
      retain: true
alias: smartctl_sda_sleep
sequence:
  - service: mqtt.publish
    data:
      topic: smartctl/sda/state
      payload: Sleep
      retain: true

Create the automation

alias: smartctl_sda
description: SmartCTL SDA
trigger:
  - platform: state
    entity_id: sensor.smartctl_sda_json
  - platform: homeassistant
    event: start
action:
  - if:
      - condition: state
        entity_id: sensor.smartctl_sda_json
        state: "0"
    then:
      - service: script.smartctl_sda_awake
        data: {}
    else:
      - service: script.smartctl_sda_sleep
        data: {}

configuration.yaml

homeassistant:
  packages: !include_dir_named packages
  allowlist_external_dirs:  
    - /config/smartctl  
  customize:
    sensor.hdd_sda:
      friendly_name: SDA # <-- GIVE YOUR HDD A FRIENDLY NAME IF YOU WANT
      icon: mdi:harddisk

mqtt:
  sensor:
    # Server disks MQTT sensors
    - name: hdd_sda
      state_topic: 'smartctl/sda/state'
      json_attributes_topic: 'smartctl/sda/attributes'

command_line:
  # Smartctl JSON scanners
  - sensor:
      name: smartctl_sda_json
      command: "/bin/cat /config/smartctl/sda.json" # <--- THIS READS THE .JSON TXT FILE
      value_template: "{{ value_json.smartctl.exit_status }}"
      json_attributes:
        - smartctl
        - device
        - model_name
        - user_capacity
        - smart_status
        - ata_smart_attributes
        - temperature
        - ata_smart_self_test_log
      scan_interval: 30

A note of caution (power consumption) using smartctl too often.
I used to run smartctl every 40 minutes to check the status of 2 WD usb drives. This works fine, and smartctl was doing what I thought it was doing, which was NOT to spin up the drive if it was asleep.

This is why I choose a 40 minute cycle, because the drives sleep after 30 minutes of inactivity.

However, I’ve recently plugged in a power monitoring plug on the USB drives, and noticed that whilst smartctl reported a SLEEP state and terminated, the drives have spun up (despite using the -n standby option), because the power monitor usage jumps every 40 minutes for 30 minutes, which is exactly the pattern from above.

A bit of reading suggests this could be do to with WD USB enclosures, so your mileage might vary, but definitely worth checking.

By changing to a twice a day check now, the power usage on those drives has dropped 90% when averaged over the day.