Testing against Custom Capabilities

Continuing the discussion from Help needed: st.zigbee.cluster_base understanding:

After addressing my custom capabilities in my test script correctly with test_utils.load_capability_definition_from_package() and running the test I got this error:

PS Q:\repos\smartthings_edge_drivers\popp-smart-thermostat\src> lua54 .\test\sample_test.lua
Running test "Check all preferences via infoChanged" (1 of 1)
-------------------------------------------------------------
Received unexpected error: .\init.lua:271: Capability preparestream40760.heatMode version 1 definition not found
stack traceback:
        [C]: in function 'error'
        ...gs_edge_drivers\lua_libs-api_v0\st\capabilities\init.lua:101: in metamethod 'index'
        .\init.lua:271: in main chunk
        [C]: in function 'require'
        [C]: in function 'xpcall'
        ...s_edge_drivers\lua_libs-api_v0\integration_test\init.lua:213: in upvalue 'run_configured_test'
        ...s_edge_drivers\lua_libs-api_v0\integration_test\init.lua:325: in function 'integration_test.run_registered_tests'
        .\test\sample_test.lua:50: in main chunk
        [C]: in ?
FAILED

Passed 0 of 1 tests

So it’s complaining about the capability_handler … here is my init.lua snippet:

-- definition
local HeatingMode = capabilities["preparestream40760.heatMode"]
...
-- capability handlers
local driver = {
    supported_capabilities = {
      ...
      HeatingMode
    },
    zigbee_handlers = {
      attr = {
        [PowerConfiguration.ID] = {
          [PowerConfiguration.attributes.BatteryVoltage.ID] = battery_voltage_handler
        },
        [Thermostat.ID] = {
          [Thermostat.attributes.ControlSequenceOfOperation.ID] = supported_thermostat_modes_handler
        }
      }
    },
    capability_handlers = {
      ...
      [HeatingMode.ID] = {
        [HeatingMode.commands.setSetpointMode.NAME] = common.heat_cmd_handler
      }
    },
    lifecycle_handlers = {
      added = device_added,
      init = device_init,
      doConfigure = do_configure,
      infoChanged = info_changed,
      driverSwitched = driver_switched,
      removed = device_removed
    }
  }

Any idea?
Against the device itself it works like a charm.

Thank you in advance :nerd_face:

This snippet is in the init.lua file of the base driver, right?
Could you share the content in the sample_test.lua, please?

Here is my sample_test.lua:

-- Mock out globals
local test = require "integration_test"
local utils = require "st.utils"
local test_utils = require "integration_test.utils"

-- clusters
local clusters = require "st.zigbee.zcl.clusters"
local ThermostatUIConfig = clusters.ThermostatUserInterfaceConfiguration

-- utils
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
local t_utils = require "integration_test.utils"
local base64 = require "st.base64"

-- custom caps
local WindowOpenDetectionCap = test_utils.load_capability_definition_from_package(".\\window_open_detection\\etrv_window_open_detection_cap_pres.json")
local HeatingMode = test_utils.load_capability_definition_from_package(".\\heating_setpoint\\heating_setpoint_cmd_cap.json")

local mock_device = test.mock_device.build_test_zigbee_device(
    { profile = t_utils.get_profile_definition("smart-thermostat.yml") }
)

zigbee_test_utils.prepare_zigbee_env_info()
local function test_init()
  test.mock_device.add_test_device(mock_device)
  zigbee_test_utils.init_noop_health_check_timer()
end

test.set_test_init_function(test_init)

test.register_coroutine_test(
    "Check all preferences via infoChanged",
    function()
      local updates = {
        preferences = {
          keypadLock = 1 -- Lock
        }
      }
      local MFG_CODE = 0x1246
      test.socket.zigbee:__set_channel_ordering("relaxed")
      test.socket.environment_update:__queue_receive({ "zigbee", { hub_zigbee_id = base64.encode(zigbee_test_utils.mock_hub_eui) } })
      test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates))
      test.socket.zigbee:__expect_send({
        mock_device.id,
        ThermostatUIConfig.attributes.KeypadLockout:write(mock_device, 0x0001)
      })
    end
)

test.run_registered_tests()

In the meantime I switched to Lua 53. You are right, with the latest version 54 there are more strange bugs …

PS Q:\repos\smartthings_edge_drivers\popp-smart-thermostat\src> lua53 .\test\sample_test.lua
Running test "Check all preferences via infoChanged" (1 of 1)
-------------------------------------------------------------
Received unexpected error: .\init.lua:271: Capability preparestream40760.heatMode version 1 definition not found
stack traceback:
        [C]: in function 'error'
        ...gs_edge_drivers\lua_libs-api_v0\st\capabilities\init.lua:98: in metamethod '__index'
        .\init.lua:271: in main chunk
        [C]: in function 'require'
        [C]: in function 'xpcall'
        ...s_edge_drivers\lua_libs-api_v0\integration_test\init.lua:212: in upvalue 'run_configured_test'
        ...s_edge_drivers\lua_libs-api_v0\integration_test\init.lua:324: in function 'integration_test.run_registered_tests'
        .\test\sample_test.lua:50: in main chunk
        [C]: in ?
FAILED


Passed 0 of 1 tests

Ok, I was getting the same error and solved it with the instructions in the repo of the sample driver using custom capabilities:

So, the line of test_utils.load_capability_definition_from_package() is not necessary anymore, I’ll investigate more about its functionality and see if we can provide more details in the docs.

Ok, one step closer :+1:t6:

Now I got the following message (same sample_test.lua):

Q:\lua\lua53.exe: .\test\test_smart_thermostat.lua:425: attempt to call a table value (field 'setSetpointMode')
stack traceback:
        .\test\test_smart_thermostat.lua:425: in main chunk     
        [C]: in ?

The capability heatMode looks like this:

{
        "id": "preparestream40760.heatMode",
        "version": 1,
        "status": "proposed",
        "name": "Heat Mode",
        "ephemeral": false,
        "attributes": {
            "setpointMode": {
                "schema": {
                    "type": "object",
                    "properties": {
                        "value": {
                            "type": "string",
                            "enum": [
                                "fast",
                                "eco"
                            ]
                        }
                    },
                    "additionalProperties": false,
                    "required": [
                        "value"
                    ]
                },
                "setter": "setSetpointMode",
                "enumCommands": []
            }
        },
        "commands": {
            "setSetpointMode": {
                "name": "setSetpointMode",
                "arguments": [
                    {
                        "name": "mode",
                        "optional": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "fast",
                                "eco"
                            ]
                        }
                    }
                ]
            }
        }
    }

How do I send the commands fast and eco correctly from my heatMode capability?
I suppose the receive looks correct?

local HeatingMode = test_utils.load_capability_definition_from_package(".\\heating_setpoint\\heating_setpoint_cmd_cap.json")

...

test.register_message_test(
    "setSetpointMode 'fast' should be handled",
    {
      {
        channel = "capability",
        direction = "receive",
        message = { mock_device.id, { capability = "preparestream40760.heatMode", component = "main", command = "setSetpointMode", args = { "fast" } } }
      },
      {
        channel = "zigbee",
        direction = "send",
        message = { mock_device.id, HeatingMode.commands.setSetpointMode({ mode = "fast" }) }
      }
    }
)

ok, that’s because this section using “channel = zigbee” is expecting to send a Zigbee message.
These are known as Message Driver tests and this case would correspond to the section Capability Command > Zigbee radio message.
If the preparestream40760.heatMode is related to a manufacturer-specific cluster, then you can use build_manufacturer_specific_command or construct the message similar to how you did in the driver itself depending on what kind of message you want to send.