Building First SmartApp for Dummies

About this post

I’m building my first SmartApp to migrate to the SmartThings Edge platform an app developed using legacy Groovy technology. I have very little knowledge about the Unix platform, NodeJS, AWS and SmartThings Edge. When I started from scratch, I made a lot of mistakes, because many things that seem obvious to me now were not obvious to me at the beginning. I just want to share my experience and the information I have gathered from various sources. I hope this will save you time to develop your first SmartApp.

Troubleshooting

You are likely to receive error messages during development. Something like this: “An unexpected error occurred… email us at support@smartthing.com … Reference ID …” or “Network error… Please try again later”. Do not contact SmartThings support (you will confuse them and they will not be able to help you) and do not try again later. This is your fault and the only way to resolve it is to look through the logs and ask this community for help. Here I found many thoughtful and helpful people who helped me solve my problems.

Hosting SmartApp

Before registering a SmartApp at Developer Workspace , you need to create it. :slight_smile: I use AWS because AWS can easily host WebHook and Lambda functions. The implementation is very similar. Here is the code I used for testing:

exports.handler = function (request, context, callback) {
    try {
        const webHood = request.lifecycle === undefined;
        if (webHood) {
            request = JSON.parse(request.body);
        }
        let response = ProcessRequest(request);
        if (response.statusCode === undefined)
            response.statusCode = 200;
        if (webHood)
            callback(null, { statusCode: 200, body: JSON.stringify(response) });
        else
            callback(null, response);
    } catch (ex) {
        callback({}, null);
    }
}

I tested both, WebHook and Lambda, and decided to use Lambda because I don’t need to check WebHook digital signatures, etc.

After you create AWS Lambda, you need to set permissions for the function. The document says to run the following command in a terminal shell:

aws lambda add-permission --profile [my-profile-name] --function-name [my-function-name] --statement-id smartthings --principal [PRINCIPAL ID FROM SMARTTHINGS] --action lambda:InvokeFunction

You must use CloudShell on AWS. CloudShell can be launched from the [>] icon in the upper right corner of the AWS console.

Don’t ask me what the “my-profile-name - your named profile as stored in your AWS config file” means. I just ran the following command and it did the job:

aws lambda add-permission --function-name [my-function-name] --statement-id smartthings --principal 906037444270 --action lambda:InvokeFunction

You can see the results in the Lambda console under Configuration/Permissions section.

SmartApp Registration

You can now register your SmartApp at Developer Workspace. You can even submit it for testing. The SmartThings backend won’t call it, but it will check permissions etc.

NodeJS SDK

Finally, you are ready to write the code. I’m using the REST API directly because I’m developing a stateless application and I want it to be lightweight and fast. But you probably want to use the NodeJS SDK.

I tried running “npm i @smartthings/smartapp --save” and found that the SDK was installed in the “/home/cloudshell-user/node_modules/@smartthings/smartapp” folder. But when I tried to add “const SmartApp = require(‘@smartthings/smartapp’)” to my source code, I got an error that the package was not found.

I believe I should add the package path to my Lambda config settings. However, I am not currently using the SDK. Can anyone share their experience of installing the NodeJS SDK?

[To be continued]

5 Likes

Hi! For SmartApps using the NodeJS SDK in AWS, you can take a reference from this sample:

I think we can’t upload the package directly to the Lambda function due to the size of the package, the sample above uses another service to upload the SmartApp.

BTW, thank you for sharing your experience!

Preparing for Installation

You need to implement lifecycle handling to be able to install SmartApp. Once again, I am using the REST API documented here: API | SmartThings Developers . If you choose to use the SDK, your implementation will be different.

SmartThings lifecycles are well documented here: Lifecycles | SmartThings Developers . Here is an example of a simple implementation:

function ProcessRequest(request) {
    const lifecycle = request.lifecycle;
    switch (lifecycle) {
        case "CONFIGURATION":
            return OnConfiguration(request);
        case "INSTALL":
            return OnInstall(request);
        case "UPDATE":
            return OnUpdate(request);
        case "UNINSTALL":
            return OnUninstall(request);
        case "EVENT":
            return OnEvent(request);
    }
}
function OnConfiguration(request) {
    const configurationData = request.configurationData;
    const phase = configurationData.phase;
    switch (phase) {
        case "INITIALIZE": {
            const response = {
                statusCode: 200,
                configurationData: {
                    initialize: {
                        id: request.appId,
                        firstPageId: "1",
                        permissions: [
                            "r:devices:*",
                            "x:devices:*"
                        ],
                        disableCustomDisplayName: false,
                        disableRemoveApp: false
                    }
                }
            };
            return response;
        }
        case "PAGE": {
            const response = {
                statusCode: 200,
                configurationData: {
                    page: {
                        pageId: "1",
                        name: "<myAppName>",
                        nextPageId: null,
                        previousPageId: null,
                        complete: true,
                        sections: [
                        ]
                    }
                }
            };
            return response;
        }
    }
}

function OnInstall(request) {
    OnAppData(request.lifecycle, request.installData);
    const response = {
        statusCode: 200,
        installData: {}
    };
    return response;
}

function OnUpdate(request) {
    OnAppData(request.lifecycle, request.updateData);
    const response = {
        statusCode: 200,
        updateData: {}
    };
    return response;
}

function OnAppData(lifecycle, data) {
    // Save data.installedApp.installedAppId
    // Save data.authToken
    // Save data.refreshToken
}

function OnUninstall(request) {
    const response = {
        statusCode: 200,
        uninstallData: {}
    };
    return response;
}

I spent a couple of days to get it to work. Many thanks to
nayelyz
for helping me with this.

First, permissions:

                        permissions: [
                            "r:devices:*",
                            "x:devices:*"
                        ],

The permissions here must match and have the same spelling as the permissions you defined during SmartApp registration at Developer Workspace

Second, the status code.

What does it mean when you see something like this in the document:

The SmartApp should respond to the INSTALL request with a 200 status and an empty installData object:
200 OK
{
  "installData": {}
}

It may be OK for the WebHood implementation that can return a status code, but you need to add the status code inside of your Lambda response:

{
   statusCode: 200,
   installData: {}
}

Finally, the appId.
Here is what was in the document:

{
  "configurationData": {
    "initialize": {
      "name": "On When Open\/Off When Shut WebHook App",
      "description": "On When Open\/Off When Shut WebHook App",
      "id": "app",
      "permissions": [],
      "firstPageId": "1"
    }
  }
}
The id must be unique among any other settings as defined below.

I was not sure what that meant. I am using the appId from the request. It works and I will not worry about it anymore.

First Installation

You need to run the SmartThings app on your mobile device (phone or tablet) in order to install your SmartApp. The Dashboard https://my.smartthings.com will not help you in this matter. You will need to enable the Developer mode in the SmartThings app settings (see Developer Workspace for instructions when submitting a SmartApp for testing). After that, go to the Automation section and try to create a new routine; tap “Discover”; find your SmartApp in the list and tap on it. This will take you to the installation screen. If you don’t get any error messages, you should see the installed app on the automation screen. Sometimes you will need to restart the SmartThings app to update the automation screen.

When you tap on your SmartApp on the Automation screen, you will be taken to the installation process. However, instead of INSTALL, you will receive an UPDATE message. This is a handy feature. I used it to refresh the authorization token before I figured out which API I should use to refresh the token.

Authorization Token

Authorization token

In order to do something useful, you need to communicate with https://api.smartthings.com. This is well documented in the doc API | SmartThings Developers. So far, no problem.

However, I had a problem with the authorization token. How to update it? The doc pointed me to an RFC that doesn’t help much. I was confused searching for this topic: different APIs, redirect URLs, etc. What was I supposed to do about it? Thanks to orangebucket who helped me with this issue.

Here is what I understood:

Personal Access Tokens (PAT)

You can get it at Authorization and Permissions | SmartThings Developers .

PAT will never expire. It’s linked to an account and I was hoping to be able to use it everywhere within an account. The answer is NO. For example, you can use PAT to get a list of devices. However, this won’t work if you want to subscribe to events, etc. What a pity. I don’t see the use of PAT for myself because I can use authToken everywhere I can use PAT.

Installed application authorization token (authToken)

You will receive authToken and refreshToken in the INSTALL/UPDATE request. The authToken will be valid for a day. After that, you will need to refresh it with a refreshToken which is valid for 30 days.

Here is an example using curl in PHP:

$ch = curl_init( "https://auth-global.api.smartthings.com/oauth/token" );
 curl_setopt( $ch, CURLOPT_FAILONERROR,   true );
 curl_setopt( $ch, CURLOPT_HTTPHEADER,    array( "Content-Type: application/x-www-form-urlencoded", "Authorization: Basic " . base64_encode( "{$clientId}:{$clientSecret}" ) ) );
 curl_setopt( $ch, CURLOPT_POSTFIELDS,    "grant_type=refresh_token&client_id={$id}&refresh_token={$refreshtoken}" );
 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true);
 $post = curl_exec( $ch );
 curl_close($ch);

I am not familiar with curl, but this example was good enough to understand how to refresh tokens.

There is something to share. I am under the impression that, unlike the Alexa API, the old refreshToken expires immediately after refresh, and you won’t be able to retry the call with the old values if you lose your updated authToken and refreshToken for some reason. However, don’t panic. You can always restore tokens using the application UPDATE on a mobile device as I mentioned above.

Event Token

The EVENT message contained an authToken which is a temporary token with a five-minute expiration time. Thus, you may not need to maintain the authToken obtained during the INSTALL step if your SmartApp only responds to EVENTS. What I mean is that if you subscribe to events during install and send SmartThings requests from event callbacks, your application is stateless and you don’t need to store and maintain the authToken at all.

Happy Development

I hope you find this information useful and share your experience as well.

3 Likes

Although the lifecycles are reasonably documented, I am sure there used to be a proper reference for the SmartApp API along the lines of the API Reference. It just vanished one day.

1 Like

Super helpful Adam! Thanks for taking the time to put together this post! :smile:

1 Like