Versions Compared

Key

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

...

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>
   #include <baciDevIO.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
template <class T> class TuyaBulbBaciDevIO;

class TuyaBulbBaciImpl : public virtual baci::CharacteristicComponentImpl, public virtual POA_acsiot::TuyaBulbBaci {
    // This is for calling private function getStatus from TuyaBulbBaciDevIO object
    friend TuyaBulbBaciDevIO<bool>;

    public:
        TuyaBulbBaciImpl(const ACE_CString& name, maci::ContainerServices* containerServices);
        virtual ~TuyaBulbBaciImpl();
 
        void turnOn();
        void turnOff();
        virtual ACS::ROboolean_ptr status();
 
    private// This is the public function to check the BACI property value
        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;
        // This is our BACI property
        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();
        bool getStatus();
};

// DevIO  
#endif

Component's implementation (click on "Expand source")

Code Block
languagecpp
titlecppTuyaBulbBaci/src/TuyaBulbBaciImpl.cpp
collapsetrue
#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_ptemplate definition for TuyaBulbBaci
template <class T> class TuyaBulbBaciDevIO : public DevIO<T> {
    public:
        TuyaBulbBaciDevIO(TuyaBulbBaciImpl* component) :
            component(component)
        {};

        virtual ~TuyaBulbBaciDevIO() {};

        virtual bool initializeValue(){ return false; }

        virtual T read(ACS::Time& timestamp) {
            timestamp = ::getTimeStamp();
    // std::cout << "API Region: " << this->api_region << std::endl;        return component->getStatus();
        }

    // std::cout << "Client ID:virtual "void << this->client_id << std::endl;write(const T& value, ACS::Time& timestamp) {
    // std::cout << "API Secret: " << this->api_secret << std::endl;
    // std::cout << "Device ID: " << this->device_id << std::endl; // no op, since status is a read only characteristic
        }

    protected:
        TuyaBulbBaciImpl* component;
};

#endif

Component's implementation (click on "Expand source")

Code Block
languagecpp
titlecppTuyaBulbBaci/src/TuyaBulbBaciImpl.cpp
collapsetrue
#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();
    TuyaBulbBaciDevIO<bool>* m_booleanDevIO = new TuyaBulbBaciDevIO<bool>(this);
    m_status_p  = new baci::ROboolean(name + ":status", getComponent(), m_booleanDevIO);
    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 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->statusstd::cout << "API Secret: " << this->api_secret << std::endl;
    // std::cout << "Device ID: " << this->device_id << std::endl;
    // std::cout << "ResponseAPI bodyToken:\n ";
    std::cout << responsethis->body>token << 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)
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  } else {= "";
            throw const std::runtime_error("Error while turning smart bulb ON.\n");
        }string body = "{\"commands\":[{\"code\":\"switch_led\",\"value\":true}]}";
    }
}
 
void TuyaBulbBaciImpl::turnOff() {const std::string body_sha256 = "8479c9c60cd5d531054c49333c7b361a9ce41b9b313ab8eb6bc9df4141f658ef";
    std:::cout << "Turning smart bulb OFF..." << std::endl;

map<std::string, std::string> headers_map;
    headers_map = {
      // Set request parameters {"Content-type", "application/json"},
    const std::string uri = "/iot-03/devices/" + this->device_id + "/commands";
{"access_token", this->token}
    };

    const std::string action = "POST";// Send request and print response
    const std::stringhttplib::Result response = this->sendRequest(uri, action, headers_map, headers_str, = ""body, body_sha256);
    const// std::stringcout body<< = "{\"commands\":[{\"code\":\"switch_led\",\"value\":false}]}""Response status:\n";
    // std::cout << response->status << std::endl;
    const// std::stringcout body_sha256 = "c9df53ad98d9c9be68680613d9ece634a27f102c5e40c7b5f60c11f6b944b6a9"<< "Response body:\n";
    // std::map<std::string,cout << response->body << std::string> headers_mapendl;

    headers_map = {// Check API response
    if (response->status == 200) {"Content-type", "application/json"},
        {"access_token", this->token}
    };

json body = json::parse(response->body);
    // Send request and print response
    httplib::Result response = this->sendRequest(uri, action, headers_map, headers_str, body, body_sha256);
if (body["success"].dump() == "true") {
            std::cout << "Response status:\n";
    std::cout << response->status Device turned ON successfully." << std::endl;
    std::cout  << "Response body:\n";
 } else {
            throw std::cout << response->body << std::endl;

runtime_error("Error while turning smart bulb ON.\n");
     // Check API response}
    if (response->status == 200}
}
 
void TuyaBulbBaciImpl::turnOff() {
    std::cout << "Turning smart json body = json::parse(response->body);
   bulb OFF..." << std::endl;

    // Set request parameters
    const if (body["success"].dump() == "true") {
   std::string uri = "/iot-03/devices/" + this->device_id + "/commands";
    const     m_status_p = new baci::ROboolean(false)std::string action = "POST";
    const std::string headers_str  } else {= "";
            throwconst std::runtime_error("Error while turning smart bulb OFF.\n");
        }
    }
}

httplib::Result TuyaBulbBaciImpl::sendRequest(
        string body = "{\"commands\":[{\"code\":\"switch_led\",\"value\":false}]}";
    const std::string uri,
  body_sha256 = "c9df53ad98d9c9be68680613d9ece634a27f102c5e40c7b5f60c11f6b944b6a9";
      const std::map<std::string action,
        const std::map<std::string, std::string>std::string> headers_map;
    headers_map, = {
        const std::string headers_str{"Content-type", "application/json"},
        const std::string body,{"access_token", this->token}
    };

    const std::string body_sha256,
        const// Send request and print response
    httplib::Result response = this->sendRequest(uri, action, headers_map, headers_str, body, body_sha256);
    // std::stringcout version
<<    ) {"Response status:\n";
    // https://developer.tuya.com/en/docs/iot/api-request?id=Ka4a8uuo1j4t4std::cout << response->status << std::endl;
    // https://developer.tuya.com/en/docs/iot/singnature?id=Ka43a5mtx1gsc
    std::cout << "Response "Sending request to Tuya API " << uribody:\n";
    // std::cout << response->body << std::endl;

    // StringCheck stringToSignAPI = response
    // HTTPMethod + "\n" + if (response->status == 200) {
    // Content-SHA256 + "\n" +
    // Headers + "\n" + json body = json::parse(response->body);
    // Url
    const std::string url if (body["success"].dump() == "/true" + version + uri;) {
    const std::string string_to_sign = action + "\n" + body_sha256 + "\n" + headers_str + "\n" + url;

 std::cout << "Device turned OFF successfully." << std::endl;
    // Get time in milliseconds since epoch, with 13 digits
  } else {
          std::time_t t =throw std::time(nullptr) * 1000;

    // str = client_id + access_token + t + nonce + stringToSign 
    // sign = HMAC-SHA256(str, secret).toUpperCase()
   runtime_error("Error while turning smart bulb OFF.\n");
        }
    }
}

httplib::Result TuyaBulbBaciImpl::sendRequest(
        const std::string str_data;uri,
    if (this->token.empty()) {
       const std::string action,
     str_data = this->client_id +const std::map<std::to_string(t) + string_to_sign;, std::string> headers_map,
    } else {
  const std::string headers_str,
    str_data = this->client_id + this->token +const std::to_string(t) + string_to_sign;
string body,
      }

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

string version
    ) {
    // 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)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;

    // Create base headers Get time in milliseconds since epoch, with 13 digits
    httplibstd::Headers request_headerstime_t t = {
  std::time(nullptr) * 1000;

    // str {= "client_id", this->client_id },
     + access_token + t + nonce + stringToSign 
    {// "sign_method", "sign = HMAC-SHA256" },(str, secret).toUpperCase()
    std::string str_data;
   { "sign", sign },if (this->token.empty()) {
        { "t", str_data = this->client_id + std::to_string(t) }+ string_to_sign;
    };

 else {
     // Add extra headers
str_data = this->client_id + this->token + std::map<std::string, std::string>::const_iterator itto_string(t) + string_to_sign;
    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)}

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

 else {
  // to uppercase
    return cli.Post(url.c_strstd::string sign = ss_result.str();
    transform(sign.begin(), request_headerssign.end(), bodysign.c_strbegin(), "application/json"::toupper);

    // Create base }
}

std::string TuyaBulbBaciImpl::getToken()headers
    httplib::Headers request_headers = {
    // https://openapi.tuyaus.com/v1.0/token?grant_type=1    { "client_id", this->client_id },
    const  std::string uri ={ "/token?grant_type=1";sign_method", "HMAC-SHA256" },
    httplib::Result  response = this->sendRequest(uri);

    // Check API response { "sign", sign },
    if (response->status == 200) {
        json body = json::parse(response->body);"t", std::to_string(t) }
    };

    
// Add extra headers
     if (body["success"].dump() == "true") {
std::map<std::string, std::string>::const_iterator it;
    for (it = headers_map.begin(); it != headers_map.end(); it++) {        
    const std::string token = body["result"]["access_token"] request_headers.insert({ it->first, it->second });
    }

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

   }
  // Send }request

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

ACS::ROboolean_ptr TuyaBulbBaciImpl::status () {
    if(m_status_p == 0) {
		return ACS::ROboolean::_nil();
	}

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

Client

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");
}

bool TuyaBulbBaciImpl::getStatus() {
    // https://openapi.tuyaus.com/v1.0/iot-03/devices/{device_id}/status
    const std::string uri = "/iot-03/devices/" + this->device_id + "/status";
    std::map<std::string, std::string> headers_map;
    headers_map = {
        {"access_token", this->token}
    };

    // Send request and print response
    httplib::Result response = this->sendRequest(uri, "GET", headers_map);
    // 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);

        // Return status
        if (body["success"].dump() == "true") {
            if (body["result"][0]["value"].dump() == "true") {
                return true;
            } else {
                return false;
            }
        }
    }

    throw std::runtime_error("Error while getting device status.\n");
}

ACS::ROboolean_ptr TuyaBulbBaciImpl::status () {
    if(m_status_p == 0) {
		return ACS::ROboolean::_nil();
	}

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

Client

Code Block
languagepy
from Acspy.Clients.SimpleClient import PySimpleClient

client = PySimpleClient()
bulb_comp = client.getComponent("TuyaBulbBaciCPP")
status = bulb_comp.status
bulb_comp.turnOn()
bulb_comp.status()
bulb_comp.turnOff()
bulb_comp.status()

Output

Code Block
languagepy
titlePython console output
>>> from Acspy.Clients.SimpleClient import PySimpleClient
>>>
>>> client = PySimpleClient()
>>> bulb_comp = client.getComponent("TuyaBulbBaciCPP")
>>> bulb_comp.status.get_sync()
(False, ACSErr.Completion(timeStamp=138772104611657330, type=0, code=0, previousError=[]))
>>> bulb_comp.turnOn()
>>> bulb_comp.status.get_sync()
(True, ACSErr.Completion(timeStamp=138772104679161260, type=0, code=0, previousError=[]))
>>> bulb_comp.turnOff()
>>> bulb_comp.status.get_sync()
(False, ACSErr.Completion(timeStamp=138772104807303050, type=0, code=0, previousError=[])
Code Block
languagepy
from Acspy.Clients.SimpleClient import PySimpleClient
 
client = PySimpleClient()
bulb_comp = client.getComponent("TuyaBulbBaciCPP")
bulb_comp.turnOn()
bulb_comp.turnOff()