First LAN Edge driver: new devices stuck in TYPED / DRIVER_SWITCH, and the device page won't open (hub fw 0.62.7)

Hi everyone, I’m pretty new to Edge drivers, so apologies in advance if I’m missing something obvious. I’d really appreciate a nudge in the right direction.

What I’m trying to do
I have a Hisense Canvas TV (Google TV) with an Art Mode, and I wanted SmartThings to control it locally, without IR blasters or virtual-switch workarounds. I wrote a small LAN Edge driver that talks to the TV over the network. It can:

  • turn Art Mode on (also wakes the TV from standby)
  • put the TV to sleep (discrete off)
  • report whether the TV is off, in Art Mode, or being watched

Functionally it works great. Commands run, attribute values update every 15 seconds, and two local Rules use it with a motion sensor:

  • motion during the day → Art Mode
  • no motion for 20 minutes → off

The problem
The device page in the phone app never opens. The tile shows only an on/off toggle, and tapping the tile does nothing. On SmartThings. Add a little smartness to your things. the device opens fine and every command works.

Looking at the device through the API, its provisioningState never reaches PROVISIONED:

  • After creation, it stays TYPED.
  • After a driver-switch test (below), it moved to DRIVER_SWITCH and stayed there.

Why I don’t think it’s only my driver

  • A popular community LAN driver whose device was also created today (vEdge Creator) is stuck in TYPED too, with the same “toggle only, won’t open” symptom.
  • All my older LAN devices on the same hub, from 2015 to 2022, are PROVISIONED and open normally.

Hub log on every driver start

received lifecycle event: added

added callback did not fail

received lifecycle event: init

received lifecycle event: doConfigure

received unhandled lifecycle event: doConfigure

doConfigure callback did not fail, transitioning device to “PROVISIONED”

The state never sticks, and doConfigure is sent again on every restart.

Setup

  • SmartThings hub, firmware 000.062.00007
  • Android app (Pixel), current version
  • Device created with driver:try_create_device{ type = "LAN", ... } from the discovery handler
  • config.yml permissions: lan: {} and discovery: {}
  • Profile with standard capabilities only: switch, refresh, momentary
  • Device ONLINE, events and commands working

What I’ve tried so far

  1. Rebooting the hub

  2. Deleting the device and re-scanning, several times; it always comes back TYPED

  3. Changing the profile category from Television to Switch

  4. Removing a less common capability (mode)

  5. A custom device presentation using a standard toggleSwitch

  6. Clearing the app cache and reassigning the room

  7. Explicitly calling device:try_update_metadata({ provisioning_state = "PROVISIONED" }):

    • in init
    • again only after the first successful connection to the TV

    No change.

  8. Switching the device to a copy of the driver (new packageKey) with edge:drivers:switch, then back. The state went TYPED → DRIVER_SWITCH and stays there.

My questions

  1. Is anyone else seeing new LAN devices stuck in TYPED (or DRIVER_SWITCH) on 0.62.x?
  2. Is there anything a LAN driver must do to reach PROVISIONED that I might be missing?
  3. If this is platform-side, is there a way to get it looked at, or is waiting for a newer hub firmware the best option?

Thanks so much for reading. I’ve learned a ton from this forum just getting this far.

This is a problem with the newest version of the mobile app (1.8.51.30). Some tiles open and some won’t. Install the previous version and see if you problem goes away.

We can’t help you without seeing the code and the logs.

The AI is telling you that it thinks it’s because of the provisioningState, but that’s usually not an issue.

air enough, here’s everything. It’s a LAN driver that talks ADB to a Hisense Canvas (Google TV). The driver itself works fine. Rules run local, commands work from Advanced, state polls every 15s. It’s only the phone app that won’t open the device page.

Device shows `lan.provisioningState` TYPED (now DRIVER_SWITCH after I tested a driver switch). Hub fw 000.062.00007. Already tried: hub reboot, clearing app cache, category Television → Switch, removing `mode`, custom presentation with toggleSwitch instead of standbyPowerSwitch.

[details=“config.yml”]

```yaml

name: ‘Hisense Canvas TV (ADB)’

packageKey: ‘hisense-canvas-adb’

permissions:

lan: {}

discovery: {}

description: ‘Local LAN control of a Hisense Canvas Google TV over ADB: Art Mode, power off, and live Off/Art/Watching state.’

```

[/details]

[details=“profiles/hisense-canvas-v2.yml”]

```yaml

name: hisense-canvas-v2

components:

  • id: main

    label: TV Power

    capabilities:

    • id: switch

      version: 1

    • id: refresh

      version: 1

    categories:

    • name: Switch
  • id: art

    label: Art Mode

    capabilities:

    • id: switch

      version: 1

    categories:

    • name: Switch
  • id: offButton

    label: Power Off

    capabilities:

    • id: momentary

      version: 1

    categories:

    • name: Switch
  • id: wakeButton

    label: Wake (Home)

    capabilities:

    • id: momentary

      version: 1

    categories:

    • name: Switch

metadata:

mnmn: SmartThingsCommunity

vid: 7c77eab6-b745-3f8b-a300-46cd2520e82e

deviceType: Switch

ocfDeviceType: oic.d.switch

preferences:

  • name: ipAddress

    title: TV IP address

    required: true

    preferenceType: string

    definition:

    stringType: text

    default: 192.168.86.73

  • name: pollSeconds

    title: State poll interval (seconds)

    required: true

    preferenceType: integer

    definition:

    minimum: 5

    maximum: 300

    default: 15

```

[/details]

[details=“device-config.json (vid 7c77eab6-b745-3f8b-a300-46cd2520e82e)”]

```json

{

“dashboard”: {

"states": \[

  {

    "component": "main",

    "capability": "switch",

    "version": 1,

    "idx": 0,

    "group": "main",

    "values": \[\],

    "composite": false

  }

\],

"actions": \[

  {

    "component": "main",

    "capability": "switch",

    "version": 1,

    "idx": 0,

    "group": "main",

    "inline": null

  }

\],

"basicPlus": \[\]

},

“detailView”: [

{

  "component": "main",

  "capability": "switch",

  "version": 1,

  "values": \[\],

  "patch": \[

    {

      "op": "replace",

      "path": "/0/displayType",

      "value": "toggleSwitch"

    },

    {

      "op": "add",

      "path": "/0/toggleSwitch",

      "value": {

        "command": {

          "on": "on",

          "off": "off"

        },

        "state": {

          "off": "off",

          "on": "on",

          "value": "switch.value"

        }

      }

    },

    {

      "op": "remove",

      "path": "/0/standbyPowerSwitch"

    }

  \]

},

{

  "component": "main",

  "capability": "refresh",

  "version": 1,

  "values": \[\],

  "patch": \[\]

},

{

  "component": "art",

  "capability": "switch",

  "version": 1,

  "values": \[\],

  "patch": \[

    {

      "op": "replace",

      "path": "/0/displayType",

      "value": "toggleSwitch"

    },

    {

      "op": "add",

      "path": "/0/toggleSwitch",

      "value": {

        "command": {

          "on": "on",

          "off": "off"

        },

        "state": {

          "off": "off",

          "on": "on",

          "value": "switch.value"

        }

      }

    },

    {

      "op": "remove",

      "path": "/0/standbyPowerSwitch"

    }

  \]

},

{

  "component": "offButton",

  "capability": "momentary",

  "version": 1,

  "values": \[\],

  "patch": \[\]

},

{

  "component": "wakeButton",

  "capability": "momentary",

  "version": 1,

  "values": \[\],

  "patch": \[\]

}

],

“automation”: {

"conditions": \[

  {

    "component": "main",

    "capability": "switch",

    "version": 1,

    "values": \[\],

    "patch": \[\],

    "exclusion": \[\]

  },

  {

    "component": "art",

    "capability": "switch",

    "version": 1,

    "values": \[\],

    "patch": \[\],

    "exclusion": \[\]

  }

\],

"actions": \[

  {

    "component": "main",

    "capability": "switch",

    "version": 1,

    "values": \[\],

    "patch": \[\],

    "exclusion": \[\]

  },

  {

    "component": "main",

    "capability": "refresh",

    "version": 1,

    "values": \[\],

    "patch": \[\],

    "exclusion": \[\]

  },

  {

    "component": "art",

    "capability": "switch",

    "version": 1,

    "values": \[\],

    "patch": \[\],

    "exclusion": \[\]

  },

  {

    "component": "offButton",

    "capability": "momentary",

    "version": 1,

    "values": \[\],

    "patch": \[\],

    "exclusion": \[\]

  },

  {

    "component": "wakeButton",

    "capability": "momentary",

    "version": 1,

    "values": \[\],

    "patch": \[\],

    "exclusion": \[\]

  }

\]

},

“type”: “profile”

}

```

[/details]

[details=“src/init.lua (adb.lua / rsa.lua are just the ADB protocol + signing, can post if needed)”]

```lua

local Driver = require “st.driver”

local capabilities = require “st.capabilities”

local log = require “log”

local socket = require “cosock.socket”

local Adb = require “adb”

local hubkey = require “hubkey”

local NETWORK_ID = “hisense-canvas-adb”

local PROFILE = “hisense-canvas-v2”

local ART_APP = “com.vt.arttv.artmode”

local KEY_ART = “708”

local KEY_SLEEP = “KEYCODE_SLEEP”

local KEY_WAKEUP = “KEYCODE_WAKEUP”

local KEY_HOME = “KEYCODE_HOME”

local clients = {}

local busy = {}

local function client_for(device)

local c = clients[device.id]

if not c then

c = Adb.new(socket, hubkey, function(msg) log.info(msg) end)

clients\[device.id\] = c

end

return c

end

-- Serialize ADB use per device: handlers and the poll timer can interleave while a socket call yields.

local function with_adb(device, fn)

local waited = 0

while busy[device.id] do

socket.sleep(0.1)

waited = waited + 0.1

if waited > 60 then return nil, "adb busy" end

end

busy[device.id] = true

local adb = client_for(device)

local ok, result = pcall(function()

if not adb.sock then

  adb:connect(device.preferences.ipAddress, 5555, 60)

end

return fn(adb)

end)

if not ok then

log.warn("adb error: " .. tostring(result))

adb:close()

end

busy[device.id] = nil

if ok then return result end

return nil, result

end

local function parse_state(out)

local wake = out:match(“mWakefulness=(%a+)”)

if not wake then return nil end

if wake ~= “Awake” then return “off” end

local app = out:match(“mResumedActivity: ActivityRecord{%S+ %S+ ([^/%s]+)”)

if app == ART_APP then return “art” end

return “watching”, app

end

local function refresh(driver, device)

local out = with_adb(device, function(adb)

return adb:shell("dumpsys power 2>/dev/null | grep -m1 mWakefulness=; "

  .. "dumpsys activity activities 2>/dev/null | grep -m1 mResumedActivity")

end)

if not out then

device:offline()

return

end

local mode, app = parse_state(out)

if not mode then

log.warn("could not parse TV state: " .. out:sub(1, 200))

return

end

device:online()

if not device:get_field(“provisioned_after_handshake”) then

log.info("handshake ok, requesting PROVISIONED")

device:try_update_metadata({ provisioning_state = "PROVISIONED" })

device:set_field("provisioned_after_handshake", true)

end

if mode ~= device:get_field(“last_mode”) then

log.info(string.format("tv mode -> %s%s", mode, app and (" (" .. app .. ")") or ""))

device:set_field("last_mode", mode)

end

local sw = capabilities.switch.switch

device:emit_event(mode == “off” and sw.off() or sw.on())

if device.profile.components.art then

device:emit_component_event(device.profile.components.art, mode == "art" and sw.on() or sw.off())

end

end

local function send_key(driver, device, key)

local _, err = with_adb(device, function(adb) return adb:shell("input keyevent " .. key) end)

if err then log.error("key " .. key .. " failed: " .. tostring(err)) end

device.thread:call_with_delay(5, function() refresh(driver, device) end)

end

local function handle_on(driver, device, command)

send_key(driver, device, KEY_ART)

end

local function handle_off(driver, device, command)

send_key(driver, device, command.component == “art” and KEY_HOME or KEY_SLEEP)

end

local BUTTON_KEYS = { offButton = KEY_SLEEP, wakeButton = KEY_WAKEUP }

local function handle_push(driver, device, command)

local key = BUTTON_KEYS[command.component]

if key then send_key(driver, device, key) end

end

local function start_polling(driver, device)

if device:get_field(“poll_timer”) then

device.thread:cancel_timer(device:get_field("poll_timer"))

end

local seconds = tonumber(device.preferences.pollSeconds) or 15

device:set_field(“poll_timer”, device.thread:call_on_schedule(seconds, function() refresh(driver, device) end))

end

local function device_init(driver, device)

if not device.profile.components.art then

log.info("migrating device to profile " .. PROFILE)

device:try_update_metadata({ profile = PROFILE })

end

– The default doConfigure transition was not persisting; the app won’t open devices left in TYPED.

device:try_update_metadata({ provisioning_state = “PROVISIONED” })

start_polling(driver, device)

device.thread:call_with_delay(2, function() refresh(driver, device) end)

end

local function info_changed(driver, device, event, args)

if args.old_st_store.preferences.ipAddress ~= device.preferences.ipAddress then

client_for(device):close()

end

start_polling(driver, device)

refresh(driver, device)

end

local function device_removed(driver, device)

if clients[device.id] then clients[device.id]:close() end

clients[device.id], busy[device.id] = nil, nil

end

local function discovery(driver, opts, should_continue)

for _, d in ipairs(driver:get_devices()) do

if d.device_network_id == NETWORK_ID then return end

end

driver:try_create_device({

type = "LAN",

device_network_id = NETWORK_ID,

label = "Living Room Canvas TV",

profile = PROFILE,

manufacturer = "Hisense",

model = "Canvas",

vendor_provided_label = "Hisense Canvas TV",

})

end

local driver = Driver(“hisense-canvas-adb”, {

discovery = discovery,

lifecycle_handlers = {

init = device_init,

infoChanged = info_changed,

removed = device_removed,

},

capability_handlers = {

\[capabilities.switch.ID\] = {

  \[capabilities.switch.commands.on.NAME\] = handle_on,

  \[capabilities.switch.commands.off.NAME\] = handle_off,

},

\[capabilities.refresh.ID\] = {

  \[capabilities.refresh.commands.refresh.NAME\] = refresh,

},

\[capabilities.momentary.ID\] = {

  \[capabilities.momentary.commands.push.NAME\] = handle_push,

},

},

})

driver:run()

```

[/details]

Logcat, normal polling. Nothing shows up in the log when I tap the tile in the app.

```

2026-10-10T15:11:46.096Z INFO Hisense Canvas TV (ADB) <Device: 0485639c-… (Living Room Canvas TV 1)> emitting event: {“attribute_id”:“switch”,“capability_id”:“switch”,“component_id”:“main”,“state”:{“value”:“on”}}

2026-10-10T15:11:46.103Z INFO Hisense Canvas TV (ADB) <Device: 0485639c-… (Living Room Canvas TV 1)> emitting event: {“attribute_id”:“switch”,“capability_id”:“switch”,“component_id”:“art”,“state”:{“value”:“on”}}

2026-10-10T15:11:46.105Z DEBUG Hisense Canvas TV (ADB) Living Room Canvas TV 1 device thread event handled

2026-10-10T15:12:01.097Z INFO Hisense Canvas TV (ADB) <Device: 0485639c-… (Living Room Canvas TV 1)> emitting event: {“attribute_id”:“switch”,“capability_id”:“switch”,“component_id”:“main”,“state”:{“value”:“on”}}

2026-10-10T15:12:01.113Z INFO Hisense Canvas TV (ADB) <Device: 0485639c-… (Living Room Canvas TV 1)> emitting event: {“attribute_id”:“switch”,“capability_id”:“switch”,“component_id”:“art”,“state”:{“value”:“on”}}

```

On startup it logs `transitioning device to “PROVISIONED”` but the state never changes.

I kind of figured an update broke it.. some of my other tools stopped opening as well.

I will try to figure out how to downgrade and test.

The whole driver was generated by AI, right?

This should be removed:

mnmn: SmartThingsCommunity

vid: 7c77eab6-b745-3f8b-a300-46cd2520e82e

deviceType: Switch

ocfDeviceType: oic.d.switch

Use the SmartThings CLI to issue commands:

USAGE
  $ smartthings devices:commands [ID] [COMMAND] [-h] [-p <value>] [-t <value>] [--language <value>] [-j] [-y] [-i
    <value>]

ARGUMENTS
  ID       the device id
  COMMAND  the command [<component>]:<capability>:<command>([<arguments>])

COMMON FLAGS
  -h, --help             Show CLI help.
  -i, --input=<value>    specify input file
  -j, --json             use JSON format of input and/or output
  -p, --profile=<value>  [default: default] configuration profile
  -t, --token=<value>    the auth token to use
  -y, --yaml             use YAML format of input and/or output
  --language=<value>     ISO language code or "NONE" to not specify a language. Defaults to the OS locale

DESCRIPTION
  execute a device command

  For API information, see:

  https://developer.smartthings.com/docs/api/public/#operation/executeDeviceCommands

EXAMPLES
  # simple capability and command

    $ smartthings devices:commands 00000000-0000-0000-0000-000000000000 switch:off

  # capability and command with an argument (note: single quote command arg to prevent specific shell parsing errors)

    $ smartthings devices:commands 00000000-0000-0000-0000-000000000000 'switchLevel:setLevel(50)'

That way you can test if the provisioningState is really the issue - it’s not.

I’d also start with a very simple profile: one switch capability and not much else.

Andreas, thank you so much for sticking with this, I really appreciate you taking the time. Seriously, thanks.

And yeah, the driver is mostly AI generated, not gonna hide that.

Did both things you said:

  1. Sent commands from the CLI (devices:commands ... main:refresh:refresh). Driver got it and responded right away. So you were right, provisioningState isn’t blocking anything, the device works fine.
  2. Made a bare minimum test driver. One switch capability, no custom presentation, no metadata, nothing else. Scanned it in, and the device page won’t open for that one either. Same as my TV and the vEdge Creator device I added today. The test device also shows TYPED, so I guess that’s just normal for new LAN devices right now.

So it looks like it’s not my driver, it’s the app. Lines up with what Paul said about 1.8.51.30. I’ll try the previous app version and wait for an update if that’s it.

Thanks again for all the help, saved me a lot of chasing the wrong thing!

Tell the AI that it has nothing to do with the provisioningState. It’s thinking in the wrong direction.

If I had a look at my devices, I’d see all kinds of states and they are all working correctly. The app itself doesn’t care in which provisioningState the driver is.

Yeah.. I did.. and it agrees. but the TAustin drivers I Installed had the exact same issues. I was expecting a UI in the app.. and got nothing.

Really not sure what is causing it or I wouldn’t have bothered posting here.

I would remove the explicit provisioning_state updates in your driver. The device is already operational, and the provisioning state is not what prevents the mobile app from opening its detail page.

Then I’d test it again, but this time turn off the WiFi on your phone, reboot it, make sure that it’s connected to mobile data (not WiFi). The current app version can talk directly to the hub over the local network and this could cause issues.

If this still doesn’t work, I’d install the previous version of the app and test it again - as @Paul_Oliver suggested.

Hi @phicks , I haven’t seen this on my side, but here’s a quick way to tell whether it’s your driver or the platform. I have a generic Google TV LAN driver that creates devices with the same try_create_device call, and they reach PROVISIONED and open fine. Details and install steps are in my thread here: [EDGE] Generic Google TV / Android TV Driver — control any Google TV via SmartThings

Channel link: SmartThings. Add a little smartness to your things.

If you install it and create a device there, and that one also sticks at TYPED on your hub, it’s platform-side and worth reporting to SmartThings support with the device ID and timestamp. If it opens fine, you can compare its profile and config against yours. It also handles power on/off and key presses on any Google TV, so it may cover part of what you want, though it has no Art Mode support.

Thanks for the suggestion. Thing is my use case is fully accomplished.. I just wanted the UI and I think something is wrong with my Hub / App etc that isn’t allowing any custom Tiles