Information
We are currently investigating an issue with the editor of some pages. Please save your work and avoid to create new pages until this banner is gone.
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:
... INCLUDES = HelloComponentImpl.h ... LIBRARIES = HelloComponentImpl HelloComponentImpl_OBJECTS = HelloComponentImpl HelloComponentImpl_LIBS = HelloComponentStubs acscomponent ...
We fill the component code as follows:
vim ../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
#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) /* ----------------------------------------------------------------*/
export INTROOT=~/workspace/introot export LD_LIBRARY_PATH=$INTROOT/lib:$LD_LIBRARY_PATH make all install
vim $ACS_CDB/CDB/MACI/Components/Components.xml
Add the following item:
<e Name="HelloWorldCPP" Code="HelloComponentImpl" Type="IDL:acsws/workshop/HelloComponent:1.0" Container="bilboContainer" ImplLang="cpp" />
Create client python file:
cd ~/workspace touch client.py vim client.py
We fill it as follow:
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
... Hello World! ...