[Endpoint SmartApp] Help executing commands based on subscriptions

I am having issue getting the Glitch code to execute, I have set up and have the smartapp opening on my phone. Im not sure if glitch requires a PAT token.

server.js code from Glitch

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const SmartApp = require('@smartthings/smartapp');

const server = module.exports = express();
server.use(bodyParser.json());

const app = new SmartApp()

/* Only here for Glitch, so that GET doesn't return an error */
server.get('/', (req, res) => {
  res.send('Simple SmartApp Example URL: https://'+ req.hostname);
});

/* Handles lifecycle events from SmartThings */
server.post('/', async (req, res) => {
    app.handleHttpCallback(req, res);
});

/* Defines the SmartApp */
app.enableEventLogging()  // Log and pretty-print all lifecycle events and responses
    .configureI18n()      // Use files from locales directory for configuration page localization
    .page('mainPage', (context, page, configData) => {
        page.section('buttons', section => {
           section.deviceSetting('Upbutton').capabilities(['button']).required(true);
        });
        page.section('buttons', section => {
           section.deviceSetting('Downbutton').capabilities(['contactSensor']).required(true);
        });
        page.section('lights', section => {
            section.deviceSetting('lights').capabilities(['switch']).multiple(true).permissions('rx');
        });
        page.section('thermostats', section => {
            section.deviceSetting('thermostat').capabilities(['temperatureMeasurement']).multiple(true).permissions('wx');
        });
    })
    .updated(async (context, updateData) => {
        await context.api.subscriptions.unsubscribeAll();
        return Promise.all([
            //context.api.subscriptions.subscribeToDevices(context.config.button, 'button', 'buttonpush', 'pushedUpDeviceEventHandler'),
            context.api.subscriptions.subscribeToDevices(context.config.sensor, 'button', 'button.push', 'pushedDownDeviceEventHandler'),
            context.api.subscriptions.subscribeToDevices(context.config.sensor, 'contactSensor', 'contact.open', 'openDeviceEventHandler'),
            context.api.subscriptions.subscribeToDevices(context.config.sensor, 'contactSensor', 'contact.closed', 'closedDeviceEventHandler')
        ])
    })
      //.subscribedEventHandler('pushedUpDeviceEventHandler', (context, deviceEvent) => {
        
        //return context.api.devices.sendCommands(context.config.buttons, 'temperature', 25);
       // return context.api.devices.sendCommands(context.config.lights, 'switch', 'on');
    //})
    .subscribedEventHandler('pushedDownDeviceEventHandler', (context, deviceEvent) => {
        
        return context.api.devices.sendCommands(context.config.buttons, 'temperature', 24);
        return context.api.devices.sendCommands(context.config.lights, 'switch', 'on');
    })
    .subscribedEventHandler('openDeviceEventHandler', (context, deviceEvent) => {
        return context.api.devices.sendCommands(context.config.lights, 'switch', 'on');
    })
    .subscribedEventHandler('closedDeviceEventHandler', (context, deviceEvent) => {
        return context.api.devices.sendCommands(context.config.lights, 'switch', 'off');
    });    

/* Starts the server */
let port = process.env.PORT;
server.listen(port);
console.log(`Open: http://127.0.0.1:${port}`);

The app loads just refuses to execute

After you remix the glitch app you need to put your rsa key in the .env file in the config.

Hi thanks for responding. Is it possible to post a screenshot eg of how this looks as, I tried it with the client ID and the secret code. It didn’t seem to work I’m not sure where I’m going wrong.

Is the RSA key commented out in the .env file? Or should be getting a RSA key from the samsung developer workspace?

What do you mean by this? The configuration page is shown in the app but what isn’t executing?

The smartapp I’m making gives you the option to select a button (ikea TrĂ„dfri or virtual buttons) and use it to turn up/down an audio device volume or thermostat temp setpoint or brightness of a smart bulb. So when Im indicating that the smart app isnt executing I’m indicated when I press the button on the selected vEdge Momentary 1 button, the selected bulb doesnt turn on/off or vEdge Temp 2 thermostat set point temperature doesn’t increase/decrease based on the current code.

Ok. Have you verified you’re getting the subscription event in your SmartApp?

Something that commonly happens with Buttons is that they don’t exactly change their attribute value, for example, if you only push it each time, the value is always “pushed”. The device must send a state_change each time. So, that’s why you need to verify the app is getting the subscription event and see if it’s being handled correctly.

Add a 2 in the logger like this: enableEventLogging(2), this way, you’ll get printed the SmartApp events in a “pretty” format.

Also, you can make sure the subscriptions were created correctly by making an API call to https://api.smartthings.com/v1/installedapps/{installedAppId}/subscriptions using the installedAppId access token shown in the SmartApp logs.

@nayelyz I tried to make the API call and didn’t get a response, which would indicate that the subscriptions weren’t correctly created? How do I correctly create the subscriptions?

If no subscriptions were created for the installedApp, you should get a response of an empty object like {}.

I didn’t noticed before you’re not creating the subscriptions correctly, so, that should be it.

The call to the device selected is incorrect, it should include the name assigned to the deviceSetting.
In your case, you have 4 device settings:

  1. Upbutton > a device with the button capability
  2. Downbutton > a device with the contactSensor capability
  3. lights > one or more devices with the switch capability
  4. thermostat > one or more devices with the temperatureMeasurement capability

And in all your subscriptions, you’re calling context.config.sensor which doesn’t exist. According to your deviceSettings names, it should be like:

context.api.subscriptions.subscribeToDevices(context.config.Upbutton, ‘button’, ‘button.push’, ‘pushedDownDeviceEventHandler’),
context.api.subscriptions.subscribeToDevices(context.config.Downbutton, ‘contactSensor’, ‘contact.open’, ‘openDeviceEventHandler’),
context.api.subscriptions.subscribeToDevices(context.config.Downbutton, ‘contactSensor’, ‘contact.closed’, ‘closedDeviceEventHandler’)

Also, the temperatureMeasurement capability doesn’t have a command to set the temperature which means this command will throw an error:

context.api.devices.sendCommands(context.config.buttons, ‘temperature’, 24);
Will the devices selected in the “thermostat” setting be temperature sensors like Z-Wave or Zigbee?

Thank you @nayelyz I got it to execute. The “thermostat” I’m currently testing with is a virtual device in smartthings but I’m hoping to be able to use any capable thermostat or sensor in the future. Also, I am getting an “No network connection could’nt find any available networks” error.

Ok, the commands you can send to a device depend on two things:

  1. The capabilities included in the device metadata (device profile > they are listed in the response to get the device list from the API)
  2. Each capability’s commands included in its definition which you can check in the following:
    a. The documentation: Production Capabilities | Developer Documentation | SmartThings
    b. The API using the capability ID in the request: API | Developer Documentation | SmartThings

I’ve seen other developers use the capabilities thermostatHeatingsetpoint and thermostatCoolingSetpoint when trying to control the temperature of their houses and the capability temperatureMeasurement just shows the current temperature but in that case, their devices have the combination of those three.

When are you getting this error? Sending a command? If so, which one?

1 Like

@nayelyz I fixed the error however I’m not sure how to implement the thermostatCoolingSetpoint, can you provide an example of how to implement this in my code?

ok, first, let’s check your device’s details, please query the device list and look for the device you’re trying to control.

This can be done using the ST CLI as well with the commands:

smartthings devices

//copy the corresponding deviceID
smartthings devices deviceId

This is to know which capabilities we need to use, so, please, share the result.

I use the smartthings browser+ for list of devices and for the smartthings “vEdge Thermostat 1” the list of capabilities are in the picture to the right. I’m trying to use capability “thermostatCoolingSetpoint” and change the value of the “coolingSetpoint” value.

This is to send a command, right?

It can be done like this:

context.api.devices.sendCommands(context.config.thermostat,'thermostatCoolingSetpoint', 'setCoolingSetpoint', [24]);

///Here's an extra sample using the capability colorControl
context.api.devices.sendCommands(context.config.colorc,'colorControl', 'setColor', [{hue:20,saturation:30}]);

To check the parameters needed in functions like sendCommand, you can go to the Core SDK repo and look for it. For example:

@nayelyz I need to send a temperature cooling setpoint command but I would also need to read the current “setCoolingSetpoint” value in order to increment/decrement based on which button is pressed

ok, in the Core SDK, you can see the functions you can use to get info from devices as well, for example, the function getCapabilityStatus helps you to get the status of a specific capability.
You need to specify the component as well which you can see in the tool you mentioned above, for example, using the following code:

let currentTemp = await context.api.devices.getCapabilityStatus(context.config.thermostat[0].deviceConfig.deviceId,'main','thermostatCoolingSetpoint')
console.log(currentTemp)

Will print you this log. So, now you know how to get this value in the response from getCapabilityStatus (just as a reference: currentTemp.coolingSetpoint.value):

{
  coolingSetpoint: { value: 24, unit: 'C', timestamp: '2023-01-10T16:11:37.856Z' }
}

@nayelyz I implemented the line of code as suggested

‘use strict’;

const express = require(‘express’);
const bodyParser = require(‘body-parser’);
const SmartApp = require(‘@smartthings/smartapp’);

const server = module.exports = express();
server.use(bodyParser.json());

const app = new SmartApp()

/* Only here for Glitch, so that GET doesn’t return an error */
server.get(‘/’, (req, res) => {
res.send(‘Simple SmartApp Example URL: https://’+ req.hostname);
});

/* Handles lifecycle events from SmartThings */
server.post(‘/’, async (req, res) => {
app.handleHttpCallback(req, res);
});

/* Defines the SmartApp */
app.enableEventLogging(2) // Log and pretty-print all lifecycle events and responses
.configureI18n() // Use files from locales directory for configuration page localization
.page(‘mainPage’, (context, page, configData) => {
page.section(‘buttons’, section => {
section.deviceSetting(‘Upbutton’).capabilities([‘button’]).required(true);
});
page.section(‘buttons’, section => {
section.deviceSetting(‘Downbutton’).capabilities([‘button’]).required(true);
});
page.section(‘thermostats’, section => {
section.deviceSetting(‘thermostat’).capabilities([‘thermostatCoolingSetpoint’]).multiple(true).permissions(‘wx’);
});
page.section(‘thermostats’, section => {
section.deviceSetting(‘thermosta’).capabilities([‘thermostatCoolingSetpoint’]).multiple(true).permissions(‘rx’);
});
})
.updated(async (context, updateData) => {

    await context.api.subscriptions.unsubscribeAll();
    return Promise.all([
        context.api.subscriptions.subscribeToDevices(context.config.Upbutton, 'button', 'button.pushed', 'pushedUpDeviceEventHandler'),
        context.api.subscriptions.subscribeToDevices(context.config.Downbutton, 'button', 'button.pushed', 'pushedDownDeviceEventHandler'),
        context.api.subscriptions.subscribeToDevices(context.config.thermosta, 'thermostatCoolingSetpoint', 'coolingSetpoint', 'tempDeviceEventHandler'),
        
        
    ])
})
   
  .subscribedEventHandler('tempDeviceEventHandler', (context, deviceEvent) => {
    
    const setpointTemperature = deviceEvent.value;
    console.log('coolingSetpoint haha',setpointTemperature);
    
})
  .subscribedEventHandler('pushedUpDeviceEventHandler', (context, deviceEvent) => {
    
   let setpointTemperature =  context.api.devices.getCapabilityStatus(context.config.thermosta[0].deviceConfig.deviceId,'main','thermostatCoolingSetpoint');
    
    //let setpointTemperature = 24;
    console.log('coolingSetpoint haha',setpointTemperature);
    
    
    // Increment the setpoint temperature by 1 degree
    let newSetpointTemperature = setpointTemperature + 1.0;
    //console.log(typeof newSetpointTemperature);
    // Set the new setpoint temperature
 
    return context.api.devices.sendCommands(context.config.thermostat, 'thermostatCoolingSetpoint', 'setCoolingSetpoint', [newSetpointTemperature]);
})

.subscribedEventHandler('pushedDownDeviceEventHandler', (context, deviceEvent) => {

    const setpointTemperature =  context.api.devices.getCapabilityStatus(context.config.thermosta[0].deviceConfig.deviceId,'main','thermostatCoolingSetpoint');
    // Decrement the setpoint temperature by 1 degree
    const newSetpointTemperature = setpointTemperature - 1.0;
    
    //return context.api.devices.sendCommands(context.config.thermostat, 'thermostatMode', 'off');
    return context.api.devices.sendCommands(context.config.thermostat, 'thermostatCoolingSetpoint', 'setCoolingSetpoint', [2]);
    //return context.api.devices.sendCommands(context.config.lights, 'switch', 'off');
})

/* Starts the server */
let port = process.env.PORT;
server.listen(port);
console.log(Open: http://127.0.0.1:${port});

However i got the follow response:

coolingSetpoint haha Promise { }

ok, I’ll share the complete code:

.subscribedDeviceHealthEventHandler('contactSensorEventHandler', async (context, deviceEvent) => {
    // catches both open/close events
    let currentTemp = await context.api.devices.getCapabilityStatus(context.config.thermostat[0].deviceConfig.deviceId,'main','thermostatCoolingSetpoint')
    console.log(currentTemp.coolingSetpoint.value)
    context.api.devices.sendCommands(context.config.thermostat,'thermostatCoolingSetpoint', 'setCoolingSetpoint', [25]);
    context.api.devices.sendCommands(context.config.colorc,'colorControl', 'setColor', [{hue:20,saturation:30}]);
})

Just as a reference, the function getCapabilityStatus is making an API request which means, it is asynchronous. So, what immediately returns to your variable setpointTemperature is the promise of this request, not the result.
That’s why I included await in the sample above because it waits for the promise to be fulfilled and saves the response in the variable. Setting the function as async is important for the await to be valid, you can get more info about it in the JavaScript documentation.
Also, I mentioned above that the result of a successful request has this format:

{
  coolingSetpoint: { value: 24, unit: 'C', timestamp: '2023-01-10T16:11:37.856Z' }
}

This means that you will be able to access the value 24 using currentTemp.coolingSetpoint.value

@nayelyz thank you my smartapp is now working perfectly

1 Like