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.
In this section we will modify our smart bulb ACS component in order to make it BACI compliant. BACI is a control interface specification we use for ACS at ALMA. For more details this document is available. There is also another useful tutorial here. BACI is only fully available for C++ components only. Java and Python have partial BACI implementations. Because of this we will implement this component in C++.
From these different sources we can take some guidelines:
When we are defining actions, we must write its arguments and what it returns. Every asynchronous action must have "in ACS::CBvoid cb, in ACS:CBDescIn desc". Note that beside arguments there is also a word "in". Here is short explanation of all possibilities (from Advance CORBA programming with C++):
...
To make our component a BACI compliant code, we must perform the following modifications:
This is the repository with all the code and external libraries needed: https://bitbucket.sco.alma.cl/users/fcaro/repos/acs-baci-smart-bulb. You should be able to insert your API credentials in the component's implementation and run this code as is.
Code Block | ||||||
---|---|---|---|---|---|---|
Code Block | ||||||
| ||||||
working_dir: /home/franciscodeveloper/workspace prefix: ACSIOT module: acsiot component_name: TuyaBulbBaci functions: - 'void turnOn()' - 'void turnOff()' properties: - 'readonly attribute ACS::ROboolean status' |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#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 |
Code Block | ||||
---|---|---|---|---|
| ||||
... TuyaBulbBaciImpl_OBJECTS = TuyaBulbBaciImpl hmac_sha256 sha256 TuyaBulbBaciImpl_LIBS = TuyaBulbBaciStubs baci ssl crypto ... |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#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(); // 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(); }; #endif |
bool getStatus();
};
// DevIO template 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();
return component->getStatus();
}
virtual void write(const T& value, ACS::Time& timestamp) {
// no op, since status is a read only characteristic
}
protected:
TuyaBulbBaciImpl* component;
};
#endif |
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
#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 Secret: " << this->api_secret << std::endl;
// std::cout << "Device ID: " << this->device_id | ||||||||
Code Block | ||||||||
| ||||||||
#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 << "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); } else { throw std::runtime_error("Error while turning smart bulb ON.\n"); 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 = { } } } void TuyaBulbBaciImpl::turnOff() { {"Content-type", "application/json"}, std::cout << {"Turning smart bulb OFF..." << std::endlaccess_token", this->token} }; // SetSend request and print parametersresponse const stdhttplib::stringResult uriresponse = "/iot-03/devices/" + this->device_id + "/commands"this->sendRequest(uri, action, headers_map, headers_str, body, body_sha256); const// std::stringcout action<< = "POST""Response status:\n"; const// std::string headers_str = ""cout << response->status << std::endl; const// std::stringcout body<< = "{\"commands\":[{\"code\":\"switch_led\",\"value\":false}]}"Response body:\n"; const// std::stringcout body_sha256 = "c9df53ad98d9c9be68680613d9ece634a27f102c5e40c7b5f60c11f6b944b6a9"; std::map<std::string, std::string> headers_map; headers_map = {<< response->body << std::endl; // 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 std::cout << response->body << std::endl; { // Check API response if (response->status == 200) { throw std::runtime_error("Error while turning smart bulb ON.\n"); } json body = json} } void TuyaBulbBaciImpl::parseturnOff(response->body); { std::cout << "Turning smart if (body["success"].dump() == "true") {bulb OFF..." << std::endl; // Set request parameters m_status_p = new baci::ROboolean(false); } else {const std::string uri = "/iot-03/devices/" + this->device_id + "/commands"; throw const std::runtime_error("Error while turning smart bulb OFF.\n")string action = "POST"; } } } httplib::Result TuyaBulbBaciImpl::sendRequest( const std::string headers_str = ""; const std::string uri, body = "{\"commands\":[{\"code\":\"switch_led\",\"value\":false}]}"; const std::string action, body_sha256 = "c9df53ad98d9c9be68680613d9ece634a27f102c5e40c7b5f60c11f6b944b6a9"; const std::map<std::string, std::string> headers_map,; const std::string headers_str, map = { const std::string body, {"Content-type", "application/json"}, const std::string body_sha256, {"access_token", this->token} }; // Send request const std::string versionand print response ) { // https://developer.tuya.com/en/docs/iot/api-request?id=Ka4a8uuo1j4t4httplib::Result response = this->sendRequest(uri, action, headers_map, headers_str, body, body_sha256); // https://developer.tuya.com/en/docs/iot/singnature?id=Ka43a5mtx1gsc std::cout << "Sending request to Tuya API " << uri"Response status:\n"; // std::cout << response->status << std::endl; // String stringToSign = std::cout << "Response body:\n"; // HTTPMethod + "\n" + std::cout << response->body << std::endl; // Content-SHA256Check + "\n" +API response // Headers + "\n" + if (response->status == 200) { // Url json body const= std::string url = "/" + version + urijson::parse(response->body); const std::string string_to_sign = action + "\n" + body_sha256 + "\n" + headers_str + "\n" + url; if (body["success"].dump() == "true") { // 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()) {std::cout << "Device turned OFF successfully." << std::endl; } else { throw std::runtime_error("Error while turning smart bulb OFF.\n"); } } } httplib::Result TuyaBulbBaciImpl::sendRequest( str_data = this->client_id + const std::to_string(t) + string_to_sign; uri, } else { const std::string action, str_data = this->client_id + this->token +const std::map<std:to_string(t) + string_to_sign;:string, std::string> headers_map, } const // Allocate memory for the HMAC std::string headers_str, const std::vector<uint8_t> out(SHA256_HASH_SIZE); string body, // 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(), const std::string body_sha256, const std::string version ) { out.data(), out.size());// https://developer.tuya.com/en/docs/iot/api-request?id=Ka4a8uuo1j4t4 // https://developer.tuya.com/en/docs/iot/singnature?id=Ka43a5mtx1gsc std::stringstream ss_result; for (uint8_t i : out) {cout << "Sending request to Tuya API " << uri << std::endl; // String stringToSign = ss_result << std::hex << std::setfill('0') << std::setw(2) << (int)i; } // HTTPMethod + "\n" + // to uppercase Content-SHA256 + "\n" + std::string sign = ss_result.str(); transform(sign.begin(), sign.end(), sign.begin(), ::toupper); // Headers + "\n" + // Create base headers Url const httplibstd::Headersstring request_headersurl = { "/" + version + uri; { "client_id", this->client_id }, { "sign_method", "HMAC-SHA256" },const std::string string_to_sign = action + "\n" + body_sha256 + "\n" + headers_str + "\n" + url; // Get time in { "sign", sign }, milliseconds since epoch, with 13 digits { "t",std::time_t t = std::to_stringtime(tnullptr) } }* 1000; // Add extra headers std::map<std::string, std::string>::const_iterator it; str = client_id + access_token + t + nonce + stringToSign for// (itsign = headers_map.begin(); it != headers_map.end(); it++) { HMAC-SHA256(str, secret).toUpperCase() std::string str_data; if (this->token.empty()) { request_headers.insert({ it->first, it->second })str_data = this->client_id + std::to_string(t) + string_to_sign; } else { // Create HTTPS client str_data = httplib::Client cli(this->base_url); this->client_id + this->token + std::to_string(t) + string_to_sign; //} Send request // Allocate if (action.compare("GET") == 0) {memory for the HMAC 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"];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" }, return token; { "sign", sign }, } { throw"t", std::runtimeto_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) /* ----------------------------------------------------------------*/ |
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");
}
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)
/* ----------------------------------------------------------------*/ |
Code Block | ||||
---|---|---|---|---|
| ||||
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() |
Code Block | ||||
---|---|---|---|---|
| ||||
>>> 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 | ||||||
---|---|---|---|---|---|---|
| ||||||
<?xml version='1.0' encoding='ISO-8859-1'?>
<!--
- History:
- Wed May 06 09:02:21 UTC 2009 modified by jDAL
- Wed May 06 09:05:40 UTC 2009 modified by jDAL
-->
<TuyaBulbBaci xmlns="urn:schemas-cosylab-com:TuyaBulbBaci:1.0" xmlns:baci="urn:schemas-cosylab-com:BACI:1.0" xmlns:cdb="urn:schemas-cosylab-com:CDB:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<status
default_timer_trig="1.0"
description="status for smart bulb device"
units="bit"
archive_min_int="0"
archive_max_int="1" />
</TuyaBulbBaci> |
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
<?xml version="1.0" encoding="ISO-8859-1"?>
<xs:schema
targetNamespace="urn:schemas-cosylab-com:TuyaBulbBaci:1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:schemas-cosylab-com:TuyaBulbBaci:1.0"
xmlns:cdb="urn:schemas-cosylab-com:CDB:1.0"
xmlns:baci="urn:schemas-cosylab-com:BACI:1.0" elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:import namespace="urn:schemas-cosylab-com:CDB:1.0" schemaLocation="CDB.xsd"/>
<xs:import namespace="urn:schemas-cosylab-com:BACI:1.0" schemaLocation="BACI.xsd"/>
<xs:complexType name="TuyaBulbBaci">
<xs:complexContent>
<xs:extension base="baci:CharacteristicComponent">
<xs:sequence>
<xs:element name="status" type="baci:ROboolean" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="TuyaBulbBaci" type="TuyaBulbBaci"/>
</xs:schema> | ||||||
Code Block | ||||||
| ||||||
from Acspy.Clients.SimpleClient import PySimpleClient
client = PySimpleClient()
bulb_comp = client.getComponent("TuyaBulbBaciCPP")
bulb_comp.turnOn()
bulb_comp.turnOff() |