Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. A single component cannot control multiple physical devices. The component must control a unique physical device, so it must have a "device_id" characteristic, instead of passing it as a parameter.
  2. The "api_region", "api_key", and "api_secret" parameters must also be characteristics of the component, since they are static data as well.
  3. Actions "turnOn" and "turnOff" will be implemented asynchronously for simplicity. In another section we will implement an asynchronous component.

...

Component's template

Code Block
languageyml
titletuya_bulb_baci.yml
linenumberstrue
working_dir: /home/francisco/workspace
prefix: ACSIOT
module: acsiot
component_name: TuyaBulbBaci
functions:
  - 'void turnOn()'
  - 'void turnOff()'
properties:
  - 'readonly attribute ACS::ROboolean status'

...

Component's IDL

Code Block
languagecpp
titleidlTuyaBulbBaci/idl/TuyaBulbBaci.idl
linenumberstrue
#ifndef _TUYABULBBACI_IDL_
#define _TUYABULBBACI_IDL_
 
#pragma prefix "ACSIOT"
 
#include <baci.idl>
 
module acsiot {
    interface TuyaBulbBaci : ACS::CharacteristicComponent {
        void turnOn();
        void turnOff();
        readonly attribute ACS::ROboolean status;
    };
};

#endif

Component

...

's Makefile

Code Block
languagecppbash
titlecppTuyaBulbBaci/include/TuyaBulbBaciImpl.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>
  
// 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 char* api_region;
        const char* api_key;
        const char* api_secret;
        const char* device_id;
        baci::ROboolean* status_p;
};
  
#endif
src/Makefile
...
TuyaBulbBaciImpl_OBJECTS = TuyaBulbBaciImpl hmac_sha256 sha256
TuyaBulbBaciImpl_LIBS = TuyaBulbBaciStubs baci ssl crypto
...

Component's header

Code Block
languagecpp
titlecppTuyaBulbBaci/include/TuyaBulbBaciImpl.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* m_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 body_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
            const std::string version = "v1.0"
        );
        std::string getToken();
};
   
#endif

Component's implementation

Code Block
languagecpp
titlecppTuyaBulbBaci/src/TuyaBulbBaciImpl.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("..."),
    api_secret("..."),
    device_id("..."),
    base_url("https://openapi.tuyaus.com"),
    m_status_p(0)
{
    std::cout << "Constructing TuyaBulbBaciImpl object." << std::endl;
    token = getToken();
    m_status_p  = new baci::ROboolean(name + ":status", getComponent());
    CHARACTERISTIC_COMPONENT_PROPERTY(status, m_status_p);
    // 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() {
	if (m_status_p != 0) {
		m_status_p->destroy();
		m_status_p=0;
	}
}
 
void TuyaBulbBaciImpl::turnOn() {
    std::cout << "Turning smart bulb ON..." << std::endl;

    // Set request parameters
    const std::string uri = "/iot-03/devices/" + this->device_id + "/commands";
    const std::string action = "POST";
    const std::string headers_str = "";
    const std::string body = "{\"commands\":[{\"code\":\"switch_led\",\"value\":true}]}";
    const std::string body_sha256 = "8479c9c60cd5d531054c49333c7b361a9ce41b9b313ab8eb6bc9df4141f658ef";
    std::map<std::string, std::string> headers_map;
    headers_map = {
        {"Content-type", "application/json"},
        {"access_token", this->token}
    };

    // Send request and print response
    httplib::Result response = this->sendRequest(uri, action, headers_map, headers_str, body, body_sha256);
    std::cout << "Response status:\n";
    std::cout << response->status << std::endl;
    std::cout << "Response body:\n";
    std::cout << response->body << std::endl;

    // Check API response
    if (response->status == 200) {
        json body = json::parse(response->body);
        if (body["success"].dump() == "true") {
            m_status_p = new baci::ROboolean(true);
        } else {
            throw std::runtime_error("Error while turning smart bulb ON.\n");
        }
    }
}
 
void TuyaBulbBaciImpl::turnOff() {
    std::cout << "Turning smart bulb OFF..." << std::endl;

    // Set request parameters
    const std::string uri = "/iot-03/devices/" + this->device_id + "/commands";
    const std::string action = "POST";
    const std::string headers_str = "";
    const std::string body = "{\"commands\":[{\"code\":\"switch_led\",\"value\":false}]}";
    const std::string body_sha256 = "c9df53ad98d9c9be68680613d9ece634a27f102c5e40c7b5f60c11f6b944b6a9";
    std::map<std::string, std::string> headers_map;
    headers_map = {
        {"Content-type", "application/json"},
        {"access_token", this->token}
    };

    // Send request and print response
    httplib::Result response = this->sendRequest(uri, action, headers_map, headers_str, body, body_sha256);
    std::cout << "Response status:\n";
    std::cout << response->status << std::endl;
    std::cout << "Response body:\n";
    std::cout << response->body << std::endl;

    // Check API response
    if (response->status == 200) {
        json body = json::parse(response->body);
        if (body["success"].dump() == "true") {
            m_status_p = new baci::ROboolean(false);
        } else {
            throw std::runtime_error("Error while turning smart bulb OFF.\n");
        }
    }
}

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 body_sha256,
        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;

    // String stringToSign = 
    // HTTPMethod + "\n" + 
    // Content-SHA256 + "\n" +
    // Headers + "\n" +
    // Url
    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;

    // str = client_id + access_token + t + nonce + stringToSign 
    // sign = HMAC-SHA256(str, secret).toUpperCase()
    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
    hmac_sha256(this->api_secret.data(), this->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 base headers
    httplib::Headers request_headers = {
        { "client_id", this->client_id },
        { "sign_method", "HMAC-SHA256" },
        { "sign", sign },
        { "t", std::to_string(t) }
    };

    // Add extra 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);

    // Check 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;
        }
    }

    throw std::runtime_error("Error while getting API token.\n")
Code Block
languagecpp
titlecppTuyaBulbBaci/src/TuyaBulbBaciImpl.cpp
#include <TuyaBulbBaciImpl.h>

#include <stdexcept>
  
TuyaBulbBaciImpl::TuyaBulbBaciImpl(const ACE_CString& name, maci::ContainerServices* containerServices) : CharacteristicComponentImpl(name, containerServices) {
    cout << "Constructing TuyaBulbBaciImpl object." << endl;
    this->api_region = "us";
    this->api_key = "...";
    this->api_secret = "...";
    this->device_id = "...";
}
  
TuyaBulbBaciImpl::~TuyaBulbBaciImpl() {
}

void TuyaBulbBaciImpl::turnOn() {
    cout << "Turning smart bulb ON..." << endl;
    cout << "API Region: " << this->api_region << endl;
    cout << "API Key: " << this->api_key << endl;
    cout << "API Secret: " << this->api_secret << endl;
    cout << "Device ID: " << this->device_id << endl;
    cout << "Done." << endl;
}

void TuyaBulbBaciImpl::turnOff() {
    cout << "Turning smart bulb OFF..." << endl;
    cout << "API Region: " << this->api_region << endl;
    cout << "API Key: " << this->api_key << endl;
    cout << "API Secret: " << this->api_secret << endl;
    cout << "Device ID: " << this->device_id << endl;
    cout << "Done." << endl;
}

ACS::ROboolean_ptr TuyaBulbBaciImpl::status () {
    cout << "Returning smart bulb status" << endl;
    returnif(m_status_p == 0) {
		return ACS::ROboolean::_nil();
	}

    ACS::ROboolean_var prop = ACS::ROboolean::_nil_narrow(m_status_p->getCORBAReference());
	return prop._retn();
}
   
/* --------------- [ MACI DLL support functions ] -----------------*/
#include <maciACSComponentDefines.h>
MACI_DLL_SUPPORT_FUNCTIONS(TuyaBulbBaciImpl)
/* ----------------------------------------------------------------*/

...