Creating a profile for air conditioner device [Edge]

Hi!

For the second week I have been studying topics on this resource and learning more and more information on smartthings.
At first, everything seemed crazy, but the more I begin to understand what is happening, the more building an API seems logical.

To begin with, I will describe what I am trying to achieve - an air conditioner with the following features:

  1. Mode (fixed list of modes - auto, cool, heat, dry, vent)
  2. Setting the temperature of the air conditioner in the range (for example, 16-30 degrees Celsius)
  3. Setting the fan operation mode: auto, low, mid, high
  4. Button to control the swing in two positions: move and stop

How I am trying to solve this with a device profile:

name: test_ac
components:
- id: main
  capabilities:
  - id: thermostatMode
    version: 1
  - id: thermostatCoolingSetpoint
    version: 1
  - id: fanSpeed
    version: 1
  - id: button
    version: 1
  - id: refresh
    version: 1
  categories:
  - name: Thermostat
metadata:
  ocfDeviceType: oic.d.airconditioner

Now I will describe the problems that I encountered, in the hope that I will get help

  1. For mode i used thermostatMode capability.
    The problem was to leave only the necessary operation modes. But it was generally resolved through device:emit_event(caps.thermostatMode.supportedThermostatModes({“auto”,“cool”, “fanonly”, “heat”, “moistair”, “off”, })) in the added handler
    Is it correct to use such a command? Perhaps there is some other way to leave only the desired modes of operation

  2. For temperature control I used thermostatCoolingSetpoint.
    In order to be able to voice control, I did not make my own capability.
    For sure, the use of such a capability is incorrect from the point of view of logic, but I can’t find a better one.

How can I set the required temperature range?
How can I change the tile name to “Set temperature”?

I understand that through the device config, but I can not get the result.
I tried the option below, but I could not achieve the result

command: setCoolingSetpoint
         argumentType: number
         unit: coolingSetpoint.unit
         range:
           - sixteen
           - thirty
  1. FanSpeed as i mention should have 4 items: auto, low, mid, high

Based on following topics
topic 1
topic 2
topic 3

i tried to create device vvid to start

I need to change options count to 4 and change title for first one to auto from off.
Based on this topic i created device presentation and after that run presentation:device-config:create to create device-config.
Device config with vvid created, but nothing happened after i added this vvid to device.
Also when ./smartthings presentation VVID is run i still can see 5 modes in fan control.

What i’m doing wrong? )

dashboard:
  states:
    - capability: thermostatMode
      component: main
      version: 1
  actions:
    - capability: thermostatMode
      component: main
      version: 1
detailView:
  - capability: thermostatMode
    component: main
    version: 1
  - capability: thermostatCoolingSetpoint
    component: main
    version: 1
  - capability: fanSpeed
    component: main
    version: 1
    values:
      - key: fanSpeed
        range:
        - 0
        - 3
        step: 1
  - capability: button
    component: main
    version: 1
  - capability: refresh
    component: main
    version: 1
automation:
  conditions:
  - capability: thermostatMode
    component: main
    version: 1
  - capability: thermostatCoolingSetpoint
    component: main
    version: 1
  - capability: fanSpeed
    component: main
    version: 1
    values:
      - key: fanSpeed
        range:
        - 0
        - 3
        step: 1
  - capability: button
    component: main
    version: 1
  - capability: refresh
    component: main
    version: 1
  actions:
  - capability: thermostatMode
    component: main
    version: 1
  - capability: thermostatCoolingSetpoint
    component: main
    version: 1
  - capability: fanSpeed
    component: main
    version: 1
    values:
      - key: fanSpeed
        range:
        - 0
        - 3
        step: 1
  - capability: button
    component: main
    version: 1
  - capability: refresh
    component: main
    version: 1

To be honest, I’ve been struggling with this for about a week, there are thoughts of quitting and rewriting everything to custom capabilities. Could this be the way out?

thnx

  1. Yes, this seems like the simplest solution. I see no real disadvantage to hiding the unused enums.

  2. If you want something more visually appealing, you can hide the stock capability and mimic using another. You would then need to manipulate the 2 capabilities with device:emit_event. Concerning changing stock values, see #3 for a working example.

  3. Here is an example. Note that I was using multiple component names. That is not required unless you want 2 of the same capability.

Concerning custom capabilities, you’ll find the mechanics very basic. Many stock capabilities have magical properties that cannot be replicated. As you’ve noted, you also won’t have proper integration with Google/Alexa unless you manipulate hidden stock capabilities.

Most people stick with stock capabilities because they want the default Z-Wave/Zigbee behaviors. Those drivers become almost trivial. If you’re targeting LAN devices, then you’ve got to write all the code yourself anyway.

I don’t see anything obviously wrong with your device-config. Maybe there’s a subtle formatting error somewhere. As with all coding, you start simple and then add incrementally. Don’t expect the CLI tool to catch errors. JSON is more verbose, but it’s more forgiving concerning indentation.

The documentation for device-config can be found in the corresponding API. Click on ‘Expand all’ in the ‘Response samples’ pane. I’ve never been able to do anything useful with the patch section. I know when I get a path right because the CLI bombs with an error.

1 Like

Thank you.

  1. Got it. It looked like attributes could be set in the device profile, but it doesn’t seem to be possible. ok let it stay like that
  2. I see, thank you. It looks like custom capabiliy (there’s no getting away from them) would be the best solution. I sketched a capability with temperature control, but something strange is happening. Have you encountered something similar?

Capability is displayed with a crossed out cloud icon. At the same time, when I try to update the device, the slider looks for a second. Capability and its presentation are registered. Where could be the error in this case?

setTemperature YAML

name: set Temperature
attributes:
  temperature:
    schema:
      type: object
      properties:
        value:
          type: integer
      additionalProperties: false
      required:
      - value
    setter: setTemperature
    enumCommands: []
commands:
  setTemperature:
    name: setTemperature
    arguments:
    - name: temperature
      optional: false
      schema:
        type: integer
        minimum: 16
        maximum: 30

setTemperature presentation YAML

dashboard:
detailView:
- label: Temperature
  displayType: slider
  slider:
    range:
    - 16
    - 30
    step: 1
    value: temperature.value
automation:
  conditions:
  - label: Temperature
    displayType: slider
    slider:
      range:
      - 16
      - 30
      step: 1
      value: temperature.value
  actions:
  - label: Set Temperature
    displayType: slider
    slider:
      command: setTemperature
      argumentType: number
      range:
        - 16
        - 30
      step: 1
id: musicschool11529.setTemperature
version: 1
  1. Thank you for explanation and following post. I will try to check everything proper with YAML <=> JSON converters too. But now i’m think that custom capabilities will be more suitable for now

It’s hard to follow everything you’re doing without having the full project. No, I don’t want it either. I have enough problems of my own. :slight_smile:

The app is unhappy when capabilities don’t have an initial value. Maybe that’s the reason for the broken cloud. Do you initialize capabilities with device:emit_event() in the init (or maybe doConfigure) life cycle handler?

I assume you know how to view the output log with smartthings edge:drivers:logcat? Very helpful.

Something else you may run into is that updates (revisions) to custom capabilities (historically) don’t always take immediate effect. I don’t know if it’s just a matter of waiting, but I always append a number to my custom capabilities and increment with each revision. Yes, lots of editing in multiple files. You’ll eventually break down and script things with the CLI. You may also try deleting the device and recreating.

Did you discover this shortcut? Will package, assign to channel, and install in one step:

smartthings edge:drivers:package --hub <hub id> --channel <channel id>

It’s hard to follow everything you’re doing without having the full project. No, I don’t want it either. I have enough problems of my own. :slight_smile:

You are damn right :grin:

The app is unhappy when capabilities don’t have an initial value. Maybe that’s the reason for the broken cloud. Do you initialize capabilities with device:emit_event() in the init (or maybe doConfigure ) life cycle handler?

Not yet, it is may be a cause of problem, i will try to debug it.

I assume you know how to view the output log with smartthings edge:drivers:logcat ? Very helpful.

This is most usable tool for me, i really didn’t know what to do without it

Thank you for your answers.
Will try to fix it with init value
You know - as soon as first capability will be ready there no problems to create other 1000

Hi @lukin.dmitriy,

I’m reading your posts and I’m going to see if I can help you with my experience in creating a virtual thermostat with a zigbee temperature sensor.

This is the link to my github in case it helps you, it has all presentations VID and capabilities in .json
https://github.com/Mariano-Github/Edge-Drivers-Beta/tree/main/Zigbee-thermostat-v3.5
I understand that what you want to do is an air conditioner controller to send it the different parameters: thermostat mode, target temperature, fan mode and swing.
All the logic of operation and measured temperature will be in the air conditioning itself.

1- I think it’s a good choice to use stock capabilities, advantages: automatic translaltion, voice assitant visivility,
 although you may need a custom one.
2- The capabilities that are added to the mosaic (dashboard) only show their information and actions, if the presentation of the capability has the value of the status and the actions to be carried out defined in the dashboard capability presentation.
For example, the thermostatMode capability does not have anything defined in its dashboard view of its presentation, so it will not show anything in the tile. I couldn’t use it on the mosaic either.

.\smartthings capabilities:presentation thermostatMode 1 -j
{
    "detailView": [
        {
            "label": "{{i18n.label}}",
            "displayType": "list",
            "list": {
                "command": {
                    "name": "setThermostatMode",
                    "alternatives": [
                        {
                            "key": "asleep",
                            "value": "Asleep",
                            "type": "active"
                        },

To see the json presentation of each capability you can use the CLI with this command:

.\smartthings capabilities:presentation thermostatMode 1 -j

The thermostatCoolingSetpoint capability does have the state value of coolingSetpoint, but the actions are not defined. So you can use it to view the temperature setpoint in the tile, but you won’t be able to change it from the tile, you’ll have to go into the details view to do so.
The Range is limited from 0ÂșC to 40ÂșC by default, but you can change the range with a custom presentation.

.\smartthings capabilities:presentation thermostatCoolingSetpoint 1 -j
{
    "dashboard": {
        "states": [
            {
                "label": "{{coolingSetpoint.value}} {{coolingSetpoint.unit}}",
                "alternatives": [
                    {
                        "key": "C",
                        "value": "°C",
                        "type": "active"
                    },
                    {
                        "key": "K",
                        "value": "°K",
                        "type": "active"
                    },
                    {
                        "key": "F",
                        "value": "°F",
                        "type": "active"
                    }
                ]
            }
        ],
        "actions": []
    },
    "detailView": [
        {
            "label": "{{i18n.label}}",
            "displayType": "slider",
            "slider": {
                "range": [
                    0,
                    40
                ],
                "step": null,
                "unit": "coolingSetpoint.unit",
                "command": "setCoolingSetpoint",
                "argumentType": "number",
                "value": "coolingSetpoint.value",
                "valueType": "number"
            },
            "state": null
        }
    ],

3- If it is as I suppose, that there is no device that responds to the commands of the capabilities, you have to write the lua code to emit the events to show them in the capabilities.
For example:
The app sends the command to change the temperature set point and you have to respond to that command to emit the event to the platform in order to show command value in the capability that sent it.
You have to capture the capability command in the driver Template in order to call specific function and emit the event:

  capability_handlers = {
    [capabilities.thermostatMode.ID] = {
      [capabilities.thermostatMode.commands.setThermostatMode.NAME] = thermostatMode_handler,
    },
    [capabilities.thermostatHeatingSetpoint.ID] = {
      [capabilities.thermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = heatingSetpoint_handler,
    },
    [capabilities.thermostatCoolingSetpoint.ID] = {
      [capabilities.thermostatCoolingSetpoint.commands.setCoolingSetpoint.NAME] = coolingSetpoint_handler,
    },
---- coolingSetpoint_handler
local function coolingSetpoint_handler(self,device,command)

  local cooling_Setpoint = command.args.setpoint

  local temp_scale = "C"
  device:emit_event(capabilities.thermostatCoolingSetpoint.coolingSetpoint({value = cooling_Setpoint, unit = temp_scale }))

end

You can see the full code in the github file

If you find it useful, ask more questions, I don’t want to get too involved without it being of any use to you.

Regards

@Mariano_Colmenarejo

Thank you for describing everything in such detail.
I looked at your repository, in general yes - it will help a lot. I will look periodically. I already made several visits to Github looking for large sets of devices to study, but somehow I missed yours.

.\smartthings capabilities:presentation thermostatMode 1 -j

this is very useful, thnx.I used to just look for capability in st->capabilities->generated and parse the JSON, but it will be much easier this way

You and @nmpu were right - it’s all about the initial initialization, as soon as I added it - capability began to show. Solved

For a general understanding SmartThings, I would first get the custom capability working in order to have a complete understanding of how it all works, but I have a very strange problem. Perhaps you have already experienced something similar.

Capability is displayed as it should, commands are set, and handlers are set. But for some reason, when I try to set the temperature with the slider, nothing happens. The code does not enter the handler and the loader on the slider spins endlessly.
Also i cannot see emitted event by logcat.
So i think this is something in capability ↔ code link.
I spent nearly day to debug this feature and still there.

Capability:

id: musicschool11529.setTemperature
version: 1
ocfResourceType: x.com.st.temperature.cooling
name: Set Temperature
attributes:
  temperature:
    schema:
      type: object
      properties:
        value:
          type: integer
          minimum: 16
          maximum: 30
        unit:
          type: string
          enum:
          - C
          default: C
      additionalProperties: false
      required:
        - value
    setter: setTemperature
    enumCommands: []
commands:
  setTemperature:
    name: setTemperature
    arguments:
      - name: value
        optional: false
        schema:
          type: integer
          minimum: 16
          maximum: 30

Capability presentation

dashboard:
  states: []
  actions: []
detailView:
  - label: Temperature
    displayType: slider
    slider:
      range:
        - 16
        - 30
      step: 1
      unit: temperature.unit
      command: setTemperature
      argumentType: integer
      value: temperature.value
      valueType: integer
    state: null
automation:
  conditions:
    - label: Temperature
      displayType: slider
      slider:
        range:
          - 16
          - 30
        step: 1
        unit: temperature.unit
        value: temperature.value
        valueType: integer
  actions:
    - label: Set Temperature
      displayType: slider
      slider:
        range:
          - 16
          - 30
        step: 1
        unit: temperature.unit
        command: setTemperature
        argumentType: integer
id: musicschool11529.setTemperature
version: 1

Device profile:

name: ac_test
components:
- id: main
  capabilities:
  - id: thermostatMode
    version: 1
  - id: musicschool11529.setTemperature
    version: 1
  - id: fanSpeed
    version: 1
  - id: button
    version: 1
  - id: refresh
    version: 1
  categories:
  - name: Thermostat
metadata:
  ocfDeviceType: oic.d.airconditioner
  deviceType: "AirConditioner"

Lua code

local caps = require('st.capabilities')
local tempSlider = caps['musicschool11529.setTemperature']

...

local driver =
  Driver(
    'test_driver',
    {
      discovery = discovery.start,
      lifecycle_handlers = lifecycles,
      supported_capabilities = {
        tempSlider,
      },
      capability_handlers = {
        [tempSlider.ID] = {
          [tempSlider.commands.setTemperature.NAME] = commands.set_temperature
        },
...

I understand that disassembling someone else’s code is a thankful task, but I bring these pieces of code because I’m sure that they should work. I saw something similar with you, but the request does not reach commands.set_temperature. Maybe the slider has some option that means it can be moved or not?

The handler function commands.set_temperature is in another module called commands.lua, right?

Please, Could you show the code of the handler function commands.set_temperature?

@Mariano_Colmenarejo So
 it was cloud cache.
I just rename capability/presentation and it works now!
Very strange programming :slightly_smiling_face:

Thank you for you help )

1 Like

Didn’t I warn you? :crazy_face:

Any time I make any changes to a custom capability, I delete the original, recreate with a new name, reapply the custom presentation, recreate the device-config, update all the references and then repackage/install. You’ll then need to delete the device and add again.

Now that you have things working, you should make note of the logs. When a device is installed, there’s a message like:

2022-04-20T19:34:46.091052570+00:00 TRACE Stuck_State  Setup driver Stuck_State with Capability handlers:
CapabilityCommandDispatcher: Stuck_State
  default_handlers:
    dictionaryorigin49938.press114:
      press
  child_dispatchers:

which will show that your handler(s) were recognized. When you actuate a control, you’ll also see something like:

2022-04-20T19:49:10.770272921+00:00 TRACE Stuck_State Found CapabilityCommandDispatcher handler in Stuck_State

Thank you and apologise my long answer.
Everything is ok right now and i created driver for several lan devices.

lua seemed complicated and unfamiliar, but the language is so simple that a couple of evenings and everything worked as it should

But the story with custom capabilities and their caching is, of course, terrible.