You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 13 Next »

Create component

cd ~/workspace
getTemplateForDirectory MODROOT_WS cppHelloComp
cd cppHelloComp/src
touch HelloComponentImpl.cpp
touch ../include/HelloComponentImpl.h
vim Makefile

We modify the Makefile for this component:

cppHelloComp/src/Makefile
...
INCLUDES = HelloComponentImpl.h
...
LIBRARIES = HelloComponentImpl
HelloComponentImpl_OBJECTS = HelloComponentImpl
HelloComponentImpl_LIBS = HelloComponentStubs acscomponent
...

We fill the component code as follows:

vim ../include/HelloComponentImpl.h
cppHelloComp/include/HelloComponentImpl.h
#ifndef _TUYABULBBACI_IMPL_H
#define _TUYABULBBACI_IMPL_H
   
#ifndef __cplusplus
#error This is a C++ include file and cannot be used from plain C
#endif
   
// Base component implementation, including container services and component lifecycle infrastructure
#include <baciCharacteristicComponentImpl.h>
#include <baciROboolean.h>
   
// Skeleton interface for server implementation
#include <TuyaBulbBaciS.h>

// Extras
#include "httplib.h"
#include "hmac_sha256.h"
#include "json.hpp"
   
// Error definitions for catching and raising exceptions
class TuyaBulbBaciImpl : public virtual baci::CharacteristicComponentImpl, public virtual POA_acsiot::TuyaBulbBaci {
    public:
        TuyaBulbBaciImpl(const ACE_CString& name, maci::ContainerServices* containerServices);
        virtual ~TuyaBulbBaciImpl();
 
        void turnOn();
        void turnOff();
        virtual ACS::ROboolean_ptr status();
 
    private:
        const std::string api_region;
        const std::string client_id;
        const std::string api_secret;
        const std::string device_id;
        const std::string base_url;
        std::string token;
        baci::ROboolean* status_p;

        httplib::Result sendRequest(
            const std::string uri,
            const std::string action = "GET",
            const std::map<std::string, std::string> headers_map = {},
            const std::string headers_str = "",
            const std::string body = "",
            const std::string version = "v1.0"
        );
        std::string getToken();
};
   
#endif
vim HelloComponentImpl.cpp
cppHelloComp/src/HelloComponentImpl.cpp
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <TuyaBulbBaciImpl.h>
 
#include <stdexcept>

// Extras
#define SHA256_HASH_SIZE 32
using json = nlohmann::json;

   
TuyaBulbBaciImpl::TuyaBulbBaciImpl(const ACE_CString& name, maci::ContainerServices* containerServices) : CharacteristicComponentImpl(name, containerServices),
    api_region("us"),
    client_id("tsvjqcdhbkr7dt2kd0jo"),
    api_secret("58a64e482618444bae58cee0482894cb"),
    device_id("eba0ded8071548b1449jy8"),
    base_url("https://openapi.tuyaus.com")
{
    std::cout << "Constructing TuyaBulbBaciImpl object." << std::endl;

    std::string token = this->getToken();
    this->token = token;

    // std::cout << "API Region: " << this->api_region << std::endl;
    // std::cout << "Client ID: " << this->client_id << std::endl;
    // std::cout << "API Secret: " << this->api_secret << std::endl;
    // std::cout << "Device ID: " << this->device_id << std::endl;
    // std::cout << "API Token: " << this->token << std::endl;
}
   
TuyaBulbBaciImpl::~TuyaBulbBaciImpl() {
}
 
void TuyaBulbBaciImpl::turnOn() {
    std::cout << "Turning smart bulb ON..." << std::endl;

    const std::string uri = "iot-03/devices/" + this->device_id + "/commands";
    const std::string action = "POST";
    std::map<std::string, std::string> headers_map;
    headers_map = {
        {"mode", "cors"},
        {"Content-Type", "application/json"},
        {"access_token", this->token}
    };
    const std::string headers_str = "mode:cors\nContent-Type:application/json";
    const std::string body = "{\"commands\":[{\"code\":\"switch_led\",\"value\":true}]}";

    httplib::Result response = this->sendRequest(uri, action, headers_map, headers_str, body);
    std::cout << "Response status:\n";
    std::cout << response->status << std::endl;
    std::cout << "Response body:\n";
    std::cout << response->body << std::endl;
}
 
void TuyaBulbBaciImpl::turnOff() {
    std::cout << "Turning smart bulb OFF..." << std::endl;
}

httplib::Result TuyaBulbBaciImpl::sendRequest(
        const std::string uri,
        const std::string action,
        const std::map<std::string, std::string> headers_map,
        const std::string headers_str,
        const std::string body,
        const std::string version
    ) {
    // https://developer.tuya.com/en/docs/iot/api-request?id=Ka4a8uuo1j4t4
    // https://developer.tuya.com/en/docs/iot/singnature?id=Ka43a5mtx1gsc
    std::cout << "Sending request to Tuya API " << uri << std::endl;

    std::cout << "Constructing sign string..." << std::endl;
    // String stringToSign = 
    // HTTPMethod + "\n" + 
    // Content-SHA256 + "\n" +
    // Headers + "\n" +
    // Url
    // 
    // str = client_id + access_token + t + nonce + stringToSign 
    // sign = HMAC-SHA256(str, secret).toUpperCase()

    std::string body_sha256;
    if (body.size() == 0) {
        // Hardcoded SHA256 empty body.
        body_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
    } else {
        // Allocate memory for the SHA256
        std::vector<uint8_t> out(SHA256_HASH_SIZE);

        // Call sha256 function and parse result to string stream
        std::cout << "Encripting body string..." << std::endl;
        sha256(body.data(), body.size(), out.data(), out.size());

        std::stringstream ss_result;
        for (uint8_t i : out) {
            ss_result << std::hex << std::setfill('0') << std::setw(2) << (int)i;
        }
        body_sha256 = ss_result.str();
        std::cout << "Body SHA256: " << body_sha256 << std::endl;
    }

    const std::string url = "/" + version + uri;
    const std::string string_to_sign = action + "\n" + body_sha256 + "\n" + headers_str + "\n" + url;

    // Get time in milliseconds since epoch, with 13 digits
    std::time_t t = std::time(nullptr) * 1000;

    std::string str_data;
    if (this->token.empty()) {
        str_data = this->client_id + std::to_string(t) + string_to_sign;
    } else {
        str_data = this->client_id + this->token + std::to_string(t) + string_to_sign;
    }

    // Allocate memory for the HMAC
    std::vector<uint8_t> out(SHA256_HASH_SIZE);

    // Call hmac-sha256 function and parse result to string stream
    std::cout << "Encripting sign string..." << std::endl;
    hmac_sha256(api_secret.data(), api_secret.size(), str_data.data(), str_data.size(),
                out.data(), out.size());
    
    std::stringstream ss_result;
    for (uint8_t i : out) {
        ss_result << std::hex << std::setfill('0') << std::setw(2) << (int)i;
    }

    // to uppercase
    std::string sign = ss_result.str();
    transform(sign.begin(), sign.end(), sign.begin(), ::toupper);

    // Create headers
    httplib::Headers request_headers = {
        { "client_id", this->client_id },
        { "secret", this->api_secret },
        { "sign", sign },
        { "t", std::to_string(t) },
        { "sign_method", "HMAC-SHA256" },
    };

    // Add headers
    std::map<std::string, std::string>::const_iterator it;
    for (it = headers_map.begin(); it != headers_map.end(); it++) {        
        request_headers.insert({ it->first, it->second });
    }

    // Create HTTPS client
    httplib::Client cli(this->base_url);

    // Send request
    if (action.compare("GET") == 0) {
        return cli.Get(url.c_str(), request_headers);
    } else {
        return cli.Post(url.c_str(), request_headers, body.c_str(), "application/json");
    }
}

std::string TuyaBulbBaciImpl::getToken() {
    // https://openapi.tuyaus.com/v1.0/token?grant_type=1
    const std::string uri = "/token?grant_type=1";
    httplib::Result response = this->sendRequest(uri);

    // parse API response
    if (response->status == 200) {
        json body = json::parse(response->body);
        
        if (body["success"].dump() == "true") {
            const std::string token = body["result"]["access_token"];
            return token;
        }
    }

    // std::cout << "Response status:" << std::endl;
    // std::cout << response->status << std::endl;
    // std::cout << "Response body:" << std::endl;
    // std::cout << response->body << std::endl;

    throw std::runtime_error("Error while getting API token.\n");
}

ACS::ROboolean_ptr TuyaBulbBaciImpl::status () {
    std::cout << "Returning smart bulb status" << std::endl;
    return ACS::ROboolean::_nil();
}
   
/* --------------- [ MACI DLL support functions ] -----------------*/
#include <maciACSComponentDefines.h>
MACI_DLL_SUPPORT_FUNCTIONS(TuyaBulbBaciImpl)
/* ----------------------------------------------------------------*/

Compile component

export INTROOT=~/workspace/introot
export LD_LIBRARY_PATH=$INTROOT/lib:$LD_LIBRARY_PATH
make all install

Add component to CDB

vim $ACS_CDB/CDB/MACI/Components/Components.xml

Add the following item:

Components.xml
<e Name="HelloWorldCPP" Code="HelloComponentImpl"
                        Type="IDL:acsws/workshop/HelloComponent:1.0"
                        Container="bilboContainer" ImplLang="cpp" />

Testing

Create client python file:

cd ~/workspace
touch client.py
vim client.py

We fill it as follow:

~/workspace/client.py
from Acspy.Clients.SimpleClient import PySimpleClient
 
client = PySimpleClient()
 
hc_cpp = client.getComponent("HelloWorldCPP")
print(hc_cpp.printHello())

Run component:

# in one terminal
acsStop
acsStart
# in a different terminal
acsStartContainer -cpp bilboContainer
# in a different terminal
python client.py

Example output:

...
Hello World!
...
  • No labels