Rewrite electricity-logger to use an sqlite3 database
This commit is contained in:
53
src/solar-logger/database.cpp
Normal file
53
src/solar-logger/database.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <solar/logger/database.hpp>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <util/date.hpp>
|
||||
|
||||
std::string Database::ToSqlInsertStatement(Row const & row) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "INSERT INTO SolarPanelOutput VALUES(" << '\'' << Util::GetSqliteDate(row.epochTime) << "'," << '\''
|
||||
<< Util::GetSqliteUtcTime(row.epochTime) << "'," << row.watt << ", " << std::fixed << std::setprecision(2)
|
||||
<< row.kilowattPerHour << ");";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Database::ToSqlUpsertStatement(Row const & row) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "INSERT INTO SolarPanelSummary(Date,KilowattHour) VALUES(" << '\'' << Util::GetSqliteDate(row.epochTime)
|
||||
<< "'," << std::fixed << std::setprecision(2) << row.kilowattPerHour << ')'
|
||||
<< "ON CONFLICT(Date) DO UPDATE SET KilowattHour = excluded.KilowattHour WHERE excluded.KilowattHour > SolarPanelSummary.KilowattHour;";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool Database::Insert(Row & row)
|
||||
{
|
||||
std::stringstream transaction;
|
||||
transaction << "BEGIN TRANSACTION;" << ToSqlInsertStatement(row) << ToSqlUpsertStatement(row) << "COMMIT;";
|
||||
|
||||
return sqlite3_exec(connectionPtr, transaction.str().c_str(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
Database::Database(std::string const & databasePath) : connectionPtr(nullptr)
|
||||
{
|
||||
if(sqlite3_open(databasePath.c_str(), &connectionPtr) != SQLITE_OK)
|
||||
{
|
||||
throw std::runtime_error("Error opening SQLite3 database");
|
||||
}
|
||||
|
||||
sqlite3_extended_result_codes(connectionPtr, 1);
|
||||
}
|
||||
|
||||
Database::~Database()
|
||||
{
|
||||
if(connectionPtr)
|
||||
{
|
||||
sqlite3_close(connectionPtr);
|
||||
}
|
||||
}
|
||||
60
src/solar-logger/main.cpp
Normal file
60
src/solar-logger/main.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <solar/logger/database.hpp>
|
||||
#include <solar/logger/zeverdata.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <tclap/CmdLine.h>
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
TCLAP::CmdLine cmd(
|
||||
"solar-logger is a small program that retrieves solarpower generation statistics from Zeverlution Sxxxx smart power inverters and stores it into a database",
|
||||
' ',
|
||||
"1.0");
|
||||
|
||||
TCLAP::ValueArg<std::string> urlArg(
|
||||
"u",
|
||||
"url",
|
||||
"Fully qualified URL path and protocol to the home.cgi resource",
|
||||
true,
|
||||
"",
|
||||
"Fully qualified URL path and protocol to the home.cgi resource");
|
||||
cmd.add(urlArg);
|
||||
|
||||
TCLAP::ValueArg<unsigned int>
|
||||
timeoutArg("t", "timeout", "Fetch time out in milliseconds", false, 1000U, "Fetch time out in milliseconds");
|
||||
cmd.add(timeoutArg);
|
||||
|
||||
TCLAP::ValueArg<std::string> databasePathArg(
|
||||
"d",
|
||||
"database-path",
|
||||
"Absolute path pointing to the solar SQLite *.db file",
|
||||
true,
|
||||
"",
|
||||
"Absolute path pointing to the solar SQLite *.db file");
|
||||
cmd.add(databasePathArg);
|
||||
|
||||
cmd.parse(argc, argv);
|
||||
|
||||
ZeverData zeverData;
|
||||
if(!zeverData.FetchDataFromURL(urlArg.getValue(), timeoutArg.getValue()))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
Row row;
|
||||
row.epochTime = std::time(nullptr); // now
|
||||
row.watt = zeverData.watt;
|
||||
row.kilowattPerHour = zeverData.kilowattPerHour;
|
||||
|
||||
Database db(databasePathArg.getValue());
|
||||
auto const insertionResult = db.Insert(row);
|
||||
if(insertionResult)
|
||||
{
|
||||
std::printf("Error %i during insertion of new value into database\n", insertionResult);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
88
src/solar-logger/zeverdata.cpp
Normal file
88
src/solar-logger/zeverdata.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include <solar/logger/zeverdata.hpp>
|
||||
|
||||
namespace detail
|
||||
{
|
||||
// libcurl callback
|
||||
size_t write_to_string(void * ptr, size_t size, size_t nmemb, void * stream)
|
||||
{
|
||||
std::string line((char *)ptr, nmemb);
|
||||
std::string * buffer = (std::string *)stream;
|
||||
buffer->append(line);
|
||||
|
||||
return nmemb * size;
|
||||
}
|
||||
}
|
||||
|
||||
bool ZeverData::ParseString(const std::string & str)
|
||||
{
|
||||
bool isValid = true;
|
||||
std::stringstream ss;
|
||||
ss.str(str);
|
||||
|
||||
ss >> number0;
|
||||
ss >> number1;
|
||||
ss >> registeryID;
|
||||
ss >> registeryKey;
|
||||
ss >> hardwareVersion;
|
||||
std::string versionInfo;
|
||||
ss >> versionInfo;
|
||||
size_t splitPos = versionInfo.find('+');
|
||||
appVersion = versionInfo.substr(0, splitPos);
|
||||
++splitPos;
|
||||
wifiVersion = versionInfo.substr(splitPos, versionInfo.size());
|
||||
|
||||
ss >> timeValue;
|
||||
ss >> dateValue;
|
||||
ss >> zeverCloudStatus;
|
||||
ss >> number3;
|
||||
ss >> inverterSN;
|
||||
isValid = (ss >> watt && ss >> kilowattPerHour);
|
||||
ss >> OKmsg;
|
||||
ss >> ERRORmsg;
|
||||
|
||||
if(!isValid)
|
||||
{
|
||||
std::fprintf(stderr, "Error during parsing of zever data:\n%s\n", str.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ZeverData::FetchDataFromURL(const std::string & url, const unsigned int timeout)
|
||||
{
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
|
||||
CURL * curl = curl_easy_init();
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);
|
||||
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
|
||||
std::string buffer;
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, detail::write_to_string);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
|
||||
buffer.reserve(256);
|
||||
if(curl_easy_perform(curl))
|
||||
{
|
||||
watt = 0;
|
||||
kilowattPerHour = 0.0;
|
||||
|
||||
std::fprintf(stderr, "Failed to fetch zever data from URL <%s>!\n", url.c_str());
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer.shrink_to_fit();
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
return ParseString(buffer);
|
||||
}
|
||||
|
||||
ZeverData::ZeverData() : watt(0), kilowattPerHour(0.0) { }
|
||||
Reference in New Issue
Block a user