SmartWings Shades (Matter Over Thread) Stuck on 'Opening' and 'Closing'

I have 7 SmartWings roller shades connected via Thread to my Aotec v3 Hub. Commands sent via SmartThings app execute fine, but the devices report ‘closing’ or ‘opening’ indefinitely, even after reaching 0%/100%.

If anyone has any advice on how I can fix this, I’d greatly appreciate it.

Other details: The lack of a confirmed ‘closed’ or ‘open’ also prevents the toggles from working in the device overview page in the SmartThings app. However, when going into details and setting a %, or sending open or close - the command works. When the shades are commanded open or closed with the SmartWings remote control, they report their ‘open’ and ‘closed’ state properly in SmartThings app. When SmartThings sends the command, they never report open or closed. The issue is 100% repeatable across 7 shades. It seems very similar to this SmartThings driver bug.

Hi @clintonkj

Thank you for the details.

Could you please collect and share driver logs while performing the following tests on the same shade?

  1. Open and close the shade using the SmartThings app.
  2. Open and close the shade using the SmartWings remote control.

Please capture the logs during both tests so we can compare how the device behaves when commands are initiated from the remote control versus the SmartThings app.

Logs of the driver:

  1. Open the ST CLI
  2. Enter the command smartthings edge:drivers:logcat
  3. Enter the information requested by CLI
  4. Now you must be able to see the logs once you see the message “connected”
  5. Then, send the commands (that aren’t working) to the devices so they get registered there
  6. Copy those logs into a file and share it with us. You can send it to build@smartthings.com.

Note: If you get an error related to the connection, verify that the hub and the computer are in the same network.

Thank you.

I have a couple of the SmartWings roller shades using Thread. Both use the Matter Window Covering driver. They both work properly by showing - open or closed in the iOS app. :slight_smile:

Thanks for the response, @Itati . I’ve sent the logs for the App and Remote commanded events to the address you provided. Two additional data points:

  1. As you can probably tell by the screenshot, I’m running the app on Android, which may explain the difference in behavior from what @jkp mentioned below.

  2. After the shade completes a command initiated in the app, performing a manual refresh (navigating to device details screen, swiping down) updates the app GUI to the correct state. Without the manual refresh, it will stay in the ‘closing’ or ‘opening’ state for days.

The problem seems to be that after a SmartThings command, the driver receives or emits the transient state opening / closing, but does not reliably resolve the final state afterward.

In the generic driver, OperationalStatus is handled for moving states, but when the value goes back to 0 - meaning the shade is no longer moving - the handler effectively does nothing. It relies on the device also sending a final CurrentPositionLiftPercent100ths report. If the shade does not send that final position report after a SmartThings-originated command, the UI can remain stuck on opening or closing.

Manual refresh fixes the state because it forces a fresh read of the current position, and then the existing position handler correctly emits open, closed, or partially_open.

So the suggested fix would be: when OperationalStatus == 0, the driver should actively read CurrentPositionLiftPercent100ths and possibly CurrentPositionTiltPercent100ths, then let the existing position handler update the final SmartThings state.

Conceptually:

elseif state == 0 then
  -- stopped / not moving
  -- read final position so the UI can leave opening/closing
  device:send(
    clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:read(device, ib.endpoint_id)
  )
end

A delayed safety read after open, close, and GoToLiftPercentage commands would also make the driver more robust for devices that do not send a clean final report.


Edit: here’s the code:

state == 0 isn’t handled.

Edit: suggested fix:

local function current_status_handler(driver, device, ib, response)
  local windowShade = capabilities.windowShade.windowShade
  local reverse = device:get_field(REVERSE_POLARITY)
  local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL

  if state == 1 then -- opening
    device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closing() or windowShade.opening())
  elseif state == 2 then -- closing
    device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.opening() or windowShade.closing())
  elseif state == 0 then -- stopped / not moving
    device:send(
      clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:read(device, ib.endpoint_id)
    )
  else -- unknown
    device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown())
  end
end

The unknown state would also be handled better, in my opinion.

The interesting part is that the special subdriver handles this differently from the main driver.

In the main driver, OperationalStatus = opening/closing is used to emit opening or closing, but when OperationalStatus later becomes 0, the handler does nothing. It simply waits for a separate final position report.

The subdriver, however, keeps a small state machine. When it sees OperationalStatus = 0, it remembers that the movement has stopped. If the final position report arrives before or after that stop event, the subdriver combines both pieces of information and then emits the final open, closed, or partially_open state.

So the subdriver is more defensive about report ordering:

Generic driver:
opening/closing, waits for final position report, may stay stuck

Subdriver:
opening/closing, tracks stopped + position reports, resolves final state

However, even the subdriver still depends on receiving a final position report at some point. A fully robust fix would probably read the current position when OperationalStatus == 0.

@Andreas_Roedl - Thanks for the explanation and code snippet. I’ll educate myself on how to make use of it as a custom driver and give the suggested changes a try.

Hopefully there’s a chance of updating the generic driver so a workaround is no longer necessary.

I mean… you could try the Matter Window Covering (TEST) driver from this channel.

But only if @Itati is okay with it. I don’t want to interfere with anything here.


Edit: this would be a better fix, because there’s not only lift, but also tilt.

Suggested fix
-- checks the current position of the shade
local function current_status_handler(driver, device, ib, response)
  local windowShade = capabilities.windowShade.windowShade
  local reverse = device:get_field(REVERSE_POLARITY)
  local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL

  if state == 1 then -- opening
    device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.closing() or windowShade.opening())
  elseif state == 2 then -- closing
    device:emit_event_for_endpoint(ib.endpoint_id, reverse and windowShade.opening() or windowShade.closing())
  elseif state == 0 then -- stopped / not moving
    -- Some devices report "stopped" but do not reliably send a final
    -- CurrentPositionLift/TiltPercent100ths report after a command.
    -- Read the current position so the existing current_pos_handler can
    -- emit open / closed / partially_open.
    local read_req = im.InteractionRequest(im.InteractionRequest.RequestType.READ, {})
    read_req:merge(
      clusters.WindowCovering.attributes.CurrentPositionLiftPercent100ths:read(
        device,
        ib.endpoint_id
      )
    )
    read_req:merge(
      clusters.WindowCovering.attributes.CurrentPositionTiltPercent100ths:read(
        device,
        ib.endpoint_id
      )
    )
    device:send(read_req)
  else -- unknown
    device:emit_event_for_endpoint(ib.endpoint_id, windowShade.unknown())
  end
end

At the risk of causing trouble again because I’m interfering… From the PR:

I have not had a chance to test this with a real device yet and won’t be in a position to for some time. Would appreciate some help testing.

@clintonkj Install the driver from this channel and test it.

Thanks for your comments, @Andreas_Roedl

This issue has already been reported to the team, and a ticket has been created to track it. We’ll let you know as soon as we have any updates.

I tested on 8 SmartWings roller shades at about 5 runs apiece. The fix you provided works consistently when commanded via SmartThings app, voice commands via Alexa, individually and in groups via automations. Open, closed, and intermediate positions (partially open) are now reported within 2-3 sec of the shade reaching the commanded position.

While steady state is reported properly, intermediate state (opening/closing) is reversed consistently for some of the shades. In 6 of the 8 shades, the interim state is reversed. Ex: after issuing a ‘close’ command, the shade will report ‘opening’ while it is actually closing but will transition properly to ‘close’ after it reaches the closed stop position. There is nothing unique about the 2 shades that report correctly. SmartWings allows forward and reverse rolling, so perhaps the messaging is just carrying over the polarity randomly assigned at the factory. This behavior is identical for commands sent from SmartThings and the SmartWings remote.

The behavior is also identical between the official SmartThings matter driver and the bug fix driver, so this isn’t something that the bug fix introduced. The bug fix seems to be successful for its intended purpose. Thanks to you both, @Andreas_Roedl and @tpmanley for the investigation and the quick fix. @Itati , also appreciate you reporting it to the team.