VTM30-SN Firmware: 1.0.4r3 timer

Hello everyone,

I am using VTM30-SN Smart On/Off Switch + Humidity Sensing with Home Assistant v2026.3.3 and device firmware v1.0.4r3. Here’s my question: I’d like to configure some sort of a “maximum run time” i.e., if someone switches on the load manually with the switch, or the switch is switched on from HA and loses connectivity with HA, the switch should turn off automatically after the “maximum run time” duration has elapsed. Is this doable?

I was playing around with “Advanced Timer” setting but that doesn’t seem to do anything. Any thoughts on how to configure an “off delay timer?”

Thank you.

I don’t know how much parity there is between the different lines of switches (White/Blue/Red), but on the Blue switches (and in the documentation of the upcoming Red On/Off switch), Parameter 12 is the Auto-Off Timer.

Assuming this parameter exists for the White switches, I’m not sure of the best way of going about setting it though. I don’t know if this is available and you are just looking over it? or if it doesn’t even show up for the White switches. If it exists, and there isn’t a way to set it through HA, you might follow the Local Configuration instructions and try to set parameter 12, keeping in mind that you may have to factory reset the switch if this doesn’t work the way you hope.

Parameter 12 is set in increments of seconds, so in line with the instructions linked above, if you wanted to set a 5 minute auto-off timer, you would have to go to that parameter and press up on the paddle 300 times (to set 300 seconds) before saving the value.

I would hope there was an easier way to set this, but this is assuming that this parameter is implemented in the firmware.

Ted,

Since you are using HA is there any reason not to use an automation? I use that for my bathroom fans and it works just fine.

Thank you everyone. As for automations, while it doesn’t happen frequently, but in those rare cases where HA loses connectivity to the switch, I want the switch to turn itself off regardless of it being connected to HA. Of course, an automation will be useless in a such a situation. Unfortunately, this issue occurred today and am looking for a solution.

Because I like the countdown bar :face_savoring_food: That, and the functionality is there, might as well use it.

@EricM_Inovelli @Kaleb_Inovelli - are there plans to add an Auto Off Timer capability to VTM30-SN? If not, this thread sounds like a feature request. :slight_smile:

Is there something about Matter or Thread that makes it infeasible to add this capability? If not, then this is well aligned with the 2026 roadmap because it improves feature parity (e.g., Red dimmers have Auto Off Timer and countdown bar) and pushes forward on Matter.

My scenario for white on/off switches is bathroom fans. The advanced timer mode is interesting and the countdown bar is awesome, but there’s no way my family will remember to 2x or 3x tap. :sweat_smile: I’d like to replace the Zooz switches that control these fans and Auto Off Timer would unblock me.

Thanks!

Its already there but …

All Matter lighting devices (and these switches count as lighting type devices), support the Matter “OnWithTimedOff” command which sends both a “on” to the switch as well as a duration. After the duration expires, the switch turns off.

I wrote some drivers for Hubitat that makes use of this command, so I know it is in the Inovelli products. However, I don’t think there is a way to send this command from Home Assistant . Consider making this a Home Assistant feature request.

As a practical solution, what I’ve used a combination of 3 automations to get “close” to this function:

  1. A first automation that turns any of a list of entities off after 20 minutes.
  2. A second automation for those same entities that looks from a transition from the “unknown” state, and then turns the device off. This takes care of the case where a device was disconnected for some reason and then reconnects.
  3. A third automation that runs on a re-start of Home Assistant that turns the entities off on restart.

The obvious issue here is this 3 automation solution doesn’t work if the device remains disconnected, but for my purposes, that rare event isn’t a problem for my use cases.

As @jvm33 this is part of the matter spec and hopefully it can be exposed in HA eventually. As for advanced timer mode I am kind of in the same boat. I use it but the family doesn’t. I have considered altering it or adding another option to have 1x click activate it. That way it will run by default when you single tap it but you can increase the timer by multi taps. Something I have been considering for some time.

I managed to get a usable “OnWithTimedOff” script for Home assistant, and while it triggers hardware based timing, it does not trigger the countdown animation :stuck_out_tongue:

# /config/matter_timer.py
import sys
import json
import asyncio
import aiohttp

async def send_command():
    # Safely pull the variables sent by your Home Assistant automation
    node_id = int(sys.argv[1])
    duration_sec = int(sys.argv[2])
    
    # Matter Spec mandates time in tenths of a second (e.g., 60s = 600)
    on_time = int(duration_sec * 10)

    # The formal, type-compliant JSON-RPC structure for python-matter-server
    payload = {
        "message_id": "inovelli_timed_off",
        "command": "device_command",
        "args": {
            "node_id": node_id,
            "endpoint_id": 1,        # Endpoint 1 controls the Inovelli White switch relay
            "cluster_id": 6,         # On/Off Cluster (0x0006)
            "command_name": "OnWithTimedOff", # Must use string name for SDK type-matching
            "payload": {
                "onOffControl": 0,    # Notice the camelCase matching the Matter SDK spec
                "onTime": on_time,    # Notice the camelCase matching the Matter SDK spec
                "offWaitTime": 0      # Notice the camelCase matching the Matter SDK spec
            }
        }
    }

    # Establish an official, persistent WebSocket handshake 
    async with aiohttp.ClientSession() as session:
        # Route correctly between containers across the internal network bridge
        async with session.ws_connect("http://core-matter-server:5580/ws") as ws:
            # Step 1: Drain the server's initial greeting message out of the network buffer
            greeting = await ws.receive_json()
            
            # Step 2: Send your validated hardware instruction
            await ws.send_str(json.dumps(payload))
            
            # Step 3: Wait for the actual execution response back from the switch
            response = await ws.receive_json()
            
            # Check the server response for success
            if "error" in response:
                print(f"Error from Matter Server: {response['error']}", file=sys.stderr)
                sys.exit(1)
            else:
                print(f"Success! Hardware timer sent. Switch Response: {response}")

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python3 matter_timer.py <node_id> <seconds>", file=sys.stderr)
        sys.exit(1)
    asyncio.run(send_command())

configuration.yaml

shell_command:
  inovelli_hardware_timer: "python3 /config/matter_timer.py {{ node }} {{ seconds }}"

Developer Tools > Actions

action: shell_command.inovelli_hardware_timer
data:
  node: 34  #Enter your matter Node number
  seconds: 60

Useful if it must absolutely turn off, I suppose.

@EricM_Inovelli , glad I’m not the only one who can’t train my family. :laughing:

Two questions:

  1. Is it feasible to update the behavior when OnWithTimedOff is sent so that the LED shows the countdown animation? I’m sure there are complexities I’m unaware of.
  2. Is there a way to create my own countdown animation with VTM30-SN that I could trigger when the switch is turned on? I didn’t see any LED effects that started a countdown animation.

To be clear, altering Advanced Timer Mode 1x behavior or adding a new option would both work for me, but I’m guessing that you also need to weigh those against the impact of a breaking change to Advanced Timer Mode or the complexity of maintaining another configuration option (especially when you have a broad product portfolio to maintain).

@jvm33 , thanks for the context. Didn’t realize it was in the Matter spec and that HA didn’t expose it.

@Ted Thank you! Your script makes it easier to ensure the fan gets turned off reliably. Bummer about the countdown animation not showing - that is a great UX!

Not exactly what you are looking for, but something you could do with current firmware…

You could make use of the LED effect animation to indicate remaining time. For example, using Slow blink if remaining time > X, Middle blink if > Y, Fast blink if > Z or something like that.

Thank you for your script Ted. I went ahead and ported it to pyscript, fixed support for matter.js and extended it to work with other devices. I’ve validated it works with eve power devices in addition to my inovelli switches.

# ruff: noqa: TRY002

def duraitonToTenthsOfSeconds(duration=None) -> int:
    if duration is None:
        return 0

    TenthsOfSeconds = 0
    TenthsOfSeconds += int(duration.get('hours', 0))
    TenthsOfSeconds *= 60
    TenthsOfSeconds += int(duration.get('minutes', 0))
    TenthsOfSeconds *= 60
    TenthsOfSeconds += int(duration.get('seconds', 0))
    TenthsOfSeconds *= 10
    return TenthsOfSeconds


@service("pyscript.matter_OnWithTimedOff", supports_response="optional")  # noqa: F821
async def OnWithTimedOff(entity_id, OnTime=None, OffWaitTime=None, AcceptOnlyWhenOn=False) -> dict:
    """yaml
name: Matter On With Timed Off
description: |
  Turns the device on, then turns it off after On time. If you send the
  command again while it is still on, the on timer is extended to whichever
  is longer: the time already left, or the new On time.

  Off wait is a lockout after it turns off. During that lockout, another
  On with timed off command will not turn it back on. A normal turn-on
  still works. Sending this command during the lockout only shortens the
  remaining wait if the new Off wait is shorter.

fields:
  entity_id:
    name: Entity
    description: Matter light or switch to send the command to.
    required: true
    selector:
      entity:
        filter:
          integration: matter
          domain:
            - switch
            - light

  OnTime:
    name: On time
    description: |
      How long the device stays on before it turns itself off.
      Maximum is 1 hour, 49 minutes, and 13 seconds.
      If it is already on, the longer of the remaining time and this value is used.
    required: true
    selector:
      duration:

  OffWaitTime:
    name: Off wait
    description: |
      After the device turns off, how long to ignore further On with timed off
      commands so it cannot be turned back on that way. Maximum is 1 hour,
      49 minutes, and 13 seconds. Leave empty for no lockout.
    required: false
    selector:
      duration:

  AcceptOnlyWhenOn:
    name: Only if already on
    description: |
      If enabled, the command is ignored when the device is already off.
      Use this to extend a timer on a device that is on, without turning
      off devices back on. If disabled, the command always turns the device on.
    required: true
    selector:
      boolean:

"""

    global hass  # noqa: PLW0602
    try:
        _ = hass
    except NameError:
        raise Exception("Enable 'Hass is global' in the Pyscript integration options")

    try:
        import asyncio
        import json
        import uuid

        import aiohttp
        from homeassistant.components.matter.helpers import get_node_from_device_entry
        from homeassistant.helpers import device_registry, entity_registry
    except ImportError:
        raise Exception("Enable 'Allow all imports' in the Pyscript integration options")

    # Matter Spec mandates time in tenths of a second (e.g., 60s = 600)
    OnTime = duraitonToTenthsOfSeconds(OnTime)
    if OnTime > 0xFFFE:
        raise Exception("OnTime is longer than 1 hour, 49 minutes, and 13 seconds")

    OffWaitTime = duraitonToTenthsOfSeconds(OffWaitTime)
    if OffWaitTime > 0xFFFE:
        raise Exception("OffWaitTime is longer than 1 hour, 49 minutes, and 13 seconds")

    OnOffControlBitmap = 0
    if isinstance(AcceptOnlyWhenOn, str):
        AcceptOnlyWhenOn = AcceptOnlyWhenOn.strip().lower() in {"1", "true", "yes", "on"}
    if AcceptOnlyWhenOn:
        OnOffControlBitmap |= 1

    entity = entity_registry.async_get(hass).async_get(entity_id)
    if entity is None:
        raise Exception(f"Failed to look up entity for {entity_id}")

    device = device_registry.async_get(hass).async_get(entity.device_id)
    if device is None:
        raise Exception(f"Failed to look up device for {entity.device_id}")

    node = get_node_from_device_entry(hass, device)
    if node is None:
        raise Exception(f"Failed to look up node for {device}")

    try:
        endpoint_id = int(entity.unique_id.rsplit("-", 4)[1])
    except (AttributeError, IndexError, ValueError, TypeError):
        raise Exception(f"Failed to parse Matter endpoint from unique_id for {entity_id}")

    endpoint = node.endpoints.get(endpoint_id)
    if endpoint is None:
        raise Exception("Unable to find the Matter endpoint for this entity.")

    cluster = endpoint.get_cluster(6)
    if cluster is None:
        raise Exception("Unable to find the OnOff cluster for this entity. Your device may not support OnWithTimedOff.")

    accepted = getattr(cluster, "acceptedCommandList", None)
    if accepted and 0x42 not in accepted:
        raise Exception("This entity's OnOff cluster does not support OnWithTimedOff.")

    payload = {
        "message_id": uuid.uuid4().hex,
        "command": "device_command",
        "args": {
            "node_id": node.node_id,
            "endpoint_id": endpoint_id,
            "cluster_id": 6,
            "command_name": "OnWithTimedOff",
            "payload": {
                "onTime": OnTime,
                "offWaitTime": OffWaitTime,
                "onOffControl": OnOffControlBitmap,
            }
        }
    }

    async with aiohttp.ClientSession() as session: # noqa: SIM117
        async with session.ws_connect("ws://core-matter-server:5580/ws") as ws:
            try:
                async with asyncio.timeout(15):
                    log.info(f"Sending Payload: {payload}")  # noqa: F821
                    await ws.send_str(json.dumps(payload))
                    while True:
                        response = await ws.receive_json()
                        if response.get("message_id") == payload["message_id"]:
                            break
            except TimeoutError:
                raise Exception("Timed out waiting for a Matter server response") from None

            if "error_code" in response or "error" in response:
                error_text = response.get("details") or response.get("error") or response
                log.error(f"Error from Matter Server: {error_text}")  # noqa: F821
                raise Exception(error_text)

            log.info(f"Success! Hardware timer sent. Response: {response}")  # noqa: F821
            return response

    return {}

Cool… I’ll have to swap over to it. I really never really intended anything beyond proof of concept, having used it on a few fan switches. I can verify OnWithTimedOff works with all my on/off matter devices, though I believe Irrigation devices have their own separate command.

Thanks. This is really useful.

Have you considered modifying it so a user can select multiple entities all of which would then be turned on with the same timing parameter? It would be a nice addition if that were possible.

In any case, I appreciate you posting this.

A buddy of mine, who is way better at development work then I am, published a HACS compatible integration for OnWithTimedOff. It filters devices by support and adds sensors for the timers.

She also published a HACS integration for saving Matter Setup Codes as well,

I’ve given them a try and they’re pretty cool, hopefully helpful for you as well.