Rewrite electricity-logger to use an sqlite3 database

This commit is contained in:
2022-06-25 22:17:46 +02:00
parent 458d824dc8
commit 5b09b06bcf
62 changed files with 5937 additions and 3 deletions

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

View 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) { }