demo version
This commit is contained in:
parent
fbb282a801
commit
672d6daa8e
125 changed files with 17918 additions and 1481 deletions
42
demo/milk_client/CMakeLists.txt
Normal file
42
demo/milk_client/CMakeLists.txt
Normal file
|
@ -0,0 +1,42 @@
|
|||
cmake_minimum_required (VERSION 3.8)
|
||||
|
||||
#Boost and openssl for https
|
||||
find_package(Boost REQUIRED)
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
#rapidjson
|
||||
find_package(RapidJSON CONFIG REQUIRED)
|
||||
|
||||
#spdlog
|
||||
find_package(spdlog CONFIG REQUIRED)
|
||||
|
||||
#date
|
||||
include( FetchContent )
|
||||
|
||||
FetchContent_Declare(date_src
|
||||
GIT_REPOSITORY https://github.com/HowardHinnant/date.git
|
||||
GIT_TAG v3.0.0 # adjust tag/branch/commit as needed
|
||||
)
|
||||
|
||||
SET(BUILD_TZ_LIB ON)
|
||||
SET(USE_SYSTEM_TZ_DB ON)
|
||||
|
||||
FetchContent_MakeAvailable(date_src)
|
||||
|
||||
add_executable(etl
|
||||
"src/etl.cpp")
|
||||
|
||||
target_include_directories(etl PRIVATE
|
||||
${RAPIDJSON_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(etl PRIVATE
|
||||
${OPENSSL_LIBRARIES}
|
||||
core
|
||||
spdlog::spdlog
|
||||
date::date
|
||||
senshamart_client)
|
||||
|
||||
IF(WIN32)
|
||||
target_compile_options(etl PRIVATE "/bigobj")
|
||||
ENDIF()
|
604
demo/milk_client/src/etl.cpp
Normal file
604
demo/milk_client/src/etl.cpp
Normal file
|
@ -0,0 +1,604 @@
|
|||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/beast/ssl.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
#include <rapidjson/document.h>
|
||||
#include <date/date.h>
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <spdlog/sinks/rotating_file_sink.h>
|
||||
#include <spdlog/sinks/stdout_sinks.h>
|
||||
|
||||
#include <senshamart/senshamart_client.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
using Clock = std::chrono::system_clock;
|
||||
|
||||
char to_base64_char(char c) {
|
||||
assert(c >= 0 && c < 64);
|
||||
|
||||
constexpr std::array<char, 64> conversion_table = {
|
||||
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
|
||||
'0','1','2','3','4','5','6','7','8','9','+','/' };
|
||||
|
||||
return conversion_table[c];
|
||||
}
|
||||
|
||||
std::string to_base64(boost::string_view str) {
|
||||
std::string returning;
|
||||
const std::size_t len = str.size() * 8;
|
||||
|
||||
char cur_sextet = 0;
|
||||
std::size_t sextet_size = 0;
|
||||
|
||||
for (std::size_t i = 0; i < str.size(); ++i) {
|
||||
for (std::size_t j = 0; j < CHAR_BIT; ++j) {
|
||||
cur_sextet <<= 1;
|
||||
cur_sextet |= (str[i] >> (CHAR_BIT - j - 1)) & 0x01;
|
||||
sextet_size++;
|
||||
if (sextet_size == 6) {
|
||||
returning.push_back(to_base64_char(cur_sextet));
|
||||
cur_sextet = 0;
|
||||
sextet_size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sextet_size != 0) {
|
||||
returning.push_back(to_base64_char(cur_sextet << (6 - sextet_size)));
|
||||
if (sextet_size == 4) {
|
||||
returning.push_back('=');
|
||||
} else if (sextet_size == 2) {
|
||||
returning.push_back('=');
|
||||
returning.push_back('=');
|
||||
}
|
||||
}
|
||||
|
||||
return returning;
|
||||
}
|
||||
|
||||
std::string as_string(Clock::time_point time) noexcept {
|
||||
return date::format("%FT%TZ", date::floor<std::chrono::milliseconds>(time));
|
||||
}
|
||||
|
||||
std::optional<Clock::time_point> from_string(std::string const& str) noexcept {
|
||||
int year = 0;
|
||||
unsigned int month = 0;
|
||||
unsigned int day = 0;
|
||||
unsigned int hour = 0;
|
||||
unsigned int minute = 0;
|
||||
unsigned int second = 0;
|
||||
unsigned int milliseconds = 0;
|
||||
|
||||
int read_count = 0;
|
||||
|
||||
if (sscanf(str.c_str(), "%d-%u-%uT%u:%u:%u.%3uZ%n",
|
||||
&year, &month, &day, &hour, &minute, &second, &milliseconds, &read_count) != 7
|
||||
|| read_count != str.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto y_m_d = date::year_month_day{
|
||||
date::year{ static_cast<int>(year) },
|
||||
date::month{ month },
|
||||
date::day{ day } };
|
||||
const auto time =
|
||||
std::chrono::hours{ hour }
|
||||
+ std::chrono::minutes{ minute }
|
||||
+ std::chrono::seconds{ second }
|
||||
+ std::chrono::milliseconds{ milliseconds };
|
||||
|
||||
return std::chrono::time_point_cast<std::chrono::system_clock::duration>(y_m_d.operator date::sys_days() + time);
|
||||
}
|
||||
|
||||
struct Decoded_type {
|
||||
std::string_view type;
|
||||
std::string_view sensor;
|
||||
std::string_view connection_type;
|
||||
std::string_view interface;
|
||||
};
|
||||
|
||||
template<typename It1, typename It2>
|
||||
std::string_view make_string_view(It1 begin, It2 end) {
|
||||
return std::string_view{ &*begin, static_cast<std::size_t>(std::distance(begin, end)) };
|
||||
}
|
||||
|
||||
std::optional<Decoded_type> decode_type(std::string_view type) {
|
||||
const auto first_space = std::find(type.begin(), type.end(), ' ');
|
||||
if (first_space == type.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto second_space = std::find(first_space + 1, type.end(), ' ');
|
||||
if (second_space == type.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto third_space = std::find(second_space + 1, type.end(), ' ');
|
||||
if (third_space == type.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
//make sure we only have 3 spaces
|
||||
if (std::find(third_space + 1, type.end(), ' ') != type.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Decoded_type returning;
|
||||
returning.type = make_string_view(type.begin(), first_space);
|
||||
returning.sensor = make_string_view(first_space + 1, second_space);
|
||||
returning.connection_type = make_string_view(second_space + 1, third_space);
|
||||
returning.interface = make_string_view(third_space + 1, type.end());
|
||||
|
||||
return returning;
|
||||
}
|
||||
|
||||
//cumulocity stuff
|
||||
|
||||
const auto data_refresh_period = boost::posix_time::seconds{ 10 };
|
||||
const auto reconnect_period = boost::posix_time::minutes{ 1 };
|
||||
const auto resend_period = boost::posix_time::minutes{ 5 };
|
||||
|
||||
struct Send_info {
|
||||
senshamart::Client* sending_to;
|
||||
std::string sending;
|
||||
};
|
||||
|
||||
struct Refresh_info {
|
||||
std::int64_t iot_device_id;
|
||||
Clock::time_point last_read_time;
|
||||
};
|
||||
|
||||
struct Cumulocity_requester :
|
||||
public std::enable_shared_from_this<Cumulocity_requester> {
|
||||
public:
|
||||
Cumulocity_requester(
|
||||
boost::asio::io_service& io,
|
||||
senshamart::Client external_temp_sensor,
|
||||
senshamart::Client external_humidity_sensor,
|
||||
senshamart::Client milk_temp_sensor,
|
||||
senshamart::Client milk_level_sensor,
|
||||
std::string_view host,
|
||||
boost::asio::ssl::context& ctx,
|
||||
const char* username,
|
||||
const char* password,
|
||||
spdlog::logger& undecodable,
|
||||
spdlog::logger& log,
|
||||
std::int64_t device_id) :
|
||||
sending_(),
|
||||
recv_buffer_(),
|
||||
recv_response_(),
|
||||
io_(io),
|
||||
ctx_(ctx),
|
||||
location_(),
|
||||
socket_(io, ctx),
|
||||
host_(host.data(), host.size()),
|
||||
target_endpoint_(),
|
||||
reconnect_timer_(io),
|
||||
refresh_timer_(io),
|
||||
resend_timer_(io),
|
||||
external_temp_sensor_(std::move(external_temp_sensor)),
|
||||
external_humidity_sensor_(std::move(external_humidity_sensor)),
|
||||
milk_temp_sensor_(std::move(milk_temp_sensor)),
|
||||
milk_level_sensor_(std::move(milk_level_sensor)),
|
||||
undecodable_(undecodable),
|
||||
log_(log) {
|
||||
|
||||
boost::asio::ip::tcp::resolver resolver(io);
|
||||
|
||||
auto resolved = resolver.resolve(host_, "443");
|
||||
|
||||
if (resolved == boost::asio::ip::tcp::resolver::iterator()) {
|
||||
log_.critical("Could not resolve host\n");
|
||||
throw "Could not resolve host\n";
|
||||
}
|
||||
|
||||
target_endpoint_ = *resolved;
|
||||
|
||||
std::string combined;
|
||||
combined.append(username);
|
||||
combined.push_back(':');
|
||||
combined.append(password);
|
||||
|
||||
auth_ = "Basic ";
|
||||
auth_.append(to_base64(combined));
|
||||
|
||||
refresh_info_.iot_device_id = device_id;
|
||||
refresh_info_.last_read_time = Clock::now();
|
||||
|
||||
}
|
||||
|
||||
void start() {
|
||||
refresh_();
|
||||
set_resend_timer_();
|
||||
}
|
||||
|
||||
private:
|
||||
void set_reconnect_timer_() {
|
||||
reconnect_timer_.expires_from_now(reconnect_period);
|
||||
reconnect_timer_.async_wait([me = this->shared_from_this()](boost::system::error_code ec) {
|
||||
if (!ec) {
|
||||
me->reconnect_();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void reconnect_() {
|
||||
socket_.async_shutdown([me = this->shared_from_this()](boost::system::error_code ec) {
|
||||
me->socket_.next_layer().close();
|
||||
me->socket_ = boost::beast::ssl_stream<boost::beast::tcp_stream>{me->io_, me->ctx_};
|
||||
me->socket_.next_layer().async_connect(me->target_endpoint_,
|
||||
[me](boost::system::error_code ec) {
|
||||
if (ec) {
|
||||
me->set_reconnect_timer_();
|
||||
} else {
|
||||
//successful reconnect
|
||||
me->socket_.async_handshake(boost::asio::ssl::stream_base::handshake_type::client,
|
||||
[me](boost::system::error_code ec) {
|
||||
if (ec) {
|
||||
me->set_reconnect_timer_();
|
||||
} else {
|
||||
//successful handshake
|
||||
me->do_refresh_();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void refresh_() {
|
||||
log_.info("refreshing");
|
||||
do_refresh_();
|
||||
}
|
||||
|
||||
void do_refresh_() {
|
||||
//clear prev state
|
||||
resend_info_.clear();
|
||||
//generate request
|
||||
sending_.method(boost::beast::http::verb::get);
|
||||
sending_.version(11);
|
||||
std::stringstream building_target;
|
||||
building_target << "/measurement/measurements?source=" << refresh_info_.iot_device_id
|
||||
<< "&dateFrom=" << as_string(refresh_info_.last_read_time)
|
||||
<< "&pageSize=5000";
|
||||
log_.info("Refreshing with target: '{}'", building_target.str());
|
||||
sending_.target(building_target.str());
|
||||
sending_.keep_alive(true);
|
||||
sending_.set(boost::beast::http::field::host, host_);
|
||||
sending_.set(boost::beast::http::field::authorization, auth_);
|
||||
|
||||
recv_buffer_.clear();
|
||||
recv_response_.body().clear();
|
||||
|
||||
boost::beast::http::async_write(socket_, sending_,
|
||||
[me = this->shared_from_this()](boost::system::error_code ec, std::size_t n) {
|
||||
if (ec) {
|
||||
me->log_.warn("Couldn't write to cumulocity: {}", ec.message());
|
||||
me->reconnect_();
|
||||
} else {
|
||||
boost::beast::http::async_read(me->socket_, me->recv_buffer_, me->recv_response_,
|
||||
[me](boost::system::error_code ec, std::size_t n) {
|
||||
if (ec) {
|
||||
me->log_.warn("Couldn't read from cumulocity: {}", ec.message());
|
||||
me->reconnect_();
|
||||
} else {
|
||||
me->do_recv_();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
//send
|
||||
}
|
||||
|
||||
void recv_measurement_(rapidjson::Value const& measurement) {
|
||||
const auto now = Clock::now();
|
||||
const auto upper_limit = now + std::chrono::hours{ 24 * 7 };
|
||||
|
||||
if (!measurement.IsObject()) {
|
||||
return;
|
||||
}
|
||||
const auto found_id = measurement.FindMember("id");
|
||||
if (found_id == measurement.MemberEnd()) {
|
||||
log_.warn("Member id not found");
|
||||
return;
|
||||
}
|
||||
if(!found_id->value.IsString()) {
|
||||
log_.warn("Member id is not a string");
|
||||
return;
|
||||
}
|
||||
const int64_t id_as_int = atoll(found_id->value.GetString());
|
||||
|
||||
const auto found_time = measurement.FindMember("time");
|
||||
if(found_time == measurement.MemberEnd()) {
|
||||
log_.warn("Member time is not found");
|
||||
return;
|
||||
}
|
||||
if(!found_time->value.IsString()) {
|
||||
log_.warn("Member time is not a string");
|
||||
return;
|
||||
}
|
||||
const auto time_val = from_string(found_time->value.GetString());
|
||||
if (!time_val.has_value()) {
|
||||
log_.warn("Couldn't parse time: {}", found_time->value.GetString());
|
||||
return;
|
||||
}
|
||||
|
||||
//if over 48 hours into the future, ignore
|
||||
if (time_val.value() > std::chrono::system_clock::now() + std::chrono::hours{48}) {
|
||||
return;
|
||||
}
|
||||
|
||||
refresh_info_.last_read_time = std::max(refresh_info_.last_read_time, time_val.value());
|
||||
|
||||
for (auto measurement_iter = measurement.MemberBegin(); measurement_iter != measurement.MemberEnd(); ++measurement_iter) {
|
||||
|
||||
//if we are metadata, skip
|
||||
|
||||
if (!measurement_iter->name.IsString() || !measurement_iter->value.IsObject()) {
|
||||
continue;
|
||||
}
|
||||
const std::string_view fragment_name{ measurement_iter->name.GetString(), measurement_iter->name.GetStringLength() };
|
||||
if (fragment_name == "id") {
|
||||
continue;
|
||||
} else if (fragment_name == "self") {
|
||||
continue;
|
||||
} else if (fragment_name == "time") {
|
||||
continue;
|
||||
} else if (fragment_name == "type") {
|
||||
continue;
|
||||
} else if (fragment_name == "source") {
|
||||
continue;
|
||||
}
|
||||
|
||||
//we aren't the metadata, we are a fragment
|
||||
|
||||
auto const& fragment = measurement_iter->value;
|
||||
|
||||
for (auto fragment_iter = fragment.MemberBegin(); fragment_iter != fragment.MemberEnd(); ++fragment_iter) {
|
||||
if (!fragment_iter->name.IsString()) {
|
||||
continue;
|
||||
}
|
||||
if (!fragment_iter->value.IsObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& reading = fragment_iter->value;
|
||||
|
||||
const auto found_unit = reading.FindMember("unit");
|
||||
if(found_unit == reading.MemberEnd() || !found_unit->value.IsString()) {
|
||||
continue;
|
||||
}
|
||||
const auto found_value = reading.FindMember("value");
|
||||
if (found_value == reading.MemberEnd() || !found_value->value.IsNumber()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string_view unit{ found_unit->value.GetString(), found_unit->value.GetStringLength() };
|
||||
|
||||
const double value = found_value->value.GetDouble();
|
||||
|
||||
//if we can't decode fragment name, log it, and skip
|
||||
const auto decoded_type = decode_type(fragment_name);
|
||||
|
||||
if (!decoded_type.has_value()) {
|
||||
undecodable_.warn("time, fragment name, unit, device id, id, value = {}, {}, {}, {}, {}, {}",
|
||||
std::string_view{found_time->value.GetString(), found_time->value.GetStringLength()},
|
||||
std::string_view{ fragment_name.data(), fragment_name.size() },
|
||||
std::string_view{ unit.data(), unit.size() },
|
||||
refresh_info_.iot_device_id,
|
||||
id_as_int,
|
||||
value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(time_val.value() >= upper_limit) {
|
||||
//we have a problem with devices returning bogus timestamps in 2035, this will stop those
|
||||
continue;
|
||||
}
|
||||
|
||||
senshamart::Client* sending_to = nullptr;
|
||||
|
||||
if (decoded_type->type == "Farm_condition_temperature") {
|
||||
sending_to = &this->external_temp_sensor_;
|
||||
} else if (decoded_type->type == "Milk_quantity") {
|
||||
sending_to = &this->milk_level_sensor_;
|
||||
} else if (decoded_type->type == "Milk_temperature") {
|
||||
sending_to = &this->milk_temp_sensor_;
|
||||
} else if (decoded_type->type == "Farm_condition_humidity") {
|
||||
sending_to = &this->external_humidity_sensor_;
|
||||
}
|
||||
|
||||
if (sending_to == nullptr) {
|
||||
log_.info("Unused sensor value for {}", decoded_type->type);
|
||||
} else {
|
||||
std::stringstream sending;
|
||||
sending <<
|
||||
"{"
|
||||
"\"time\":\"" << found_time->value.GetString() << "\","
|
||||
"\"value\":" << value <<
|
||||
"}";
|
||||
resend_info_.push_back(Send_info{
|
||||
sending_to,
|
||||
sending.str() });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void do_recv_() {
|
||||
//read request
|
||||
do {
|
||||
if (recv_response_.result() != boost::beast::http::status::ok) {
|
||||
break;
|
||||
}
|
||||
|
||||
rapidjson::Document body;
|
||||
if (body.Parse(recv_response_.body().c_str()).HasParseError()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!body.IsObject()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!body.HasMember("measurements")) {
|
||||
break;
|
||||
}
|
||||
|
||||
rapidjson::Value const& measurement_array = body["measurements"];
|
||||
|
||||
if (!measurement_array.IsArray()) {
|
||||
break;
|
||||
}
|
||||
|
||||
rapidjson::Value::ConstArray as_array = measurement_array.GetArray();
|
||||
|
||||
for (auto const& element : as_array) {
|
||||
recv_measurement_(element);
|
||||
}
|
||||
} while (false);
|
||||
|
||||
for (auto const& sending : resend_info_) {
|
||||
sending.sending_to->send(sending.sending);
|
||||
}
|
||||
|
||||
set_timer_();
|
||||
}
|
||||
|
||||
void set_timer_() {
|
||||
refresh_timer_.expires_from_now(data_refresh_period);
|
||||
refresh_timer_.async_wait([me = this->shared_from_this()](boost::system::error_code ec) {
|
||||
if (!ec) {
|
||||
me->refresh_();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void set_resend_timer_() {
|
||||
resend_timer_.expires_from_now(resend_period);
|
||||
resend_timer_.async_wait([me = this->shared_from_this()](boost::system::error_code ec) {
|
||||
if (!ec) {
|
||||
me->resend_();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void resend_() {
|
||||
|
||||
for (auto const& resending : resend_info_) {
|
||||
resending.sending_to->send(resending.sending);
|
||||
}
|
||||
|
||||
set_resend_timer_();
|
||||
}
|
||||
|
||||
boost::beast::http::request<boost::beast::http::empty_body> sending_;
|
||||
boost::beast::flat_buffer recv_buffer_;
|
||||
boost::beast::http::response<boost::beast::http::string_body> recv_response_;
|
||||
|
||||
boost::asio::io_service& io_;
|
||||
boost::asio::ssl::context& ctx_;
|
||||
|
||||
boost::beast::tcp_stream::endpoint_type location_;
|
||||
boost::beast::ssl_stream<boost::beast::tcp_stream> socket_;
|
||||
boost::asio::ip::tcp::endpoint target_endpoint_;
|
||||
std::string host_;
|
||||
boost::asio::deadline_timer reconnect_timer_;
|
||||
boost::asio::deadline_timer refresh_timer_;
|
||||
boost::asio::deadline_timer resend_timer_;
|
||||
std::vector<Send_info> resend_info_;
|
||||
std::string auth_;
|
||||
|
||||
Refresh_info refresh_info_;
|
||||
senshamart::Client external_temp_sensor_;
|
||||
senshamart::Client external_humidity_sensor_;
|
||||
senshamart::Client milk_temp_sensor_;
|
||||
senshamart::Client milk_level_sensor_;
|
||||
spdlog::logger& undecodable_;
|
||||
spdlog::logger& log_;
|
||||
};
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
if (argc < 12) {
|
||||
fprintf(stderr, "Expected"
|
||||
" %s"
|
||||
" <cumulocity host>"
|
||||
" <cumulocity username>"
|
||||
" <cumulocity password>"
|
||||
" <broker location>"
|
||||
" <cumulocity device id>"
|
||||
" <external temp sensor name>"
|
||||
" <external humidity sensor name>"
|
||||
" <milk temp sensor name>"
|
||||
" <milk level sensor name>"
|
||||
" <log location>"
|
||||
" <undecoded log>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* host = argv[1]; //"bega.apj.cumulocity.com";//
|
||||
const char* username = argv[2]; //"jkaraboticmilovac@swin.edu.au";//
|
||||
const char* password = argv[3]; //"swin.iotLab";//
|
||||
const char* broker_location = argv[4]; //"tcp://127.0.0.1:8001";//
|
||||
const char* cumulocity_device_id_str = argv[5];
|
||||
const char* external_temp_sensor_name = argv[6];
|
||||
const char* external_humidity_sensor_name = argv[7];
|
||||
const char* milk_temp_sensor_name = argv[8];
|
||||
const char* milk_level_sensor_name = argv[9];
|
||||
const char* log_location = argv[10]; //"etl_log";//
|
||||
const char* undecodable_location = argv[11]; //"undecodable.log";//
|
||||
|
||||
const auto undecodable_file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
undecodable_location, 4 * 1024 * 1024, 4, true);
|
||||
const auto log_file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
log_location, 4 * 1024 * 1024, 4, true);
|
||||
const auto stderr_sink = std::make_shared<spdlog::sinks::stderr_sink_mt>();
|
||||
|
||||
spdlog::logger undecodable{ "undecodable", { undecodable_file_sink, log_file_sink, stderr_sink } };
|
||||
spdlog::logger log{ "etl", { log_file_sink, stderr_sink } };
|
||||
log.info("initing");
|
||||
|
||||
//milk supply chain monitoring system side
|
||||
|
||||
senshamart::Client external_temp_sensor{broker_location, external_temp_sensor_name};
|
||||
senshamart::Client external_humidity_sensor{broker_location, external_humidity_sensor_name};
|
||||
senshamart::Client milk_temp_sensor{broker_location, milk_temp_sensor_name};
|
||||
senshamart::Client milk_level_sensor{broker_location, milk_level_sensor_name};
|
||||
|
||||
//cumulocity side stuff
|
||||
|
||||
boost::asio::io_service io;
|
||||
|
||||
boost::asio::ssl::context ctx(boost::asio::ssl::context_base::tls_client);
|
||||
ctx.set_verify_callback([](auto&&...) {return true; });
|
||||
|
||||
const std::int64_t device_id = std::strtoll(cumulocity_device_id_str, nullptr, 10);
|
||||
|
||||
const auto cumulocity_requester = std::make_shared<Cumulocity_requester>(
|
||||
io,
|
||||
std::move(external_temp_sensor),
|
||||
std::move(external_humidity_sensor),
|
||||
std::move(milk_temp_sensor),
|
||||
std::move(milk_level_sensor),
|
||||
host,
|
||||
ctx,
|
||||
username,
|
||||
password,
|
||||
undecodable,
|
||||
log,
|
||||
device_id);
|
||||
|
||||
log.info("starting");
|
||||
|
||||
cumulocity_requester->start();
|
||||
|
||||
io.run();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue