Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
0
tools/EVerest-main/modules/Misc/BUILD.bazel
Normal file
0
tools/EVerest-main/modules/Misc/BUILD.bazel
Normal file
11
tools/EVerest-main/modules/Misc/CMakeLists.txt
Normal file
11
tools/EVerest-main/modules/Misc/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
ev_add_module(ChargerInfo)
|
||||
ev_add_module(ErrorHistory)
|
||||
ev_add_module(Linux_Systemd_Rauc)
|
||||
ev_add_module(LocalAllowlistTokenValidator)
|
||||
ev_add_module(PacketSniffer)
|
||||
ev_add_module(PersistentStore)
|
||||
ev_add_module(SerialCommHub)
|
||||
ev_add_module(Setup)
|
||||
ev_add_module(Store)
|
||||
ev_add_module(System)
|
||||
ev_add_module(YamlStore)
|
||||
21
tools/EVerest-main/modules/Misc/ChargerInfo/CMakeLists.txt
Normal file
21
tools/EVerest-main/modules/Misc/ChargerInfo/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/charger_informationImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
15
tools/EVerest-main/modules/Misc/ChargerInfo/ChargerInfo.cpp
Normal file
15
tools/EVerest-main/modules/Misc/ChargerInfo/ChargerInfo.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "ChargerInfo.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void ChargerInfo::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void ChargerInfo::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
72
tools/EVerest-main/modules/Misc/ChargerInfo/ChargerInfo.hpp
Normal file
72
tools/EVerest-main/modules/Misc/ChargerInfo/ChargerInfo.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef CHARGER_INFO_HPP
|
||||
#define CHARGER_INFO_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/charger_information/Implementation.hpp>
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/charger_information/Interface.hpp>
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string firmware_version_file;
|
||||
};
|
||||
|
||||
class ChargerInfo : public Everest::ModuleBase {
|
||||
public:
|
||||
ChargerInfo() = delete;
|
||||
ChargerInfo(const ModuleInfo& info, std::unique_ptr<charger_informationImplBase> p_main,
|
||||
std::vector<std::unique_ptr<kvsIntf>> r_kvs,
|
||||
std::vector<std::unique_ptr<charger_informationIntf>> r_charger_information, Conf& config) :
|
||||
ModuleBase(info),
|
||||
p_main(std::move(p_main)),
|
||||
r_kvs(std::move(r_kvs)),
|
||||
r_charger_information(std::move(r_charger_information)),
|
||||
config(config){};
|
||||
|
||||
const std::unique_ptr<charger_informationImplBase> p_main;
|
||||
const std::vector<std::unique_ptr<kvsIntf>> r_kvs;
|
||||
const std::vector<std::unique_ptr<charger_informationIntf>> r_charger_information;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // CHARGER_INFO_HPP
|
||||
@@ -0,0 +1,6 @@
|
||||
manufacturer: "Pionix GmbH"
|
||||
manufacturer_url: "https://www.pionix.com/"
|
||||
vendor: "Pionix"
|
||||
model: "BelayBox"
|
||||
chargepoint_serial: "0123"
|
||||
firmware_version: "0.1.2"
|
||||
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "charger_informationImpl.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
void charger_informationImpl::init() {
|
||||
}
|
||||
|
||||
void charger_informationImpl::ready() {
|
||||
json info(handle_get_charger_information());
|
||||
|
||||
EVLOG_debug << "ChargerInformation: " << info.dump();
|
||||
}
|
||||
|
||||
std::string charger_informationImpl::load_fw_version_from_file(const std::string& fn) {
|
||||
std::ifstream fw_version_file(fn);
|
||||
std::string first_line;
|
||||
|
||||
if (!fw_version_file) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!std::getline(fw_version_file, first_line)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return first_line;
|
||||
}
|
||||
|
||||
types::charger_information::ChargerInformation charger_informationImpl::handle_get_charger_information() {
|
||||
std::vector<std::string> keys = {"vendor", "model", "chargepoint_serial", "chargebox_serial",
|
||||
"friendly_name", "manufacturer", "manufacturer_url", "model_url",
|
||||
"model_number", "model_revision", "board_revision", "firmware_version"};
|
||||
json info = {};
|
||||
|
||||
// in case we are chained, retrieve data from previous module
|
||||
if (not this->mod->r_charger_information.empty()) {
|
||||
info = this->mod->r_charger_information[0]->call_get_charger_information();
|
||||
}
|
||||
|
||||
// iterate over all linked key-value stores and merge all items,
|
||||
// when a key exists in more than one kvs then the last one wins
|
||||
for (const auto& kvs : mod->r_kvs) {
|
||||
for (const auto k : keys) {
|
||||
if (kvs->call_exists(k)) {
|
||||
const auto v = kvs->call_load(k);
|
||||
info[k] = std::get<std::string>(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finally check whether we should load the firmware version from a simple plain text file
|
||||
if (!mod->config.firmware_version_file.empty()) {
|
||||
info["firmware_version"] = load_fw_version_from_file(mod->config.firmware_version_file);
|
||||
}
|
||||
|
||||
// generate fallback friendly_name: we use the chargepoint's serial,
|
||||
// because we assume that this one is printed on a device label and not the (internal)
|
||||
// chargebox' serial number (aka controller serial number) which is usually only important for
|
||||
// the manufacturer itself
|
||||
if (info.contains("vendor") and info.contains("model") and info.contains("chargepoint_serial") and
|
||||
not info.contains("friendly_name")) {
|
||||
info["friendly_name"] = info["vendor"].get<std::string>() + " " + info["model"].get<std::string>() + " [" +
|
||||
info["chargepoint_serial"].get<std::string>() + "]";
|
||||
}
|
||||
|
||||
EVLOG_debug << "ChargerInformation: " << info.dump();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_CHARGER_INFORMATION_IMPL_HPP
|
||||
#define MAIN_CHARGER_INFORMATION_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/charger_information/Implementation.hpp>
|
||||
|
||||
#include "../ChargerInfo.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
#include <string>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class charger_informationImpl : public charger_informationImplBase {
|
||||
public:
|
||||
charger_informationImpl() = delete;
|
||||
charger_informationImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<ChargerInfo>& mod, Conf& config) :
|
||||
charger_informationImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::charger_information::ChargerInformation handle_get_charger_information() override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<ChargerInfo>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
std::string load_fw_version_from_file(const std::string& fn);
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_CHARGER_INFORMATION_IMPL_HPP
|
||||
30
tools/EVerest-main/modules/Misc/ChargerInfo/manifest.yaml
Normal file
30
tools/EVerest-main/modules/Misc/ChargerInfo/manifest.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
description: >-
|
||||
Provides a charger information interface, backed by simple KVS interface.
|
||||
With the optional dependency to another charger information node, it is possible to chain
|
||||
multiple sources of information.
|
||||
config:
|
||||
firmware_version_file:
|
||||
description: >-
|
||||
Sometimes the firmware version cannot be retrieved from the key-value interface.
|
||||
Then it is possible to provide the firmware version string from a simple text file
|
||||
in the filesystem. The first line of this file is used as-is, the remaining file content
|
||||
is ignored.
|
||||
Give the full path to the file which contains the firmware version string.
|
||||
type: string
|
||||
provides:
|
||||
main:
|
||||
interface: charger_information
|
||||
description: Provides the charger information interface
|
||||
requires:
|
||||
kvs:
|
||||
interface: kvs
|
||||
min_connections: 1
|
||||
max_connections: 10
|
||||
charger_information:
|
||||
interface: charger_information
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Michael Heimpold
|
||||
43
tools/EVerest-main/modules/Misc/ErrorHistory/BUILD.bazel
Normal file
43
tools/EVerest-main/modules/Misc/ErrorHistory/BUILD.bazel
Normal file
@@ -0,0 +1,43 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_test")
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
load("//third-party/bazel/toolchains:defs.bzl", "CROSS_TEST_INCOMPATIBLE")
|
||||
|
||||
cc_everest_module(
|
||||
name = "ErrorHistory",
|
||||
srcs = glob([
|
||||
"*.cpp",
|
||||
"*.hpp",
|
||||
]),
|
||||
impls = ["error_history"],
|
||||
deps = [
|
||||
"//lib/everest/sqlite:everest-sqlite",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ErrorHistory_test",
|
||||
target_compatible_with = CROSS_TEST_INCOMPATIBLE,
|
||||
srcs = glob(
|
||||
[
|
||||
"tests/*.cpp",
|
||||
"tests/*.hpp",
|
||||
"ErrorDatabaseSqlite.*",
|
||||
],
|
||||
exclude = [
|
||||
"main/car_simulatorImpl.*",
|
||||
"main/car_simulation.*",
|
||||
],
|
||||
),
|
||||
local_defines = [
|
||||
"BUILD_TZ_LIB=ON",
|
||||
"USE_SYSTEM_TZ_DB=ON",
|
||||
"USE_OS_TZDB=1",
|
||||
"USE_AUTOLOAD=0",
|
||||
"HAS_REMOTE_API=0",
|
||||
],
|
||||
deps = [
|
||||
"//lib/everest/framework",
|
||||
"//lib/everest/log:liblog",
|
||||
"@catch2//:catch2_main",
|
||||
],
|
||||
)
|
||||
36
tools/EVerest-main/modules/Misc/ErrorHistory/CMakeLists.txt
Normal file
36
tools/EVerest-main/modules/Misc/ErrorHistory/CMakeLists.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
option(BUILD_TESTING "Run unit tests" OFF)
|
||||
set(CMAKE_PREFIX_PATH "/usr/lib/x86_64-linux-gnu" ${CMAKE_PREFIX_PATH})
|
||||
|
||||
find_package(SQLite3 REQUIRED)
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
SQLite::SQLite3
|
||||
everest::sqlite
|
||||
)
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"ErrorDatabaseSqlite.cpp"
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"error_history/error_historyImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
if(EVEREST_CORE_BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,320 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ErrorDatabaseSqlite.hpp"
|
||||
|
||||
#include <everest/database/exceptions.hpp>
|
||||
#include <everest/database/sqlite/connection.hpp>
|
||||
#include <everest/database/sqlite/statement.hpp>
|
||||
#include <everest/exceptions.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
#include <utils/date.hpp>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace module {
|
||||
|
||||
ErrorDatabaseSqlite::ErrorDatabaseSqlite(const fs::path& db_path_, const bool reset_) :
|
||||
db_path(fs::absolute(db_path_)) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
std::lock_guard<std::mutex> lock(this->db_mutex);
|
||||
|
||||
bool reset = reset_ || !fs::exists(this->db_path);
|
||||
if (reset) {
|
||||
EVLOG_info << "Resetting database";
|
||||
this->reset_database();
|
||||
} else {
|
||||
EVLOG_info << "Using database at " << this->db_path;
|
||||
try {
|
||||
this->check_database();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error checking database: " << e.what();
|
||||
EVLOG_info << "Resetting database";
|
||||
this->reset_database();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorDatabaseSqlite::check_database() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
EVLOG_info << "Checking database";
|
||||
try {
|
||||
auto db = std::make_shared<everest::db::sqlite::Connection>(this->db_path);
|
||||
if (!db->open_connection()) {
|
||||
EVLOG_error << "Error opening database";
|
||||
throw everest::db::ConnectionException(db->get_error_message());
|
||||
}
|
||||
std::string sql = "SELECT name";
|
||||
sql += " FROM sqlite_schema";
|
||||
sql += " WHERE type = 'table' AND name NOT LIKE 'sqlite_%';";
|
||||
auto stmt = db->new_statement(sql);
|
||||
bool has_errors_table = false;
|
||||
int status;
|
||||
while ((status = stmt->step()) == SQLITE_ROW) {
|
||||
std::string table_name = stmt->column_text(0);
|
||||
if (table_name == "errors") {
|
||||
if (has_errors_table) {
|
||||
throw Everest::EverestConfigError("Database contains multiple errors tables");
|
||||
}
|
||||
has_errors_table = true;
|
||||
EVLOG_debug << "Found errors table";
|
||||
} else {
|
||||
EVLOG_warning << "Found unknown table: " << table_name;
|
||||
}
|
||||
}
|
||||
if (status != SQLITE_DONE) {
|
||||
throw Everest::EverestConfigError(db->get_error_message());
|
||||
}
|
||||
if (!has_errors_table) {
|
||||
throw Everest::EverestConfigError("Database does not contain errors table");
|
||||
}
|
||||
sql = "PRAGMA table_info(errors);";
|
||||
auto stmt2 = db->new_statement(sql);
|
||||
std::set<std::string> columns;
|
||||
while ((status = stmt2->step()) == SQLITE_ROW) {
|
||||
auto variant = stmt2->column_variant("name");
|
||||
columns.insert(std::get<std::string>(variant));
|
||||
}
|
||||
std::set<std::string> required_columns = {
|
||||
"uuid", "type", "description", "message", "origin_module", "origin_implementation",
|
||||
"timestamp", "severity", "state", "sub_type", "vendor_id"};
|
||||
if (status != SQLITE_DONE) {
|
||||
throw Everest::EverestConfigError(db->get_error_message());
|
||||
}
|
||||
if (columns != required_columns) {
|
||||
throw Everest::EverestConfigError("Errors table does not contain all required columns");
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error checking database: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorDatabaseSqlite::reset_database() {
|
||||
BOOST_LOG_FUNCTION();
|
||||
fs::path database_directory = this->db_path.parent_path();
|
||||
if (!fs::exists(database_directory)) {
|
||||
fs::create_directories(database_directory);
|
||||
}
|
||||
if (fs::exists(this->db_path)) {
|
||||
fs::remove(this->db_path);
|
||||
}
|
||||
try {
|
||||
everest::db::sqlite::Connection db(this->db_path);
|
||||
if (!db.open_connection()) {
|
||||
EVLOG_error << "Error opening database during reset";
|
||||
throw everest::db::ConnectionException(db.get_error_message());
|
||||
}
|
||||
std::string sql = "CREATE TABLE errors("
|
||||
"uuid TEXT PRIMARY KEY NOT NULL,"
|
||||
"type TEXT NOT NULL,"
|
||||
"description TEXT NOT NULL,"
|
||||
"message TEXT NOT NULL,"
|
||||
"origin_module TEXT NOT NULL,"
|
||||
"origin_implementation TEXT NOT NULL,"
|
||||
"timestamp TEXT NOT NULL,"
|
||||
"severity TEXT NOT NULL,"
|
||||
"state TEXT NOT NULL,"
|
||||
"sub_type TEXT NOT NULL,"
|
||||
"vendor_id TEXT NOT NULL);";
|
||||
|
||||
if (!db.execute_statement(sql)) {
|
||||
EVLOG_error << "Error creating database during reset";
|
||||
throw everest::db::QueryExecutionException(db.get_error_message());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error resetting the database: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void ErrorDatabaseSqlite::add_error(Everest::error::ErrorPtr error) {
|
||||
std::lock_guard<std::mutex> lock(this->db_mutex);
|
||||
this->add_error_without_mutex(error);
|
||||
}
|
||||
|
||||
void ErrorDatabaseSqlite::add_error_without_mutex(Everest::error::ErrorPtr error) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
try {
|
||||
everest::db::sqlite::Connection db(this->db_path);
|
||||
if (!db.open_connection()) {
|
||||
EVLOG_error << "Error opening database";
|
||||
throw everest::db::ConnectionException(db.get_error_message());
|
||||
}
|
||||
std::string sql = "INSERT INTO errors(uuid, type, description, message, origin_module, origin_implementation, "
|
||||
"timestamp, severity, state, sub_type, vendor_id) VALUES(";
|
||||
sql += "?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11);";
|
||||
auto stmt = db.new_statement(sql);
|
||||
stmt->bind_text(1, error->uuid.to_string(), everest::db::sqlite::SQLiteString::Transient);
|
||||
stmt->bind_text(2, error->type);
|
||||
stmt->bind_text(3, error->description);
|
||||
stmt->bind_text(4, error->message);
|
||||
stmt->bind_text(5, error->origin.module_id);
|
||||
stmt->bind_text(6, error->origin.implementation_id);
|
||||
stmt->bind_text(7, Everest::Date::to_rfc3339(error->timestamp), everest::db::sqlite::SQLiteString::Transient);
|
||||
stmt->bind_text(8, Everest::error::severity_to_string(error->severity),
|
||||
everest::db::sqlite::SQLiteString::Transient);
|
||||
stmt->bind_text(9, Everest::error::state_to_string(error->state), everest::db::sqlite::SQLiteString::Transient);
|
||||
stmt->bind_text(10, error->sub_type);
|
||||
stmt->bind_text(11, error->vendor_id);
|
||||
if (stmt->step() != SQLITE_DONE) {
|
||||
throw everest::db::QueryExecutionException(db.get_error_message());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error adding error to database: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ErrorDatabaseSqlite::filter_to_sql_condition(const Everest::error::ErrorFilter& filter) {
|
||||
std::string condition{};
|
||||
switch (filter.get_filter_type()) {
|
||||
case Everest::error::FilterType::State: {
|
||||
condition = "(state = '" + Everest::error::state_to_string(filter.get_state_filter()) + "')";
|
||||
} break;
|
||||
case Everest::error::FilterType::Origin: {
|
||||
condition = "(origin_module = '" + filter.get_origin_filter().module_id + "' AND " +
|
||||
"origin_implementation = '" + filter.get_origin_filter().implementation_id + "')";
|
||||
} break;
|
||||
case Everest::error::FilterType::Type: {
|
||||
condition = "(type = '" + filter.get_type_filter().value + "')";
|
||||
} break;
|
||||
case Everest::error::FilterType::Severity: {
|
||||
switch (filter.get_severity_filter()) {
|
||||
case Everest::error::SeverityFilter::LOW_GE: {
|
||||
condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Low) +
|
||||
"' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Medium) +
|
||||
"' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')";
|
||||
} break;
|
||||
case Everest::error::SeverityFilter::MEDIUM_GE: {
|
||||
condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Medium) +
|
||||
"' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')";
|
||||
} break;
|
||||
case Everest::error::SeverityFilter::HIGH_GE: {
|
||||
condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')";
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
case Everest::error::FilterType::TimePeriod: {
|
||||
condition = "(timestamp BETWEEN '" + Everest::Date::to_rfc3339(filter.get_time_period_filter().from) +
|
||||
"' AND '" + Everest::Date::to_rfc3339(filter.get_time_period_filter().to) + "')";
|
||||
} break;
|
||||
case Everest::error::FilterType::Handle: {
|
||||
condition = "(uuid = '" + filter.get_handle_filter().to_string() + "')";
|
||||
} break;
|
||||
case Everest::error::FilterType::SubType: {
|
||||
condition = "(sub_type = '" + filter.get_sub_type_filter().value + "')";
|
||||
} break;
|
||||
case Everest::error::FilterType::VendorId: {
|
||||
condition = "(vendor_id = '" + filter.get_vendor_id_filter().value + "')";
|
||||
} break;
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
std::optional<std::string>
|
||||
ErrorDatabaseSqlite::filters_to_sql_condition(const std::list<Everest::error::ErrorFilter>& filters) {
|
||||
std::optional<std::string> condition = std::nullopt;
|
||||
if (!filters.empty()) {
|
||||
auto it = filters.begin();
|
||||
condition = filter_to_sql_condition(*it);
|
||||
it++;
|
||||
while (it != filters.end()) {
|
||||
condition = condition.value() + " AND " + ErrorDatabaseSqlite::filter_to_sql_condition(*it);
|
||||
it++;
|
||||
}
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr>
|
||||
ErrorDatabaseSqlite::get_errors(const std::list<Everest::error::ErrorFilter>& filters) const {
|
||||
std::lock_guard<std::mutex> lock(this->db_mutex);
|
||||
return this->get_errors(ErrorDatabaseSqlite::filters_to_sql_condition(filters));
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr> ErrorDatabaseSqlite::get_errors(const std::optional<std::string>& condition) const {
|
||||
BOOST_LOG_FUNCTION();
|
||||
std::list<Everest::error::ErrorPtr> result;
|
||||
try {
|
||||
everest::db::sqlite::Connection db(this->db_path);
|
||||
if (!db.open_connection()) {
|
||||
EVLOG_error << "Error opening database";
|
||||
throw everest::db::ConnectionException(db.get_error_message());
|
||||
}
|
||||
std::string sql = "SELECT * FROM errors";
|
||||
if (condition.has_value()) {
|
||||
sql += " WHERE " + condition.value();
|
||||
}
|
||||
EVLOG_debug << "Executing SQL statement: " << sql;
|
||||
auto stmt = db.new_statement(sql);
|
||||
int status;
|
||||
while ((status = stmt->step()) == SQLITE_ROW) {
|
||||
const Everest::error::ErrorType err_type(std::get<std::string>(stmt->column_variant("type")));
|
||||
const std::string err_description = std::get<std::string>(stmt->column_variant("description"));
|
||||
const std::string err_msg = std::get<std::string>(stmt->column_variant("message"));
|
||||
const std::string err_origin_module_id = std::get<std::string>(stmt->column_variant("origin_module"));
|
||||
const std::string err_origin_impl_id = std::get<std::string>(stmt->column_variant("origin_implementation"));
|
||||
const ImplementationIdentifier err_origin(err_origin_module_id, err_origin_impl_id);
|
||||
const Everest::error::Error::time_point err_timestamp =
|
||||
Everest::Date::from_rfc3339(std::get<std::string>(stmt->column_variant("timestamp")));
|
||||
const Everest::error::Severity err_severity =
|
||||
Everest::error::string_to_severity(std::get<std::string>(stmt->column_variant("severity")));
|
||||
const Everest::error::State err_state =
|
||||
Everest::error::string_to_state(std::get<std::string>(stmt->column_variant("state")));
|
||||
const Everest::error::ErrorHandle err_handle(
|
||||
Everest::error::ErrorHandle(std::get<std::string>(stmt->column_variant("uuid"))));
|
||||
const Everest::error::ErrorSubType err_sub_type(std::get<std::string>(stmt->column_variant("sub_type")));
|
||||
const std::string err_vendor_id = std::get<std::string>(stmt->column_variant("vendor_id"));
|
||||
Everest::error::ErrorPtr error = std::make_shared<Everest::error::Error>(
|
||||
err_type, err_sub_type, err_msg, err_description, err_origin, err_vendor_id, err_severity,
|
||||
err_timestamp, err_handle, err_state);
|
||||
result.push_back(error);
|
||||
}
|
||||
if (status != SQLITE_DONE) {
|
||||
throw everest::db::QueryExecutionException(db.get_error_message());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error getting errors from database: " << e.what();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr>
|
||||
ErrorDatabaseSqlite::edit_errors(const std::list<Everest::error::ErrorFilter>& filters, EditErrorFunc edit_func) {
|
||||
std::lock_guard<std::mutex> lock(this->db_mutex);
|
||||
std::list<Everest::error::ErrorPtr> result = this->remove_errors_without_mutex(filters);
|
||||
for (Everest::error::ErrorPtr& error : result) {
|
||||
edit_func(error);
|
||||
this->add_error_without_mutex(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr>
|
||||
ErrorDatabaseSqlite::remove_errors(const std::list<Everest::error::ErrorFilter>& filters) {
|
||||
std::lock_guard<std::mutex> lock(this->db_mutex);
|
||||
return this->remove_errors_without_mutex(filters);
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr>
|
||||
ErrorDatabaseSqlite::remove_errors_without_mutex(const std::list<Everest::error::ErrorFilter>& filters) {
|
||||
BOOST_LOG_FUNCTION();
|
||||
std::optional<std::string> condition = ErrorDatabaseSqlite::filters_to_sql_condition(filters);
|
||||
std::list<Everest::error::ErrorPtr> result = this->get_errors(condition);
|
||||
try {
|
||||
everest::db::sqlite::Connection db(this->db_path);
|
||||
if (!db.open_connection()) {
|
||||
EVLOG_error << "Error opening database";
|
||||
throw everest::db::ConnectionException(db.get_error_message());
|
||||
}
|
||||
std::string sql = "DELETE FROM errors";
|
||||
if (condition.has_value()) {
|
||||
sql += " WHERE " + condition.value();
|
||||
}
|
||||
db.execute_statement(sql);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error removing errors from database: " << e.what();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef ERROR_HISTORY_ERROR_DATABASE_SQLITE_HPP
|
||||
#define ERROR_HISTORY_ERROR_DATABASE_SQLITE_HPP
|
||||
|
||||
#include <utils/error/error_database.hpp>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace module {
|
||||
|
||||
class ErrorDatabaseSqlite : public Everest::error::ErrorDatabase {
|
||||
public:
|
||||
explicit ErrorDatabaseSqlite(const fs::path& db_path_, const bool reset_ = false);
|
||||
|
||||
std::list<Everest::error::ErrorPtr>
|
||||
get_errors(const std::list<Everest::error::ErrorFilter>& filters) const override;
|
||||
|
||||
void add_error(Everest::error::ErrorPtr error) override;
|
||||
std::list<Everest::error::ErrorPtr> edit_errors(const std::list<Everest::error::ErrorFilter>& filters,
|
||||
EditErrorFunc edit_func) override;
|
||||
std::list<Everest::error::ErrorPtr> remove_errors(const std::list<Everest::error::ErrorFilter>& filters) override;
|
||||
|
||||
private:
|
||||
void add_error_without_mutex(Everest::error::ErrorPtr error);
|
||||
std::list<Everest::error::ErrorPtr>
|
||||
remove_errors_without_mutex(const std::list<Everest::error::ErrorFilter>& filters);
|
||||
std::list<Everest::error::ErrorPtr> get_errors(const std::optional<std::string>& condition) const;
|
||||
static std::string filter_to_sql_condition(const Everest::error::ErrorFilter& filter);
|
||||
static std::optional<std::string> filters_to_sql_condition(const std::list<Everest::error::ErrorFilter>& filters);
|
||||
|
||||
void reset_database();
|
||||
void check_database();
|
||||
const fs::path db_path;
|
||||
mutable std::mutex db_mutex;
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // ERROR_HISTORY_ERROR_DATABASE_SQLITE_HPP
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "ErrorHistory.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void ErrorHistory::init() {
|
||||
invoke_init(*p_error_history);
|
||||
}
|
||||
|
||||
void ErrorHistory::ready() {
|
||||
invoke_ready(*p_error_history);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef ERROR_HISTORY_HPP
|
||||
#define ERROR_HISTORY_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/error_history/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class ErrorHistory : public Everest::ModuleBase {
|
||||
public:
|
||||
ErrorHistory() = delete;
|
||||
ErrorHistory(const ModuleInfo& info, std::unique_ptr<error_historyImplBase> p_error_history, Conf& config) :
|
||||
ModuleBase(info), p_error_history(std::move(p_error_history)), config(config){};
|
||||
|
||||
const std::unique_ptr<error_historyImplBase> p_error_history;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // ERROR_HISTORY_HPP
|
||||
@@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "error_historyImpl.hpp"
|
||||
|
||||
#include "../ErrorDatabaseSqlite.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace module {
|
||||
namespace error_history {
|
||||
|
||||
void error_historyImpl::init() {
|
||||
this->db = std::make_shared<ErrorDatabaseSqlite>(this->config.database_path);
|
||||
|
||||
Everest::error::StateFilter state_filter(Everest::error::State::Active);
|
||||
Everest::error::ErrorFilter error_filter(state_filter);
|
||||
this->db->edit_errors(
|
||||
{error_filter}, [](Everest::error::ErrorPtr error) { error->state = Everest::error::State::ClearedByReboot; });
|
||||
subscribe_global_all_errors(
|
||||
[this](const Everest::error::Error& error) { this->handle_global_all_errors(error); },
|
||||
[this](const Everest::error::Error& error) { this->handle_global_all_errors_cleared(error); });
|
||||
}
|
||||
|
||||
void error_historyImpl::ready() {
|
||||
}
|
||||
|
||||
Everest::error::StateFilter convert_state_filter(types::error_history::State filter) {
|
||||
switch (filter) {
|
||||
case types::error_history::State::Active:
|
||||
return Everest::error::StateFilter::Active;
|
||||
case types::error_history::State::ClearedByModule:
|
||||
return Everest::error::StateFilter::ClearedByModule;
|
||||
case types::error_history::State::ClearedByReboot:
|
||||
return Everest::error::StateFilter::ClearedByReboot;
|
||||
}
|
||||
throw std::out_of_range("No known enum conversion from enum type types::error_history::State to enum type "
|
||||
"Everest::error::StateFilter");
|
||||
}
|
||||
|
||||
Everest::error::SeverityFilter convert_severity_filter(types::error_history::SeverityFilter filter) {
|
||||
switch (filter) {
|
||||
case types::error_history::SeverityFilter::LOW_GE:
|
||||
return Everest::error::SeverityFilter::LOW_GE;
|
||||
case types::error_history::SeverityFilter::MEDIUM_GE:
|
||||
return Everest::error::SeverityFilter::MEDIUM_GE;
|
||||
case types::error_history::SeverityFilter::HIGH_GE:
|
||||
return Everest::error::SeverityFilter::HIGH_GE;
|
||||
}
|
||||
throw std::out_of_range("No known enum conversion from enum type types::error_history::SeverityFilter to enum type "
|
||||
"Everest::error::SeverityFilter");
|
||||
}
|
||||
|
||||
std::vector<types::error_history::ErrorObject>
|
||||
error_historyImpl::handle_get_errors(types::error_history::FilterArguments& filters) {
|
||||
std::list<Everest::error::ErrorFilter> error_filters;
|
||||
if (filters.state_filter.has_value()) {
|
||||
Everest::error::StateFilter state_filter = convert_state_filter(filters.state_filter.value());
|
||||
error_filters.push_back(Everest::error::ErrorFilter(state_filter));
|
||||
}
|
||||
if (filters.origin_filter.has_value()) {
|
||||
Everest::error::OriginFilter origin_filter(filters.origin_filter.value().module_id,
|
||||
filters.origin_filter.value().implementation_id);
|
||||
error_filters.push_back(Everest::error::ErrorFilter(origin_filter));
|
||||
}
|
||||
if (filters.type_filter.has_value()) {
|
||||
Everest::error::TypeFilter type_filter(filters.type_filter.value());
|
||||
error_filters.push_back(Everest::error::ErrorFilter(type_filter));
|
||||
}
|
||||
if (filters.severity_filter.has_value()) {
|
||||
Everest::error::SeverityFilter severity_filter = convert_severity_filter(filters.severity_filter.value());
|
||||
error_filters.push_back(Everest::error::ErrorFilter(severity_filter));
|
||||
}
|
||||
if (filters.timeperiod_filter.has_value()) {
|
||||
Everest::error::TimePeriodFilter timeperiod_filter;
|
||||
timeperiod_filter.from = Everest::Date::from_rfc3339(filters.timeperiod_filter.value().timestamp_from);
|
||||
timeperiod_filter.to = Everest::Date::from_rfc3339(filters.timeperiod_filter.value().timestamp_to);
|
||||
error_filters.push_back(Everest::error::ErrorFilter(timeperiod_filter));
|
||||
}
|
||||
if (filters.handle_filter.has_value()) {
|
||||
Everest::error::HandleFilter handle_filter(filters.handle_filter.value());
|
||||
error_filters.push_back(Everest::error::ErrorFilter(handle_filter));
|
||||
}
|
||||
std::list<Everest::error::ErrorPtr> errors = this->db->get_errors(error_filters);
|
||||
std::vector<types::error_history::ErrorObject> result;
|
||||
std::transform(errors.begin(), errors.end(), std::back_inserter(result), [](Everest::error::ErrorPtr error) {
|
||||
types::error_history::ErrorObject error_object;
|
||||
error_object.uuid = error->uuid.to_string();
|
||||
error_object.timestamp = Everest::Date::to_rfc3339(error->timestamp);
|
||||
std::string string_state = Everest::error::state_to_string(error->state);
|
||||
error_object.state = types::error_history::string_to_state(string_state);
|
||||
std::string string_severity = Everest::error::severity_to_string(error->severity);
|
||||
error_object.severity = types::error_history::string_to_severity(string_severity);
|
||||
error_object.type = error->type;
|
||||
error_object.sub_type = error->sub_type;
|
||||
error_object.origin.module_id = error->origin.module_id;
|
||||
error_object.origin.implementation_id = error->origin.implementation_id;
|
||||
error_object.message = error->message;
|
||||
error_object.description = error->description;
|
||||
return error_object;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void error_historyImpl::handle_global_all_errors(const Everest::error::Error& error) {
|
||||
Everest::error::ErrorPtr error_ptr = std::make_shared<Everest::error::Error>(error);
|
||||
this->db->add_error(error_ptr);
|
||||
}
|
||||
|
||||
void error_historyImpl::handle_global_all_errors_cleared(const Everest::error::Error& error) {
|
||||
Everest::error::HandleFilter handle_filter(error.uuid);
|
||||
Everest::error::ErrorFilter error_filter(handle_filter);
|
||||
int edited_errors =
|
||||
this->db
|
||||
->edit_errors({error_filter},
|
||||
[](Everest::error::ErrorPtr error) { error->state = Everest::error::State::ClearedByModule; })
|
||||
.size();
|
||||
if (edited_errors == 0) {
|
||||
EVLOG_error << "ErrorHistory: Error with uuid " << error.uuid.to_string() << " not found in database.";
|
||||
} else if (edited_errors > 1) {
|
||||
EVLOG_error << "ErrorHistory: Multiple errors with uuid " << error.uuid.to_string() << " found in database.";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace error_history
|
||||
} // namespace module
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef ERROR_HISTORY_ERROR_HISTORY_IMPL_HPP
|
||||
#define ERROR_HISTORY_ERROR_HISTORY_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/error_history/Implementation.hpp>
|
||||
|
||||
#include "../ErrorHistory.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
#include "../ErrorDatabaseSqlite.hpp"
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace error_history {
|
||||
|
||||
struct Conf {
|
||||
std::string database_path;
|
||||
};
|
||||
|
||||
class error_historyImpl : public error_historyImplBase {
|
||||
public:
|
||||
error_historyImpl() = delete;
|
||||
error_historyImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<ErrorHistory>& mod, Conf& config) :
|
||||
error_historyImplBase(ev, "error_history"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
friend class ErrorDatabaseSqlite; // for write access to db
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual std::vector<types::error_history::ErrorObject>
|
||||
handle_get_errors(types::error_history::FilterArguments& filters) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<ErrorHistory>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
void handle_global_all_errors(const Everest::error::Error& error);
|
||||
void handle_global_all_errors_cleared(const Everest::error::Error& error);
|
||||
|
||||
std::shared_ptr<ErrorDatabaseSqlite> db;
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace error_history
|
||||
} // namespace module
|
||||
|
||||
#endif // ERROR_HISTORY_ERROR_HISTORY_IMPL_HPP
|
||||
14
tools/EVerest-main/modules/Misc/ErrorHistory/manifest.yaml
Normal file
14
tools/EVerest-main/modules/Misc/ErrorHistory/manifest.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
description: This module provides a persistent error history
|
||||
provides:
|
||||
error_history:
|
||||
description: Error history
|
||||
interface: error_history
|
||||
config:
|
||||
database_path:
|
||||
type: string
|
||||
description: Absolute path to the database file
|
||||
enable_global_errors: true
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Andreas Heinrich
|
||||
@@ -0,0 +1,30 @@
|
||||
set(TARGET_NAME ${PROJECT_NAME}_module_error_history_tests)
|
||||
add_executable(${TARGET_NAME})
|
||||
|
||||
target_sources(${TARGET_NAME}
|
||||
PRIVATE
|
||||
error_database_sqlite_tests.cpp
|
||||
../ErrorDatabaseSqlite.cpp
|
||||
helpers.cpp
|
||||
)
|
||||
|
||||
# target_include_directories(module_error_history_tests
|
||||
# PRIVATE
|
||||
# )
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
PRIVATE
|
||||
everest::framework
|
||||
everest::log
|
||||
everest::sqlite
|
||||
SQLite::SQLite3
|
||||
Catch2::Catch2WithMain
|
||||
)
|
||||
if(NOT DISABLE_EDM)
|
||||
list(APPEND CMAKE_MODULE_PATH ${CPM_PACKAGE_catch2_SOURCE_DIR}/extras)
|
||||
include(Catch)
|
||||
catch_discover_tests(${TARGET_NAME})
|
||||
endif()
|
||||
|
||||
add_test(${TARGET_NAME} ${TARGET_NAME})
|
||||
ev_register_test_target(${TARGET_NAME})
|
||||
@@ -0,0 +1,288 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "../ErrorDatabaseSqlite.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
SCENARIO("Check ErrorDatabaseSqlite class", "[!throws]") {
|
||||
GIVEN("An ErrorDatabaseSqlite object") {
|
||||
const std::string bin_dir = get_bin_dir().string() + "/";
|
||||
const std::string db_name = get_unique_db_name();
|
||||
TestDatabase db(bin_dir + "/databases/" + db_name, true);
|
||||
WHEN("Getting all errors") {
|
||||
THEN("The database should be empty") {
|
||||
auto errors = db.get_errors(std::list<Everest::error::ErrorFilter>());
|
||||
REQUIRE(errors.empty());
|
||||
}
|
||||
}
|
||||
WHEN("Adding an error") {
|
||||
std::vector<Everest::error::ErrorPtr> test_errors = {std::make_shared<Everest::error::Error>(
|
||||
"test_type", "test_sub_type", "test_message", "test_description",
|
||||
ImplementationIdentifier("test_origin_module", "test_origin_implementation"), "everest-test",
|
||||
Everest::error::Severity::Low, date::utc_clock::now(), Everest::error::UUID(),
|
||||
Everest::error::State::Active)};
|
||||
db.add_error(test_errors.at(0));
|
||||
THEN("The error should be in the database") {
|
||||
check_expected_errors_in_list(test_errors, db.get_errors(std::list<Everest::error::ErrorFilter>()));
|
||||
}
|
||||
}
|
||||
WHEN("Adding multiple errors") {
|
||||
std::vector<Everest::error::ErrorPtr> test_errors = {
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_a", "test_sub_type_a", "test_message_a", "test_description_a",
|
||||
ImplementationIdentifier("test_origin_module_a", "test_origin_implementation_a"), "everest-test",
|
||||
Everest::error::Severity::High, date::utc_clock::now(), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByModule),
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_b", "test_sub_type_b", "test_message_b", "test_description_b",
|
||||
ImplementationIdentifier("test_origin_module_b", "test_origin_implementation_b"), "everest-test",
|
||||
Everest::error::Severity::Medium, date::utc_clock::now(), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByReboot)};
|
||||
for (Everest::error::ErrorPtr error : test_errors) {
|
||||
db.add_error(error);
|
||||
}
|
||||
THEN("The errors should be in the database") {
|
||||
auto errors = db.get_errors(std::list<Everest::error::ErrorFilter>());
|
||||
check_expected_errors_in_list(test_errors, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
GIVEN("12 Errors in a connected ErrorDatabaseSqlite object") {
|
||||
const std::string bin_dir = get_bin_dir().string() + "/";
|
||||
const std::string db_name = get_unique_db_name();
|
||||
TestDatabase db(bin_dir + "/databases/" + db_name, true);
|
||||
std::vector<Everest::error::ErrorPtr> test_errors = get_test_errors();
|
||||
for (Everest::error::ErrorPtr error : test_errors) {
|
||||
db.add_error(error);
|
||||
}
|
||||
WHEN("Getting all errors") {
|
||||
auto errors = db.get_errors(std::list<Everest::error::ErrorFilter>());
|
||||
THEN("The result should contain all 12 errors") {
|
||||
check_expected_errors_in_list(test_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Getting all errors with StateFilter") {
|
||||
auto errors = db.get_errors({Everest::error::ErrorFilter(Everest::error::StateFilter::Active)});
|
||||
THEN("The result should contain specific errors") {
|
||||
std::vector<Everest::error::ErrorPtr> expected_errors({test_errors[3], test_errors[4], test_errors[5]});
|
||||
check_expected_errors_in_list(expected_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Getting all errors with OriginFilter") {
|
||||
auto errors = db.get_errors({Everest::error::ErrorFilter(
|
||||
Everest::error::OriginFilter("test_origin_module_a", "test_origin_implementation_a"))});
|
||||
THEN("The result should contain specific errors") {
|
||||
std::vector<Everest::error::ErrorPtr> expected_errors(
|
||||
{test_errors[0], test_errors[2], test_errors[4], test_errors[6], test_errors[8], test_errors[10]});
|
||||
check_expected_errors_in_list(expected_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Getting all errors with TypeFilter") {
|
||||
auto errors = db.get_errors({Everest::error::ErrorFilter(Everest::error::TypeFilter("test_type_c"))});
|
||||
THEN("The result should contain specific errors") {
|
||||
std::vector<Everest::error::ErrorPtr> expected_errors(
|
||||
{test_errors[2], test_errors[3], test_errors[4], test_errors[5], test_errors[9], test_errors[10]});
|
||||
check_expected_errors_in_list(expected_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Getting all errors with SeverityFilter") {
|
||||
auto errors = db.get_errors({Everest::error::ErrorFilter(Everest::error::SeverityFilter::MEDIUM_GE)});
|
||||
THEN("The result should contain specific errors") {
|
||||
std::vector<Everest::error::ErrorPtr> expected_errors({test_errors[4], test_errors[5], test_errors[6],
|
||||
test_errors[7], test_errors[8], test_errors[9],
|
||||
test_errors[10], test_errors[11]});
|
||||
check_expected_errors_in_list(expected_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Getting all errors with TimePeriodFilter") {
|
||||
auto errors = db.get_errors({Everest::error::ErrorFilter(
|
||||
Everest::error::TimePeriodFilter{date::utc_clock::now() + std::chrono::minutes(150),
|
||||
date::utc_clock::now() + std::chrono::minutes(270)})});
|
||||
THEN("The result should contain specific errors") {
|
||||
std::vector<Everest::error::ErrorPtr> expected_errors({test_errors[3], test_errors[4]});
|
||||
check_expected_errors_in_list(expected_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Getting all errors with HandleFilter") {
|
||||
auto errors =
|
||||
db.get_errors({Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))});
|
||||
THEN("The result should contain specific errors") {
|
||||
std::vector<Everest::error::ErrorPtr> expected_errors({test_errors[4]});
|
||||
check_expected_errors_in_list(expected_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Getting all errors with multiple filters") {
|
||||
auto errors = db.get_errors({Everest::error::ErrorFilter(Everest::error::StateFilter::Active),
|
||||
Everest::error::ErrorFilter(Everest::error::OriginFilter(
|
||||
"test_origin_module_a", "test_origin_implementation_a"))});
|
||||
THEN("The result should contain specific errors") {
|
||||
std::vector<Everest::error::ErrorPtr> expected_errors({test_errors[4]});
|
||||
check_expected_errors_in_list(expected_errors, errors);
|
||||
}
|
||||
}
|
||||
WHEN("Filtering all errors out") {
|
||||
auto errors = db.get_errors({
|
||||
Everest::error::ErrorFilter(Everest::error::StateFilter::ClearedByModule),
|
||||
Everest::error::ErrorFilter(
|
||||
Everest::error::OriginFilter("test_origin_module_a", "test_origin_implementation_a")),
|
||||
Everest::error::ErrorFilter(Everest::error::TypeFilter("test_type_c")),
|
||||
Everest::error::ErrorFilter(Everest::error::SeverityFilter::HIGH_GE),
|
||||
});
|
||||
THEN("The result should contain no errors") {
|
||||
REQUIRE(errors.empty());
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Edit error type") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [](Everest::error::ErrorPtr error) {
|
||||
error->type = "new_type";
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 1);
|
||||
REQUIRE(errors.front()->type == "new_type");
|
||||
}
|
||||
}
|
||||
WHEN("Edit error state") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [](Everest::error::ErrorPtr error) {
|
||||
error->state = Everest::error::State::ClearedByModule;
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 1);
|
||||
REQUIRE(errors.front()->state == Everest::error::State::ClearedByModule);
|
||||
}
|
||||
}
|
||||
WHEN("Edit error severity") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [](Everest::error::ErrorPtr error) {
|
||||
error->severity = Everest::error::Severity::High;
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 1);
|
||||
REQUIRE(errors.front()->severity == Everest::error::Severity::High);
|
||||
}
|
||||
}
|
||||
WHEN("Edit error message") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [](Everest::error::ErrorPtr error) {
|
||||
error->message = "new_message";
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 1);
|
||||
REQUIRE(errors.front()->message == "new_message");
|
||||
}
|
||||
}
|
||||
WHEN("Edit error description") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [](Everest::error::ErrorPtr error) {
|
||||
error->description = "new_description";
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 1);
|
||||
REQUIRE(errors.front()->description == "new_description");
|
||||
}
|
||||
}
|
||||
WHEN("Edit error origin") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [](Everest::error::ErrorPtr error) {
|
||||
error->origin = ImplementationIdentifier("new_origin_module", "new_origin_implementation");
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 1);
|
||||
REQUIRE(errors.front()->origin ==
|
||||
ImplementationIdentifier("new_origin_module", "new_origin_implementation"));
|
||||
}
|
||||
}
|
||||
WHEN("Edit error timestamp") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
auto new_timestamp = date::utc_clock::now() + std::chrono::hours(10);
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [&new_timestamp](Everest::error::ErrorPtr error) {
|
||||
error->timestamp = new_timestamp;
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 1);
|
||||
REQUIRE(Everest::Date::to_rfc3339(errors.front()->timestamp) ==
|
||||
Everest::Date::to_rfc3339(new_timestamp));
|
||||
}
|
||||
}
|
||||
WHEN("Edit error uuid") {
|
||||
Everest::error::UUID new_uuid;
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func = [&new_uuid](Everest::error::ErrorPtr error) {
|
||||
error->uuid = new_uuid;
|
||||
};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.edit_errors(filters, edit_func);
|
||||
THEN("The error should be edited") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 0);
|
||||
errors = db.get_errors({Everest::error::ErrorFilter(Everest::error::HandleFilter(new_uuid))});
|
||||
REQUIRE(errors.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Remove error") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::HandleFilter(test_errors[4]->uuid))};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.remove_errors(filters);
|
||||
THEN("The error should be removed") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 0);
|
||||
}
|
||||
}
|
||||
WHEN("Remove multiple errors") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {
|
||||
Everest::error::ErrorFilter(Everest::error::StateFilter::Active),
|
||||
Everest::error::ErrorFilter(
|
||||
Everest::error::OriginFilter("test_origin_module_c", "test_origin_implementation_c"))};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.remove_errors(filters);
|
||||
THEN("The errors should be removed") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 0);
|
||||
}
|
||||
}
|
||||
WHEN("Remove all errors") {
|
||||
std::list<Everest::error::ErrorFilter> filters = {};
|
||||
REQUIRE(db.get_errors(filters).size() > 0);
|
||||
db.remove_errors(filters);
|
||||
THEN("The errors should be removed") {
|
||||
auto errors = db.get_errors(filters);
|
||||
REQUIRE(errors.size() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
134
tools/EVerest-main/modules/Misc/ErrorHistory/tests/helpers.cpp
Normal file
134
tools/EVerest-main/modules/Misc/ErrorHistory/tests/helpers.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <utils/error.hpp>
|
||||
|
||||
fs::path get_bin_dir() {
|
||||
return fs::canonical("/proc/self/exe").parent_path();
|
||||
}
|
||||
|
||||
std::string get_unique_db_name() {
|
||||
return "error_database_sqlite_" + Everest::error::UUID().to_string() + ".db";
|
||||
}
|
||||
|
||||
std::vector<Everest::error::ErrorPtr> get_test_errors() {
|
||||
return {// index 0
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_a", "test_sub_type_a", "test_message_a", "test_description_a",
|
||||
ImplementationIdentifier("test_origin_module_a", "test_origin_implementation_a"), "everest-test",
|
||||
Everest::error::Severity::Low, date::utc_clock::now(), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByModule),
|
||||
// index 1
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_b", "test_sub_type_b", "test_message_b", "test_description_b",
|
||||
ImplementationIdentifier("test_origin_module_b", "test_origin_implementation_b"), "everest-test",
|
||||
Everest::error::Severity::Low, date::utc_clock::now() + std::chrono::hours(1), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByModule),
|
||||
// index 2
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_c", "test_sub_type_c", "test_message_c", "test_description_c",
|
||||
ImplementationIdentifier("test_origin_module_a", "test_origin_implementation_a"), "everest-test",
|
||||
Everest::error::Severity::Low, date::utc_clock::now() + std::chrono::hours(2), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByModule),
|
||||
// index 3
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_c", "test_sub_type_c", "test_message_c", "test_description_c",
|
||||
ImplementationIdentifier("test_origin_module_c", "test_origin_implementation_c"), "everest-test",
|
||||
Everest::error::Severity::Low, date::utc_clock::now() + std::chrono::hours(3), Everest::error::UUID(),
|
||||
Everest::error::State::Active),
|
||||
// index 4
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_c", "test_sub_type_a", "test_message_c", "test_description_c",
|
||||
ImplementationIdentifier("test_origin_module_a", "test_origin_implementation_a"), "everest-test",
|
||||
Everest::error::Severity::Medium, date::utc_clock::now() + std::chrono::hours(4),
|
||||
Everest::error::UUID(), Everest::error::State::Active),
|
||||
// index 5
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_c", "test_sub_type_a", "test_message_c", "test_description_c",
|
||||
ImplementationIdentifier("test_origin_module_c", "test_origin_implementation_c"), "everest-test",
|
||||
Everest::error::Severity::Medium, date::utc_clock::now() + std::chrono::hours(5),
|
||||
Everest::error::UUID(), Everest::error::State::Active),
|
||||
// index 6
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_a", "test_sub_type_a", "test_message_a", "test_description_a",
|
||||
ImplementationIdentifier("test_origin_module_a", "test_origin_implementation_a"), "everest-test",
|
||||
Everest::error::Severity::Medium, date::utc_clock::now() + std::chrono::hours(6),
|
||||
Everest::error::UUID(), Everest::error::State::ClearedByReboot),
|
||||
// index 7
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_a", "test_sub_type_a", "test_message_a", "test_description_a",
|
||||
ImplementationIdentifier("test_origin_module_c", "test_origin_implementation_c"), "everest-test",
|
||||
Everest::error::Severity::Medium, date::utc_clock::now() + std::chrono::hours(7),
|
||||
Everest::error::UUID(), Everest::error::State::ClearedByReboot),
|
||||
// index 8
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_a", "test_sub_type_a", "test_message_a", "test_description_a",
|
||||
ImplementationIdentifier("test_origin_module_a", "test_origin_implementation_a"), "everest-test",
|
||||
Everest::error::Severity::High, date::utc_clock::now() + std::chrono::hours(8), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByReboot),
|
||||
// index 9
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_c", "test_sub_type_c", "test_message_c", "test_description_c",
|
||||
ImplementationIdentifier("test_origin_module_c", "test_origin_implementation_c"), "everest-test",
|
||||
Everest::error::Severity::High, date::utc_clock::now() + std::chrono::hours(9), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByReboot),
|
||||
// index 10
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_c", "test_sub_type_c", "test_message_c", "test_description_c",
|
||||
ImplementationIdentifier("test_origin_module_a", "test_origin_implementation_a"), "everest-test",
|
||||
Everest::error::Severity::High, date::utc_clock::now() + std::chrono::hours(10), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByReboot),
|
||||
// index 11
|
||||
std::make_shared<Everest::error::Error>(
|
||||
"test_type_b", "test_sub_type_b", "test_message_b", "test_description_b",
|
||||
ImplementationIdentifier("test_origin_module_c", "test_origin_implementation_c"), "everest-test",
|
||||
Everest::error::Severity::High, date::utc_clock::now() + std::chrono::hours(11), Everest::error::UUID(),
|
||||
Everest::error::State::ClearedByReboot)};
|
||||
}
|
||||
|
||||
void check_expected_errors_in_list(const std::vector<Everest::error::ErrorPtr>& expected_errors,
|
||||
const std::list<Everest::error::ErrorPtr>& errors) {
|
||||
REQUIRE(expected_errors.size() == errors.size());
|
||||
for (Everest::error::ErrorPtr exp_err : expected_errors) {
|
||||
auto result = std::find_if(errors.begin(), errors.end(), [&exp_err](const Everest::error::ErrorPtr& err) {
|
||||
return exp_err->uuid == err->uuid;
|
||||
});
|
||||
REQUIRE(result != errors.end());
|
||||
REQUIRE((*result)->type == exp_err->type);
|
||||
REQUIRE((*result)->message == exp_err->message);
|
||||
REQUIRE((*result)->description == exp_err->description);
|
||||
REQUIRE((*result)->origin == exp_err->origin);
|
||||
REQUIRE((*result)->severity == exp_err->severity);
|
||||
REQUIRE(Everest::Date::to_rfc3339((*result)->timestamp) == Everest::Date::to_rfc3339(exp_err->timestamp));
|
||||
REQUIRE((*result)->state == exp_err->state);
|
||||
}
|
||||
}
|
||||
|
||||
TestDatabase::TestDatabase(const fs::path& db_path_, const bool reset_) :
|
||||
db_path(db_path_), db(std::make_unique<module::ErrorDatabaseSqlite>(db_path_, reset_)) {
|
||||
}
|
||||
|
||||
TestDatabase::~TestDatabase() {
|
||||
fs::remove(db_path);
|
||||
}
|
||||
|
||||
void TestDatabase::add_error(Everest::error::ErrorPtr error) {
|
||||
db->add_error(error);
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr>
|
||||
TestDatabase::get_errors(const std::list<Everest::error::ErrorFilter>& filters) const {
|
||||
return db->get_errors(filters);
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr> TestDatabase::edit_errors(const std::list<Everest::error::ErrorFilter>& filters,
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func) {
|
||||
return db->edit_errors(filters, edit_func);
|
||||
}
|
||||
|
||||
std::list<Everest::error::ErrorPtr> TestDatabase::remove_errors(const std::list<Everest::error::ErrorFilter>& filters) {
|
||||
return db->remove_errors(filters);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef ERROR_HISTORY_TESTS_HELPERS_HPP
|
||||
#define ERROR_HISTORY_TESTS_HELPERS_HPP
|
||||
|
||||
#include "../ErrorDatabaseSqlite.hpp"
|
||||
#include <filesystem>
|
||||
#include <list>
|
||||
#include <utils/error.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
///
|
||||
/// \brief get the path to the binary directory
|
||||
/// \return the path to the binary directory
|
||||
///
|
||||
fs::path get_bin_dir();
|
||||
|
||||
///
|
||||
/// \brief get a unique database name
|
||||
/// \return a unique database name
|
||||
///
|
||||
std::string get_unique_db_name();
|
||||
|
||||
///
|
||||
/// \brief get a vector of test errors
|
||||
/// \return a vector of test errors
|
||||
///
|
||||
std::vector<Everest::error::ErrorPtr> get_test_errors();
|
||||
|
||||
///
|
||||
/// \brief check if the given errors are equal
|
||||
/// \param expected_errors the expected errors
|
||||
/// \param errors the errors to check
|
||||
///
|
||||
void check_expected_errors_in_list(const std::vector<Everest::error::ErrorPtr>& expected_errors,
|
||||
const std::list<Everest::error::ErrorPtr>& errors);
|
||||
|
||||
///
|
||||
/// \brief wrapper class for the ErrorDatabaseSqlite class
|
||||
/// This class is used to test the ErrorDatabaseSqlite class
|
||||
/// It proxies the ErrorDatabaseSqlite class, but
|
||||
/// the destructor deletes the database file
|
||||
///
|
||||
class TestDatabase {
|
||||
public:
|
||||
explicit TestDatabase(const fs::path& db_path_, const bool reset_ = false);
|
||||
~TestDatabase();
|
||||
void add_error(Everest::error::ErrorPtr error);
|
||||
std::list<Everest::error::ErrorPtr> get_errors(const std::list<Everest::error::ErrorFilter>& filters) const;
|
||||
std::list<Everest::error::ErrorPtr> edit_errors(const std::list<Everest::error::ErrorFilter>& filters,
|
||||
Everest::error::ErrorDatabase::EditErrorFunc edit_func);
|
||||
std::list<Everest::error::ErrorPtr> remove_errors(const std::list<Everest::error::ErrorFilter>& filters);
|
||||
|
||||
private:
|
||||
std::unique_ptr<module::ErrorDatabaseSqlite> db;
|
||||
const fs::path db_path;
|
||||
};
|
||||
|
||||
#endif // ERROR_HISTORY_TESTS_HELPERS_HPP
|
||||
@@ -0,0 +1,40 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
Pal::Sigslot
|
||||
everest::run_application
|
||||
everest::system
|
||||
)
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"diagnostics_handler.cpp"
|
||||
"rauc_dbus.cpp"
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/systemImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
install(
|
||||
PROGRAMS
|
||||
constants.env
|
||||
diagnostics_uploader.sh
|
||||
diagnostics_collector.sh
|
||||
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
|
||||
)
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "Linux_Systemd_Rauc.hpp"
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#include <everest/system/safe_system.hpp>
|
||||
|
||||
namespace {
|
||||
template <typename T> void safe_extract(T& dst, const Object& src, const std::string_view& item) {
|
||||
try {
|
||||
std::string s{item};
|
||||
dst = src.at(s);
|
||||
} catch (const std::exception& ex) {
|
||||
EVLOG_error << "Store[" << item << "] error: " << ex.what();
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
dst = module::Rauc::request_id_default;
|
||||
} else {
|
||||
dst.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace module {
|
||||
|
||||
void Linux_Systemd_Rauc::init() {
|
||||
invoke_init(*p_main);
|
||||
rauc.configure(this->config.VerifyUpdateScriptPath);
|
||||
|
||||
store_path = info.id + "_update_transaction";
|
||||
|
||||
// This is a transaction we should store permanently
|
||||
rauc.signal_store_update_transaction.connect([this](Rauc::UpdateTransaction t) {
|
||||
EVLOG_info << "Store update transaction: " << t.boot_slot << ' ' << t.primary_slot << " id: " << t.request_id;
|
||||
Object tx = {
|
||||
{"current_primary_slot", t.primary_slot}, {"current_boot_slot", t.boot_slot}, {"request_id", t.request_id}};
|
||||
r_store->call_store(store_path, tx);
|
||||
});
|
||||
|
||||
rauc.signal_remove_update_transaction.connect([this]() {
|
||||
EVLOG_info << "Update transaction removed.";
|
||||
r_store->call_delete(store_path);
|
||||
});
|
||||
|
||||
rauc.signal_firmware_update_status.connect(
|
||||
[this](const types::system::FirmwareUpdateStatusEnum& status, int32_t request_id) {
|
||||
EVLOG_info << "Report status to OCPP: " << types::system::firmware_update_status_enum_to_string(status)
|
||||
<< " Request id: " << request_id;
|
||||
p_main->publish_firmware_update_status({status, request_id});
|
||||
|
||||
if (status == types::system::FirmwareUpdateStatusEnum::InstallRebooting) {
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
|
||||
if (this->firmware_update_waiting_for_ocpp_unblocking) {
|
||||
EVLOG_info << "Reboot is blocked by OCPP (waiting for 'allow_firmware_installation' call)";
|
||||
this->firmware_update_reboot_scheduled = true;
|
||||
} else {
|
||||
reboot_after_firmware_update();
|
||||
}
|
||||
}
|
||||
|
||||
if (status == types::system::FirmwareUpdateStatusEnum::InstallVerificationFailed) {
|
||||
EVLOG_info << "Resetting firmware update state due to reported 'InstallVerificationFailed' status.";
|
||||
std::unique_lock<std::recursive_mutex> lock(this->firmware_update_progress_mx);
|
||||
this->firmware_update_waiting_for_ocpp_unblocking = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Linux_Systemd_Rauc::reboot_after_firmware_update() {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
|
||||
this->firmware_update_reboot_scheduled = false;
|
||||
}
|
||||
EVLOG_error << "-------------- Reboot after installation of update in 10 seconds ---------------";
|
||||
sleep(10);
|
||||
try {
|
||||
auto [cmd, args] = everest::lib::system::split_command_line(config.RebootCommand);
|
||||
auto res = everest::lib::system::safe_system(cmd, &args);
|
||||
if (res.status != everest::lib::system::CommandExecutionStatus::CMD_SUCCESS || res.code != 0) {
|
||||
EVLOG_error << "Unable to trigger reboot: ("
|
||||
<< everest::lib::system::cmd_execution_status_to_string(res.status) << ": "
|
||||
<< std::to_string(res.code) << ")";
|
||||
EVLOG_info << "Failed command: '" << everest::lib::system::command_string_repr(cmd, args) << "'";
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
EVLOG_error << "Configured reboot command is invalid: " << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
void Linux_Systemd_Rauc::ready() {
|
||||
invoke_ready(*p_main);
|
||||
|
||||
// Check if we are booting directly after an update install,
|
||||
// in this case close the update process on the CSMS
|
||||
if (r_store->call_exists(store_path)) {
|
||||
Rauc::UpdateTransaction tx;
|
||||
auto t = std::get<Object>(r_store->call_load(store_path));
|
||||
safe_extract(tx.boot_slot, t, "current_boot_slot");
|
||||
safe_extract(tx.primary_slot, t, "current_primary_slot");
|
||||
safe_extract(tx.request_id, t, "request_id");
|
||||
rauc.check_previous_transaction(tx);
|
||||
}
|
||||
}
|
||||
|
||||
void Linux_Systemd_Rauc::firmware_update_may_proceed_with_reboot_callback() {
|
||||
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
|
||||
this->firmware_update_waiting_for_ocpp_unblocking = false;
|
||||
if (this->firmware_update_reboot_scheduled) {
|
||||
this->reboot_after_firmware_update();
|
||||
}
|
||||
}
|
||||
|
||||
void Linux_Systemd_Rauc::install_firmware_bundle(const std::string& filename, int32_t request_id) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(this->firmware_update_progress_mx);
|
||||
this->firmware_update_waiting_for_ocpp_unblocking = true;
|
||||
}
|
||||
this->rauc.install_bundle(filename, request_id);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef LINUX_SYSTEMD_RAUC_HPP
|
||||
#define LINUX_SYSTEMD_RAUC_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/system/Implementation.hpp>
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
#include "rauc_dbus.hpp"
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
double DefaultRetries;
|
||||
double DefaultRetryInterval;
|
||||
std::string OCPPLogPath;
|
||||
std::string SessionLogPath;
|
||||
std::string RebootCommand;
|
||||
std::string VerifyUpdateScriptPath;
|
||||
};
|
||||
|
||||
class Linux_Systemd_Rauc : public Everest::ModuleBase {
|
||||
public:
|
||||
Linux_Systemd_Rauc() = delete;
|
||||
Linux_Systemd_Rauc(const ModuleInfo& info, std::unique_ptr<systemImplBase> p_main, std::unique_ptr<kvsIntf> r_store,
|
||||
Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), r_store(std::move(r_store)), config(config){};
|
||||
|
||||
const std::unique_ptr<systemImplBase> p_main;
|
||||
const std::unique_ptr<kvsIntf> r_store;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
Rauc rauc;
|
||||
void firmware_update_may_proceed_with_reboot_callback();
|
||||
void install_firmware_bundle(const std::string& filename, int32_t request_id);
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
std::string store_path;
|
||||
|
||||
std::recursive_mutex firmware_update_progress_mx;
|
||||
bool firmware_update_waiting_for_ocpp_unblocking =
|
||||
false; // Set to true in case of OTA update via OCPP; set to false when OCPP has signaled that installation may
|
||||
// proceed
|
||||
bool firmware_update_reboot_scheduled = false; // Set to true once a firmware update is installed but a restart has
|
||||
// been blocked by firmware_update_waiting_for_ocpp_unblocking
|
||||
void reboot_after_firmware_update();
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // LINUX_SYSTEMD_RAUC_HPP
|
||||
@@ -0,0 +1,27 @@
|
||||
CONNECTION_TIMEOUT=20
|
||||
|
||||
DOWNLOADED="Downloaded"
|
||||
DOWNLOADING="Downloading"
|
||||
DOWNLOAD_FAILED="DownloadFailed"
|
||||
DOWNLOAD_SCHEDULED="DownloadScheduled"
|
||||
DOWNLOAD_PAUSED="DownloadPaused"
|
||||
|
||||
IDLE="Idle"
|
||||
|
||||
INSTALLATION_FAILED="InstallationFailed"
|
||||
INSTALLING="Installing"
|
||||
INSTALLED="Installed"
|
||||
INSTALL_REBOOTING="InstallRebooting"
|
||||
INSTALL_SCHEDULED="InstallScheduled"
|
||||
INSTALL_VERIFICATION_FAILED="InstallVerificationFailed"
|
||||
|
||||
PERMISSION_DENIED="PermissionDenied"
|
||||
NOT_SUPPORTED_OPERATION="NotSupportedOperation"
|
||||
BAD_MESSAGE="BadMessage"
|
||||
|
||||
INVALID_SIGNATURE="InvalidSignature"
|
||||
SIGNATURE_VERIFIED="SignatureVerified"
|
||||
|
||||
UPLOADED="Uploaded"
|
||||
UPLOAD_FAILURE="UploadFailure"
|
||||
UPLOADING="Uploading"
|
||||
186
tools/EVerest-main/modules/Misc/Linux_Systemd_Rauc/diagnostics_collector.sh
Executable file
186
tools/EVerest-main/modules/Misc/Linux_Systemd_Rauc/diagnostics_collector.sh
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
#
|
||||
#
|
||||
# time windowed collection is supported as follows:
|
||||
# - systemd journal supports "since" and "until" to specify the time window
|
||||
# - OCPP logs are rotated daily and a log file is included when its modification
|
||||
# time falls between "since" and "until"
|
||||
# - Completed session logs are included when their modification time falls
|
||||
# between "since" and "until" (i.e. the end of session time is used)
|
||||
# - In-progress session logs are included when their modification time falls
|
||||
# between "since" and now i.e. where "until" is specified then in-progress
|
||||
# sessions are unlikely to be included
|
||||
|
||||
error() {
|
||||
echo "$script error: $1" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: $script <journal|ocpp|session>"
|
||||
echo " [--dir /full/path/to/log/directory]"
|
||||
echo " [--since \"yyyy-mm-dd hh:mm:ss\"]"
|
||||
echo " [--until \"yyyy-mm-dd hh:mm:ss\"]"
|
||||
exit 2
|
||||
}
|
||||
|
||||
section_start() {
|
||||
printf "### START $1\n"
|
||||
}
|
||||
|
||||
section_end() {
|
||||
printf "\n### END $1\n"
|
||||
}
|
||||
|
||||
# remove T from date/time strings
|
||||
# 2023-12-18T16:06:03.435Z -> 2023-12-18 16:06:03.435Z
|
||||
reformat_date() {
|
||||
pre=$(echo $1 | cut -c1-10)
|
||||
post=$(echo $1 | cut -c12-19)
|
||||
if [ -n "$pre" ] && [ -n "$post" ]; then
|
||||
echo "$pre $post"
|
||||
fi
|
||||
}
|
||||
|
||||
# obtain the modification time of a file as a timestamp
|
||||
modify_ts() {
|
||||
if [ -f "$1" ]; then
|
||||
stat -c %Y "$1"
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
# convert the supplied date/time to a timestamp
|
||||
date_ts() {
|
||||
if [ -n "$1" ]; then
|
||||
date -u +%s -d "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
# output file if modified between optional dates
|
||||
# $1 file name
|
||||
# $2 optional not before date
|
||||
# $3 optional not after date
|
||||
# $4 line to output before file
|
||||
output_file() {
|
||||
if [ -f "$1" ]; then
|
||||
local modify_time=$(modify_ts "$1")
|
||||
local not_before=$(date_ts "$2")
|
||||
local not_after=$(date_ts "$3")
|
||||
local output=1
|
||||
[ -n "$not_before" ] && [ $modify_time -lt $not_before ] && output=0
|
||||
[ -n "$not_after" ] && [ $modify_time -gt $not_after ] && output=0
|
||||
if [ $output -ne 0 ]; then
|
||||
[ -n "$4" ] && echo "$4"
|
||||
sed -e '/^$/d' "$1"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# output systemd journal
|
||||
# $1 optional not before date
|
||||
# $2 optional not after date
|
||||
#
|
||||
# journalctl doesn't like empty arguments
|
||||
do_journal() {
|
||||
section_start JOURNAL
|
||||
if [ -n "$1" ]; then
|
||||
if [ -n "$2" ]; then
|
||||
journalctl --output=json --no-pager "--since=$1" "--until=$2"
|
||||
else
|
||||
journalctl --output=json --no-pager "--since=$1"
|
||||
fi
|
||||
else
|
||||
journalctl --output=json --no-pager
|
||||
fi
|
||||
local res=$?
|
||||
section_end JOURNAL
|
||||
return $res
|
||||
}
|
||||
|
||||
# output Everest OCPP logs
|
||||
# $1 Everest OCPP log directory
|
||||
# $2 optional not before date
|
||||
# $3 optional not after date
|
||||
do_ocpp() {
|
||||
[ -z "$1" ] && error "Missing OCPP log directory"
|
||||
local res=0
|
||||
section_start OCPP
|
||||
if [ -d "$1" ]; then
|
||||
find "$1" -name \*.log\* | sort -r | while read ocpp
|
||||
do
|
||||
output_file "$ocpp" "$2" "$3"
|
||||
done
|
||||
res=$?
|
||||
fi
|
||||
section_end OCPP
|
||||
return $res
|
||||
}
|
||||
|
||||
# output Everest charging session logs
|
||||
# $1 Everest session log directory
|
||||
# $2 optional not before date
|
||||
# $3 optional not after date
|
||||
do_session() {
|
||||
[ -z "$1" ] && error "Missing session log directory"
|
||||
local name
|
||||
local res=0
|
||||
section_start SESSION
|
||||
if [ -d "$1" ]; then
|
||||
find "$1" -type d | sort -n | while read session
|
||||
do
|
||||
name=$(basename "${session}")
|
||||
if [ -f "${session}/eventlog.csv" ]; then
|
||||
output_file "${session}/eventlog.csv" "$2" "$3" "--- ${name}"
|
||||
res=$?
|
||||
fi
|
||||
if [ -f "${session}/incomplete-eventlog.csv" ]; then
|
||||
output_file "${session}/incomplete-eventlog.csv" "$2" "$3" "--- ${name}"
|
||||
res=$?
|
||||
fi
|
||||
done
|
||||
fi
|
||||
section_end SESSION
|
||||
return $res
|
||||
}
|
||||
|
||||
script=$(basename $0)
|
||||
[ -z "$1" ] && error "Missing sub-command"
|
||||
cmd=$(basename "${1}")
|
||||
shift
|
||||
|
||||
TEMP=$(getopt -o "" --long 'dir:,since:,until:' -n "$script" -- "$@")
|
||||
if [ $? -ne 0 ]; then
|
||||
error "getopt parsing"
|
||||
fi
|
||||
|
||||
# Note the quotes around "$TEMP": they are essential!
|
||||
eval set -- "$TEMP"
|
||||
unset TEMP
|
||||
|
||||
dir=
|
||||
since=
|
||||
until=
|
||||
|
||||
while true
|
||||
do
|
||||
case "$1" in
|
||||
"--dir"|"--since"|"--until") eval "${1#--}=\"$2\""; shift 2; continue;;
|
||||
"--") shift; break;;
|
||||
esac
|
||||
done
|
||||
|
||||
since=$(reformat_date "$since")
|
||||
until=$(reformat_date "$until")
|
||||
|
||||
case $cmd in
|
||||
journal) do_journal "$since" "$until";;
|
||||
ocpp) do_ocpp "$dir" "$since" "$until";;
|
||||
session) do_session "$dir" "$since" "$until";;
|
||||
*) usage;;
|
||||
esac
|
||||
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "diagnostics_handler.hpp"
|
||||
#include <everest/logging.hpp>
|
||||
#include <everest/system/safe_system.hpp>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace module {
|
||||
|
||||
DiagnosticsHandler::log_result_t DiagnosticsHandler::create_log(const std::string& filename,
|
||||
const std::optional<std::string>& from,
|
||||
const std::optional<std::string>& to) {
|
||||
log_result_t result = log_result_t::success;
|
||||
|
||||
int fd;
|
||||
if ((fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IXUSR)) == -1) {
|
||||
EVLOG_error << "Unable to create file journal file: " << filename << " (" << errno << ")";
|
||||
result = log_result_t::error_file;
|
||||
} else {
|
||||
result = create_journal_log(fd, from, to);
|
||||
if (result == log_result_t::success) {
|
||||
result = create_ocpp_log(fd, from, to);
|
||||
}
|
||||
if (result == log_result_t::success) {
|
||||
result = create_session_log(fd, from, to);
|
||||
}
|
||||
(void)close(fd);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DiagnosticsHandler::log_result_t DiagnosticsHandler::collect_logs(int fd, const char* logType,
|
||||
const std::optional<std::string>& logDir,
|
||||
const std::optional<std::string>& from,
|
||||
const std::optional<std::string>& to) {
|
||||
log_result_t result = log_result_t::success;
|
||||
|
||||
std::string arg_cmd = "diagnostics_collector.sh";
|
||||
|
||||
std::vector<std::string> args;
|
||||
std::string cmd = script_dir + "/" + arg_cmd;
|
||||
|
||||
args.push_back(arg_cmd);
|
||||
args.emplace_back(logType);
|
||||
|
||||
if (logDir.has_value()) {
|
||||
args.emplace_back("--dir");
|
||||
args.push_back(logDir.value());
|
||||
}
|
||||
if (from.has_value()) {
|
||||
args.emplace_back("--since");
|
||||
args.push_back(from.value());
|
||||
}
|
||||
if (to.has_value()) {
|
||||
args.emplace_back("--until");
|
||||
args.push_back(to.value());
|
||||
}
|
||||
|
||||
const auto res = everest::lib::system::safe_system(fd, cmd, &args);
|
||||
if (res.status != everest::lib::system::CommandExecutionStatus::CMD_SUCCESS || res.code != 0) {
|
||||
EVLOG_error << "Unable to extract journal logs from:" << from.value_or("<not specified>")
|
||||
<< " to:" << to.value_or("<not specified>") << " ("
|
||||
<< everest::lib::system::cmd_execution_status_to_string(res.status) << ": "
|
||||
<< std::to_string(res.code) << ")";
|
||||
EVLOG_info << "Failed command: '" << everest::lib::system::command_string_repr(cmd, args) << "'";
|
||||
result = log_result_t::error_parameter;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef DIAGNOSTICS_HANDLER_HPP
|
||||
#define DIAGNOSTICS_HANDLER_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace module {
|
||||
|
||||
class DiagnosticsHandler {
|
||||
protected:
|
||||
std::string script_dir;
|
||||
std::string ocpp_dir;
|
||||
std::string session_dir;
|
||||
|
||||
public:
|
||||
enum class log_result_t {
|
||||
success,
|
||||
error,
|
||||
error_file,
|
||||
error_parameter,
|
||||
};
|
||||
DiagnosticsHandler() = delete;
|
||||
DiagnosticsHandler(const std::string& scriptDir, const std::string& ocppDir, const std::string& sessionDir) :
|
||||
script_dir(scriptDir), ocpp_dir(ocppDir), session_dir(sessionDir) {
|
||||
}
|
||||
log_result_t create_log(const std::string& filename, const std::optional<std::string>& from,
|
||||
const std::optional<std::string>& to);
|
||||
|
||||
protected:
|
||||
log_result_t create_journal_log(int fd, const std::optional<std::string>& from,
|
||||
const std::optional<std::string>& to) {
|
||||
return collect_logs(fd, "journal", std::nullopt, from, to);
|
||||
}
|
||||
log_result_t create_ocpp_log(int fd, const std::optional<std::string>& from, const std::optional<std::string>& to) {
|
||||
return collect_logs(fd, "ocpp", ocpp_dir, from, to);
|
||||
}
|
||||
log_result_t create_session_log(int fd, const std::optional<std::string>& from,
|
||||
const std::optional<std::string>& to) {
|
||||
return collect_logs(fd, "session", session_dir, from, to);
|
||||
}
|
||||
|
||||
/// @brief collect logs between two optional dates
|
||||
/// @param fd - the file descriptor to write the logs to
|
||||
/// @param logType - journal/ocpp/session
|
||||
/// @param logDir - directory location for OCPP and session logs
|
||||
/// @param from - format: "yyyy-mm-dd hh:mm"
|
||||
/// @param to - format: "yyyy-mm-dd hh:mm"
|
||||
/// @return result
|
||||
log_result_t collect_logs(int fd, const char* logType, const std::optional<std::string>& logDir,
|
||||
const std::optional<std::string>& from, const std::optional<std::string>& to);
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // DIAGNOSTICS_HANDLER_HPP
|
||||
27
tools/EVerest-main/modules/Misc/Linux_Systemd_Rauc/diagnostics_uploader.sh
Executable file
27
tools/EVerest-main/modules/Misc/Linux_Systemd_Rauc/diagnostics_uploader.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
#
|
||||
|
||||
|
||||
. "${1}"
|
||||
|
||||
echo "$UPLOADING"
|
||||
sleep 2
|
||||
curl -L --progress-bar --connect-timeout "$CONNECTION_TIMEOUT" -T "${4}" "${2}"
|
||||
curl_exit_code=$?
|
||||
if [[ $curl_exit_code -eq 0 ]]; then
|
||||
echo "$UPLOADED"
|
||||
elif [[ $curl_exit_code -eq 67 ]] || [[ $curl_exit_code -eq 35 ]] || [[ $curl_exit_code -eq 69 ]] ||
|
||||
[[ $curl_exit_code -eq 9 ]]; then
|
||||
echo "$PERMISSION_DENIED"
|
||||
elif [[ $curl_exit_code -eq 3 ]] || [[ $curl_exit_code -eq 6 ]] || [[ $curl_exit_code -eq 10 ]] ||
|
||||
[[ $curl_exit_code -eq 87 ]]; then
|
||||
echo "$BAD_MESSAGE"
|
||||
elif [[ $curl_exit_code -eq 1 ]]; then
|
||||
echo "$NOT_SUPPORTED_OPERATION"
|
||||
else
|
||||
echo "$UPLOAD_FAILURE"
|
||||
fi
|
||||
@@ -0,0 +1,191 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "systemImpl.hpp"
|
||||
#include "diagnostics_handler.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include <everest/run_application/run_application.hpp>
|
||||
|
||||
using namespace everest::run_application;
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
const std::string CONSTANTS = "constants.env";
|
||||
const std::string DIAGNOSTICS_UPLOADER = "diagnostics_uploader.sh";
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// FIXME (aw): this function needs to be refactored into some kind of utility library
|
||||
fs::path create_temp_file(const fs::path& dir, const std::string& prefix) {
|
||||
const std::string fn_template = (dir / prefix).string() + "XXXXXX" + std::string(1, '\0');
|
||||
std::vector<char> fn_template_buffer{fn_template.begin(), fn_template.end()};
|
||||
|
||||
// mkstemp needs to have at least 6 XXXXXX at the end and it will replace these
|
||||
// with a valid file name
|
||||
auto fd = mkstemp(fn_template_buffer.data());
|
||||
|
||||
if (fd == -1) {
|
||||
EVLOG_AND_THROW(Everest::EverestBaseRuntimeError("Failed to create temporary file at: " + fn_template));
|
||||
}
|
||||
|
||||
// close the file descriptor
|
||||
close(fd);
|
||||
|
||||
return fn_template_buffer.data();
|
||||
}
|
||||
|
||||
void systemImpl::init() {
|
||||
this->scripts_path = mod->info.paths.libexec;
|
||||
}
|
||||
|
||||
void systemImpl::ready() {
|
||||
}
|
||||
|
||||
types::system::UpdateFirmwareResponse
|
||||
systemImpl::handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) {
|
||||
// FIXME: implement planned updates at a specific time
|
||||
// FIXME: we don't care about the certificate and signature provided as an argument for now.
|
||||
// RAUC will not use them anyhow and updates will be equally secure whether they are launched by OCPP secure update
|
||||
// mechanism or the old non-secure mechanism.
|
||||
if (mod->rauc.is_idle()) {
|
||||
EVLOG_info << "Installing bundle from URL: " << firmware_update_request.location;
|
||||
this->mod->install_firmware_bundle(firmware_update_request.location, firmware_update_request.request_id);
|
||||
return types::system::UpdateFirmwareResponse::Accepted;
|
||||
} else {
|
||||
return types::system::UpdateFirmwareResponse::Rejected;
|
||||
}
|
||||
}
|
||||
|
||||
types::system::UploadLogsResponse
|
||||
systemImpl::handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) {
|
||||
types::system::UploadLogsResponse response;
|
||||
|
||||
if (this->log_upload_running) {
|
||||
response.upload_logs_status = types::system::UploadLogsStatus::AcceptedCanceled;
|
||||
} else {
|
||||
response.upload_logs_status = types::system::UploadLogsStatus::Accepted;
|
||||
}
|
||||
|
||||
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
const auto diagnostics_file_path = create_temp_file(fs::temp_directory_path(), "diagnostics-" + date_time);
|
||||
const auto diagnostics_file_name = diagnostics_file_path.filename().string();
|
||||
|
||||
response.upload_logs_status = types::system::UploadLogsStatus::Accepted;
|
||||
response.file_name = diagnostics_file_name;
|
||||
|
||||
// populate file with available logs within the specified time window
|
||||
DiagnosticsHandler diag(mod->info.paths.libexec, mod->config.OCPPLogPath, mod->config.SessionLogPath);
|
||||
const auto create_result = diag.create_log(diagnostics_file_path.c_str(), upload_logs_request.oldest_timestamp,
|
||||
upload_logs_request.latest_timestamp);
|
||||
|
||||
this->upload_logs_thread =
|
||||
std::thread([this, create_result, upload_logs_request, diagnostics_file_name, diagnostics_file_path]() {
|
||||
if (this->log_upload_running) {
|
||||
EVLOG_info << "Received Log upload request and log upload already running - cancelling current upload";
|
||||
this->interrupt_log_upload.exchange(true);
|
||||
EVLOG_info << "Waiting for other log upload to finish...";
|
||||
std::unique_lock<std::mutex> lk(this->log_upload_mutex);
|
||||
this->log_upload_cv.wait(lk, [this]() { return !this->log_upload_running; });
|
||||
EVLOG_info << "Previous Log upload finished!";
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lg(this->log_upload_mutex);
|
||||
EVLOG_info << "Starting upload of log file";
|
||||
this->interrupt_log_upload.exchange(false);
|
||||
this->log_upload_running = true;
|
||||
const auto diagnostics_uploader = this->scripts_path / DIAGNOSTICS_UPLOADER;
|
||||
const auto constants = this->scripts_path / CONSTANTS;
|
||||
|
||||
std::vector<std::string> args = {constants.string(), upload_logs_request.location, diagnostics_file_name,
|
||||
diagnostics_file_path.string()};
|
||||
bool uploaded = false;
|
||||
int32_t retries = 0;
|
||||
const auto total_retries = upload_logs_request.retries.value_or(this->mod->config.DefaultRetries);
|
||||
const auto retry_interval =
|
||||
upload_logs_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
|
||||
|
||||
types::system::LogStatus log_status;
|
||||
if (create_result == DiagnosticsHandler::log_result_t::error_file) {
|
||||
// problem creating the file - nothing to upload
|
||||
log_status.log_status = types::system::LogStatusEnum::UploadFailure;
|
||||
this->publish_log_status(log_status);
|
||||
} else {
|
||||
while (!uploaded && retries <= total_retries && !this->interrupt_log_upload) {
|
||||
retries += 1;
|
||||
log_status.request_id = upload_logs_request.request_id.value_or(-1);
|
||||
run_application(
|
||||
diagnostics_uploader.string(), args, [this, &log_status](const std::string& output_line) {
|
||||
if (output_line == "Uploaded") {
|
||||
log_status.log_status = types::system::string_to_log_status_enum(output_line);
|
||||
} else if (output_line == "UploadFailure" || output_line == "PermissionDenied" ||
|
||||
output_line == "BadMessage" || output_line == "NotSupportedOperation") {
|
||||
log_status.log_status = types::system::LogStatusEnum::UploadFailure;
|
||||
} else {
|
||||
log_status.log_status = types::system::LogStatusEnum::Uploading;
|
||||
}
|
||||
this->publish_log_status(log_status);
|
||||
if (this->interrupt_log_upload) {
|
||||
return CmdControl::Terminate;
|
||||
}
|
||||
return CmdControl::Continue;
|
||||
});
|
||||
if (this->interrupt_log_upload) {
|
||||
EVLOG_info << "Uploading Logs was interrupted, terminating upload script, requestId: "
|
||||
<< log_status.request_id;
|
||||
// N01.FR.20
|
||||
log_status.log_status = types::system::LogStatusEnum::AcceptedCanceled;
|
||||
this->publish_log_status(log_status);
|
||||
} else if (log_status.log_status != types::system::LogStatusEnum::Uploaded &&
|
||||
retries <= total_retries) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
|
||||
} else {
|
||||
uploaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->log_upload_running = false;
|
||||
this->log_upload_cv.notify_one();
|
||||
EVLOG_info << "Log upload thread finished";
|
||||
});
|
||||
this->upload_logs_thread.detach();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
bool systemImpl::handle_is_reset_allowed(types::system::ResetType& type) {
|
||||
// Allow resets at any time for now
|
||||
return true;
|
||||
}
|
||||
|
||||
void systemImpl::handle_reset(types::system::ResetType& type, bool& scheduled) {
|
||||
if (type == types::system::ResetType::Soft) {
|
||||
EVLOG_info << "Performing soft reset";
|
||||
// This will effectivly stop everest and make it restart via systemd
|
||||
exit(255);
|
||||
} else {
|
||||
EVLOG_info << "Performing hard reset";
|
||||
|
||||
// this reboots the whole linux system
|
||||
system("/sbin/reboot");
|
||||
}
|
||||
}
|
||||
|
||||
bool systemImpl::handle_set_system_time(std::string& timestamp) {
|
||||
// currently not supported, system runs on network time
|
||||
return true;
|
||||
}
|
||||
|
||||
types::system::BootReason systemImpl::handle_get_boot_reason() {
|
||||
return types::system::BootReason::Unknown;
|
||||
}
|
||||
|
||||
void systemImpl::handle_allow_firmware_installation() {
|
||||
EVLOG_info << "Received allow_firmware_installation command - allow firmware update to proceed with reboot.";
|
||||
this->mod->firmware_update_may_proceed_with_reboot_callback();
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_SYSTEM_IMPL_HPP
|
||||
#define MAIN_SYSTEM_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/system/Implementation.hpp>
|
||||
|
||||
#include "../Linux_Systemd_Rauc.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
#include <filesystem>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class systemImpl : public systemImplBase {
|
||||
public:
|
||||
systemImpl() = delete;
|
||||
systemImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Linux_Systemd_Rauc>& mod, Conf& config) :
|
||||
systemImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::system::UpdateFirmwareResponse
|
||||
handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) override;
|
||||
virtual void handle_allow_firmware_installation() override;
|
||||
virtual types::system::UploadLogsResponse
|
||||
handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) override;
|
||||
virtual bool handle_is_reset_allowed(types::system::ResetType& type) override;
|
||||
virtual void handle_reset(types::system::ResetType& type, bool& scheduled) override;
|
||||
virtual bool handle_set_system_time(std::string& timestamp) override;
|
||||
virtual types::system::BootReason handle_get_boot_reason() override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<Linux_Systemd_Rauc>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
std::filesystem::path scripts_path;
|
||||
bool log_upload_running{false};
|
||||
std::atomic_bool interrupt_log_upload;
|
||||
std::thread upload_logs_thread;
|
||||
std::mutex log_upload_mutex;
|
||||
std::condition_variable log_upload_cv;
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_SYSTEM_IMPL_HPP
|
||||
@@ -0,0 +1,45 @@
|
||||
description: This module implements system wide operations for the base linux system
|
||||
config:
|
||||
DefaultRetries:
|
||||
description: Specifies how many times Charge Point tries to upload or download files on previous failure.
|
||||
type: number
|
||||
default: 1
|
||||
DefaultRetryInterval:
|
||||
description: >-
|
||||
Specifies in seconds after which time a retry of an upload or download on previous failure may be attempted.
|
||||
type: number
|
||||
default: 1
|
||||
OCPPLogPath:
|
||||
description: Path to folder where logs of all OCPP messages get written to (see ocpp MessageLogPath)
|
||||
type: string
|
||||
default: /var/log/everest/ocpp
|
||||
SessionLogPath:
|
||||
description: Output directory for session log files (see evse_manager session_logging_path)
|
||||
type: string
|
||||
default: /var/log/everest/session
|
||||
RebootCommand:
|
||||
description: Command to execute for rebooting the system
|
||||
type: string
|
||||
default: /sbin/reboot
|
||||
VerifyUpdateScriptPath:
|
||||
description: >-
|
||||
Full path to shell script that checks if an OTA update has been successful. The script is executed after
|
||||
an update and shall verify the success of the update. It shall return 0 in case of success and
|
||||
non-zero in case of failure.
|
||||
If empty no check is performed and the update is considered successful and it is marked as good.
|
||||
type: string
|
||||
default: ""
|
||||
provides:
|
||||
main:
|
||||
description: Implements the system interface
|
||||
interface: system
|
||||
requires:
|
||||
store:
|
||||
interface: kvs
|
||||
enable_external_mqtt: false
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Piet Gömpel
|
||||
- Cornelius Claussen
|
||||
- James Chapman
|
||||
165
tools/EVerest-main/modules/Misc/Linux_Systemd_Rauc/rauc_dbus.cpp
Normal file
165
tools/EVerest-main/modules/Misc/Linux_Systemd_Rauc/rauc_dbus.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "rauc_dbus.hpp"
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace module {
|
||||
using namespace everest::lib::system;
|
||||
|
||||
void Rauc::configure_handlers() {
|
||||
namespace interface = rauc_dbus::interface;
|
||||
namespace property = rauc_dbus::property;
|
||||
namespace signal = rauc_dbus::signal;
|
||||
|
||||
// Subscribe to Complete signal (when install_bundle command finishes)
|
||||
dbus::registerSignalHandler(proxy, interface::Installer, signal::Completed, [this](sdbus::Signal signal) {
|
||||
// Complete signal has one int argument
|
||||
int i;
|
||||
signal >> i;
|
||||
if (update_request_id != request_id_default) {
|
||||
if (i == 0) {
|
||||
EVLOG_info << "RAUC: Installation successful, needs reboot to activate";
|
||||
// Signal to the module code to store the transaction in the database.
|
||||
// We will use this on next boot to signal a Success/Failed Installation
|
||||
signal_store_update_transaction(create_transaction(update_request_id, timeout_us));
|
||||
// The module code should reboot now since we signal InstallRebooting.
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InstallRebooting,
|
||||
update_request_id);
|
||||
} else {
|
||||
EVLOG_error << "RAUC: Installation failed with error code: " << i;
|
||||
if (is_installing) {
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InstallVerificationFailed,
|
||||
update_request_id);
|
||||
is_installing = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
EVLOG_debug << "RAUC: status from another source";
|
||||
}
|
||||
|
||||
// this was the last message, so reset request_id
|
||||
update_request_id = request_id_default;
|
||||
});
|
||||
|
||||
// Subscribe to property changes std::function<void(Signal signal)>;
|
||||
dbus::registerSignalHandler(
|
||||
proxy, interface::DBus_Properties, signal::PropertiesChanged, [this](sdbus::Signal signal) {
|
||||
// org.freedesktop.DBus.Properties.PropertiesChanged (STRING interface_name,
|
||||
// ARRAY of DICT_ENTRY<STRING,VARIANT>
|
||||
// changed_properties, ARRAY<STRING>
|
||||
// invalidated_properties);
|
||||
std::string interface;
|
||||
signal >> interface;
|
||||
// ignore updates when initiated by someone else
|
||||
if (update_request_id != request_id_default) {
|
||||
if (interface == interface::Installer) {
|
||||
std::map<std::string, sdbus::Variant> changed_properties;
|
||||
signal >> changed_properties;
|
||||
for (const auto& [key, value] : changed_properties) {
|
||||
if (key == property::Progress) {
|
||||
Progress r = value.get<Progress>();
|
||||
EVLOG_info << "Progress " << r.percent << "% " << r.description;
|
||||
|
||||
// Map progress to OCPP structs
|
||||
|
||||
if (r.description.find("Verifying signature done") != std::string::npos) {
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Downloaded,
|
||||
update_request_id);
|
||||
signal_firmware_update_status(
|
||||
types::system::FirmwareUpdateStatusEnum::SignatureVerified, update_request_id);
|
||||
signature_verified = true;
|
||||
|
||||
} else if (r.description.find("Verifying signature failed") != std::string::npos) {
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Downloaded,
|
||||
update_request_id);
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InvalidSignature,
|
||||
update_request_id);
|
||||
signature_verified = true;
|
||||
|
||||
// If bundle checking failed but we never got to signature verification download must
|
||||
// have failed
|
||||
} else if (!signature_verified &&
|
||||
r.description.find("Checking bundle failed") != std::string::npos) {
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::DownloadFailed,
|
||||
update_request_id);
|
||||
|
||||
} else if (r.description.find("Copying") != std::string::npos &&
|
||||
r.description.find("done") == std::string::npos) {
|
||||
is_installing = true;
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Installing,
|
||||
update_request_id);
|
||||
}
|
||||
|
||||
} else if (key == property::Operation) {
|
||||
auto r = rauc_dbus::rauc_messages::string_to_operation(value.get<std::string>());
|
||||
|
||||
if (r == Operation::Idle) {
|
||||
EVLOG_info << "RAUC operation: Idle";
|
||||
} else if (r == Operation::Installing) {
|
||||
EVLOG_info << "RAUC operation: Installing";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> invalidated_properties;
|
||||
signal >> invalidated_properties;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool Rauc::decide_if_good(const rauc_dbus::rauc_messages::UpdateTransaction& saved, const CurrentState& current) {
|
||||
// The original approach uses the primary slot, however this might not be
|
||||
// as reliable as hoped. A change in boot slot should be more reliable
|
||||
// however prior to this change the boot slot wasn't saved
|
||||
|
||||
bool result{false};
|
||||
|
||||
if (RaucBase::decide_if_good(saved, current)) {
|
||||
if (saved.boot_slot.empty()) {
|
||||
// use the previous approach
|
||||
EVLOG_warning << "OTA: fallback to using primary slot";
|
||||
result = saved.primary_slot == current.primary_slot;
|
||||
} else {
|
||||
// use the new approach
|
||||
result = saved.boot_slot != current.boot_slot;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Call on boot and pass a previous transaction that was not closed yet
|
||||
void Rauc::check_previous_transaction(UpdateTransaction t) {
|
||||
signal_remove_update_transaction();
|
||||
|
||||
if (rauc_dbus::RaucBaseSync::check_previous_transaction(t, timeout_us)) {
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Installed, t.request_id);
|
||||
} else {
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::InstallationFailed, t.request_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns immediately. Progress is signalled with signal_firmware_update_status
|
||||
rauc_dbus::rauc_messages::CmdResult Rauc::install_bundle(const std::string& filename, int32_t request_id) {
|
||||
signature_verified = false;
|
||||
is_installing = false;
|
||||
update_request_id = request_id;
|
||||
|
||||
const auto ret = rauc_dbus::RaucBaseSync::install_bundle(filename, timeout_us);
|
||||
if (ret.success) {
|
||||
signal_firmware_update_status(types::system::FirmwareUpdateStatusEnum::Downloading, update_request_id);
|
||||
} else {
|
||||
update_request_id = request_id_default;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Rauc::is_idle() {
|
||||
// Note it is important to query rauc here as well as it may be busy with a local install
|
||||
return (get_operation() == rauc_dbus::rauc_messages::Operation::Idle) && !is_installing;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef RAUC_DBUS_HPP
|
||||
#define RAUC_DBUS_HPP
|
||||
|
||||
#include <everest/system/rauc_dbus_base.hpp>
|
||||
#include <sigslot/signal.hpp>
|
||||
|
||||
#include <generated/types/system.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace module {
|
||||
namespace rauc_dbus = everest::lib::system::rauc_dbus;
|
||||
|
||||
class Rauc : public rauc_dbus::RaucBaseSync {
|
||||
public:
|
||||
// Note OCPP defaults to -1 when not provided
|
||||
static constexpr std::int32_t request_id_default = 0;
|
||||
|
||||
private:
|
||||
constexpr static std::uint64_t timeout_us = 10 * 1000 * 1000; // 10 seconds
|
||||
|
||||
std::atomic<int32_t> update_request_id{request_id_default};
|
||||
std::atomic_bool is_installing{false};
|
||||
std::atomic_bool signature_verified{false};
|
||||
|
||||
void configure_handlers() override;
|
||||
bool decide_if_good(const rauc_dbus::rauc_messages::UpdateTransaction& saved, const CurrentState& current) override;
|
||||
|
||||
public:
|
||||
using CmdResult = rauc_dbus::rauc_messages::CmdResult;
|
||||
using UpdateTransaction = rauc_dbus::rauc_messages::UpdateTransaction;
|
||||
using Operation = rauc_dbus::rauc_messages::Operation;
|
||||
using Progress = rauc_dbus::rauc_messages::Progress;
|
||||
|
||||
using rauc_dbus::RaucBaseSync::RaucBaseSync;
|
||||
Rauc(sdbus::dont_run_event_loop_thread_t) = delete;
|
||||
|
||||
void check_previous_transaction(UpdateTransaction t);
|
||||
bool is_idle();
|
||||
|
||||
CmdResult install_bundle(const std::string& filename, int32_t request_id);
|
||||
|
||||
void mark(const std::string& mark_s, const std::string& slot) {
|
||||
rauc_dbus::RaucBaseSync::mark(mark_s, slot, timeout_us);
|
||||
}
|
||||
|
||||
sigslot::signal<types::system::FirmwareUpdateStatusEnum, int32_t> signal_firmware_update_status;
|
||||
// Emitted when installed update is ready for reboot, the transaction needs to be stored persistently. On next boot,
|
||||
// call check_previous_transaction() with this as an argument
|
||||
sigslot::signal<UpdateTransaction> signal_store_update_transaction;
|
||||
sigslot::signal<> signal_remove_update_transaction;
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"token_validator/auth_token_validatorImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include "LocalAllowlistTokenValidator.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void LocalAllowlistTokenValidator::init() {
|
||||
invoke_init(*p_token_validator);
|
||||
}
|
||||
|
||||
void LocalAllowlistTokenValidator::ready() {
|
||||
invoke_ready(*p_token_validator);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef LOCAL_ALLOWLIST_TOKEN_VALIDATOR_HPP
|
||||
#define LOCAL_ALLOWLIST_TOKEN_VALIDATOR_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/auth_token_validator/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string allowlist_file;
|
||||
};
|
||||
|
||||
class LocalAllowlistTokenValidator : public Everest::ModuleBase {
|
||||
public:
|
||||
LocalAllowlistTokenValidator() = delete;
|
||||
LocalAllowlistTokenValidator(const ModuleInfo& info,
|
||||
std::unique_ptr<auth_token_validatorImplBase> p_token_validator, Conf& config) :
|
||||
ModuleBase(info), p_token_validator(std::move(p_token_validator)), config(config){};
|
||||
|
||||
const std::unique_ptr<auth_token_validatorImplBase> p_token_validator;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // LOCAL_ALLOWLIST_TOKEN_VALIDATOR_HPP
|
||||
@@ -0,0 +1,15 @@
|
||||
description: Token Validator for local allow list of RFID tags
|
||||
config:
|
||||
allowlist_file:
|
||||
description: path/filename of the file containing one RFID token per line
|
||||
type: string
|
||||
default: /mnt/user_data/etc/allowlist_rfid.txt
|
||||
provides:
|
||||
token_validator:
|
||||
description: Validator interface for auth
|
||||
interface: auth_token_validator
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Cornelius Claussen
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "auth_token_validatorImpl.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace token_validator {
|
||||
|
||||
void auth_token_validatorImpl::init() {
|
||||
}
|
||||
|
||||
void auth_token_validatorImpl::ready() {
|
||||
}
|
||||
|
||||
types::authorization::ValidationResult
|
||||
auth_token_validatorImpl::handle_validate_token(types::authorization::ProvidedIdToken& provided_token) {
|
||||
types::authorization::ValidationResult result;
|
||||
result.authorization_status = types::authorization::AuthorizationStatus::Invalid;
|
||||
|
||||
// load file each time we validate so that EVerest requires no restart when the file is changed
|
||||
std::ifstream file;
|
||||
|
||||
try {
|
||||
file.open(mod->config.allowlist_file);
|
||||
while (!file.eof()) {
|
||||
std::string token;
|
||||
getline(file, token);
|
||||
if (token == provided_token.id_token.value) {
|
||||
result.authorization_status = types::authorization::AuthorizationStatus::Accepted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (std::ifstream::failure e) {
|
||||
EVLOG_error << "Error opening/reading file " + mod->config.allowlist_file;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace token_validator
|
||||
} // namespace module
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef TOKEN_VALIDATOR_AUTH_TOKEN_VALIDATOR_IMPL_HPP
|
||||
#define TOKEN_VALIDATOR_AUTH_TOKEN_VALIDATOR_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/auth_token_validator/Implementation.hpp>
|
||||
|
||||
#include "../LocalAllowlistTokenValidator.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace token_validator {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class auth_token_validatorImpl : public auth_token_validatorImplBase {
|
||||
public:
|
||||
auth_token_validatorImpl() = delete;
|
||||
auth_token_validatorImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<LocalAllowlistTokenValidator>& mod,
|
||||
Conf& config) :
|
||||
auth_token_validatorImplBase(ev, "token_validator"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::authorization::ValidationResult
|
||||
handle_validate_token(types::authorization::ProvidedIdToken& provided_token) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<LocalAllowlistTokenValidator>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace token_validator
|
||||
} // namespace module
|
||||
|
||||
#endif // TOKEN_VALIDATOR_AUTH_TOKEN_VALIDATOR_IMPL_HPP
|
||||
23
tools/EVerest-main/modules/Misc/PacketSniffer/CMakeLists.txt
Normal file
23
tools/EVerest-main/modules/Misc/PacketSniffer/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
find_package(PCAP REQUIRED)
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
${PCAP_LIBRARY}
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
113
tools/EVerest-main/modules/Misc/PacketSniffer/PacketSniffer.cpp
Normal file
113
tools/EVerest-main/modules/Misc/PacketSniffer/PacketSniffer.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "PacketSniffer.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
namespace module {
|
||||
|
||||
const bool PROMISC_MODE = true;
|
||||
const int PACKET_BUFFER_TIMEOUT_MS = 1000;
|
||||
const int ALL_PACKETS_PROCESSED = -1;
|
||||
const int WAIT_FOR_MS = 10;
|
||||
const int BUFFERSIZE = 8192;
|
||||
|
||||
void PacketSniffer::init() {
|
||||
p_handle = pcap_open_live(config.device.c_str(), BUFFERSIZE, PROMISC_MODE, PACKET_BUFFER_TIMEOUT_MS, errbuf);
|
||||
std::string errb{errbuf};
|
||||
if (p_handle == nullptr) {
|
||||
EVLOG_error << fmt::format("Could not open device \"{}\", Sniffing disabled.{}", config.device,
|
||||
errb.size() > 0 ? (std::string(" Error: ") + errb) : "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.device != "any" && pcap_datalink(p_handle) != DLT_EN10MB) {
|
||||
EVLOG_error << fmt::format("Device \"{}\" doesn't provide Ethernet headers - not supported. Sniffing disabled.",
|
||||
config.device);
|
||||
pcap_close(p_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
EVLOG_info << fmt::format("Sniffing on device \"{}\"", config.device);
|
||||
|
||||
r_evse_manager->subscribe_session_event([this](types::evse_manager::SessionEvent session_event) {
|
||||
if (session_event.event == types::evse_manager::SessionEventEnum::SessionStarted) {
|
||||
if (!already_started) {
|
||||
|
||||
if (!session_event.session_started.has_value()) {
|
||||
EVLOG_warning
|
||||
<< "SessionStarted event type doesn't contain session_started data. Ignoring this event.";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string logging_path;
|
||||
if (!config.session_logging_path.empty()) {
|
||||
logging_path = config.session_logging_path;
|
||||
} else if (session_event.session_started->logging_path.has_value()) {
|
||||
logging_path = session_event.session_started->logging_path.value();
|
||||
} else {
|
||||
EVLOG_warning << "No logging path configured and none provided in SessionStarted event. "
|
||||
"Skipping capture.";
|
||||
return;
|
||||
}
|
||||
|
||||
capturing_stopped = false;
|
||||
std::thread(&PacketSniffer::capture, this, logging_path, session_event.uuid).detach();
|
||||
} else {
|
||||
EVLOG_warning << fmt::format("Capturing already started. Ignoring this SessionStarted event");
|
||||
}
|
||||
} else if (session_event.event == types::evse_manager::SessionEventEnum::SessionFinished) {
|
||||
capturing_stopped = true;
|
||||
pcap_breakloop(p_handle);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void PacketSniffer::ready() {
|
||||
}
|
||||
|
||||
void PacketSniffer::capture(const std::string& logpath, const std::string& session_id) {
|
||||
already_started = true;
|
||||
|
||||
std::string fn = fmt::format("{}/ethernet-traffic.pcap", logpath);
|
||||
if (not config.session_logging_path.empty()) {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto time_t_now = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm local_tm{};
|
||||
localtime_r(&time_t_now, &local_tm);
|
||||
const auto timestamp = fmt::format("{:%Y-%m-%d_%H-%M-%S%z}", local_tm);
|
||||
fn = fmt::format("{}/{}_{}.pcap", logpath, timestamp, session_id);
|
||||
}
|
||||
|
||||
EVLOG_info << fmt::format("Starting capturing to {}", fn);
|
||||
|
||||
if ((pdumpfile = pcap_dump_open(p_handle, fn.c_str())) == nullptr) {
|
||||
EVLOG_error << fmt::format("Error opening savefile {} for writing: {}", fn, pcap_geterr(p_handle));
|
||||
return;
|
||||
}
|
||||
|
||||
while (!capturing_stopped) {
|
||||
const int ret =
|
||||
pcap_dispatch(p_handle, ALL_PACKETS_PROCESSED, &pcap_dump, reinterpret_cast<u_char*>(pdumpfile));
|
||||
if (ret <= PCAP_ERROR) {
|
||||
const std::string base_msg = fmt::format("Error reading packets from interface \"{}\"", config.device);
|
||||
if (ret == PCAP_ERROR) {
|
||||
EVLOG_error << fmt::format("{}, error: {}", base_msg, pcap_geterr(p_handle));
|
||||
} else if (ret == PCAP_ERROR_BREAK) {
|
||||
EVLOG_warning << fmt::format("{}, interrupted but no packets received", base_msg);
|
||||
} else {
|
||||
EVLOG_error << fmt::format("{}, unexpected error: {}", base_msg, ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_FOR_MS));
|
||||
}
|
||||
|
||||
pcap_dump_close(pdumpfile);
|
||||
EVLOG_info << fmt::format("Stopped capturing to {}", fn);
|
||||
already_started = false;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef PACKET_SNIFFER_HPP
|
||||
#define PACKET_SNIFFER_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/evse_manager/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
#include <pcap.h>
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string device;
|
||||
std::string session_logging_path;
|
||||
};
|
||||
|
||||
class PacketSniffer : public Everest::ModuleBase {
|
||||
public:
|
||||
PacketSniffer() = delete;
|
||||
PacketSniffer(const ModuleInfo& info, std::unique_ptr<evse_managerIntf> r_evse_manager, Conf& config) :
|
||||
ModuleBase(info), r_evse_manager(std::move(r_evse_manager)), config(config){};
|
||||
|
||||
const std::unique_ptr<evse_managerIntf> r_evse_manager;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
void capture(const std::string& logpath, const std::string& session_id);
|
||||
pcap_t* p_handle{nullptr};
|
||||
pcap_dumper_t* pdumpfile{nullptr};
|
||||
char errbuf[PCAP_ERRBUF_SIZE]{""};
|
||||
std::atomic_bool capturing_stopped{false};
|
||||
std::atomic_bool already_started{false};
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // PACKET_SNIFFER_HPP
|
||||
@@ -0,0 +1,118 @@
|
||||
# FindPCAP.cmake
|
||||
# ===========================================
|
||||
# See https://github.com/zeek/cmake/FindPCAP.cmake for usage and update instructions.
|
||||
#
|
||||
# BSD License
|
||||
# -----------
|
||||
#[[
|
||||
Copyright (c) 1995-2017, The Regents of the University of California
|
||||
through the Lawrence Berkeley National Laboratory and the
|
||||
International Computer Science Institute. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
(1) Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
(2) Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
(3) Neither the name of the University of California, Lawrence Berkeley
|
||||
National Laboratory, U.S. Dept. of Energy, International Computer
|
||||
Science Institute, nor the names of contributors may be used to endorse
|
||||
or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Note that some files in the distribution may carry their own copyright
|
||||
notices.
|
||||
]]
|
||||
|
||||
# - Try to find libpcap include dirs and libraries
|
||||
#
|
||||
# Usage of this module as follows:
|
||||
#
|
||||
# find_package(PCAP)
|
||||
#
|
||||
# Variables used by this module, they can change the default behaviour and need
|
||||
# to be set before calling find_package:
|
||||
#
|
||||
# PCAP_ROOT_DIR Set this variable to the root installation of
|
||||
# libpcap if the module has problems finding the
|
||||
# proper installation path.
|
||||
#
|
||||
# Variables defined by this module:
|
||||
#
|
||||
# PCAP_FOUND System has libpcap, include and library dirs found
|
||||
# PCAP_INCLUDE_DIR The libpcap include directories.
|
||||
# PCAP_LIBRARY The libpcap library (possibly includes a thread
|
||||
# library e.g. required by pf_ring's libpcap)
|
||||
# HAVE_PF_RING If a found version of libpcap supports PF_RING
|
||||
|
||||
find_path(PCAP_ROOT_DIR
|
||||
NAMES include/pcap.h
|
||||
)
|
||||
|
||||
find_path(PCAP_INCLUDE_DIR
|
||||
NAMES pcap.h
|
||||
HINTS ${PCAP_ROOT_DIR}/include
|
||||
)
|
||||
|
||||
find_library(PCAP_LIBRARY
|
||||
NAMES pcap
|
||||
HINTS ${PCAP_ROOT_DIR}/lib
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(PCAP DEFAULT_MSG
|
||||
PCAP_LIBRARY
|
||||
PCAP_INCLUDE_DIR
|
||||
)
|
||||
|
||||
include(CheckCSourceCompiles)
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${PCAP_LIBRARY})
|
||||
check_c_source_compiles("int main() { return 0; }" PCAP_LINKS_SOLO)
|
||||
set(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
# check if linking against libpcap also needs to link against a thread library
|
||||
if (NOT PCAP_LINKS_SOLO)
|
||||
find_package(Threads)
|
||||
if (THREADS_FOUND)
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${PCAP_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
|
||||
check_c_source_compiles("int main() { return 0; }" PCAP_NEEDS_THREADS)
|
||||
set(CMAKE_REQUIRED_LIBRARIES)
|
||||
endif ()
|
||||
if (THREADS_FOUND AND PCAP_NEEDS_THREADS)
|
||||
set(_tmp ${PCAP_LIBRARY} ${CMAKE_THREAD_LIBS_INIT})
|
||||
list(REMOVE_DUPLICATES _tmp)
|
||||
set(PCAP_LIBRARY ${_tmp}
|
||||
CACHE STRING "Libraries needed to link against libpcap" FORCE)
|
||||
else ()
|
||||
message(FATAL_ERROR "Couldn't determine how to link against libpcap")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
include(CheckFunctionExists)
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${PCAP_LIBRARY})
|
||||
check_function_exists(pcap_get_pfring_id HAVE_PF_RING)
|
||||
check_function_exists(pcap_dump_open_append HAVE_PCAP_DUMP_OPEN_APPEND)
|
||||
set(CMAKE_REQUIRED_LIBRARIES)
|
||||
|
||||
mark_as_advanced(
|
||||
PCAP_ROOT_DIR
|
||||
PCAP_INCLUDE_DIR
|
||||
PCAP_LIBRARY
|
||||
)
|
||||
23
tools/EVerest-main/modules/Misc/PacketSniffer/manifest.yaml
Normal file
23
tools/EVerest-main/modules/Misc/PacketSniffer/manifest.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
description: >-
|
||||
Using the "PacketSniffer" EVerest module it is possible to capture and
|
||||
store the different packets on the PLC interface.
|
||||
config:
|
||||
device:
|
||||
description: >-
|
||||
The ethernet device on which the messages are to be captured
|
||||
type: string
|
||||
default: eth1
|
||||
session_logging_path:
|
||||
description: >-
|
||||
Output directory for session capture dump files. Will be used only if not empty.
|
||||
If empty, the logging path provided in the SessionStarted event will be used.
|
||||
If no logging path is provided there either, no capture will be performed.
|
||||
type: string
|
||||
default: /tmp
|
||||
requires:
|
||||
evse_manager:
|
||||
interface: evse_manager
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Sebastian Lukas
|
||||
@@ -0,0 +1,9 @@
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
|
||||
cc_everest_module(
|
||||
name = "PersistentStore",
|
||||
impls = ["main"],
|
||||
deps = [
|
||||
"@sqlite3",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
find_package(SQLite3 REQUIRED)
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
SQLite::SQLite3
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/kvsImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include "PersistentStore.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void PersistentStore::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void PersistentStore::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef PERSISTENT_STORE_HPP
|
||||
#define PERSISTENT_STORE_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/kvs/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string sqlite_db_file_path;
|
||||
};
|
||||
|
||||
class PersistentStore : public Everest::ModuleBase {
|
||||
public:
|
||||
PersistentStore() = delete;
|
||||
PersistentStore(const ModuleInfo& info, std::unique_ptr<kvsImplBase> p_main, Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), config(config){};
|
||||
|
||||
const std::unique_ptr<kvsImplBase> p_main;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // PERSISTENT_STORE_HPP
|
||||
258
tools/EVerest-main/modules/Misc/PersistentStore/main/kvsImpl.cpp
Normal file
258
tools/EVerest-main/modules/Misc/PersistentStore/main/kvsImpl.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "kvsImpl.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
/**
|
||||
* Wrapper class around a sqlite3_stmt pointer to ensure it is always
|
||||
* finalised via sqlite3_finalize()
|
||||
*/
|
||||
class Sqlite3_Stmt {
|
||||
private:
|
||||
sqlite3_stmt* m_statement_ptr = nullptr;
|
||||
|
||||
public:
|
||||
void finalize(const char* error_message = nullptr) {
|
||||
const auto res = sqlite3_finalize(m_statement_ptr);
|
||||
m_statement_ptr = nullptr; // prevent double free
|
||||
if (res != SQLITE_OK) {
|
||||
if (error_message != nullptr) {
|
||||
EVLOG_error << error_message;
|
||||
}
|
||||
throw std::runtime_error("PersistentStore db access error");
|
||||
}
|
||||
}
|
||||
|
||||
~Sqlite3_Stmt() {
|
||||
(void)sqlite3_finalize(m_statement_ptr);
|
||||
}
|
||||
|
||||
constexpr operator sqlite3_stmt*() {
|
||||
return m_statement_ptr;
|
||||
}
|
||||
|
||||
constexpr operator sqlite3_stmt**() {
|
||||
return &m_statement_ptr;
|
||||
}
|
||||
|
||||
constexpr sqlite3_stmt** operator&() {
|
||||
return &m_statement_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
void kvsImpl::init() {
|
||||
// open and initialize database
|
||||
fs::path sqlite_db_path = fs::absolute(fs::path(mod->config.sqlite_db_file_path));
|
||||
fs::path database_directory = sqlite_db_path.parent_path();
|
||||
if (!fs::exists(database_directory)) {
|
||||
fs::create_directories(database_directory);
|
||||
}
|
||||
|
||||
int ret = sqlite3_open(sqlite_db_path.c_str(), &this->db);
|
||||
|
||||
if (ret != SQLITE_OK) {
|
||||
EVLOG_error << "Error opening PersistentStore database '" << sqlite_db_path << "': " << sqlite3_errmsg(db);
|
||||
throw std::runtime_error("Could not open PersistentStore database at provided path.");
|
||||
}
|
||||
|
||||
EVLOG_debug << "Using SQLite version " << sqlite3_libversion();
|
||||
|
||||
// prepare the database
|
||||
std::string create_sql = "CREATE TABLE IF NOT EXISTS KVS ("
|
||||
"KEY TEXT UNIQUE,"
|
||||
"VALUE TEXT,"
|
||||
"TYPE TEXT);";
|
||||
|
||||
Sqlite3_Stmt create_statement;
|
||||
sqlite3_prepare_v2(this->db, create_sql.c_str(), create_sql.size(), &create_statement, NULL);
|
||||
int res = sqlite3_step(create_statement);
|
||||
if (res != SQLITE_DONE) {
|
||||
EVLOG_error << "Could not create KVS table: " << res << sqlite3_errmsg(this->db);
|
||||
throw std::runtime_error("PersistentStore db access error");
|
||||
}
|
||||
|
||||
create_statement.finalize("Error creating KVS table");
|
||||
}
|
||||
|
||||
void kvsImpl::ready() {
|
||||
}
|
||||
|
||||
class TypeNameVisitor {
|
||||
public:
|
||||
std::string operator()(std::nullptr_t t) const {
|
||||
return "nullptr_t";
|
||||
}
|
||||
|
||||
std::string operator()(const Array& t) const {
|
||||
return "Array";
|
||||
}
|
||||
|
||||
std::string operator()(const Object& t) const {
|
||||
return "Object";
|
||||
}
|
||||
|
||||
std::string operator()(const bool& t) const {
|
||||
return "bool";
|
||||
}
|
||||
|
||||
std::string operator()(const double& t) const {
|
||||
return "double";
|
||||
}
|
||||
|
||||
std::string operator()(const int& t) const {
|
||||
return "int";
|
||||
}
|
||||
|
||||
std::string operator()(const std::string& t) const {
|
||||
return "std::string";
|
||||
}
|
||||
};
|
||||
|
||||
class StringValueVisitor {
|
||||
public:
|
||||
std::string operator()(std::nullptr_t t) const {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string operator()(Array t) const {
|
||||
json a = t;
|
||||
return a.dump();
|
||||
}
|
||||
|
||||
std::string operator()(Object t) const {
|
||||
json o = t;
|
||||
return o.dump();
|
||||
}
|
||||
|
||||
std::string operator()(bool t) const {
|
||||
if (t) {
|
||||
return "true";
|
||||
}
|
||||
return "false";
|
||||
}
|
||||
|
||||
std::string operator()(double t) const {
|
||||
return std::to_string(t);
|
||||
}
|
||||
|
||||
std::string operator()(int t) const {
|
||||
return std::to_string(t);
|
||||
}
|
||||
|
||||
std::string operator()(std::string t) const {
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
void kvsImpl::handle_store(std::string& key,
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>& value) {
|
||||
std::string type = std::visit(TypeNameVisitor(), value);
|
||||
std::string string_value = std::visit(StringValueVisitor(), value);
|
||||
;
|
||||
|
||||
std::string insert_sql_str = "INSERT OR REPLACE INTO KVS (KEY, VALUE, TYPE) VALUES "
|
||||
"(@key, @value, @type)";
|
||||
Sqlite3_Stmt insert_statement;
|
||||
sqlite3_prepare_v2(db, insert_sql_str.c_str(), insert_sql_str.size(), &insert_statement, NULL);
|
||||
|
||||
sqlite3_bind_text(insert_statement, 1, key.c_str(), -1, NULL);
|
||||
sqlite3_bind_text(insert_statement, 2, string_value.c_str(), -1, NULL);
|
||||
sqlite3_bind_text(insert_statement, 3, type.c_str(), -1, NULL);
|
||||
|
||||
int res = sqlite3_step(insert_statement);
|
||||
if (res != SQLITE_DONE) {
|
||||
EVLOG_error << "Could not insert into KVS table: " << res << sqlite3_errmsg(db);
|
||||
throw std::runtime_error("PersistentStore db access error");
|
||||
}
|
||||
|
||||
insert_statement.finalize("Error inserting into KVS table");
|
||||
};
|
||||
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string> kvsImpl::handle_load(std::string& key) {
|
||||
std::string select_sql_str = "SELECT KEY, VALUE, TYPE FROM KVS WHERE KEY = @key";
|
||||
Sqlite3_Stmt select_statement;
|
||||
sqlite3_prepare_v2(db, select_sql_str.c_str(), select_sql_str.size(), &select_statement, NULL);
|
||||
|
||||
sqlite3_bind_text(select_statement, 1, key.c_str(), -1, NULL);
|
||||
|
||||
int res = sqlite3_step(select_statement);
|
||||
if (res != SQLITE_ROW) {
|
||||
// no key with that name exists in the database
|
||||
return {};
|
||||
}
|
||||
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string> value;
|
||||
|
||||
auto value_ptr = sqlite3_column_text(select_statement, 1);
|
||||
if (value_ptr != nullptr) {
|
||||
std::string value_str = std::string(reinterpret_cast<const char*>(value_ptr));
|
||||
auto type_ptr = sqlite3_column_text(select_statement, 2);
|
||||
if (type_ptr != nullptr) {
|
||||
std::string type_str = std::string(reinterpret_cast<const char*>(type_ptr));
|
||||
if (type_str == "Array") {
|
||||
Array value_array = json::parse(value_str);
|
||||
value = value_array;
|
||||
} else if (type_str == "Object") {
|
||||
Object value_object = json::parse(value_str);
|
||||
value = value_object;
|
||||
} else if (type_str == "bool") {
|
||||
if (value_str == "true") {
|
||||
value = true;
|
||||
} else {
|
||||
value = false;
|
||||
}
|
||||
} else if (type_str == "double") {
|
||||
value = std::stod(value_str);
|
||||
} else if (type_str == "int") {
|
||||
value = std::stoi(value_str);
|
||||
} else if (type_str == "std::string") {
|
||||
value = value_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select_statement.finalize("Error selecting from KVS table");
|
||||
return value;
|
||||
};
|
||||
|
||||
void kvsImpl::handle_delete(std::string& key) {
|
||||
std::string delete_sql_str = "DELETE FROM KVS WHERE KEY = @key";
|
||||
Sqlite3_Stmt delete_statement;
|
||||
sqlite3_prepare_v2(db, delete_sql_str.c_str(), delete_sql_str.size(), &delete_statement, NULL);
|
||||
|
||||
sqlite3_bind_text(delete_statement, 1, key.c_str(), -1, NULL);
|
||||
|
||||
int res = sqlite3_step(delete_statement);
|
||||
if (res != SQLITE_DONE) {
|
||||
EVLOG_error << "Could not delete from KVS table: " << res << sqlite3_errmsg(db);
|
||||
throw std::runtime_error("PersistentStore db access error");
|
||||
}
|
||||
|
||||
delete_statement.finalize("Error deleting from KVS table");
|
||||
};
|
||||
|
||||
bool kvsImpl::handle_exists(std::string& key) {
|
||||
std::string select_sql_str = "SELECT KEY FROM KVS WHERE KEY = @key";
|
||||
Sqlite3_Stmt select_statement;
|
||||
sqlite3_prepare_v2(db, select_sql_str.c_str(), select_sql_str.size(), &select_statement, NULL);
|
||||
|
||||
sqlite3_bind_text(select_statement, 1, key.c_str(), -1, NULL);
|
||||
|
||||
int res = sqlite3_step(select_statement);
|
||||
if (res != SQLITE_ROW) {
|
||||
// no key with that name exists in the database
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_KVS_IMPL_HPP
|
||||
#define MAIN_KVS_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/kvs/Implementation.hpp>
|
||||
|
||||
#include "../PersistentStore.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
#include <mutex>
|
||||
#include <sqlite3.h>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class kvsImpl : public kvsImplBase {
|
||||
public:
|
||||
kvsImpl() = delete;
|
||||
kvsImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<PersistentStore>& mod, Conf& config) :
|
||||
kvsImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void
|
||||
handle_store(std::string& key,
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>& value) override;
|
||||
virtual std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>
|
||||
handle_load(std::string& key) override;
|
||||
virtual void handle_delete(std::string& key) override;
|
||||
virtual bool handle_exists(std::string& key) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<PersistentStore>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
sqlite3* db;
|
||||
std::mutex db_mutex;
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_KVS_IMPL_HPP
|
||||
@@ -0,0 +1,14 @@
|
||||
description: Simple implementation of a SQLite backed persistent key-value store
|
||||
config:
|
||||
sqlite_db_file_path:
|
||||
description: Path to the SQLite db file.
|
||||
type: string
|
||||
default: everest_persistent_store.db
|
||||
provides:
|
||||
main:
|
||||
interface: kvs
|
||||
description: This implements a persistent key-value store
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Kai-Uwe Hermann
|
||||
1
tools/EVerest-main/modules/Misc/README.md
Normal file
1
tools/EVerest-main/modules/Misc/README.md
Normal file
@@ -0,0 +1 @@
|
||||
Miscellaneous modules implementing various supporting functionality
|
||||
17
tools/EVerest-main/modules/Misc/SerialCommHub/BUILD.bazel
Normal file
17
tools/EVerest-main/modules/Misc/SerialCommHub/BUILD.bazel
Normal file
@@ -0,0 +1,17 @@
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
|
||||
IMPLS = [
|
||||
"main",
|
||||
]
|
||||
|
||||
cc_everest_module(
|
||||
name = "SerialCommHub",
|
||||
srcs = glob([
|
||||
"*.cpp",
|
||||
"*.hpp",
|
||||
]),
|
||||
impls = IMPLS,
|
||||
deps = [
|
||||
"//lib/everest/gpio",
|
||||
],
|
||||
)
|
||||
33
tools/EVerest-main/modules/Misc/SerialCommHub/CMakeLists.txt
Normal file
33
tools/EVerest-main/modules/Misc/SerialCommHub/CMakeLists.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
everest::gpio
|
||||
)
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
tiny_modbus_rtu.cpp
|
||||
crc16.cpp
|
||||
)
|
||||
|
||||
target_compile_features(${MODULE_NAME} PUBLIC cxx_std_17)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/serial_communication_hubImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include "SerialCommHub.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void SerialCommHub::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void SerialCommHub::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SERIAL_COMM_HUB_HPP
|
||||
#define SERIAL_COMM_HUB_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/serial_communication_hub/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class SerialCommHub : public Everest::ModuleBase {
|
||||
public:
|
||||
SerialCommHub() = delete;
|
||||
SerialCommHub(const ModuleInfo& info, std::unique_ptr<serial_communication_hubImplBase> p_main, Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), config(config){};
|
||||
|
||||
const std::unique_ptr<serial_communication_hubImplBase> p_main;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // SERIAL_COMM_HUB_HPP
|
||||
46
tools/EVerest-main/modules/Misc/SerialCommHub/crc16.cpp
Normal file
46
tools/EVerest-main/modules/Misc/SerialCommHub/crc16.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Tiago Ventura
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "crc16.hpp"
|
||||
|
||||
uint16_t calculate_modbus_crc16(const uint8_t* buf, int len) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
char i;
|
||||
|
||||
while (len--) {
|
||||
crc ^= (*buf++);
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (crc & 1) {
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
8
tools/EVerest-main/modules/Misc/SerialCommHub/crc16.hpp
Normal file
8
tools/EVerest-main/modules/Misc/SerialCommHub/crc16.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef CRC16_HPP
|
||||
#define CRC16_HPP
|
||||
#include <stdint.h>
|
||||
|
||||
uint16_t calculate_modbus_crc16(const uint8_t* buf, int len);
|
||||
#endif
|
||||
@@ -0,0 +1,246 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "serial_communication_hubImpl.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <fmt/core.h>
|
||||
#include <mutex>
|
||||
#include <typeinfo>
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
template <typename T, typename U> static void append_array(std::vector<T>& m, const std::vector<U>& a) {
|
||||
for (auto it = a.begin(); it != a.end(); ++it)
|
||||
m.push_back(*it);
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
static std::vector<int> vector_to_int(const std::vector<uint16_t>& response) {
|
||||
std::vector<int> i;
|
||||
i.reserve(response.size());
|
||||
for (auto r : response) {
|
||||
i.push_back((int)r);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts a Result to a ResultBool by looking at each bit of the uint16_t values and converting them to
|
||||
* bools in the right order. Used for Modbus read coils responses where the result is a bit-packed array of coil states.
|
||||
* @param result The Result to convert
|
||||
* @param number_of_coils The number of coils that were requested to read, used to limit the number of bools in the
|
||||
* output
|
||||
* @return The converted ResultBool
|
||||
*/
|
||||
static types::serial_comm_hub_requests::ResultBool
|
||||
convert_read_coils_result(const types::serial_comm_hub_requests::Result& result, size_t number_of_coils) {
|
||||
constexpr uint8_t BITS_PER_BYTE = 8;
|
||||
constexpr uint16_t BYTE_MASK = 0xFF;
|
||||
|
||||
types::serial_comm_hub_requests::ResultBool out;
|
||||
out.status_code = result.status_code;
|
||||
|
||||
if (result.value.has_value()) {
|
||||
std::vector<bool> result_bool;
|
||||
for (const uint16_t packed_bytes : result.value.value()) {
|
||||
// Modbus read coils response packs bits into raw bytes, the modbus library uses big-endian to build uint16
|
||||
// from those. Here we extract the original MSB and LSB from the BE uint16_t and process them in the correct
|
||||
// order.
|
||||
const auto msb = static_cast<uint8_t>((packed_bytes >> BITS_PER_BYTE) & BYTE_MASK);
|
||||
const auto lsb = static_cast<uint8_t>(packed_bytes & BYTE_MASK);
|
||||
|
||||
for (const uint8_t byte : {msb, lsb}) {
|
||||
for (int bit = 0; bit < BITS_PER_BYTE; bit++) {
|
||||
if (result_bool.size() >= number_of_coils) {
|
||||
break;
|
||||
}
|
||||
result_bool.push_back((byte & (1U << bit)) != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.value = std::move(result_bool);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Implementation
|
||||
|
||||
void serial_communication_hubImpl::init() {
|
||||
using namespace std::chrono;
|
||||
Everest::GpioSettings rxtx_gpio_settings;
|
||||
|
||||
rxtx_gpio_settings.chip_name = config.rxtx_gpio_chip;
|
||||
rxtx_gpio_settings.line_number = config.rxtx_gpio_line;
|
||||
rxtx_gpio_settings.inverted = config.rxtx_gpio_tx_high;
|
||||
|
||||
system_error_logged = false;
|
||||
|
||||
if (!modbus.open_device(config.serial_port, config.baudrate, config.ignore_echo, rxtx_gpio_settings,
|
||||
static_cast<tiny_modbus::Parity>(config.parity), config.rtscts,
|
||||
milliseconds(config.initial_timeout_ms), milliseconds(config.within_message_timeout_ms))) {
|
||||
EVLOG_error << fmt::format("Cannot open serial port {}, ModBus will not work.", config.serial_port);
|
||||
}
|
||||
}
|
||||
|
||||
void serial_communication_hubImpl::ready() {
|
||||
}
|
||||
|
||||
types::serial_comm_hub_requests::Result
|
||||
serial_communication_hubImpl::perform_modbus_request(uint8_t device_address, tiny_modbus::FunctionCode function,
|
||||
uint16_t first_register_address, uint16_t register_quantity,
|
||||
bool wait_for_reply, std::vector<uint16_t> request) {
|
||||
std::scoped_lock lock(serial_mutex);
|
||||
types::serial_comm_hub_requests::Result result;
|
||||
std::vector<uint16_t> response;
|
||||
auto retry_counter = config.retries + 1;
|
||||
bool last_error_was_timeout = false;
|
||||
|
||||
while (retry_counter > 0) {
|
||||
auto current_trial = config.retries + 1 - retry_counter + 1;
|
||||
|
||||
EVLOG_debug << fmt::format("Trial {}/{}: calling {}(id {} addr {}({:#06x}) len {})", current_trial,
|
||||
config.retries + 1, tiny_modbus::FunctionCode_to_string_with_hex(function),
|
||||
device_address, first_register_address, first_register_address, register_quantity);
|
||||
|
||||
last_error_was_timeout = false;
|
||||
try {
|
||||
response = modbus.txrx(device_address, function, first_register_address, register_quantity,
|
||||
config.max_packet_size, wait_for_reply, request);
|
||||
} catch (const tiny_modbus::TimeoutException& e) {
|
||||
// TimeoutException is a specific type of communication error
|
||||
last_error_was_timeout = true;
|
||||
auto logmsg = fmt::format("Modbus call {} for device id {} addr {}({:#06x}) failed: {}",
|
||||
tiny_modbus::FunctionCode_to_string_with_hex(function), device_address,
|
||||
first_register_address, first_register_address, e.what());
|
||||
|
||||
if (retry_counter != 1) {
|
||||
EVLOG_debug << logmsg;
|
||||
} else {
|
||||
EVLOG_warning << logmsg;
|
||||
}
|
||||
} catch (const tiny_modbus::TinyModbusException& e) {
|
||||
auto logmsg = fmt::format("Modbus call {} for device id {} addr {}({:#06x}) failed: {}",
|
||||
tiny_modbus::FunctionCode_to_string_with_hex(function), device_address,
|
||||
first_register_address, first_register_address, e.what());
|
||||
|
||||
if (retry_counter != 1) {
|
||||
EVLOG_debug << logmsg;
|
||||
} else {
|
||||
EVLOG_warning << logmsg;
|
||||
}
|
||||
} catch (const std::logic_error& e) {
|
||||
EVLOG_warning << "Logic error in Modbus implementation: " << e.what();
|
||||
} catch (const std::system_error& e) {
|
||||
// FIXME: report this to the infrastructure, as soon as an error interface for this is available
|
||||
// Log this only once, as we are convinced this will not go away
|
||||
if (not system_error_logged) {
|
||||
EVLOG_error << "System error in accessing Modbus: [" << e.code() << "] " << e.what();
|
||||
system_error_logged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (response.size() > 0)
|
||||
break;
|
||||
|
||||
retry_counter--;
|
||||
}
|
||||
|
||||
if (response.size() > 0) {
|
||||
EVLOG_debug << fmt::format("Process response (size {})", response.size());
|
||||
result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Success;
|
||||
result.value = vector_to_int(response);
|
||||
system_error_logged = false; // reset after success
|
||||
} else {
|
||||
// If the last error was a timeout, return Timeout status, otherwise Error
|
||||
if (last_error_was_timeout) {
|
||||
result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Timeout;
|
||||
} else {
|
||||
result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Error;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Commands
|
||||
|
||||
types::serial_comm_hub_requests::Result
|
||||
serial_communication_hubImpl::handle_modbus_read_holding_registers(int& target_device_id, int& first_register_address,
|
||||
int& num_registers_to_read) {
|
||||
|
||||
return perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS,
|
||||
first_register_address, num_registers_to_read);
|
||||
}
|
||||
|
||||
types::serial_comm_hub_requests::Result
|
||||
serial_communication_hubImpl::handle_modbus_read_input_registers(int& target_device_id, int& first_register_address,
|
||||
int& num_registers_to_read) {
|
||||
|
||||
return perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::READ_INPUT_REGISTERS,
|
||||
first_register_address, num_registers_to_read);
|
||||
}
|
||||
|
||||
types::serial_comm_hub_requests::StatusCodeEnum serial_communication_hubImpl::handle_modbus_write_multiple_registers(
|
||||
int& target_device_id, int& first_register_address, types::serial_comm_hub_requests::VectorUint16& data_raw) {
|
||||
|
||||
types::serial_comm_hub_requests::Result result;
|
||||
std::vector<uint16_t> data;
|
||||
append_array<uint16_t, int>(data, data_raw.data);
|
||||
|
||||
result = perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS,
|
||||
first_register_address, data.size(), true, data);
|
||||
|
||||
return result.status_code;
|
||||
}
|
||||
|
||||
types::serial_comm_hub_requests::StatusCodeEnum
|
||||
serial_communication_hubImpl::handle_modbus_write_single_register(int& target_device_id, int& register_address,
|
||||
int& data) {
|
||||
types::serial_comm_hub_requests::Result result;
|
||||
|
||||
result = perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::WRITE_SINGLE_HOLDING_REGISTER,
|
||||
register_address, 1, true, {static_cast<uint16_t>(data)});
|
||||
|
||||
return result.status_code;
|
||||
}
|
||||
|
||||
types::serial_comm_hub_requests::StatusCodeEnum
|
||||
serial_communication_hubImpl::handle_modbus_write_single_coil(int& target_device_id, int& coil_address, bool& data) {
|
||||
types::serial_comm_hub_requests::Result result;
|
||||
|
||||
result = perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::WRITE_SINGLE_COIL, coil_address, 1,
|
||||
true, {static_cast<uint16_t>(data ? 0xFF00 : 0x0000)});
|
||||
|
||||
return result.status_code;
|
||||
}
|
||||
|
||||
types::serial_comm_hub_requests::ResultBool
|
||||
serial_communication_hubImpl::handle_modbus_read_coils(int& target_device_id, int& first_coil_address,
|
||||
int& num_coils_to_read) {
|
||||
const auto result = perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::READ_COILS,
|
||||
first_coil_address, num_coils_to_read);
|
||||
|
||||
return convert_read_coils_result(result, num_coils_to_read);
|
||||
}
|
||||
|
||||
void serial_communication_hubImpl::handle_nonstd_write(int& target_device_id, int& first_register_address,
|
||||
int& num_registers_to_read) {
|
||||
}
|
||||
|
||||
types::serial_comm_hub_requests::Result serial_communication_hubImpl::handle_nonstd_read(int& target_device_id,
|
||||
int& first_register_address,
|
||||
int& num_registers_to_read) {
|
||||
types::serial_comm_hub_requests::Result result;
|
||||
result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Error;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
@@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_SERIAL_COMMUNICATION_HUB_IMPL_HPP
|
||||
#define MAIN_SERIAL_COMMUNICATION_HUB_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/serial_communication_hub/Implementation.hpp>
|
||||
|
||||
#include "../SerialCommHub.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
#include "tiny_modbus_rtu.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <termios.h>
|
||||
#include <utils/thread.hpp>
|
||||
#include <vector>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {
|
||||
std::string serial_port;
|
||||
int baudrate;
|
||||
int parity;
|
||||
bool rtscts;
|
||||
bool ignore_echo;
|
||||
std::string rxtx_gpio_chip;
|
||||
int rxtx_gpio_line;
|
||||
bool rxtx_gpio_tx_high;
|
||||
int max_packet_size;
|
||||
int initial_timeout_ms;
|
||||
int within_message_timeout_ms;
|
||||
int retries;
|
||||
};
|
||||
|
||||
class serial_communication_hubImpl : public serial_communication_hubImplBase {
|
||||
public:
|
||||
serial_communication_hubImpl() = delete;
|
||||
serial_communication_hubImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<SerialCommHub>& mod,
|
||||
Conf& config) :
|
||||
serial_communication_hubImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::serial_comm_hub_requests::Result
|
||||
handle_modbus_read_holding_registers(int& target_device_id, int& first_register_address,
|
||||
int& num_registers_to_read) override;
|
||||
virtual types::serial_comm_hub_requests::Result
|
||||
handle_modbus_read_input_registers(int& target_device_id, int& first_register_address,
|
||||
int& num_registers_to_read) override;
|
||||
virtual types::serial_comm_hub_requests::StatusCodeEnum
|
||||
handle_modbus_write_multiple_registers(int& target_device_id, int& first_register_address,
|
||||
types::serial_comm_hub_requests::VectorUint16& data_raw) override;
|
||||
virtual types::serial_comm_hub_requests::StatusCodeEnum
|
||||
handle_modbus_write_single_register(int& target_device_id, int& register_address, int& data) override;
|
||||
virtual types::serial_comm_hub_requests::ResultBool
|
||||
handle_modbus_read_coils(int& target_device_id, int& first_coil_address, int& num_coils_to_read) override;
|
||||
virtual types::serial_comm_hub_requests::StatusCodeEnum
|
||||
handle_modbus_write_single_coil(int& target_device_id, int& coil_address, bool& data) override;
|
||||
virtual void handle_nonstd_write(int& target_device_id, int& first_register_address,
|
||||
int& num_registers_to_read) override;
|
||||
virtual types::serial_comm_hub_requests::Result
|
||||
handle_nonstd_read(int& target_device_id, int& first_register_address, int& num_registers_to_read) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<SerialCommHub>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
types::serial_comm_hub_requests::Result
|
||||
perform_modbus_request(uint8_t device_address, tiny_modbus::FunctionCode function, uint16_t first_register_address,
|
||||
uint16_t register_quantity, bool wait_for_reply = true,
|
||||
std::vector<uint16_t> request = std::vector<uint16_t>());
|
||||
|
||||
tiny_modbus::TinyModbusRTU modbus;
|
||||
|
||||
std::mutex serial_mutex;
|
||||
bool system_error_logged{false};
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_SERIAL_COMMUNICATION_HUB_IMPL_HPP
|
||||
71
tools/EVerest-main/modules/Misc/SerialCommHub/manifest.yaml
Normal file
71
tools/EVerest-main/modules/Misc/SerialCommHub/manifest.yaml
Normal file
@@ -0,0 +1,71 @@
|
||||
description: Hub to communicate with attached serial devices
|
||||
provides:
|
||||
main:
|
||||
description: Implementation of serial communication hub
|
||||
interface: serial_communication_hub
|
||||
config:
|
||||
serial_port:
|
||||
description: Serial port the hardware is connected to
|
||||
type: string
|
||||
default: /dev/ttyUSB0
|
||||
baudrate:
|
||||
description: Baudrate
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 230400
|
||||
default: 9600
|
||||
parity:
|
||||
description: 'Parity bit: 0: None, 1: Odd, 2: Even'
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 2
|
||||
default: 0
|
||||
rtscts:
|
||||
description: Use RTS/CTS hardware flow control
|
||||
type: boolean
|
||||
default: false
|
||||
ignore_echo:
|
||||
description: On some hardware every message that is sent is read back, this setting filters the sent message in the reply.
|
||||
type: boolean
|
||||
default: false
|
||||
rxtx_gpio_chip:
|
||||
description: GPIO chip to use to switch between RX/TX. An empty string disables GPIO usage.
|
||||
type: string
|
||||
default: ''
|
||||
rxtx_gpio_line:
|
||||
description: GPIO line to use to switch between RX/TX
|
||||
type: integer
|
||||
default: 0
|
||||
rxtx_gpio_tx_high:
|
||||
description: GPIO direction, false means low for TX, true means high for TX
|
||||
type: boolean
|
||||
default: false
|
||||
max_packet_size:
|
||||
description: >-
|
||||
Maximum size of a packet to read/write in bytes. Payload exceeding the size will be chunked.
|
||||
The APU size according to [wikipedia](https://en.wikipedia.org/wiki/Modbus) is 256 bytes,
|
||||
which is used as default here.
|
||||
type: integer
|
||||
# 7 is a minimum packet size to transfer a response
|
||||
minimum: 7
|
||||
maximum: 65536
|
||||
default: 256
|
||||
initial_timeout_ms:
|
||||
description: Timeout in ms for the first packet.
|
||||
type: integer
|
||||
default: 500
|
||||
within_message_timeout_ms:
|
||||
description: Timeout in ms for subsequent packets.
|
||||
type: integer
|
||||
default: 100
|
||||
retries:
|
||||
description: Count of retries in case of error in Modbus query.
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 10
|
||||
default: 2
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Lars Dieckmann
|
||||
- Cornelius Claussen
|
||||
@@ -0,0 +1,526 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
// TODOs:
|
||||
// - sometimes we receive 0 bytes from sofar, find out why
|
||||
// - implement echo removal for chargebyte
|
||||
// - implement GPIO to switch rx/tx
|
||||
|
||||
#include "tiny_modbus_rtu.hpp"
|
||||
#include "crc16.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <endian.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <fmt/core.h>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace tiny_modbus {
|
||||
|
||||
std::string FunctionCode_to_string(FunctionCode fc) {
|
||||
switch (fc) {
|
||||
case FunctionCode::READ_COILS:
|
||||
return "READ_COILS";
|
||||
case FunctionCode::READ_DISCRETE_INPUTS:
|
||||
return "READ_DISCRETE_INPUTS";
|
||||
case FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS:
|
||||
return "READ_MULTIPLE_HOLDING_REGISTERS";
|
||||
case FunctionCode::READ_INPUT_REGISTERS:
|
||||
return "READ_INPUT_REGISTERS";
|
||||
case FunctionCode::WRITE_SINGLE_COIL:
|
||||
return "WRITE_SINGLE_COIL";
|
||||
case FunctionCode::WRITE_SINGLE_HOLDING_REGISTER:
|
||||
return "WRITE_SINGLE_HOLDING_REGISTER";
|
||||
case FunctionCode::WRITE_MULTIPLE_COILS:
|
||||
return "WRITE_MULTIPLE_COILS";
|
||||
case FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS:
|
||||
return "WRITE_MULTIPLE_HOLDING_REGISTERS";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string FunctionCode_to_string_with_hex(FunctionCode fc) {
|
||||
return fmt::format("{}({:#04x})", FunctionCode_to_string(fc), (unsigned int)fc);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const FunctionCode& fc) {
|
||||
os << FunctionCode_to_string_with_hex(fc);
|
||||
return os;
|
||||
}
|
||||
|
||||
// This is a replacement for system library tcdrain().
|
||||
// tcdrain() returns when all bytes are written to the UART, but it actually returns about 10msecs or more after the
|
||||
// last byte has been written. This function tries to return as fast as possible instead.
|
||||
static void fast_tcdrain(int fd) {
|
||||
// in user space, the only way to find out if there are still bits to be shiftet out is to poll line status register
|
||||
// as fast as we can
|
||||
uint32_t lsr;
|
||||
do {
|
||||
ioctl(fd, TIOCSERGETLSR, &lsr);
|
||||
} while (!(lsr & TIOCSER_TEMT));
|
||||
}
|
||||
|
||||
static auto check_for_exception(uint8_t received_function_code) {
|
||||
return received_function_code & (1 << 7);
|
||||
}
|
||||
|
||||
static void clear_exception_bit(uint8_t& received_function_code) {
|
||||
received_function_code &= ~(1 << 7);
|
||||
}
|
||||
|
||||
static std::string hexdump(const uint8_t* msg, int msg_len) {
|
||||
std::stringstream ss;
|
||||
for (int i = 0; i < msg_len; i++) {
|
||||
ss << "<" << std::nouppercase << std::setfill('0') << std::setw(2) << std::hex << (int)msg[i] << ">";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static void append_checksum(uint8_t* msg, int msg_len) {
|
||||
if (msg_len < 5)
|
||||
return;
|
||||
uint16_t crc_sum = calculate_modbus_crc16(msg, msg_len - 2);
|
||||
memcpy(msg + msg_len - 2, &crc_sum, 2);
|
||||
}
|
||||
|
||||
static bool validate_checksum(const uint8_t* msg, int msg_len) {
|
||||
if (msg_len < 5)
|
||||
return false;
|
||||
// check crc
|
||||
uint16_t crc_sum = calculate_modbus_crc16(msg, msg_len - 2);
|
||||
uint16_t crc_msg;
|
||||
memcpy(&crc_msg, msg + msg_len - 2, 2);
|
||||
return (crc_msg == crc_sum);
|
||||
}
|
||||
|
||||
static std::vector<uint16_t> decode_reply(const uint8_t* buf, int len, uint8_t expected_device_address,
|
||||
FunctionCode function) {
|
||||
std::vector<uint16_t> result;
|
||||
if (len == 0) {
|
||||
throw TimeoutException("Packet receive timeout");
|
||||
} else if (len < MODBUS_MIN_REPLY_SIZE) {
|
||||
throw ShortPacketException(fmt::format("Packet too small: only {} bytes", len));
|
||||
}
|
||||
if (expected_device_address != buf[DEVICE_ADDRESS_POS]) {
|
||||
throw AddressMismatchException(fmt::format("Device address mismatch: expected: {} received: {}",
|
||||
expected_device_address, buf[DEVICE_ADDRESS_POS]) +
|
||||
": " + hexdump(buf, len));
|
||||
}
|
||||
|
||||
bool exception = false;
|
||||
uint8_t function_code_recvd = buf[FUNCTION_CODE_POS];
|
||||
if (check_for_exception(function_code_recvd)) {
|
||||
// highest bit is set for exception reply
|
||||
exception = true;
|
||||
// clear error bit
|
||||
clear_exception_bit(function_code_recvd);
|
||||
}
|
||||
|
||||
if (function != function_code_recvd) {
|
||||
throw FunctionCodeMismatchException(fmt::format("Function code mismatch: expected: {} received: {}",
|
||||
static_cast<std::underlying_type_t<FunctionCode>>(function),
|
||||
function_code_recvd));
|
||||
}
|
||||
|
||||
if (!validate_checksum(buf, len)) {
|
||||
throw ChecksumErrorException("Retrieved Modbus checksum does not match calculated value.");
|
||||
}
|
||||
|
||||
if (exception) {
|
||||
// handle exception message
|
||||
uint8_t err_code = buf[RES_EXCEPTION_CODE];
|
||||
switch (err_code) {
|
||||
case 0x01:
|
||||
throw ModbusException("Modbus exception: Illegal function");
|
||||
break;
|
||||
case 0x02:
|
||||
throw ModbusException("Modbus exception: Illegal data address");
|
||||
break;
|
||||
case 0x03:
|
||||
throw ModbusException("Modbus exception: Illegal data value");
|
||||
break;
|
||||
case 0x04:
|
||||
throw ModbusException("Modbus exception: Client device failure");
|
||||
break;
|
||||
case 0x05:
|
||||
throw ModbusException("Modbus ACK");
|
||||
break;
|
||||
case 0x06:
|
||||
throw ModbusException("Modbus exception: Client device busy");
|
||||
break;
|
||||
case 0x07:
|
||||
throw ModbusException("Modbus exception: NACK");
|
||||
break;
|
||||
case 0x08:
|
||||
throw ModbusException("Modbus exception: Memory parity error");
|
||||
break;
|
||||
case 0x09:
|
||||
throw ModbusException("Modbus exception: Out of resources");
|
||||
break;
|
||||
case 0x0A:
|
||||
throw ModbusException("Modbus exception: Gateway path unavailable");
|
||||
break;
|
||||
case 0x0B:
|
||||
throw ModbusException("Modbus exception: Gateway target device failed to respond");
|
||||
break;
|
||||
default:
|
||||
throw ModbusException("Modbus exception: Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
// For a write reply we always get 4 bytes
|
||||
uint8_t byte_cnt = 4;
|
||||
int start_of_result = RES_TX_START_OF_PAYLOAD;
|
||||
bool even_byte_cnt_expected = false;
|
||||
|
||||
// Was it a read reply?
|
||||
switch (function) {
|
||||
case FunctionCode::WRITE_SINGLE_COIL:
|
||||
case FunctionCode::WRITE_SINGLE_HOLDING_REGISTER:
|
||||
case FunctionCode::WRITE_MULTIPLE_COILS:
|
||||
case FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS:
|
||||
// no - nothing to do
|
||||
break;
|
||||
case FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS:
|
||||
case FunctionCode::READ_INPUT_REGISTERS:
|
||||
// yes - for 16-bit wide registers thus we can assume an even byte count
|
||||
even_byte_cnt_expected = true;
|
||||
[[fallthrough]];
|
||||
case FunctionCode::READ_COILS:
|
||||
case FunctionCode::READ_DISCRETE_INPUTS:
|
||||
// yes
|
||||
// adapt byte count and starting pos
|
||||
byte_cnt = buf[RES_RX_LEN_POS];
|
||||
start_of_result = RES_RX_START_OF_PAYLOAD;
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("Missing implementation for function code " + FunctionCode_to_string_with_hex(function));
|
||||
}
|
||||
|
||||
// check if result is completely in received data
|
||||
if (start_of_result + byte_cnt > len) {
|
||||
throw IncompletePacketException("Result data not completely in received message.");
|
||||
}
|
||||
|
||||
// check even number of bytes
|
||||
if (even_byte_cnt_expected && byte_cnt % 2 == 1) {
|
||||
throw OddByteCountException("For " + FunctionCode_to_string_with_hex(function) +
|
||||
" an even byte count is expected in the response.");
|
||||
}
|
||||
|
||||
// ready to copy actual result data to output, so pre-allocate enough memory for the output
|
||||
result.reserve((byte_cnt + 1) / 2);
|
||||
|
||||
for (int i = start_of_result; i < start_of_result + byte_cnt; i += 2) {
|
||||
uint16_t t = 0;
|
||||
const size_t num_bytes_to_copy = (i < len - 1) ? 2 : 1;
|
||||
memcpy(&t, buf + i, num_bytes_to_copy);
|
||||
t = be16toh(t);
|
||||
result.push_back(t);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TinyModbusRTU::~TinyModbusRTU() {
|
||||
if (fd != -1)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
bool TinyModbusRTU::open_device(const std::string& device, int _baud, bool _ignore_echo,
|
||||
const Everest::GpioSettings& rxtx_gpio_settings, const Parity parity, bool rtscts,
|
||||
std::chrono::milliseconds _initial_timeout,
|
||||
std::chrono::milliseconds _within_message_timeout) {
|
||||
|
||||
initial_timeout = _initial_timeout;
|
||||
within_message_timeout = _within_message_timeout;
|
||||
ignore_echo = _ignore_echo;
|
||||
|
||||
rxtx_gpio.open(rxtx_gpio_settings);
|
||||
rxtx_gpio.set_output(true);
|
||||
|
||||
fd = open(device.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
|
||||
if (fd < 0) {
|
||||
EVLOG_error << fmt::format("Serial: error {} opening {}: {}\n", errno, device, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int baud;
|
||||
switch (_baud) {
|
||||
case 9600:
|
||||
baud = B9600;
|
||||
break;
|
||||
case 19200:
|
||||
baud = B19200;
|
||||
break;
|
||||
case 38400:
|
||||
baud = B38400;
|
||||
break;
|
||||
case 57600:
|
||||
baud = B57600;
|
||||
break;
|
||||
case 115200:
|
||||
baud = B115200;
|
||||
break;
|
||||
case 230400:
|
||||
baud = B230400;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
struct termios tty;
|
||||
if (tcgetattr(fd, &tty) != 0) {
|
||||
printf("Serial: error %d from tcgetattr\n", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
cfsetospeed(&tty, baud);
|
||||
cfsetispeed(&tty, baud);
|
||||
|
||||
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
|
||||
// disable IGNBRK for mismatched speed tests; otherwise receive break
|
||||
// as \000 chars
|
||||
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY);
|
||||
tty.c_lflag = 0; // no signaling chars, no echo,
|
||||
// no canonical processing
|
||||
tty.c_oflag = 0; // no remapping, no delays
|
||||
tty.c_cc[VMIN] = 1; // read blocks
|
||||
tty.c_cc[VTIME] = 1; // 0.1 seconds inter character read timeout after first byte was received
|
||||
|
||||
tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
|
||||
// enable reading
|
||||
if (parity == Parity::ODD) {
|
||||
tty.c_cflag |= (PARENB | PARODD); // odd parity
|
||||
} else if (parity == Parity::EVEN) { // even parity
|
||||
tty.c_cflag &= ~PARODD;
|
||||
tty.c_cflag |= PARENB;
|
||||
} else {
|
||||
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
|
||||
}
|
||||
tty.c_cflag &= ~CSTOPB; // 1 Stop bit
|
||||
|
||||
if (rtscts) {
|
||||
tty.c_cflag |= CRTSCTS;
|
||||
} else {
|
||||
tty.c_cflag &= ~CRTSCTS;
|
||||
}
|
||||
|
||||
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
|
||||
printf("Serial: error %d from tcsetattr\n", errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int TinyModbusRTU::read_reply(uint8_t* rxbuf, int rxbuf_len) {
|
||||
if (fd == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Lambda to convert std::chrono to timeval.
|
||||
auto to_timeval = [](const auto& time) {
|
||||
using namespace std::chrono;
|
||||
struct timeval timeout;
|
||||
auto sec = duration_cast<seconds>(time);
|
||||
timeout.tv_sec = sec.count();
|
||||
timeout.tv_usec = duration_cast<microseconds>(time - sec).count();
|
||||
return timeout;
|
||||
};
|
||||
|
||||
auto timeout = to_timeval(initial_timeout);
|
||||
const auto within_message_timeval = to_timeval(within_message_timeout);
|
||||
|
||||
fd_set set;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(fd, &set);
|
||||
|
||||
int bytes_read_total = 0;
|
||||
while (true) {
|
||||
int rv = select(fd + 1, &set, NULL, NULL, &timeout);
|
||||
timeout = within_message_timeval;
|
||||
if (rv == -1) { // error in select function call
|
||||
perror("txrx: select:");
|
||||
break;
|
||||
} else if (rv == 0) { // no more bytes to read within timeout, so transfer is complete
|
||||
break;
|
||||
} else { // received more bytes, add them to buffer
|
||||
// do we have space in the rx buffer left?
|
||||
if (bytes_read_total >= rxbuf_len) {
|
||||
// no buffer space left, but more to read.
|
||||
break;
|
||||
}
|
||||
|
||||
int bytes_read = read(fd, rxbuf + bytes_read_total, rxbuf_len - bytes_read_total);
|
||||
if (bytes_read > 0) {
|
||||
bytes_read_total += bytes_read;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytes_read_total;
|
||||
}
|
||||
|
||||
std::vector<uint16_t> TinyModbusRTU::txrx(uint8_t device_address, FunctionCode function,
|
||||
uint16_t first_register_address, uint16_t register_quantity,
|
||||
uint16_t max_packet_size, bool wait_for_reply,
|
||||
std::vector<uint16_t> request) {
|
||||
// This only supports chunking of the read-requests.
|
||||
std::vector<uint16_t> out;
|
||||
|
||||
if (max_packet_size < MODBUS_MIN_REPLY_SIZE + 2) {
|
||||
EVLOG_error << fmt::format("Max packet size too small: {}", max_packet_size);
|
||||
return {};
|
||||
}
|
||||
|
||||
const uint16_t register_chunk = (max_packet_size - MODBUS_MIN_REPLY_SIZE) / 2;
|
||||
size_t written_elements = 0;
|
||||
while (register_quantity) {
|
||||
const auto current_register_quantity = std::min(register_quantity, register_chunk);
|
||||
std::vector<uint16_t> current_request;
|
||||
if (request.size() > written_elements + current_register_quantity) {
|
||||
current_request = std::vector<uint16_t>(request.begin() + written_elements,
|
||||
request.begin() + written_elements + current_register_quantity);
|
||||
written_elements += current_register_quantity;
|
||||
} else {
|
||||
current_request = std::vector<uint16_t>(request.begin() + written_elements, request.end());
|
||||
written_elements = request.size();
|
||||
}
|
||||
|
||||
const auto res = txrx_impl(device_address, function, first_register_address, current_register_quantity,
|
||||
wait_for_reply, current_request);
|
||||
|
||||
// We failed to read/write.
|
||||
if (res.empty()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
out.insert(out.end(), res.begin(), res.end());
|
||||
first_register_address += current_register_quantity;
|
||||
register_quantity -= current_register_quantity;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> _make_single_write_request(uint8_t device_address, FunctionCode function,
|
||||
uint16_t register_address, bool wait_for_reply, uint16_t data) {
|
||||
const int req_len = 8;
|
||||
std::vector<uint8_t> req(req_len);
|
||||
|
||||
req[DEVICE_ADDRESS_POS] = device_address;
|
||||
req[FUNCTION_CODE_POS] = static_cast<uint8_t>(function);
|
||||
|
||||
register_address = htobe16(register_address);
|
||||
data = htobe16(data);
|
||||
memcpy(req.data() + REQ_TX_FIRST_REGISTER_ADDR_POS, ®ister_address, 2);
|
||||
memcpy(req.data() + REQ_TX_SINGLE_REG_PAYLOAD_POS, &data, 2);
|
||||
append_checksum(req.data(), req_len);
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> _make_generic_request(uint8_t device_address, FunctionCode function,
|
||||
uint16_t first_register_address, uint16_t register_quantity,
|
||||
std::vector<uint16_t> request) {
|
||||
// size of request
|
||||
int req_len = (request.size() == 0 ? 0 : 2 * request.size() + 1) + MODBUS_BASE_PAYLOAD_SIZE;
|
||||
std::vector<uint8_t> req(req_len);
|
||||
|
||||
// add header
|
||||
req[DEVICE_ADDRESS_POS] = device_address;
|
||||
req[FUNCTION_CODE_POS] = function;
|
||||
|
||||
first_register_address = htobe16(first_register_address);
|
||||
register_quantity = htobe16(register_quantity);
|
||||
memcpy(req.data() + REQ_TX_FIRST_REGISTER_ADDR_POS, &first_register_address, 2);
|
||||
memcpy(req.data() + REQ_TX_QUANTITY_POS, ®ister_quantity, 2);
|
||||
|
||||
if (function == FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS) {
|
||||
// write byte count
|
||||
req[REQ_TX_MULTIPLE_REG_BYTE_COUNT_POS] = request.size() * 2;
|
||||
// add request data
|
||||
int i = REQ_TX_MULTIPLE_REG_BYTE_COUNT_POS + 1;
|
||||
for (auto r : request) {
|
||||
r = htobe16(r);
|
||||
memcpy(req.data() + i, &r, 2);
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// set checksum in the last 2 bytes
|
||||
append_checksum(req.data(), req_len);
|
||||
|
||||
return req;
|
||||
}
|
||||
/*
|
||||
This function transmits a modbus request and waits for the reply.
|
||||
Parameter request is optional and is only used for writing multiple registers.
|
||||
*/
|
||||
std::vector<uint16_t> TinyModbusRTU::txrx_impl(uint8_t device_address, FunctionCode function,
|
||||
uint16_t first_register_address, uint16_t register_quantity,
|
||||
bool wait_for_reply, std::vector<uint16_t> request) {
|
||||
{
|
||||
if (fd == -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto req =
|
||||
function == FunctionCode::WRITE_SINGLE_HOLDING_REGISTER or function == FunctionCode::WRITE_SINGLE_COIL
|
||||
? _make_single_write_request(device_address, function, first_register_address, wait_for_reply,
|
||||
request.at(0))
|
||||
: _make_generic_request(device_address, function, first_register_address, register_quantity, request);
|
||||
// clear input and output buffer
|
||||
tcflush(fd, TCIOFLUSH);
|
||||
|
||||
// write to serial port
|
||||
rxtx_gpio.set(false);
|
||||
|
||||
uint8_t* buffer = req.data();
|
||||
ssize_t written = 0;
|
||||
|
||||
while (written < req.size()) {
|
||||
ssize_t c = write(fd, &buffer[written], req.size() - written);
|
||||
if (c == -1)
|
||||
throw std::system_error(errno, std::generic_category(), "Could not send Modbus request");
|
||||
written += c;
|
||||
}
|
||||
|
||||
if (rxtx_gpio.is_ready()) {
|
||||
// if we are using GPIO to switch between RX/TX, use the fast version of tcdrain with exact timing
|
||||
fast_tcdrain(fd);
|
||||
} else {
|
||||
// without GPIO switching, use regular tcdrain as not all UART drivers implement the ioctl
|
||||
tcdrain(fd);
|
||||
}
|
||||
rxtx_gpio.set(true);
|
||||
|
||||
if (ignore_echo) {
|
||||
// read back echo of what we sent and ignore it
|
||||
read_reply(req.data(), req.size());
|
||||
}
|
||||
}
|
||||
|
||||
if (wait_for_reply) {
|
||||
// wait for reply
|
||||
uint8_t rxbuf[MODBUS_MAX_REPLY_SIZE];
|
||||
int bytes_read_total = read_reply(rxbuf, sizeof(rxbuf));
|
||||
return decode_reply(rxbuf, bytes_read_total, device_address, function);
|
||||
}
|
||||
return std::vector<uint16_t>();
|
||||
}
|
||||
|
||||
} // namespace tiny_modbus
|
||||
@@ -0,0 +1,118 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
This is a tiny and fast modbus RTU implementation
|
||||
*/
|
||||
#ifndef TINY_MODBUS_RTU
|
||||
#define TINY_MODBUS_RTU
|
||||
|
||||
#include <chrono>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <stdint.h>
|
||||
#include <termios.h>
|
||||
|
||||
#include <everest/gpio/gpio.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace tiny_modbus {
|
||||
|
||||
constexpr int DEVICE_ADDRESS_POS = 0x00;
|
||||
constexpr int FUNCTION_CODE_POS = 0x01;
|
||||
|
||||
constexpr int REQ_TX_FIRST_REGISTER_ADDR_POS = 0x02;
|
||||
constexpr int REQ_TX_QUANTITY_POS = 0x04;
|
||||
constexpr int REQ_TX_SINGLE_REG_PAYLOAD_POS = 0x04;
|
||||
|
||||
constexpr int REQ_TX_MULTIPLE_REG_BYTE_COUNT_POS = 0x06;
|
||||
|
||||
constexpr int RES_RX_LEN_POS = 0x02;
|
||||
constexpr int RES_RX_START_OF_PAYLOAD = 0x03;
|
||||
constexpr int RES_TX_START_OF_PAYLOAD = 0x02;
|
||||
constexpr int RES_EXCEPTION_CODE = 0x02;
|
||||
|
||||
constexpr int MODBUS_MAX_REPLY_SIZE = 255 + 6;
|
||||
constexpr int MODBUS_MIN_REPLY_SIZE = 5;
|
||||
constexpr int MODBUS_BASE_PAYLOAD_SIZE = 8;
|
||||
|
||||
enum class Parity : uint8_t {
|
||||
NONE = 0,
|
||||
ODD = 1,
|
||||
EVEN = 2
|
||||
};
|
||||
|
||||
enum FunctionCode : uint8_t {
|
||||
READ_COILS = 0x01,
|
||||
READ_DISCRETE_INPUTS = 0x02,
|
||||
READ_MULTIPLE_HOLDING_REGISTERS = 0x03,
|
||||
READ_INPUT_REGISTERS = 0x04,
|
||||
WRITE_SINGLE_COIL = 0x05,
|
||||
WRITE_SINGLE_HOLDING_REGISTER = 0x06,
|
||||
WRITE_MULTIPLE_COILS = 0x0F,
|
||||
WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10,
|
||||
};
|
||||
|
||||
std::string FunctionCode_to_string(FunctionCode fc);
|
||||
std::string FunctionCode_to_string_with_hex(FunctionCode fc);
|
||||
std::ostream& operator<<(std::ostream& os, const FunctionCode& fc);
|
||||
|
||||
class TinyModbusException : public std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
class TimeoutException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
class ShortPacketException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
class AddressMismatchException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
class FunctionCodeMismatchException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
class ChecksumErrorException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
class IncompletePacketException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
class OddByteCountException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
class ModbusException : public TinyModbusException {
|
||||
using TinyModbusException::TinyModbusException;
|
||||
};
|
||||
|
||||
class TinyModbusRTU {
|
||||
|
||||
public:
|
||||
~TinyModbusRTU();
|
||||
|
||||
bool open_device(const std::string& device, int baud, bool ignore_echo,
|
||||
const Everest::GpioSettings& rxtx_gpio_settings, const Parity parity, bool rtscts,
|
||||
std::chrono::milliseconds initial_timeout, std::chrono::milliseconds within_message_timeout);
|
||||
|
||||
std::vector<uint16_t> txrx(uint8_t device_address, FunctionCode function, uint16_t first_register_address,
|
||||
uint16_t register_quantity, uint16_t chunk_size, bool wait_for_reply = true,
|
||||
std::vector<uint16_t> request = std::vector<uint16_t>());
|
||||
|
||||
private:
|
||||
// Serial interface
|
||||
int fd{-1};
|
||||
bool ignore_echo{false};
|
||||
|
||||
std::vector<uint16_t> txrx_impl(uint8_t device_address, FunctionCode function, uint16_t first_register_address,
|
||||
uint16_t register_quantity, bool wait_for_reply = true,
|
||||
std::vector<uint16_t> request = std::vector<uint16_t>());
|
||||
|
||||
int read_reply(uint8_t* rxbuf, int rxbuf_len);
|
||||
|
||||
Everest::Gpio rxtx_gpio;
|
||||
std::chrono::milliseconds initial_timeout;
|
||||
std::chrono::milliseconds within_message_timeout;
|
||||
};
|
||||
|
||||
} // namespace tiny_modbus
|
||||
#endif
|
||||
32
tools/EVerest-main/modules/Misc/Setup/BUILD.bazel
Normal file
32
tools/EVerest-main/modules/Misc/Setup/BUILD.bazel
Normal file
@@ -0,0 +1,32 @@
|
||||
load("@rules_cc//cc:defs.bzl", "cc_test")
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
load("//third-party/bazel/toolchains:defs.bzl", "CROSS_TEST_INCOMPATIBLE")
|
||||
|
||||
cc_everest_module(
|
||||
name = "Setup",
|
||||
srcs = glob([
|
||||
"*.cpp",
|
||||
"*.hpp",
|
||||
]),
|
||||
deps = [
|
||||
"//lib/everest/run_application",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "Setup_test",
|
||||
target_compatible_with = CROSS_TEST_INCOMPATIBLE,
|
||||
srcs = glob(
|
||||
[
|
||||
"tests/*.cpp",
|
||||
"tests/*.hpp",
|
||||
"WiFiSetup.*",
|
||||
],
|
||||
),
|
||||
includes = ["."],
|
||||
deps = [
|
||||
"//lib/everest/run_application",
|
||||
"@com_github_nlohmann_json//:json",
|
||||
"@googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
29
tools/EVerest-main/modules/Misc/Setup/CMakeLists.txt
Normal file
29
tools/EVerest-main/modules/Misc/Setup/CMakeLists.txt
Normal file
@@ -0,0 +1,29 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"WiFiSetup.cpp"
|
||||
)
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
everest::run_application
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
target_compile_features(${MODULE_NAME} PRIVATE cxx_std_17)
|
||||
if(EVEREST_CORE_BUILD_TESTING)
|
||||
include(CTest)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
245
tools/EVerest-main/modules/Misc/Setup/README.md
Normal file
245
tools/EVerest-main/modules/Misc/Setup/README.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Setup module API documentation
|
||||
This module is responsible for setup tasks that might need privileged access, for example wifi configuration.
|
||||
|
||||
If not run as root user, set at least the following capabilities in your EVerest config file: CAP_NET_ADMIN, CAP_NET_RAW, CAP_DAC_OVERRIDE.
|
||||
They will be passed on to the child processes such as wpa_cli etc.
|
||||
|
||||
## Periodically published variables
|
||||
### everest_api/setup/var/supported_setup_features
|
||||
This variable is published periodically and contains a JSON object with the supported features in the following form:
|
||||
```json
|
||||
{
|
||||
"localization": true,
|
||||
"setup_simulation": true,
|
||||
"setup_wifi": true
|
||||
}
|
||||
```
|
||||
|
||||
### everest_api/setup/var/hostname
|
||||
This variable is published periodically and contains the hostname.
|
||||
|
||||
## Commands and variables published in response
|
||||
### everest_api/setup/cmd/scan_wifi
|
||||
If any arbitrary payload is published to this topic a list of available wifi networks is published on the following topic:
|
||||
__everest_api/setup/var/wifi_info__
|
||||
|
||||
with the following payload format:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"bssid": "00:11:22:33:44:55",
|
||||
"flags": [
|
||||
"WPA2-PSK-CCMP",
|
||||
"ESS"
|
||||
],
|
||||
"frequency": 2437,
|
||||
"signal_level": -41,
|
||||
"ssid": "Example"
|
||||
},
|
||||
{
|
||||
"bssid": "66:77:88:99:aa:bb",
|
||||
"flags": [
|
||||
"WPA2-PSK-CCMP",
|
||||
"ESS"
|
||||
],
|
||||
"frequency": 5180,
|
||||
"signal_level": -56,
|
||||
"ssid": "Example2"
|
||||
}
|
||||
]
|
||||
```
|
||||
additionally general network device information is published on the following topic:
|
||||
__everest_api/setup/var/network_device_info__
|
||||
|
||||
with the following payload format:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"blocked": false,
|
||||
"interface": "wlan0",
|
||||
"ipv4": ["192.0.2.23"],
|
||||
"ipv6": ["2001:db8:0:0:0:0:0:23"],
|
||||
"mac": "00:11:22:33:44:55",
|
||||
"rfkill_id": "0",
|
||||
"wireless": true
|
||||
},
|
||||
{
|
||||
"blocked": false,
|
||||
"interface": "eth0",
|
||||
"ipv4": "192.0.2.42",
|
||||
"ipv6": ["2001:db8:0:0:0:0:0:42"],
|
||||
"mac": "11:22:33:44:55:66",
|
||||
"rfkill_id": "",
|
||||
"wireless": false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
additionally the list of configured wifi networks is published to the following topic:
|
||||
__everest_api/setup/var/configured_networks__
|
||||
|
||||
with the following payload format:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"connected": true,
|
||||
"interface": "wlan0",
|
||||
"network_id": 0,
|
||||
"signal_level": -56,
|
||||
"ssid": "Example"
|
||||
},
|
||||
{
|
||||
"connected": false,
|
||||
"interface": "wlan0",
|
||||
"network_id": 1,
|
||||
"signal_level": -100,
|
||||
"ssid": "Example2"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### everest_api/setup/cmd/enable_wifi_scanning
|
||||
If any arbitrary payload is published to this topic the list of available wifi networks and general network device information just mentioned is published periodically.
|
||||
|
||||
### everest_api/setup/cmd/disable_wifi_scanning
|
||||
If any arbitrary payload is published to this topic the list of available wifi networks and general network device information stops being periodically published.
|
||||
|
||||
### everest_api/setup/cmd/rfkill_unblock
|
||||
If a rfkill_id is published to this topic the wifi interface with this id will be unblocked.
|
||||
|
||||
### everest_api/setup/cmd/rfkill_block
|
||||
If a rfkill_id is published to this topic the wifi interface with this id will be blocked.
|
||||
|
||||
### everest_api/setup/cmd/list_configured_networks
|
||||
If any arbitrary payload is published to this topic the list of configured wifi networks is published to the following topic:
|
||||
__everest_api/setup/var/configured_networks__
|
||||
|
||||
with the following payload format:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"connected": true,
|
||||
"interface": "wlan0",
|
||||
"network_id": 0,
|
||||
"ssid": "Example"
|
||||
},
|
||||
{
|
||||
"connected": false,
|
||||
"interface": "wlan0",
|
||||
"network_id": 1,
|
||||
"ssid": "Example2"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### everest_api/setup/cmd/add_network
|
||||
To add a wifi network a payload with the following format must be published to this topic:
|
||||
```json
|
||||
{
|
||||
"interface": "wlan0",
|
||||
"ssid": "Example",
|
||||
"psk": "20fcb529dee0aad11b0568f553942850d06e4c4531c0d75b35345d580b300f78"
|
||||
}
|
||||
```
|
||||
The PSK field can represent the passphrase instead using escaped quotes:
|
||||
```json
|
||||
{
|
||||
"interface": "wlan0",
|
||||
"ssid": "Example",
|
||||
"psk": "\"A_valid_passphrase\""
|
||||
}
|
||||
```
|
||||
For open WiFi networks the psk must be an empty string `"psk": ""`.
|
||||
|
||||
For hidden networks an optional item is needed:
|
||||
```json
|
||||
{
|
||||
"interface": "wlan0",
|
||||
"ssid": "Example",
|
||||
"psk": "\"A_valid_passphrase\"",
|
||||
"hidden": true
|
||||
}
|
||||
```
|
||||
When `hidden` is not supplied then it is assumed to be false.
|
||||
|
||||
### everest_api/setup/cmd/enable_network
|
||||
To enable a wifi network a payload with the following format must be published to this topic:
|
||||
```json
|
||||
{
|
||||
"interface": "wlan0",
|
||||
"network_id": 0
|
||||
}
|
||||
```
|
||||
|
||||
### everest_api/setup/cmd/disable_network
|
||||
To disable a wifi network a payload with the following format must be published to this topic:
|
||||
```json
|
||||
{
|
||||
"interface": "wlan0",
|
||||
"network_id": 0
|
||||
}
|
||||
```
|
||||
|
||||
### everest_api/setup/cmd/select_network
|
||||
To select a wifi network a payload with the following format must be published to this topic:
|
||||
```json
|
||||
{
|
||||
"interface": "wlan0",
|
||||
"network_id": 0
|
||||
}
|
||||
```
|
||||
|
||||
### everest_api/setup/cmd/remove_network
|
||||
To remove a wifi network a payload with the following format must be published to this topic:
|
||||
```json
|
||||
{
|
||||
"interface": "wlan0",
|
||||
"network_id": 0
|
||||
}
|
||||
```
|
||||
|
||||
### everest_api/setup/cmd/remove_all_networks
|
||||
If any arbitrary payload is published to this topic all wifi networks will be removed.
|
||||
|
||||
### everest_api/setup/cmd/enable_ap
|
||||
If any arbitrary payload is published to this topic a wireless access point will be enabled on the interface configured in the module config.
|
||||
|
||||
### everest_api/setup/cmd/disable_ap
|
||||
If any arbitrary payload is published to this topic the wireless access point will be disabled.
|
||||
|
||||
### everest_api/setup/cmd/check_online_status
|
||||
If any arbitrary payload is published to this topic a ping will be sent to the host configured in the configuration key "online_check_host". Depending on the success of this ping a status of "online" or "offline" will be reported on the following topic:
|
||||
__everest_api/setup/var/online_status__
|
||||
|
||||
### everest_api/setup/cmd/reboot
|
||||
If any arbitrary payload is published to this topic the system will reboot.
|
||||
|
||||
## Application Info / Localization
|
||||
### everest_api/setup/cmd/set_mode
|
||||
If a mode _private_ or _public_ is published to this topic it will be stored permanently.
|
||||
|
||||
### everest_api/setup/cmd/set_initialized
|
||||
If any arbitrary payload is published to this topic the system will be marked as "initialized" permanently.
|
||||
|
||||
### everest_api/setup/cmd/reset_initialized
|
||||
If any arbitrary payload is published to this topic the system will be marked as "uninitialized" permanently.
|
||||
|
||||
### everest_api/setup/cmd/change_default_language
|
||||
You can set a [three-letter language code](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) to be set as the default language which will be stored permanently.
|
||||
|
||||
### everest_api/setup/cmd/change_current_language
|
||||
You can set a [three-letter language code](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) to be set as the current language.
|
||||
|
||||
### everest_api/setup/cmd/get_application_info
|
||||
If any arbitrary payload is published to this topic a application info object is published to the following topic:
|
||||
__everest_api/setup/var/application_info__
|
||||
|
||||
with the following payload format:
|
||||
```json
|
||||
{
|
||||
"initialized": true,
|
||||
"mode": "private",
|
||||
"default_language": "eng",
|
||||
"current_language": "ger"
|
||||
}
|
||||
```
|
||||
707
tools/EVerest-main/modules/Misc/Setup/Setup.cpp
Normal file
707
tools/EVerest-main/modules/Misc/Setup/Setup.cpp
Normal file
@@ -0,0 +1,707 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "Setup.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <locale>
|
||||
|
||||
#include <everest/run_application/run_application.hpp>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
using namespace everest::run_application;
|
||||
|
||||
namespace module {
|
||||
|
||||
// set WifiConfigureClass to the class to use for configuring WiFi
|
||||
typedef WpaCliSetup WifiConfigureClass;
|
||||
|
||||
void to_json(json& j, const NetworkDeviceInfo& k) {
|
||||
j = json::object({{"interface", k.interface},
|
||||
{"wireless", k.wireless},
|
||||
{"blocked", k.blocked},
|
||||
{"rfkill_id", k.rfkill_id},
|
||||
{"ipv4", k.ipv4},
|
||||
{"ipv6", k.ipv6},
|
||||
{"mac", k.mac},
|
||||
{"link_type", k.link_type}});
|
||||
}
|
||||
|
||||
void to_json(json& j, const WifiCredentials& k) {
|
||||
j = json::object({{"interface", k.interface}, {"ssid", k.ssid}, {"psk", k.psk}, {"hidden", k.hidden}});
|
||||
}
|
||||
|
||||
void from_json(const json& j, WifiCredentials& k) {
|
||||
k.interface = j.at("interface");
|
||||
k.ssid = j.at("ssid");
|
||||
k.psk = j.at("psk");
|
||||
k.hidden = false;
|
||||
// optional item
|
||||
auto it = j.find("hidden");
|
||||
if ((it != j.end() && *it)) {
|
||||
k.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& j, const InterfaceAndNetworkId& k) {
|
||||
j = json::object({{"interface", k.interface}, {"network_id", k.network_id}});
|
||||
}
|
||||
void from_json(const json& j, InterfaceAndNetworkId& k) {
|
||||
k.interface = j.at("interface");
|
||||
k.network_id = j.at("network_id");
|
||||
}
|
||||
|
||||
void to_json(json& j, const SupportedSetupFeatures& k) {
|
||||
j = json::object(
|
||||
{{"setup_wifi", k.setup_wifi}, {"localization", k.localization}, {"setup_simulation", k.setup_simulation}});
|
||||
}
|
||||
|
||||
void to_json(json& j, const ApplicationInfo& k) {
|
||||
j = json::object({{"initialized", k.initialized},
|
||||
{"mode", k.mode},
|
||||
{"default_language", k.default_language},
|
||||
{"current_language", k.current_language},
|
||||
{"release_metadata_file", k.release_metadata_file}});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// JSON conversion for WifiConfigureClass types
|
||||
static void to_json(json& j, const WifiConfigureClass::WifiNetworkStatus& k) {
|
||||
j = json::object({{"interface", k.interface},
|
||||
{"network_id", k.network_id},
|
||||
{"ssid", k.ssid},
|
||||
{"connected", k.connected},
|
||||
{"signal_level", k.signal_level}});
|
||||
}
|
||||
|
||||
static void to_json(json& j, const WifiConfigureClass::WifiScan& k) {
|
||||
auto flags_array = json::array();
|
||||
flags_array = k.flags;
|
||||
j = json::object({{"bssid", k.bssid},
|
||||
{"ssid", k.ssid},
|
||||
{"frequency", k.frequency},
|
||||
{"signal_level", k.signal_level},
|
||||
{"flags", flags_array}});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
void Setup::init() {
|
||||
// Set default locale "C" when no locale is set at all
|
||||
try {
|
||||
std::locale loc("");
|
||||
} catch (const std::runtime_error& e) {
|
||||
setenv("LC_ALL", "C", 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Setup::ready() {
|
||||
this->discover_network_thread = std::thread([this]() {
|
||||
while (true) {
|
||||
if ((this->config.setup_wifi) && (wifi_scan_enabled)) {
|
||||
this->discover_network();
|
||||
}
|
||||
this->publish_hostname();
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
}
|
||||
});
|
||||
|
||||
this->publish_application_info_thread = std::thread([this]() {
|
||||
while (true) {
|
||||
this->publish_supported_features();
|
||||
this->publish_application_info();
|
||||
this->publish_ap_state();
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
});
|
||||
|
||||
std::string set_mode_cmd = this->cmd_base + "set_mode";
|
||||
this->mqtt.subscribe(set_mode_cmd, [this](const std::string& data) { this->set_mode(data); });
|
||||
|
||||
std::string set_initialized_cmd = this->cmd_base + "set_initialized";
|
||||
this->mqtt.subscribe(set_initialized_cmd, [this](const std::string& data) { this->set_initialized(true); });
|
||||
|
||||
std::string reset_initialized_cmd = this->cmd_base + "reset_initialized";
|
||||
this->mqtt.subscribe(reset_initialized_cmd, [this](const std::string& data) { this->set_initialized(false); });
|
||||
|
||||
std::string change_default_language_cmd = this->cmd_base + "change_default_language";
|
||||
this->mqtt.subscribe(change_default_language_cmd,
|
||||
[this](const std::string& data) { this->set_default_language(data); });
|
||||
|
||||
std::string change_current_language_cmd = this->cmd_base + "change_current_language";
|
||||
this->mqtt.subscribe(change_current_language_cmd,
|
||||
[this](const std::string& data) { this->set_current_language(data); });
|
||||
|
||||
std::string get_application_info_cmd = this->cmd_base + "get_application_info";
|
||||
this->mqtt.subscribe(get_application_info_cmd,
|
||||
[this](const std::string& data) { this->publish_application_info(); });
|
||||
|
||||
if (this->config.setup_wifi) {
|
||||
std::string rfkill_unblock_cmd = this->cmd_base + "rfkill_unblock";
|
||||
this->mqtt.subscribe(rfkill_unblock_cmd, [this](const std::string& data) { this->rfkill_unblock(data); });
|
||||
|
||||
std::string rfkill_block_cmd = this->cmd_base + "rfkill_block";
|
||||
this->mqtt.subscribe(rfkill_block_cmd, [this](const std::string& data) { this->rfkill_block(data); });
|
||||
|
||||
std::string list_configured_networks_cmd = this->cmd_base + "list_configured_networks";
|
||||
this->mqtt.subscribe(list_configured_networks_cmd,
|
||||
[this](const std::string& data) { this->publish_configured_networks(); });
|
||||
|
||||
std::string add_network_cmd = this->cmd_base + "add_network";
|
||||
this->mqtt.subscribe(add_network_cmd, [this](const std::string& data) {
|
||||
WifiCredentials wifi_credentials = json::parse(data);
|
||||
WifiConfigureClass wifi;
|
||||
this->add_and_enable_network(wifi_credentials.interface, wifi_credentials.ssid, wifi_credentials.psk,
|
||||
wifi_credentials.hidden);
|
||||
wifi.save_config(wifi_credentials.interface);
|
||||
this->publish_configured_networks();
|
||||
});
|
||||
|
||||
std::string enable_network_cmd = this->cmd_base + "enable_network";
|
||||
this->mqtt.subscribe(enable_network_cmd, [this](const std::string& data) {
|
||||
InterfaceAndNetworkId wifi_details = json::parse(data);
|
||||
WifiConfigureClass wifi;
|
||||
wifi.enable_network(wifi_details.interface, wifi_details.network_id);
|
||||
wifi.save_config(wifi_details.interface);
|
||||
this->publish_configured_networks();
|
||||
});
|
||||
|
||||
std::string disable_network_cmd = this->cmd_base + "disable_network";
|
||||
this->mqtt.subscribe(disable_network_cmd, [this](const std::string& data) {
|
||||
InterfaceAndNetworkId wifi_details = json::parse(data);
|
||||
WifiConfigureClass wifi;
|
||||
wifi.disable_network(wifi_details.interface, wifi_details.network_id);
|
||||
wifi.save_config(wifi_details.interface);
|
||||
this->publish_configured_networks();
|
||||
});
|
||||
|
||||
std::string select_network_cmd = this->cmd_base + "select_network";
|
||||
this->mqtt.subscribe(select_network_cmd, [this](const std::string& data) {
|
||||
InterfaceAndNetworkId wifi_details = json::parse(data);
|
||||
WifiConfigureClass wifi;
|
||||
wifi.select_network(wifi_details.interface, wifi_details.network_id);
|
||||
wifi.save_config(wifi_details.interface);
|
||||
this->publish_configured_networks();
|
||||
});
|
||||
|
||||
std::string remove_network_cmd = this->cmd_base + "remove_network";
|
||||
this->mqtt.subscribe(remove_network_cmd, [this](const std::string& data) {
|
||||
InterfaceAndNetworkId wifi_details = json::parse(data);
|
||||
WifiConfigureClass wifi;
|
||||
wifi.remove_network(wifi_details.interface, wifi_details.network_id);
|
||||
wifi.save_config(wifi_details.interface);
|
||||
this->publish_configured_networks();
|
||||
});
|
||||
|
||||
std::string scan_wifi_cmd = this->cmd_base + "scan_wifi";
|
||||
this->mqtt.subscribe(scan_wifi_cmd, [this](const std::string& data) { this->discover_network(); });
|
||||
|
||||
std::string enable_wifi_scanning_cmd = this->cmd_base + "enable_wifi_scanning";
|
||||
this->mqtt.subscribe(enable_wifi_scanning_cmd,
|
||||
[this](const std::string& data) { this->wifi_scan_enabled = true; });
|
||||
|
||||
std::string disable_wifi_scanning_cmd = this->cmd_base + "disable_wifi_scanning";
|
||||
this->mqtt.subscribe(disable_wifi_scanning_cmd,
|
||||
[this](const std::string& data) { this->wifi_scan_enabled = false; });
|
||||
|
||||
std::string remove_all_networks_cmd = this->cmd_base + "remove_all_networks";
|
||||
this->mqtt.subscribe(remove_all_networks_cmd, [this](const std::string& data) {
|
||||
this->remove_all_networks();
|
||||
this->publish_configured_networks();
|
||||
});
|
||||
|
||||
std::string reboot_cmd = this->cmd_base + "reboot";
|
||||
this->mqtt.subscribe(reboot_cmd, [this](const std::string& data) { this->reboot(); });
|
||||
|
||||
std::string check_online_status_cmd = this->cmd_base + "check_online_status";
|
||||
this->mqtt.subscribe(check_online_status_cmd, [this](const std::string& data) { this->check_online_status(); });
|
||||
|
||||
std::string enable_ap_cmd = this->cmd_base + "enable_ap";
|
||||
this->mqtt.subscribe(enable_ap_cmd, [this](const std::string& data) { enable_ap(); });
|
||||
|
||||
std::string disable_ap_cmd = this->cmd_base + "disable_ap";
|
||||
this->mqtt.subscribe(disable_ap_cmd, [this](const std::string& data) { disable_ap(); });
|
||||
}
|
||||
}
|
||||
|
||||
void Setup::publish_supported_features() {
|
||||
SupportedSetupFeatures supported_setup_features;
|
||||
supported_setup_features.setup_wifi = this->config.setup_wifi;
|
||||
supported_setup_features.localization = this->config.localization;
|
||||
supported_setup_features.setup_simulation = this->config.setup_simulation;
|
||||
|
||||
std::string supported_setup_features_var = this->var_base + "supported_setup_features";
|
||||
|
||||
json supported_setup_features_json = supported_setup_features;
|
||||
|
||||
this->mqtt.publish(supported_setup_features_var, supported_setup_features_json.dump());
|
||||
}
|
||||
|
||||
void Setup::publish_application_info() {
|
||||
ApplicationInfo application_info;
|
||||
application_info.initialized = this->get_initialized();
|
||||
application_info.mode = this->get_mode();
|
||||
application_info.default_language = this->get_default_language();
|
||||
application_info.current_language = this->get_current_language();
|
||||
application_info.release_metadata_file = this->info.paths.etc / this->config.release_metadata_file;
|
||||
|
||||
std::string application_info_var = this->var_base + "application_info";
|
||||
|
||||
json application_info_json = application_info;
|
||||
|
||||
this->mqtt.publish(application_info_var, application_info_json.dump());
|
||||
}
|
||||
|
||||
void Setup::publish_hostname() {
|
||||
std::string hostname_var = this->var_base + "hostname";
|
||||
|
||||
this->mqtt.publish(hostname_var, this->get_hostname());
|
||||
}
|
||||
|
||||
void Setup::publish_ap_state() {
|
||||
std::string ap_state_var = this->var_base + "ap_state";
|
||||
|
||||
auto hostapd_enabled_output = run_application("systemctl", {"is-active", "--quiet", "hostapd"});
|
||||
if (hostapd_enabled_output.exit_code == 0) {
|
||||
this->ap_state = "enabled";
|
||||
} else {
|
||||
this->ap_state = "disabled";
|
||||
}
|
||||
|
||||
this->mqtt.publish(ap_state_var, this->ap_state);
|
||||
}
|
||||
|
||||
void Setup::set_default_language(std::string language) {
|
||||
this->r_store->call_store("everest_localization_default_language", language);
|
||||
}
|
||||
|
||||
std::string Setup::get_default_language() {
|
||||
auto language = this->r_store->call_load("everest_localization_default_language");
|
||||
if (!std::holds_alternative<std::string>(language)) {
|
||||
return "unknown";
|
||||
}
|
||||
return std::get<std::string>(language);
|
||||
}
|
||||
|
||||
void Setup::set_current_language(const std::string& language) {
|
||||
this->current_language = language;
|
||||
}
|
||||
|
||||
std::string Setup::get_current_language() {
|
||||
if (this->current_language.empty()) {
|
||||
this->current_language = this->get_default_language();
|
||||
}
|
||||
|
||||
return this->current_language;
|
||||
}
|
||||
|
||||
void Setup::set_mode(std::string mode) {
|
||||
this->r_store->call_store("everest_mode", mode);
|
||||
}
|
||||
|
||||
std::string Setup::get_mode() {
|
||||
auto mode = this->r_store->call_load("everest_mode");
|
||||
if (!std::holds_alternative<std::string>(mode)) {
|
||||
return "unknown";
|
||||
}
|
||||
return std::get<std::string>(mode);
|
||||
}
|
||||
|
||||
void Setup::set_initialized(bool initialized) {
|
||||
this->r_store->call_store("everest_initialized", initialized);
|
||||
}
|
||||
|
||||
bool Setup::get_initialized() {
|
||||
if (this->config.initialized_by_default) {
|
||||
return true;
|
||||
}
|
||||
auto initialized = this->r_store->call_load("everest_initialized");
|
||||
if (!std::holds_alternative<bool>(initialized)) {
|
||||
return false;
|
||||
}
|
||||
return std::get<bool>(initialized);
|
||||
}
|
||||
|
||||
void Setup::discover_network() {
|
||||
std::vector<NetworkDeviceInfo> device_info = this->get_network_devices();
|
||||
|
||||
this->populate_rfkill_status(device_info);
|
||||
this->populate_ip_addresses(device_info);
|
||||
|
||||
std::string network_device_info_var = this->var_base + "network_device_info";
|
||||
json device_info_json = json::array();
|
||||
device_info_json = device_info;
|
||||
this->mqtt.publish(network_device_info_var, device_info_json.dump());
|
||||
|
||||
auto wifi_info = this->scan_wifi(device_info);
|
||||
|
||||
std::string wifi_info_var = this->var_base + "wifi_info";
|
||||
json wifi_info_json = json::array();
|
||||
wifi_info_json = wifi_info;
|
||||
this->mqtt.publish(wifi_info_var, wifi_info_json.dump());
|
||||
|
||||
this->publish_configured_networks();
|
||||
}
|
||||
|
||||
std::string Setup::read_type_file(const fs::path& type_path) {
|
||||
if (!fs::exists(type_path)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::ifstream ifs(type_path.c_str());
|
||||
std::string type_file((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
// trim newlines
|
||||
type_file.erase(std::remove(type_file.begin(), type_file.end(), '\n'), type_file.end());
|
||||
|
||||
return type_file;
|
||||
}
|
||||
|
||||
std::vector<NetworkDeviceInfo> Setup::get_network_devices() {
|
||||
auto sys_net_path = fs::path("/sys/class/net");
|
||||
auto sys_virtual_net_path = fs::path("/sys/devices/virtual/net");
|
||||
|
||||
std::vector<NetworkDeviceInfo> device_info;
|
||||
|
||||
for (auto&& net_it : fs::directory_iterator(sys_net_path)) {
|
||||
auto net_path = net_it.path();
|
||||
auto type_path = net_path / "type";
|
||||
if (!fs::exists(type_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string type_file = this->read_type_file(type_path);
|
||||
|
||||
auto interface = net_path.filename();
|
||||
auto virtual_interface = sys_virtual_net_path / interface;
|
||||
|
||||
// check if type is ethernet:
|
||||
if (type_file == "1") {
|
||||
if (fs::exists(virtual_interface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto device = NetworkDeviceInfo();
|
||||
device.interface = interface.string();
|
||||
device.link_type = "ether";
|
||||
|
||||
// check if its wireless or not:
|
||||
auto wireless_path = net_path / "wireless";
|
||||
if (fs::exists(wireless_path)) {
|
||||
device.wireless = true;
|
||||
auto phy80211_path = net_path / "phy80211";
|
||||
for (auto&& rfkill_it : fs::directory_iterator(phy80211_path)) {
|
||||
auto phy_file_path = rfkill_it.path().filename().string();
|
||||
std::string rfkill = "rfkill";
|
||||
if (phy_file_path.find(rfkill) == 0) {
|
||||
phy_file_path.erase(0, rfkill.size());
|
||||
device.rfkill_id = phy_file_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
device_info.push_back(device);
|
||||
} else if (type_file == "65534") {
|
||||
if (!fs::exists(virtual_interface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto virtual_type_path = virtual_interface / "type";
|
||||
|
||||
if (!fs::exists(virtual_type_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string virtual_type_file = this->read_type_file(virtual_type_path);
|
||||
if (virtual_type_file == type_file) {
|
||||
// assume it's a vpn, but check ip link
|
||||
auto ip_output = run_application("ip", {"--json", "-details", "link", "show", interface});
|
||||
if (ip_output.exit_code != 0) {
|
||||
continue;
|
||||
}
|
||||
const auto ip_json = json::parse(ip_output.output);
|
||||
if (ip_json.size() < 1) {
|
||||
continue;
|
||||
}
|
||||
const auto& entry = ip_json.at(0);
|
||||
if (entry.contains("linkinfo") and entry.at("linkinfo").contains("info_kind")) {
|
||||
auto device = NetworkDeviceInfo();
|
||||
device.interface = interface.string();
|
||||
device.link_type = entry.at("linkinfo").at("info_kind");
|
||||
device_info.push_back(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return device_info;
|
||||
}
|
||||
|
||||
void Setup::populate_rfkill_status(std::vector<NetworkDeviceInfo>& device_info) {
|
||||
auto rfkill_output = run_application("rfkill", {"--json"});
|
||||
if (rfkill_output.exit_code != 0) {
|
||||
return;
|
||||
}
|
||||
auto rfkill_json = json::parse(rfkill_output.output);
|
||||
for (auto rfkill_object : rfkill_json.items()) {
|
||||
for (auto rfkill_device : rfkill_object.value()) {
|
||||
for (auto& device : device_info) {
|
||||
int device_id = rfkill_device.at("id");
|
||||
if (!device.rfkill_id.empty() && std::to_string(device_id) == device.rfkill_id) {
|
||||
if (rfkill_device.at("soft") == "blocked") {
|
||||
device.blocked = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Setup::rfkill_unblock(std::string rfkill_id) {
|
||||
auto network_devices = this->get_network_devices();
|
||||
this->populate_rfkill_status(network_devices);
|
||||
bool found = false;
|
||||
for (auto device : network_devices) {
|
||||
if (device.rfkill_id == rfkill_id) {
|
||||
if (!device.blocked) {
|
||||
return true;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto rfkill_output = run_application("rfkill", {"unblock", rfkill_id});
|
||||
if (rfkill_output.exit_code != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Setup::rfkill_block(std::string rfkill_id) {
|
||||
auto network_devices = this->get_network_devices();
|
||||
this->populate_rfkill_status(network_devices);
|
||||
bool found = false;
|
||||
for (auto device : network_devices) {
|
||||
if (device.rfkill_id == rfkill_id) {
|
||||
if (device.blocked) {
|
||||
return true;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto rfkill_output = run_application("rfkill", {"block", rfkill_id});
|
||||
if (rfkill_output.exit_code != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Setup::publish_configured_networks() {
|
||||
auto network_devices = this->get_network_devices();
|
||||
|
||||
WpaCliSetup::WifiNetworkStatusList all_wifi_networks;
|
||||
|
||||
for (auto device : network_devices) {
|
||||
if (!device.wireless) {
|
||||
continue;
|
||||
}
|
||||
WifiConfigureClass wifi;
|
||||
auto network_list = wifi.list_networks_status(device.interface);
|
||||
for (auto& i : network_list) {
|
||||
all_wifi_networks.push_back(std::move(i));
|
||||
}
|
||||
}
|
||||
|
||||
std::string network_list_var = this->var_base + "configured_networks";
|
||||
json configured_networks_json = json::array();
|
||||
configured_networks_json = all_wifi_networks;
|
||||
this->mqtt.publish(network_list_var, configured_networks_json.dump());
|
||||
}
|
||||
|
||||
bool Setup::add_and_enable_network(const std::string& interface, const std::string& ssid, const std::string& psk,
|
||||
bool hidden) {
|
||||
WifiConfigureClass wifi;
|
||||
|
||||
std::string net_if = interface;
|
||||
if (net_if.empty()) {
|
||||
EVLOG_warning << "Attempting to add a network without an interface, attempting to use the first one";
|
||||
auto network_devices = this->get_network_devices();
|
||||
for (auto device : network_devices) {
|
||||
if (device.wireless) {
|
||||
net_if = device.interface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto network_id = wifi.add_network(net_if);
|
||||
bool bResult = network_id != -1;
|
||||
bResult = bResult && wifi.set_network(net_if, network_id, ssid, psk, hidden);
|
||||
bResult = bResult && wifi.enable_network(net_if, network_id);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
bool Setup::remove_all_networks() {
|
||||
auto network_devices = this->get_network_devices();
|
||||
std::uint32_t remove_fail = 0;
|
||||
|
||||
for (auto device : network_devices) {
|
||||
if (!device.wireless) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WifiConfigureClass wifi;
|
||||
auto networks = wifi.list_networks(device.interface);
|
||||
|
||||
for (auto network : networks) {
|
||||
if (!wifi.remove_network(device.interface, network.network_id)) {
|
||||
remove_fail++;
|
||||
}
|
||||
}
|
||||
|
||||
wifi.save_config(device.interface);
|
||||
}
|
||||
|
||||
return remove_fail == 0;
|
||||
}
|
||||
|
||||
bool Setup::reboot() {
|
||||
bool success = true;
|
||||
auto reboot_output = run_application("systemctl", {"reboot"});
|
||||
if (reboot_output.exit_code != 0) {
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool Setup::is_online() {
|
||||
bool success = true;
|
||||
auto reboot_output = run_application("ping", {"-c", "1", this->config.online_check_host});
|
||||
if (reboot_output.exit_code != 0) {
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void Setup::check_online_status() {
|
||||
std::string online_status_var = this->var_base + "online_status";
|
||||
|
||||
if (this->is_online()) {
|
||||
this->mqtt.publish(online_status_var, "online");
|
||||
} else {
|
||||
this->mqtt.publish(online_status_var, "offline");
|
||||
}
|
||||
}
|
||||
|
||||
void Setup::enable_ap() {
|
||||
auto wpa_cli_output = run_application("wpa_cli", {"-i", this->config.ap_interface, "disconnect"});
|
||||
if (wpa_cli_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not disconnect from wireless LAN";
|
||||
}
|
||||
auto start_hostapd_output = run_application("systemctl", {"start", "hostapd"});
|
||||
if (start_hostapd_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not start hostapd";
|
||||
}
|
||||
auto start_dnsmasq_output = run_application("systemctl", {"start", "dnsmasq"});
|
||||
if (start_dnsmasq_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not start dnsmasq";
|
||||
}
|
||||
auto add_static_ip_output =
|
||||
run_application("ip", {"addr", "add", this->config.ap_ipv4, "dev", this->config.ap_interface});
|
||||
if (add_static_ip_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not add static ip to interface " << this->config.ap_interface;
|
||||
}
|
||||
}
|
||||
|
||||
void Setup::disable_ap() {
|
||||
auto del_static_ip_output =
|
||||
run_application("ip", {"addr", "del", this->config.ap_ipv4, "dev", this->config.ap_interface});
|
||||
if (del_static_ip_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not del static ip " << this->config.ap_ipv4 << " from interface "
|
||||
<< this->config.ap_interface;
|
||||
}
|
||||
auto stop_dnsmasq_output = run_application("systemctl", {"stop", "dnsmasq"});
|
||||
if (stop_dnsmasq_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not stop dnsmasq";
|
||||
}
|
||||
auto stop_hostapd_output = run_application("systemctl", {"stop", "hostapd"});
|
||||
if (stop_hostapd_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not stop hostapd";
|
||||
}
|
||||
|
||||
auto wpa_cli_output = run_application("wpa_cli", {"-i", this->config.ap_interface, "reconnect"});
|
||||
if (wpa_cli_output.exit_code != 0) {
|
||||
EVLOG_error << "Could not reconnect to wireless LAN";
|
||||
}
|
||||
}
|
||||
|
||||
static void add_addr_infos_to_device(const json& addr_infos, NetworkDeviceInfo& device) {
|
||||
for (const auto& addr_info : addr_infos) {
|
||||
if (addr_info.at("family") == "inet") {
|
||||
device.ipv4.push_back(addr_info.at("local"));
|
||||
} else if (addr_info.at("family") == "inet6") {
|
||||
device.ipv6.push_back(addr_info.at("local"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Setup::populate_ip_addresses(std::vector<NetworkDeviceInfo>& device_info) {
|
||||
auto ip_output = run_application("ip", {"--json", "address", "show"});
|
||||
if (ip_output.exit_code != 0) {
|
||||
return;
|
||||
}
|
||||
const auto ip_json = json::parse(ip_output.output);
|
||||
for (const auto& ip_object : ip_json) {
|
||||
const std::string ifname = ip_object.at("ifname");
|
||||
auto device = std::find_if(device_info.begin(), device_info.end(),
|
||||
[&ifname](NetworkDeviceInfo& device) { return device.interface == ifname; });
|
||||
if (device == device_info.end()) {
|
||||
continue;
|
||||
}
|
||||
if (ip_object.contains("address")) {
|
||||
device->mac = ip_object.at("address");
|
||||
}
|
||||
add_addr_infos_to_device(ip_object.at("addr_info"), *device);
|
||||
}
|
||||
}
|
||||
|
||||
WifiConfigureClass::WifiScanList Setup::scan_wifi(const std::vector<NetworkDeviceInfo>& device_info) {
|
||||
WifiConfigureClass::WifiScanList wifi_info;
|
||||
WifiConfigureClass wifi;
|
||||
|
||||
for (auto device : device_info) {
|
||||
if (!device.wireless) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dev_list = wifi.scan_wifi(device.interface);
|
||||
wifi_info.insert(wifi_info.end(), dev_list.begin(), dev_list.end());
|
||||
}
|
||||
|
||||
return wifi_info;
|
||||
}
|
||||
|
||||
std::string Setup::get_hostname() {
|
||||
auto hostname_output = run_application("hostname", {});
|
||||
if (hostname_output.exit_code == 0 && hostname_output.split_output.size() > 0) {
|
||||
return hostname_output.split_output.at(0);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
189
tools/EVerest-main/modules/Misc/Setup/Setup.hpp
Normal file
189
tools/EVerest-main/modules/Misc/Setup/Setup.hpp
Normal file
@@ -0,0 +1,189 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SETUP_HPP
|
||||
#define SETUP_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
#include "WiFiSetup.hpp"
|
||||
#include <regex>
|
||||
|
||||
namespace module {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct WifiCredentials {
|
||||
std::string interface;
|
||||
std::string ssid;
|
||||
std::string psk;
|
||||
bool hidden;
|
||||
|
||||
operator std::string() {
|
||||
|
||||
json wifi_credentials = *this;
|
||||
|
||||
return wifi_credentials.dump();
|
||||
}
|
||||
};
|
||||
void to_json(json& j, const WifiCredentials& k);
|
||||
void from_json(const json& j, WifiCredentials& k);
|
||||
|
||||
struct InterfaceAndNetworkId {
|
||||
std::string interface;
|
||||
int network_id;
|
||||
|
||||
operator std::string() {
|
||||
json remove_wifi = *this;
|
||||
|
||||
return remove_wifi.dump();
|
||||
}
|
||||
};
|
||||
void to_json(json& j, const InterfaceAndNetworkId& k);
|
||||
void from_json(const json& j, InterfaceAndNetworkId& k);
|
||||
|
||||
struct NetworkDeviceInfo {
|
||||
std::string interface;
|
||||
bool wireless = false;
|
||||
bool blocked = false;
|
||||
std::string rfkill_id;
|
||||
std::vector<std::string> ipv4;
|
||||
std::vector<std::string> ipv6;
|
||||
std::string mac;
|
||||
std::string link_type;
|
||||
|
||||
operator std::string() {
|
||||
json device_info = *this;
|
||||
|
||||
return device_info.dump();
|
||||
}
|
||||
};
|
||||
void to_json(json& j, const NetworkDeviceInfo& k);
|
||||
|
||||
struct SupportedSetupFeatures {
|
||||
bool setup_wifi;
|
||||
bool localization;
|
||||
bool setup_simulation;
|
||||
|
||||
operator std::string() {
|
||||
json supported_setup_features = *this;
|
||||
|
||||
return supported_setup_features.dump();
|
||||
}
|
||||
};
|
||||
void to_json(json& j, const SupportedSetupFeatures& k);
|
||||
|
||||
struct ApplicationInfo {
|
||||
bool initialized;
|
||||
std::string mode;
|
||||
std::string default_language;
|
||||
std::string current_language;
|
||||
std::string release_metadata_file;
|
||||
|
||||
operator std::string() {
|
||||
json application_info = *this;
|
||||
|
||||
return application_info.dump();
|
||||
}
|
||||
};
|
||||
void to_json(json& j, const ApplicationInfo& k);
|
||||
} // namespace module
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
bool setup_wifi;
|
||||
bool localization;
|
||||
bool setup_simulation;
|
||||
std::string online_check_host;
|
||||
bool initialized_by_default;
|
||||
std::string release_metadata_file;
|
||||
std::string ap_interface;
|
||||
std::string ap_ipv4;
|
||||
};
|
||||
|
||||
class Setup : public Everest::ModuleBase {
|
||||
public:
|
||||
Setup() = delete;
|
||||
Setup(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, std::unique_ptr<kvsIntf> r_store,
|
||||
Conf& config) :
|
||||
ModuleBase(info), mqtt(mqtt_provider), r_store(std::move(r_store)), config(config){};
|
||||
|
||||
Everest::MqttProvider& mqtt;
|
||||
const std::unique_ptr<kvsIntf> r_store;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
std::string api_base = "everest_api/setup/";
|
||||
std::string var_base = api_base + "var/";
|
||||
std::string cmd_base = api_base + "cmd/";
|
||||
std::thread discover_network_thread;
|
||||
std::thread publish_application_info_thread;
|
||||
bool wifi_scan_enabled = false;
|
||||
std::string ap_state = "unknown";
|
||||
void publish_supported_features();
|
||||
void publish_application_info();
|
||||
void publish_hostname();
|
||||
void publish_ap_state();
|
||||
void set_default_language(std::string language);
|
||||
std::string get_default_language();
|
||||
std::string current_language;
|
||||
void set_current_language(const std::string& language);
|
||||
std::string get_current_language();
|
||||
void set_mode(std::string mode);
|
||||
std::string get_mode();
|
||||
void set_initialized(bool initialized);
|
||||
bool get_initialized();
|
||||
void discover_network();
|
||||
std::string read_type_file(const fs::path& type_path);
|
||||
std::vector<NetworkDeviceInfo> get_network_devices();
|
||||
void populate_rfkill_status(std::vector<NetworkDeviceInfo>& device_info);
|
||||
bool rfkill_unblock(std::string rfkill_id);
|
||||
bool rfkill_block(std::string rfkill_id);
|
||||
|
||||
void publish_configured_networks();
|
||||
bool add_and_enable_network(const std::string& interface, const std::string& ssid, const std::string& psk,
|
||||
bool hidden = false);
|
||||
bool remove_all_networks();
|
||||
bool reboot();
|
||||
bool is_online();
|
||||
void check_online_status();
|
||||
void enable_ap();
|
||||
void disable_ap();
|
||||
void populate_ip_addresses(std::vector<NetworkDeviceInfo>& device_info);
|
||||
WpaCliSetup::WifiScanList scan_wifi(const std::vector<NetworkDeviceInfo>& device_info);
|
||||
std::string get_hostname();
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // SETUP_HPP
|
||||
578
tools/EVerest-main/modules/Misc/Setup/WiFiSetup.cpp
Normal file
578
tools/EVerest-main/modules/Misc/Setup/WiFiSetup.cpp
Normal file
@@ -0,0 +1,578 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "WiFiSetup.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <everest/run_application/run_application.hpp>
|
||||
|
||||
using namespace everest::run_application;
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief wpa_cli command failure detection
|
||||
*
|
||||
* `wpa_cli` sets an exit code of 0 unless the command is malformed.
|
||||
* Failures are presented via text to stdout.
|
||||
* Hence checking for failure to remove a network would mean checking
|
||||
* the output for OK or FAIL.
|
||||
*
|
||||
* This is common across all calls to `wpa_cli`.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
inline int hex_digit_to_nibble(std::uint8_t c) {
|
||||
int result{-1};
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
result = static_cast<std::uint8_t>(c - '0');
|
||||
} else if ((c >= 'a') && (c <= 'f')) {
|
||||
result = static_cast<std::uint8_t>(c - 'a') + 10;
|
||||
} else if ((c >= 'A') && (c <= 'F')) {
|
||||
result = static_cast<std::uint8_t>(c - 'A') + 10;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline int hex_to_int(std::uint8_t high, std::uint8_t low) {
|
||||
int result{-1};
|
||||
const auto h = hex_digit_to_nibble(high);
|
||||
const auto l = hex_digit_to_nibble(low);
|
||||
if ((h != -1) && (l != -1)) {
|
||||
const auto hc = static_cast<std::uint8_t>(h) << 4U;
|
||||
const auto lc = static_cast<std::uint8_t>(l);
|
||||
result = static_cast<int>(hc | lc);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::uint8_t nibble_to_hex_digit(std::uint8_t c) {
|
||||
std::uint8_t result{};
|
||||
c &= 0x0fU;
|
||||
if (c <= 9) {
|
||||
result = c + '0';
|
||||
} else {
|
||||
result = (c - 10) + 'a';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void int_to_hex(std::uint8_t& high, std::uint8_t& low, std::uint8_t c) {
|
||||
high = nibble_to_hex_digit(c >> 4U);
|
||||
low = nibble_to_hex_digit(c);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace module {
|
||||
|
||||
constexpr const char* wpa_cli = "/usr/sbin/wpa_cli";
|
||||
constexpr const int not_connected_rssi = -100; // -100 dBm is the minimum for wifi
|
||||
|
||||
bool WpaCliSetup::do_scan(const std::string& interface) {
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "scan"});
|
||||
return output.exit_code == 0;
|
||||
}
|
||||
|
||||
WpaCliSetup::WifiScanList WpaCliSetup::do_scan_results(const std::string& interface) {
|
||||
WifiScanList result = {};
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "scan_results"});
|
||||
if (output.exit_code == 0) {
|
||||
auto scan_results = output.split_output;
|
||||
if (scan_results.size() >= 2) {
|
||||
// skip header
|
||||
for (auto scan_results_it = std::next(scan_results.begin()); scan_results_it != scan_results.end();
|
||||
++scan_results_it) {
|
||||
|
||||
std::vector<std::string> columns;
|
||||
std::istringstream stream(*scan_results_it);
|
||||
for (std::string value; std::getline(stream, value, '\t');) {
|
||||
columns.push_back(std::move(value));
|
||||
}
|
||||
|
||||
if (columns.size() >= 5) {
|
||||
WifiScan info;
|
||||
info.bssid = columns[0];
|
||||
info.ssid = columns[4];
|
||||
info.frequency = std::stoi(columns[1]);
|
||||
info.signal_level = std::stoi(columns[2]);
|
||||
info.flags = std::move(parse_flags(columns[3]));
|
||||
result.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
WpaCliSetup::Status WpaCliSetup::do_status(const std::string& interface) {
|
||||
Status result = {};
|
||||
if (is_wifi_interface(interface)) {
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "status"});
|
||||
if (output.exit_code == 0) {
|
||||
auto scan_results = output.split_output;
|
||||
for (auto& scan_result : scan_results) {
|
||||
std::vector<std::string> columns;
|
||||
std::istringstream ss(scan_result);
|
||||
for (std::string value; std::getline(ss, value, '=');) {
|
||||
columns.push_back(std::move(value));
|
||||
}
|
||||
|
||||
if (columns.size() == 2) {
|
||||
result[columns[0]] = columns[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
WpaCliSetup::Poll WpaCliSetup::do_signal_poll(const std::string& interface) {
|
||||
Poll result = {};
|
||||
if (is_wifi_interface(interface)) {
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "signal_poll"});
|
||||
if (output.exit_code == 0) {
|
||||
auto scan_results = output.split_output;
|
||||
for (auto& scan_result : scan_results) {
|
||||
std::vector<std::string> columns;
|
||||
std::istringstream ss(scan_result);
|
||||
for (std::string value; std::getline(ss, value, '=');) {
|
||||
columns.push_back(std::move(value));
|
||||
}
|
||||
|
||||
if (columns.size() == 2) {
|
||||
result[columns[0]] = columns[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
WpaCliSetup::flags_t WpaCliSetup::parse_flags(const std::string& flags) {
|
||||
const std::regex flags_regex("\\[(.*?)\\]");
|
||||
|
||||
flags_t parsed_flags;
|
||||
|
||||
for (auto it = std::sregex_iterator(flags.begin(), flags.end(), flags_regex); it != std::sregex_iterator(); ++it) {
|
||||
parsed_flags.push_back((*it).str(1));
|
||||
}
|
||||
|
||||
return parsed_flags;
|
||||
}
|
||||
|
||||
int WpaCliSetup::add_network(const std::string& interface) {
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "add_network"});
|
||||
|
||||
if ((output.exit_code != 0) || (output.split_output.size() != 1)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return std::stoi(output.split_output.at(0));
|
||||
}
|
||||
|
||||
bool WpaCliSetup::set_network(const std::string& interface, int network_id, const std::string& ssid,
|
||||
const std::string& psk, network_security_t mode, bool hidden) {
|
||||
/*
|
||||
* configuring a network needs:
|
||||
* - ssid "<SSID>"
|
||||
* - psk "<Passphrase>" or ABCDEF0123456789... (for WPA2)
|
||||
* - sae_password "<Passphrase>" (for WPA3)
|
||||
* - key_mgmt NONE (for open networks)
|
||||
* - scan_ssid 1 (for hidden networks)
|
||||
*
|
||||
* Support for WPA3 requires:
|
||||
* - key_mgmt WPA-PSK WPA-PSK-SHA256 SAE or SAE if WPA3 only
|
||||
* - sae_password replaces psk, WPA3 doesn't support PreSharedKey (64 hex digits)
|
||||
* the passphrase is required
|
||||
* - for interworking WPA2 and WPA3 the passphrase is needed
|
||||
* - psk with hex digits (PreSharedKey) doesn't work
|
||||
*/
|
||||
|
||||
/*
|
||||
* From wpa_supplicant/hostapd
|
||||
* ieee80211w: Whether management frame protection (MFP) is enabled
|
||||
* 0 = disabled (default)
|
||||
* 1 = optional
|
||||
* 2 = required
|
||||
* The most common configuration options for this based on the PMF (protected
|
||||
* management frames) certification program are:
|
||||
* PMF enabled: ieee80211w=1 and wpa_key_mgmt=WPA-EAP WPA-EAP-SHA256
|
||||
* PMF required: ieee80211w=2 and wpa_key_mgmt=WPA-EAP-SHA256
|
||||
* (and similarly for WPA-PSK and WPA-PSK-SHA256 if WPA2-Personal is used)
|
||||
* WPA3-Personal-only mode: ieee80211w=2 and wpa_key_mgmt=SAE
|
||||
*/
|
||||
|
||||
constexpr std::uint8_t wpa2_psk_size = 64U;
|
||||
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (psk.empty()) {
|
||||
// force security mode to none
|
||||
mode = network_security_t::none;
|
||||
}
|
||||
|
||||
const char* key_mgt = nullptr;
|
||||
const char* psk_name = nullptr;
|
||||
const char* ieee80211w = nullptr;
|
||||
|
||||
switch (mode) {
|
||||
case network_security_t::none:
|
||||
key_mgt = "NONE";
|
||||
break;
|
||||
case network_security_t::wpa2_only:
|
||||
key_mgt = "WPA-PSK";
|
||||
psk_name = "psk";
|
||||
break;
|
||||
case network_security_t::wpa3_only:
|
||||
key_mgt = "SAE";
|
||||
psk_name = "sae_password";
|
||||
ieee80211w = "2";
|
||||
break;
|
||||
case network_security_t::wpa2_and_wpa3:
|
||||
default:
|
||||
if (psk.size() == wpa2_psk_size) {
|
||||
// WPA3 doesn't support PSK (hex digits), it needs a passphrase
|
||||
key_mgt = "WPA-PSK";
|
||||
psk_name = "psk";
|
||||
} else if (psk.size() > wpa2_psk_size) {
|
||||
// WPA2 doesn't support passphrases > 63 characters
|
||||
key_mgt = "SAE";
|
||||
psk_name = "sae_password";
|
||||
ieee80211w = "2";
|
||||
} else {
|
||||
key_mgt = "WPA-PSK WPA-PSK-SHA256 SAE";
|
||||
psk_name = "psk";
|
||||
ieee80211w = "1";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
auto network_id_string = std::to_string(network_id);
|
||||
|
||||
// de-escaping SSID strings in wpa_supplicant is not reliable.
|
||||
// hence providing the SSID as a string of hex digits
|
||||
auto ssid_parameter = ssid_to_hex(ssid);
|
||||
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "ssid", ssid_parameter});
|
||||
|
||||
if ((output.exit_code == 0) && (psk_name != nullptr)) {
|
||||
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, psk_name, psk});
|
||||
}
|
||||
|
||||
if (output.exit_code == 0) {
|
||||
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "key_mgmt", key_mgt});
|
||||
}
|
||||
|
||||
if ((output.exit_code == 0) && (ieee80211w != nullptr)) {
|
||||
output =
|
||||
run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "ieee80211w", ieee80211w});
|
||||
}
|
||||
|
||||
if (hidden && (output.exit_code == 0)) {
|
||||
output = run_application(wpa_cli, {"-i", interface, "set_network", network_id_string, "scan_ssid", "1"});
|
||||
}
|
||||
|
||||
return output.exit_code == 0;
|
||||
}
|
||||
|
||||
bool WpaCliSetup::enable_network(const std::string& interface, int network_id) {
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto network_id_string = std::to_string(network_id);
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "enable_network", network_id_string});
|
||||
return output.exit_code == 0;
|
||||
}
|
||||
|
||||
bool WpaCliSetup::disable_network(const std::string& interface, int network_id) {
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto network_id_string = std::to_string(network_id);
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "disable_network", network_id_string});
|
||||
return output.exit_code == 0;
|
||||
}
|
||||
|
||||
bool WpaCliSetup::select_network(const std::string& interface, int network_id) {
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto network_id_string = std::to_string(network_id);
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "select_network", network_id_string});
|
||||
return output.exit_code == 0;
|
||||
}
|
||||
|
||||
bool WpaCliSetup::remove_network(const std::string& interface, int network_id) {
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto network_id_string = std::to_string(network_id);
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "remove_network", network_id_string});
|
||||
return output.exit_code == 0;
|
||||
}
|
||||
|
||||
bool WpaCliSetup::save_config(const std::string& interface) {
|
||||
if (!is_wifi_interface(interface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "save_config"});
|
||||
return output.exit_code == 0;
|
||||
}
|
||||
|
||||
WpaCliSetup::WifiScanList WpaCliSetup::scan_wifi(const std::string& interface) {
|
||||
WifiScanList result = {};
|
||||
|
||||
if (do_scan(interface)) {
|
||||
// FIXME: is there a proper signal to check if the scan is ready? Maybe in the socket based interface
|
||||
std::this_thread::sleep_for(std::chrono::seconds(3));
|
||||
result = std::move(do_scan_results(interface));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WpaCliSetup::WifiNetworkList WpaCliSetup::list_networks(const std::string& interface) {
|
||||
WifiNetworkList result = {};
|
||||
if (is_wifi_interface(interface)) {
|
||||
auto output = run_application(wpa_cli, {"-i", interface, "list_networks"});
|
||||
if (output.exit_code == 0) {
|
||||
auto scan_results = output.split_output;
|
||||
if (scan_results.size() >= 2) {
|
||||
// skip header
|
||||
for (auto scan_results_it = std::next(scan_results.begin()); scan_results_it != scan_results.end();
|
||||
++scan_results_it) {
|
||||
|
||||
std::vector<std::string> columns;
|
||||
std::istringstream ss(*scan_results_it);
|
||||
for (std::string value; std::getline(ss, value, '\t');) {
|
||||
columns.push_back(std::move(value));
|
||||
}
|
||||
|
||||
if (columns.size() >= 2) {
|
||||
WifiNetwork info;
|
||||
info.network_id = std::stoi(columns[0]);
|
||||
info.ssid = columns[1];
|
||||
result.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
WpaCliSetup::WifiNetworkStatusList WpaCliSetup::list_networks_status(const std::string& interface) {
|
||||
WifiNetworkStatusList result = {};
|
||||
if (is_wifi_interface(interface)) {
|
||||
auto network_list = list_networks(interface);
|
||||
auto status_map = do_status(interface);
|
||||
int connected_rssi = not_connected_rssi;
|
||||
|
||||
// signal_poll raises errors when not connected
|
||||
if (status_map["wpa_state"] == "COMPLETED") {
|
||||
auto signal_map = do_signal_poll(interface);
|
||||
if (auto it = signal_map.find("RSSI"); it != signal_map.end()) {
|
||||
connected_rssi = std::stoi(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& i : network_list) {
|
||||
WifiNetworkStatus net;
|
||||
net.interface = interface;
|
||||
net.network_id = i.network_id;
|
||||
net.ssid = i.ssid;
|
||||
net.connected = false;
|
||||
net.signal_level = not_connected_rssi;
|
||||
|
||||
auto id_it = status_map.find("id");
|
||||
auto ssid_it = status_map.find("ssid");
|
||||
|
||||
if ((id_it != status_map.end()) && (ssid_it != status_map.end()) &&
|
||||
(std::stoi(id_it->second) == i.network_id) && (ssid_it->second == i.ssid)) {
|
||||
net.connected = true;
|
||||
net.signal_level = connected_rssi;
|
||||
}
|
||||
result.push_back(net);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WpaCliSetup::is_wifi_interface(const std::string& interface) {
|
||||
// check if /sys/class/net/<interface>/wireless exists
|
||||
|
||||
auto path = std::filesystem::path("/sys/class/net");
|
||||
path /= interface;
|
||||
path /= "wireless";
|
||||
|
||||
return std::filesystem::exists(path);
|
||||
}
|
||||
|
||||
int Ssid::standard(std::uint8_t c) {
|
||||
int output{-1};
|
||||
if (c == '\\') {
|
||||
handler = &Ssid::backslash;
|
||||
} else {
|
||||
output = static_cast<int>(c);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
int Ssid::backslash(std::uint8_t c) {
|
||||
int output{static_cast<int>(c)};
|
||||
handler = &Ssid::standard;
|
||||
switch (c) {
|
||||
case '"':
|
||||
case '\\':
|
||||
break;
|
||||
case 'n':
|
||||
output = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
output = '\r';
|
||||
break;
|
||||
case 't':
|
||||
output = '\t';
|
||||
break;
|
||||
case 'e':
|
||||
output = '\033';
|
||||
break;
|
||||
case 'x':
|
||||
handler = &Ssid::hex_1;
|
||||
output = -1;
|
||||
break;
|
||||
default:
|
||||
// malformed
|
||||
error = true;
|
||||
output = -1;
|
||||
break;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
int Ssid::hex_1(std::uint8_t c) {
|
||||
tmp = static_cast<std::uint8_t>(c);
|
||||
handler = &Ssid::hex_2;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Ssid::hex_2(std::uint8_t c) {
|
||||
int output{-1};
|
||||
handler = &Ssid::standard;
|
||||
const auto res = hex_to_int(tmp, c);
|
||||
if (res != -1) {
|
||||
output = res;
|
||||
} else {
|
||||
error = true;
|
||||
// malformed
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
std::string Ssid::to_hex(const std::string& ssid) {
|
||||
std::ostringstream ss;
|
||||
handler = &Ssid::standard;
|
||||
error = false;
|
||||
|
||||
for (const auto& c : ssid) {
|
||||
const auto output = std::invoke(handler, this, static_cast<std::uint8_t>(c));
|
||||
if (error) {
|
||||
break;
|
||||
}
|
||||
if (output != -1) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << output;
|
||||
}
|
||||
}
|
||||
return (error) ? std::string{} : ss.str();
|
||||
}
|
||||
|
||||
void Ssid::encode(int c, std::ostringstream& ss) {
|
||||
if ((c < 0) || (c > 255)) {
|
||||
error = true;
|
||||
} else {
|
||||
switch (c) {
|
||||
case '"':
|
||||
ss << R"(\")";
|
||||
break;
|
||||
case '\\':
|
||||
ss << R"(\\)";
|
||||
break;
|
||||
case '\033':
|
||||
ss << R"(\e)";
|
||||
break;
|
||||
case '\n':
|
||||
ss << R"(\n)";
|
||||
break;
|
||||
case '\r':
|
||||
ss << R"(\r)";
|
||||
break;
|
||||
case '\t':
|
||||
ss << R"(\t)";
|
||||
break;
|
||||
default: {
|
||||
if ((c >= 32) && (c <= 126)) {
|
||||
ss << static_cast<char>(c);
|
||||
} else {
|
||||
std::uint8_t high{};
|
||||
std::uint8_t low{};
|
||||
int_to_hex(high, low, c);
|
||||
ss << R"(\x)" << static_cast<char>(high) << static_cast<char>(low);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Ssid::from_hex(const std::string& hex) {
|
||||
std::ostringstream ss;
|
||||
bool high{true};
|
||||
|
||||
error = (hex.size() % 2) != 0;
|
||||
for (const auto& c : hex) {
|
||||
if (error) {
|
||||
break;
|
||||
}
|
||||
if (high) {
|
||||
tmp = static_cast<std::uint8_t>(c);
|
||||
high = false;
|
||||
} else {
|
||||
const auto output = hex_to_int(tmp, static_cast<std::uint8_t>(c));
|
||||
encode(output, ss);
|
||||
high = true;
|
||||
}
|
||||
}
|
||||
return (error) ? std::string{} : ss.str();
|
||||
}
|
||||
|
||||
std::string WpaCliSetup::hex_to_ssid(const std::string& hex) {
|
||||
Ssid converter;
|
||||
return converter.from_hex(hex);
|
||||
}
|
||||
|
||||
std::string WpaCliSetup::ssid_to_hex(const std::string& ssid) {
|
||||
Ssid converter;
|
||||
return converter.to_hex(ssid);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
122
tools/EVerest-main/modules/Misc/Setup/WiFiSetup.hpp
Normal file
122
tools/EVerest-main/modules/Misc/Setup/WiFiSetup.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef WIFISETUP_HPP
|
||||
#define WIFISETUP_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* SSID encoding
|
||||
* From Wikipedia:
|
||||
* "SSIDs can be zero to 32 octets long, and are, for convenience, usually
|
||||
* in a natural language, such as English"
|
||||
*
|
||||
* wpa-cli escapes SSID strings in scan results; character values 32..126 are
|
||||
* not converted. The following conversions are applied:
|
||||
* - \" double quote
|
||||
* - \\ backslash
|
||||
* - \e escape (\033)
|
||||
* - \n newline
|
||||
* - \r return
|
||||
* - \t tab
|
||||
* - \xnn for other values
|
||||
*
|
||||
* JSON strings are UTF-8 with \ converted to \\
|
||||
*/
|
||||
|
||||
namespace module {
|
||||
|
||||
class Ssid {
|
||||
private:
|
||||
using fn_p = int (Ssid::*)(std::uint8_t c);
|
||||
fn_p handler{nullptr};
|
||||
std::uint8_t tmp{0};
|
||||
bool error{false};
|
||||
|
||||
int standard(std::uint8_t c);
|
||||
int backslash(std::uint8_t c);
|
||||
int hex_1(std::uint8_t c);
|
||||
int hex_2(std::uint8_t c);
|
||||
|
||||
void encode(int c, std::ostringstream& ss);
|
||||
|
||||
public:
|
||||
std::string to_hex(const std::string& ssid);
|
||||
std::string from_hex(const std::string& hex);
|
||||
};
|
||||
|
||||
class WpaCliSetup {
|
||||
public:
|
||||
using flags_t = std::vector<std::string>;
|
||||
|
||||
enum class network_security_t : std::uint8_t {
|
||||
none,
|
||||
wpa2_only,
|
||||
wpa3_only,
|
||||
wpa2_and_wpa3,
|
||||
};
|
||||
|
||||
struct WifiScan {
|
||||
std::string bssid;
|
||||
std::string ssid;
|
||||
flags_t flags;
|
||||
int frequency;
|
||||
int signal_level;
|
||||
};
|
||||
using WifiScanList = std::vector<WifiScan>;
|
||||
|
||||
struct WifiNetworkStatus {
|
||||
std::string interface;
|
||||
std::string ssid;
|
||||
int network_id;
|
||||
int signal_level;
|
||||
bool connected;
|
||||
};
|
||||
using WifiNetworkStatusList = std::vector<WifiNetworkStatus>;
|
||||
|
||||
struct WifiNetwork {
|
||||
std::string ssid;
|
||||
int network_id;
|
||||
};
|
||||
using WifiNetworkList = std::vector<WifiNetwork>;
|
||||
|
||||
using Status = std::map<std::string, std::string>;
|
||||
using Poll = std::map<std::string, std::string>;
|
||||
|
||||
protected:
|
||||
virtual bool do_scan(const std::string& interface);
|
||||
virtual WifiScanList do_scan_results(const std::string& interface);
|
||||
virtual Status do_status(const std::string& interface);
|
||||
virtual Poll do_signal_poll(const std::string& interface);
|
||||
virtual flags_t parse_flags(const std::string& flags);
|
||||
|
||||
public:
|
||||
virtual ~WpaCliSetup() = default;
|
||||
virtual int add_network(const std::string& interface);
|
||||
virtual bool set_network(const std::string& interface, int network_id, const std::string& ssid,
|
||||
const std::string& psk, network_security_t mode, bool hidden);
|
||||
virtual bool set_network(const std::string& interface, int network_id, const std::string& ssid,
|
||||
const std::string& psk, bool hidden) {
|
||||
return set_network(interface, network_id, ssid, psk, network_security_t::wpa2_and_wpa3, hidden);
|
||||
}
|
||||
virtual bool enable_network(const std::string& interface, int network_id);
|
||||
virtual bool disable_network(const std::string& interface, int network_id);
|
||||
virtual bool select_network(const std::string& interface, int network_id);
|
||||
virtual bool remove_network(const std::string& interface, int network_id);
|
||||
virtual bool save_config(const std::string& interface);
|
||||
virtual WifiScanList scan_wifi(const std::string& interface);
|
||||
virtual WifiNetworkList list_networks(const std::string& interface);
|
||||
virtual WifiNetworkStatusList list_networks_status(const std::string& interface);
|
||||
virtual bool is_wifi_interface(const std::string& interface);
|
||||
|
||||
static std::string hex_to_ssid(const std::string& hex);
|
||||
static std::string ssid_to_hex(const std::string& ssid);
|
||||
};
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // WIFISETUP_HPP
|
||||
44
tools/EVerest-main/modules/Misc/Setup/manifest.yaml
Normal file
44
tools/EVerest-main/modules/Misc/Setup/manifest.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
description: >-
|
||||
The EVerest Setup module for setting up a LAN or WIFI network connection. This module needs privileged access and
|
||||
should not run during normal operations
|
||||
config:
|
||||
setup_wifi:
|
||||
description: Allow wifi setup
|
||||
type: boolean
|
||||
default: false
|
||||
localization:
|
||||
description: Enable localization support
|
||||
type: boolean
|
||||
default: false
|
||||
setup_simulation:
|
||||
description: Allow simulation setup
|
||||
type: boolean
|
||||
default: false
|
||||
online_check_host:
|
||||
description: Hostname or IP to use to check for internet connectivity
|
||||
type: string
|
||||
default: lfenergy.org
|
||||
initialized_by_default:
|
||||
description: Always report as if the charger was initialized
|
||||
type: boolean
|
||||
default: true
|
||||
release_metadata_file:
|
||||
description: Location of the release metadata file relative to the EVerest prefix
|
||||
type: string
|
||||
default: "release.json"
|
||||
ap_interface:
|
||||
description: Wifi interface for AP mode
|
||||
type: string
|
||||
default: wlan0
|
||||
ap_ipv4:
|
||||
description: IPv4 address of the AP
|
||||
type: string
|
||||
default: "192.168.1.1/24"
|
||||
requires:
|
||||
store:
|
||||
interface: kvs
|
||||
enable_external_mqtt: true
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Kai-Uwe Hermann
|
||||
19
tools/EVerest-main/modules/Misc/Setup/tests/CMakeLists.txt
Normal file
19
tools/EVerest-main/modules/Misc/Setup/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
set(TEST_TARGET_NAME ${PROJECT_NAME}_setup_tests)
|
||||
add_executable(${TEST_TARGET_NAME})
|
||||
|
||||
target_include_directories(${TEST_TARGET_NAME} PUBLIC ${GTEST_INCLUDE_DIRS} . ..)
|
||||
|
||||
target_sources(${TEST_TARGET_NAME} PRIVATE
|
||||
RunApplicationStub.cpp
|
||||
WiFiSetupTest.cpp
|
||||
../WiFiSetup.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
|
||||
GTest::gtest_main
|
||||
nlohmann_json::nlohmann_json
|
||||
everest::run_application
|
||||
)
|
||||
|
||||
add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
|
||||
ev_register_test_target(${TEST_TARGET_NAME})
|
||||
@@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "RunApplicationStub.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace everest::run_application;
|
||||
|
||||
namespace stub {
|
||||
|
||||
RunApplication* RunApplication::active_p = nullptr;
|
||||
|
||||
RunApplication::RunApplication() :
|
||||
results({
|
||||
{"add_network", {{}, {{"0"}}, 0}},
|
||||
{"set_network", {{}, {{"OK"}}, 0}},
|
||||
{"enable_network", {{}, {{"OK"}}, 0}},
|
||||
{"disable_network", {{}, {{"OK"}}, 0}},
|
||||
{"select_network", {{}, {{"OK"}}, 0}},
|
||||
{"remove_network", {{}, {{"OK"}}, 0}},
|
||||
{"save_config", {{}, {{"OK"}}, 0}},
|
||||
// scan_wifi uses scan and scan_results
|
||||
{"scan", {{}, {{"OK"}}, 0}},
|
||||
{"scan_results",
|
||||
{{},
|
||||
{
|
||||
{"bssid / frequency / signal level / flags / ssid"},
|
||||
},
|
||||
0}},
|
||||
{"list_networks",
|
||||
{{},
|
||||
{
|
||||
{"network id / ssid / bssid / flags"},
|
||||
},
|
||||
0}},
|
||||
// list_networks_status uses list_networks status signal_poll
|
||||
{"status",
|
||||
{{},
|
||||
{
|
||||
{"wpa_state=INACTIVE"},
|
||||
{"p2p_device_address=c2:ee:40:b0:57:b8"},
|
||||
{"address=c0:ee:40:b0:57:b8"},
|
||||
{"uuid=7dd9abf8-53f0-532b-a763-2f43537e4234"},
|
||||
},
|
||||
0}},
|
||||
{"signal_poll", {{}, {{"FAIL"}}, 0}},
|
||||
}),
|
||||
signal_poll_called(false),
|
||||
psk_called(false),
|
||||
sae_password_called(false),
|
||||
key_mgmt_called(false),
|
||||
scan_ssid_called(false),
|
||||
ieee80211w_called(false),
|
||||
key_mgmt_value(),
|
||||
ieee80211w_value() {
|
||||
active_p = this;
|
||||
}
|
||||
|
||||
RunApplication::~RunApplication() {
|
||||
active_p = nullptr;
|
||||
}
|
||||
|
||||
CmdOutput RunApplication::run_application(const std::string& name, std::vector<std::string> args) {
|
||||
CmdOutput result = {{}, {}, -1};
|
||||
EXPECT_EQ(name, "/usr/sbin/wpa_cli");
|
||||
EXPECT_EQ(args[0], "-i");
|
||||
if (args[2] == "signal_poll") {
|
||||
signal_poll_called = true;
|
||||
} else if (args[2] == "set_network") {
|
||||
if (args[4] == "psk") {
|
||||
psk_called = true;
|
||||
} else if (args[4] == "sae_password") {
|
||||
sae_password_called = true;
|
||||
} else if (args[4] == "key_mgmt") {
|
||||
key_mgmt_called = true;
|
||||
key_mgmt_value = args[5];
|
||||
} else if (args[4] == "ieee80211w") {
|
||||
ieee80211w_called = true;
|
||||
ieee80211w_value = args[5];
|
||||
} else if (args[4] == "scan_ssid") {
|
||||
scan_ssid_called = true;
|
||||
}
|
||||
}
|
||||
auto it = results.find(args[2]);
|
||||
if (it != results.end()) {
|
||||
result = it->second;
|
||||
if (!result.split_output.empty() && result.output.empty()) {
|
||||
for (auto& line : result.output) {
|
||||
result.output += line + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace stub
|
||||
|
||||
namespace everest::run_application {
|
||||
CmdOutput run_application(const std::string& name, std::vector<std::string> args,
|
||||
const std::function<CmdControl(const std::string& output_line)> output_callback) {
|
||||
CmdOutput result = {{}, {}, -1};
|
||||
if (stub::RunApplication::active_p != nullptr) {
|
||||
result = std::move(stub::RunApplication::active_p->run_application(name, args));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
} // namespace everest::run_application
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef RUNAPPLICATIONSTUB_HPP
|
||||
#define RUNAPPLICATIONSTUB_HPP
|
||||
|
||||
#include <everest/run_application/run_application.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace stub {
|
||||
|
||||
class RunApplication {
|
||||
public:
|
||||
static RunApplication* active_p;
|
||||
|
||||
std::map<std::string, everest::run_application::CmdOutput> results;
|
||||
bool signal_poll_called;
|
||||
bool psk_called;
|
||||
bool sae_password_called;
|
||||
bool key_mgmt_called;
|
||||
bool scan_ssid_called;
|
||||
bool ieee80211w_called;
|
||||
std::string key_mgmt_value;
|
||||
std::string ieee80211w_value;
|
||||
|
||||
RunApplication();
|
||||
virtual ~RunApplication();
|
||||
|
||||
virtual everest::run_application::CmdOutput run_application(const std::string& name, std::vector<std::string> args);
|
||||
};
|
||||
|
||||
} // namespace stub
|
||||
namespace everest::run_application {
|
||||
CmdOutput run_application(const std::string& name, std::vector<std::string> args,
|
||||
const std::function<CmdControl(const std::string& output_line)> output_callback);
|
||||
}
|
||||
|
||||
#endif // RUNAPPLICATIONSTUB_HPP
|
||||
726
tools/EVerest-main/modules/Misc/Setup/tests/WiFiSetupTest.cpp
Normal file
726
tools/EVerest-main/modules/Misc/Setup/tests/WiFiSetupTest.cpp
Normal file
@@ -0,0 +1,726 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "RunApplicationStub.hpp"
|
||||
#include <WiFiSetup.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
using namespace module;
|
||||
using nlohmann::json;
|
||||
|
||||
constexpr const char* example_psk = "e3003974af901976485f3e655b455791dcc20a5380f42a7839de3bfdc9d70d71";
|
||||
constexpr const char* example_password = "LetMeIn2";
|
||||
constexpr const char* example_long_password = "e3003974af901976485f3e655b455791dcc20a5380f42a7839de3bfdc9d70d71X";
|
||||
|
||||
class WpaCliSetupTest : public WpaCliSetup {
|
||||
public:
|
||||
// override to support testing
|
||||
virtual bool is_wifi_interface(const std::string& interface) override {
|
||||
if (interface == "ap0") {
|
||||
return false;
|
||||
} else if (interface == "eth0") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
struct WifiCredentials {
|
||||
std::string interface;
|
||||
std::string ssid;
|
||||
std::string psk;
|
||||
bool hidden;
|
||||
|
||||
operator std::string() {
|
||||
|
||||
json wifi_credentials = *this;
|
||||
|
||||
return wifi_credentials.dump();
|
||||
}
|
||||
};
|
||||
void to_json(json& j, const WifiCredentials& k) {
|
||||
j = json::object({{"interface", k.interface}, {"ssid", k.ssid}, {"psk", k.psk}, {"hidden", k.hidden}});
|
||||
}
|
||||
|
||||
void from_json(const json& j, WifiCredentials& k) {
|
||||
k.interface = j.at("interface");
|
||||
k.ssid = j.at("ssid");
|
||||
k.psk = j.at("psk");
|
||||
k.hidden = false;
|
||||
// optional item
|
||||
auto it = j.find("hidden");
|
||||
if ((it != j.end() && *it)) {
|
||||
k.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// SSID conversions
|
||||
TEST(Ssid, toHex) {
|
||||
Ssid ssid;
|
||||
EXPECT_EQ(ssid.to_hex("0123456789"), "30313233343536373839");
|
||||
EXPECT_EQ(ssid.to_hex("abcdef"), "616263646566");
|
||||
EXPECT_EQ(ssid.to_hex("ABCDEF"), "414243444546");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\" \\ \e\n\r\t)"), "22205c201b0a0d09");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\x00\x01\xfd)"), "0001fd");
|
||||
}
|
||||
|
||||
TEST(Ssid, fromHex) {
|
||||
Ssid ssid;
|
||||
EXPECT_EQ(ssid.from_hex("30313233343536373839"), "0123456789");
|
||||
EXPECT_EQ(ssid.from_hex("616263646566"), "abcdef");
|
||||
EXPECT_EQ(ssid.from_hex("414243444546"), "ABCDEF");
|
||||
EXPECT_EQ(ssid.from_hex("22205c201b0a0d09"), R"(\" \\ \e\n\r\t)");
|
||||
EXPECT_EQ(ssid.from_hex("0001fd"), R"(\x00\x01\xfd)");
|
||||
}
|
||||
|
||||
TEST(Ssid, complete) {
|
||||
Ssid ssid;
|
||||
|
||||
std::stringstream ss;
|
||||
for (std::uint16_t i = 0; i < 256; i++) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << i;
|
||||
}
|
||||
const std::string values_str(ss.str());
|
||||
const auto result_str = ssid.from_hex(values_str);
|
||||
const auto result_hex = ssid.to_hex(result_str);
|
||||
// std::cout << result_str << std::endl;
|
||||
// std::cout << result_hex << std::endl;
|
||||
EXPECT_EQ(values_str, result_hex);
|
||||
}
|
||||
|
||||
TEST(Ssid, unusual) {
|
||||
Ssid ssid;
|
||||
|
||||
// allowing upper case hex digits
|
||||
EXPECT_EQ(ssid.from_hex("E0E1E2E3E4E5E6E7E8E9EaEbEcEdEeEfEAEBECEDEEEF"),
|
||||
R"(\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xea\xeb\xec\xed\xee\xef)");
|
||||
EXPECT_EQ(
|
||||
ssid.to_hex(R"(\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xEA\xEB\xEC\xED\xEE\xEF)"),
|
||||
"e0e1e2e3e4e5e6e7e8e9eaebecedeeefeaebecedeeef");
|
||||
|
||||
// parsing errors
|
||||
EXPECT_EQ(ssid.to_hex(R"(123\?456)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(123\?)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\?456)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\034)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\03)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\0)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\x)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\xz)"), "");
|
||||
EXPECT_EQ(ssid.to_hex(R"(\xaz)"), "");
|
||||
|
||||
EXPECT_EQ(ssid.from_hex(R"(G123)"), "");
|
||||
EXPECT_EQ(ssid.from_hex(R"(12G3)"), "");
|
||||
EXPECT_EQ(ssid.from_hex(R"(123G)"), "");
|
||||
EXPECT_EQ(ssid.from_hex(R"(1234568)"), "");
|
||||
}
|
||||
|
||||
TEST(Ssid, json) {
|
||||
/*
|
||||
* worked example
|
||||
* ssid=PP€-310034
|
||||
* scan_results
|
||||
* bssid / frequency / signal level / flags / ssid
|
||||
* c2:ee:40:10:57:b8 2417 -45 [WPA2-PSK-CCMP][ESS] PP\xe2\x82\xac-310034
|
||||
*
|
||||
* MQTT
|
||||
* everest_api/setup/var/wifi_info: [
|
||||
* {"bssid":"c2:ee:40:10:57:b8","flags":["WPA2-PSK-CCMP","ESS"],"frequency":2417,"signal_level":-46,"ssid":"PP\\xe2\\x82\\xac-310034"}]
|
||||
*/
|
||||
Ssid conv;
|
||||
|
||||
std::string ssid_hex{"535349443de282ac3132"};
|
||||
std::string ssid = conv.from_hex(ssid_hex);
|
||||
WifiCredentials wifi = {"eth0", ssid, "\"psk\"", false};
|
||||
nlohmann::json j;
|
||||
to_json(j, wifi);
|
||||
|
||||
const auto transmitted = j.dump();
|
||||
WifiCredentials received = json::parse(transmitted);
|
||||
EXPECT_EQ(received.ssid, ssid);
|
||||
|
||||
const auto rec_ssid_hex = conv.to_hex(received.ssid);
|
||||
EXPECT_EQ(rec_ssid_hex, ssid_hex);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// add_network()
|
||||
TEST(add_network, wired) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_EQ(obj.add_network("eth0"), -1);
|
||||
}
|
||||
|
||||
TEST(add_network, wireless) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_NE(obj.add_network("wlan0"), -1);
|
||||
}
|
||||
|
||||
TEST(add_network, access_point) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_EQ(obj.add_network("ap0"), -1);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// set_network()
|
||||
TEST(set_network, none_no_psk) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", "", WpaCliSetup::network_security_t::none, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "NONE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, none_psk) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(
|
||||
obj.set_network("wlan0", 0, "PlusnetWireless", example_psk, WpaCliSetup::network_security_t::none, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "NONE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, none_password) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(
|
||||
obj.set_network("wlan0", 0, "PlusnetWireless", example_password, WpaCliSetup::network_security_t::none, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "NONE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2_no_psk) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", "", WpaCliSetup::network_security_t::wpa2_only, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "NONE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2_psk) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(
|
||||
obj.set_network("wlan0", 0, "PlusnetWireless", example_psk, WpaCliSetup::network_security_t::wpa2_only, false));
|
||||
ASSERT_TRUE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "WPA-PSK");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2_password) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", example_password,
|
||||
WpaCliSetup::network_security_t::wpa2_only, false));
|
||||
ASSERT_TRUE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "WPA-PSK");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa3_no_psk) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", "", WpaCliSetup::network_security_t::wpa3_only, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "NONE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa3_psk) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(
|
||||
obj.set_network("wlan0", 0, "PlusnetWireless", example_psk, WpaCliSetup::network_security_t::wpa3_only, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_TRUE(ra.sae_password_called);
|
||||
ASSERT_TRUE(ra.ieee80211w_called);
|
||||
ASSERT_EQ(ra.ieee80211w_value, "2");
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "SAE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa3_password) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", example_password,
|
||||
WpaCliSetup::network_security_t::wpa3_only, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_TRUE(ra.sae_password_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_TRUE(ra.ieee80211w_called);
|
||||
ASSERT_EQ(ra.ieee80211w_value, "2");
|
||||
ASSERT_EQ(ra.key_mgmt_value, "SAE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2_and_wpa3_no_psk) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(
|
||||
obj.set_network("wlan0", 0, "PlusnetWireless", "", WpaCliSetup::network_security_t::wpa2_and_wpa3, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "NONE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2_and_wpa3_psk) {
|
||||
// with a PSK result is same as wpa2_only
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", example_psk,
|
||||
WpaCliSetup::network_security_t::wpa2_and_wpa3, false));
|
||||
ASSERT_TRUE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_FALSE(ra.ieee80211w_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "WPA-PSK");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2_and_wpa3_password) {
|
||||
// configure for both
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", example_password,
|
||||
WpaCliSetup::network_security_t::wpa2_and_wpa3, false));
|
||||
ASSERT_TRUE(ra.psk_called);
|
||||
ASSERT_FALSE(ra.sae_password_called);
|
||||
ASSERT_TRUE(ra.ieee80211w_called);
|
||||
ASSERT_EQ(ra.ieee80211w_value, "1");
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "WPA-PSK WPA-PSK-SHA256 SAE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2_and_wpa3_password_long) {
|
||||
// configure for WPA3 only
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", example_long_password,
|
||||
WpaCliSetup::network_security_t::wpa2_and_wpa3, false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_TRUE(ra.sae_password_called);
|
||||
ASSERT_TRUE(ra.ieee80211w_called);
|
||||
ASSERT_EQ(ra.ieee80211w_value, "2");
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_EQ(ra.key_mgmt_value, "SAE");
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, wpa2) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "PlusnetWireless", "LetMeIn2", false));
|
||||
ASSERT_TRUE(ra.psk_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, open) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "OpenNet", "", false));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_FALSE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, hidden_wpa2) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "Hidden", "LetMeIn3", true));
|
||||
ASSERT_TRUE(ra.psk_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_TRUE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
TEST(set_network, hidden_open) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.set_network("wlan0", 0, "Hidden", "", true));
|
||||
ASSERT_FALSE(ra.psk_called);
|
||||
ASSERT_TRUE(ra.key_mgmt_called);
|
||||
ASSERT_TRUE(ra.scan_ssid_called);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// enable_network()
|
||||
TEST(enable_network, exists) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.enable_network("wlan0", 0));
|
||||
}
|
||||
|
||||
TEST(enable_network, doesnt_exist) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["enable_network"] = {{}, {{"FAIL"}}, 0};
|
||||
WpaCliSetupTest obj;
|
||||
// still returns an exit code of 0
|
||||
ASSERT_TRUE(obj.enable_network("wlan0", 1));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// disable_network()
|
||||
TEST(disable_network, exists) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.disable_network("wlan0", 0));
|
||||
}
|
||||
|
||||
TEST(disable_network, doesnt_exist) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["disable_network"] = {{}, {{"FAIL"}}, 0};
|
||||
WpaCliSetupTest obj;
|
||||
// still returns an exit code of 0
|
||||
ASSERT_TRUE(obj.disable_network("wlan0", 1));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// select_network()
|
||||
TEST(select_network, exists) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.select_network("wlan0", 0));
|
||||
}
|
||||
|
||||
TEST(select_network, doesnt_exist) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["select_network"] = {{}, {{"FAIL"}}, 0};
|
||||
WpaCliSetupTest obj;
|
||||
// still returns an exit code of 0
|
||||
ASSERT_TRUE(obj.select_network("wlan0", 1));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// remove_network()
|
||||
TEST(remove_network, exists) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.remove_network("wlan0", 0));
|
||||
}
|
||||
|
||||
TEST(remove_network, doesnt_exist) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["remove_network"] = {{}, {{"FAIL"}}, 0};
|
||||
WpaCliSetupTest obj;
|
||||
// still returns an exit code of 0
|
||||
ASSERT_TRUE(obj.remove_network("wlan0", 1));
|
||||
}
|
||||
|
||||
TEST(remove_network, fail) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["remove_network"] = {{}, {{"Invalid REMOVE_NETWORK command - at least 1 argument is required."}}, 255};
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_FALSE(obj.remove_network("wlan0", -99));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// save_config()
|
||||
TEST(save_config, success) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_TRUE(obj.save_config("wlan0"));
|
||||
}
|
||||
|
||||
TEST(save_config, fail) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
ASSERT_FALSE(obj.save_config("ap0"));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// scan_wifi()
|
||||
TEST(scan_wifi, none) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.scan_wifi("wlan0");
|
||||
ASSERT_TRUE(res.empty());
|
||||
}
|
||||
|
||||
TEST(scan_wifi, some) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["scan_results"] = {
|
||||
{},
|
||||
{
|
||||
{"bssid / frequency / signal level / flags / ssid"},
|
||||
{"14:49:bc:06:81:19\t2412\t-72\t[WPA2-PSK-CCMP][ESS]\tPlusnetWireless"},
|
||||
{"6a:82:8c:38:b2:a1\t2412\t-93\t[WPA2-PSK-CCMP][ESS]\t\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"},
|
||||
},
|
||||
0};
|
||||
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.scan_wifi("wlan0");
|
||||
ASSERT_FALSE(res.empty());
|
||||
ASSERT_EQ(res.size(), 2);
|
||||
|
||||
module::WpaCliSetup::flags_t expected = {"WPA2-PSK-CCMP", "ESS"};
|
||||
|
||||
EXPECT_EQ(res[0].bssid, "14:49:bc:06:81:19");
|
||||
EXPECT_EQ(res[0].ssid, "PlusnetWireless");
|
||||
EXPECT_EQ(res[0].frequency, 2412);
|
||||
EXPECT_EQ(res[0].signal_level, -72);
|
||||
EXPECT_EQ(res[0].flags, expected);
|
||||
|
||||
EXPECT_EQ(res[1].bssid, "6a:82:8c:38:b2:a1");
|
||||
EXPECT_EQ(res[1].ssid, "\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00");
|
||||
EXPECT_EQ(res[1].frequency, 2412);
|
||||
EXPECT_EQ(res[1].signal_level, -93);
|
||||
EXPECT_EQ(res[1].flags, expected);
|
||||
}
|
||||
|
||||
TEST(scan_wifi, more) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["scan_results"] = {
|
||||
{},
|
||||
{
|
||||
{"bssid / frequency / signal level / flags / ssid"},
|
||||
{"14:49:bc:06:81:19\t2412\t-71\t[WPA2-PSK-CCMP][ESS]\tPlusnetWireless"},
|
||||
{"14:49:bc:06:81:1b\t2412\t-71\t[WPA2-PSK-CCMP][ESS]\t\\x00\\x00\\x00\\x00\\x00\\x00\\x00"},
|
||||
{"00:1e:42:33:62:07\t2462\t-89\t[WPA2-PSK-CCMP+TKIP][ESS][UTF-8]\tRUT950_6207"},
|
||||
{"b4:ba:9d:16:e2:ba\t2437\t-92\t[WPA2-PSK-CCMP][WPS][ESS]\tSKYLZMEY"},
|
||||
{"14:49:bc:06:81:1c\t2412\t-72\t[ESS]\tTesting123"},
|
||||
{"36:49:5b:f8:e1:07\t2412\t-92\t[ESS]\tEE WiFi"},
|
||||
{"14:49:bc:06:81:18\t2412\t-73\t[WPA2-PSK-CCMP][ESS]\t\\x00\\x00\\x00\\x00\\x00\\x00\\x00"},
|
||||
{"6a:82:8c:38:b2:a1\t2412\t-88\t[WPA2-PSK-CCMP][ESS]\t\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00"},
|
||||
{"18:82:8c:38:b2:a5\t2412\t-92\t[WPA2-PSK-CCMP][WPS][ESS]\tBT-3GAG3M"},
|
||||
{"6a:82:8c:38:b2:a6\t2412\t-92\t[ESS]\tEE WiFi"},
|
||||
},
|
||||
0};
|
||||
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.scan_wifi("wlan0");
|
||||
ASSERT_FALSE(res.empty());
|
||||
ASSERT_EQ(res.size(), 10);
|
||||
|
||||
module::WpaCliSetup::flags_t expected1 = {"WPA2-PSK-CCMP", "ESS"};
|
||||
module::WpaCliSetup::flags_t expected2 = {"WPA2-PSK-CCMP+TKIP", "ESS", "UTF-8"};
|
||||
module::WpaCliSetup::flags_t expected3 = {"WPA2-PSK-CCMP", "WPS", "ESS"};
|
||||
module::WpaCliSetup::flags_t expected4 = {"ESS"};
|
||||
|
||||
EXPECT_EQ(res[0].bssid, "14:49:bc:06:81:19");
|
||||
EXPECT_EQ(res[0].ssid, "PlusnetWireless");
|
||||
EXPECT_EQ(res[0].frequency, 2412);
|
||||
EXPECT_EQ(res[0].signal_level, -71);
|
||||
EXPECT_EQ(res[0].flags, expected1);
|
||||
|
||||
EXPECT_EQ(res[1].bssid, "14:49:bc:06:81:1b");
|
||||
EXPECT_EQ(res[1].ssid, "\\x00\\x00\\x00\\x00\\x00\\x00\\x00");
|
||||
EXPECT_EQ(res[1].frequency, 2412);
|
||||
EXPECT_EQ(res[1].signal_level, -71);
|
||||
EXPECT_EQ(res[1].flags, expected1);
|
||||
|
||||
EXPECT_EQ(res[2].bssid, "00:1e:42:33:62:07");
|
||||
EXPECT_EQ(res[2].ssid, "RUT950_6207");
|
||||
EXPECT_EQ(res[2].frequency, 2462);
|
||||
EXPECT_EQ(res[2].signal_level, -89);
|
||||
EXPECT_EQ(res[2].flags, expected2);
|
||||
|
||||
EXPECT_EQ(res[3].bssid, "b4:ba:9d:16:e2:ba");
|
||||
EXPECT_EQ(res[3].ssid, "SKYLZMEY");
|
||||
EXPECT_EQ(res[3].frequency, 2437);
|
||||
EXPECT_EQ(res[3].signal_level, -92);
|
||||
EXPECT_EQ(res[3].flags, expected3);
|
||||
|
||||
EXPECT_EQ(res[4].bssid, "14:49:bc:06:81:1c");
|
||||
EXPECT_EQ(res[4].ssid, "Testing123");
|
||||
EXPECT_EQ(res[4].frequency, 2412);
|
||||
EXPECT_EQ(res[4].signal_level, -72);
|
||||
EXPECT_EQ(res[4].flags, expected4);
|
||||
|
||||
EXPECT_EQ(res[5].bssid, "36:49:5b:f8:e1:07");
|
||||
EXPECT_EQ(res[5].ssid, "EE WiFi");
|
||||
EXPECT_EQ(res[5].frequency, 2412);
|
||||
EXPECT_EQ(res[5].signal_level, -92);
|
||||
EXPECT_EQ(res[5].flags, expected4);
|
||||
|
||||
EXPECT_EQ(res[6].bssid, "14:49:bc:06:81:18");
|
||||
EXPECT_EQ(res[6].ssid, "\\x00\\x00\\x00\\x00\\x00\\x00\\x00");
|
||||
EXPECT_EQ(res[6].frequency, 2412);
|
||||
EXPECT_EQ(res[6].signal_level, -73);
|
||||
EXPECT_EQ(res[6].flags, expected1);
|
||||
|
||||
EXPECT_EQ(res[7].bssid, "6a:82:8c:38:b2:a1");
|
||||
EXPECT_EQ(res[7].ssid, "\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00");
|
||||
EXPECT_EQ(res[7].frequency, 2412);
|
||||
EXPECT_EQ(res[7].signal_level, -88);
|
||||
EXPECT_EQ(res[7].flags, expected1);
|
||||
|
||||
EXPECT_EQ(res[8].bssid, "18:82:8c:38:b2:a5");
|
||||
EXPECT_EQ(res[8].ssid, "BT-3GAG3M");
|
||||
EXPECT_EQ(res[8].frequency, 2412);
|
||||
EXPECT_EQ(res[8].signal_level, -92);
|
||||
EXPECT_EQ(res[8].flags, expected3);
|
||||
|
||||
EXPECT_EQ(res[9].bssid, "6a:82:8c:38:b2:a6");
|
||||
EXPECT_EQ(res[9].ssid, "EE WiFi");
|
||||
EXPECT_EQ(res[9].frequency, 2412);
|
||||
EXPECT_EQ(res[9].signal_level, -92);
|
||||
EXPECT_EQ(res[9].flags, expected4);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// list_networks()
|
||||
TEST(list_networks, none) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.list_networks("wlan0");
|
||||
ASSERT_TRUE(res.empty());
|
||||
}
|
||||
|
||||
TEST(list_networks, one) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["list_networks"] = {{},
|
||||
{
|
||||
{"network id / ssid / bssid / flags"},
|
||||
{"0\t\tany\t[DISABLED]"},
|
||||
},
|
||||
0};
|
||||
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.list_networks("wlan0");
|
||||
ASSERT_FALSE(res.empty());
|
||||
ASSERT_EQ(res.size(), 1);
|
||||
|
||||
module::WpaCliSetup::flags_t expected = {"WPA2-PSK-CCMP", "ESS"};
|
||||
|
||||
EXPECT_EQ(res[0].network_id, 0);
|
||||
EXPECT_EQ(res[0].ssid, "");
|
||||
}
|
||||
|
||||
TEST(list_networks, two) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["list_networks"] = {{},
|
||||
{
|
||||
{"network id / ssid / bssid / flags"},
|
||||
{"0\t\tany\t[DISABLED]"},
|
||||
{"1\tPlusnetWireless\tany\t[CURRENT]"},
|
||||
},
|
||||
0};
|
||||
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.list_networks("wlan0");
|
||||
ASSERT_FALSE(res.empty());
|
||||
ASSERT_EQ(res.size(), 2);
|
||||
|
||||
module::WpaCliSetup::flags_t expected = {"WPA2-PSK-CCMP", "ESS"};
|
||||
|
||||
EXPECT_EQ(res[0].network_id, 0);
|
||||
EXPECT_EQ(res[0].ssid, "");
|
||||
|
||||
EXPECT_EQ(res[1].network_id, 1);
|
||||
EXPECT_EQ(res[1].ssid, "PlusnetWireless");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// list_networks_status()
|
||||
TEST(list_networks, not_connected) {
|
||||
stub::RunApplication ra;
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.list_networks_status("wlan0");
|
||||
ASSERT_TRUE(res.empty());
|
||||
ASSERT_FALSE(ra.signal_poll_called);
|
||||
}
|
||||
|
||||
TEST(list_networks, connected) {
|
||||
stub::RunApplication ra;
|
||||
ra.results["list_networks"] = {{},
|
||||
{
|
||||
{"network id / ssid / bssid / flags"},
|
||||
{"0\t\tany\t[DISABLED]"},
|
||||
{"1\tPlusnetWireless\tany\t[CURRENT]"},
|
||||
{"2\tHiddenNet\tany\t[DISABLED]"},
|
||||
},
|
||||
0};
|
||||
ra.results["status"] = {{},
|
||||
{
|
||||
{"bssid=14:49:bc:06:81:19"},
|
||||
{"freq=2412"},
|
||||
{"ssid=PlusnetWireless"},
|
||||
{"id=1"},
|
||||
{"mode=station"},
|
||||
{"wifi_generation=4"},
|
||||
{"pairwise_cipher=CCMP"},
|
||||
{"group_cipher=CCMP"},
|
||||
{"key_mgmt=WPA2-PSK"},
|
||||
{"wpa_state=COMPLETED"},
|
||||
{"ip_address=172.25.1.11"},
|
||||
{"p2p_device_address=c2:ee:40:b0:57:b8"},
|
||||
{"address=c0:ee:40:b0:57:b8"},
|
||||
{"uuid=7dd9abf8-53f0-532b-a763-2f43537e4234"},
|
||||
},
|
||||
0};
|
||||
ra.results["signal_poll"] = {{},
|
||||
{
|
||||
{"RSSI=-73"},
|
||||
{"LINKSPEED=54"},
|
||||
{"NOISE=9999"},
|
||||
{"FREQUENCY=2412"},
|
||||
{"WIDTH=20 MHz"},
|
||||
{"CENTER_FRQ1=2412"},
|
||||
},
|
||||
0};
|
||||
WpaCliSetupTest obj;
|
||||
auto res = obj.list_networks_status("wlan0");
|
||||
ASSERT_FALSE(res.empty());
|
||||
ASSERT_TRUE(ra.signal_poll_called);
|
||||
ASSERT_EQ(res.size(), 3);
|
||||
|
||||
EXPECT_EQ(res[0].interface, "wlan0");
|
||||
EXPECT_EQ(res[0].network_id, 0);
|
||||
EXPECT_EQ(res[0].ssid, "");
|
||||
EXPECT_FALSE(res[0].connected);
|
||||
EXPECT_EQ(res[0].signal_level, -100);
|
||||
|
||||
EXPECT_EQ(res[1].interface, "wlan0");
|
||||
EXPECT_EQ(res[1].network_id, 1);
|
||||
EXPECT_EQ(res[1].ssid, "PlusnetWireless");
|
||||
EXPECT_TRUE(res[1].connected);
|
||||
EXPECT_EQ(res[1].signal_level, -73);
|
||||
|
||||
EXPECT_EQ(res[2].interface, "wlan0");
|
||||
EXPECT_EQ(res[2].network_id, 2);
|
||||
EXPECT_EQ(res[2].ssid, "HiddenNet");
|
||||
EXPECT_FALSE(res[2].connected);
|
||||
EXPECT_EQ(res[2].signal_level, -100);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// is_wifi_interface()
|
||||
// not tested as it is checking for files in /proc so depends on the
|
||||
// machine the tests are running on
|
||||
|
||||
} // namespace
|
||||
21
tools/EVerest-main/modules/Misc/Store/CMakeLists.txt
Normal file
21
tools/EVerest-main/modules/Misc/Store/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/kvsImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
15
tools/EVerest-main/modules/Misc/Store/Store.cpp
Normal file
15
tools/EVerest-main/modules/Misc/Store/Store.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
#include "Store.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void Store::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void Store::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
58
tools/EVerest-main/modules/Misc/Store/Store.hpp
Normal file
58
tools/EVerest-main/modules/Misc/Store/Store.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef STORE_HPP
|
||||
#define STORE_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/kvs/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class Store : public Everest::ModuleBase {
|
||||
public:
|
||||
Store() = delete;
|
||||
Store(const ModuleInfo& info, std::unique_ptr<kvsImplBase> p_main, Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), config(config){};
|
||||
|
||||
const std::unique_ptr<kvsImplBase> p_main;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // STORE_HPP
|
||||
32
tools/EVerest-main/modules/Misc/Store/main/kvsImpl.cpp
Normal file
32
tools/EVerest-main/modules/Misc/Store/main/kvsImpl.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
#include "kvsImpl.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
void kvsImpl::init() {
|
||||
}
|
||||
|
||||
void kvsImpl::ready() {
|
||||
}
|
||||
|
||||
void kvsImpl::handle_store(std::string& key,
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>& value) {
|
||||
kvs[key] = value;
|
||||
};
|
||||
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string> kvsImpl::handle_load(std::string& key) {
|
||||
return kvs[key];
|
||||
};
|
||||
|
||||
void kvsImpl::handle_delete(std::string& key) {
|
||||
kvs.erase(key);
|
||||
};
|
||||
|
||||
bool kvsImpl::handle_exists(std::string& key) {
|
||||
return kvs.count(key) != 0;
|
||||
};
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
67
tools/EVerest-main/modules/Misc/Store/main/kvsImpl.hpp
Normal file
67
tools/EVerest-main/modules/Misc/Store/main/kvsImpl.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_KVS_IMPL_HPP
|
||||
#define MAIN_KVS_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/kvs/Implementation.hpp>
|
||||
|
||||
#include "../Store.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
#include <map>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class kvsImpl : public kvsImplBase {
|
||||
public:
|
||||
kvsImpl() = delete;
|
||||
kvsImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Store>& mod, Conf& config) :
|
||||
kvsImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual void
|
||||
handle_store(std::string& key,
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>& value) override;
|
||||
virtual std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>
|
||||
handle_load(std::string& key) override;
|
||||
virtual void handle_delete(std::string& key) override;
|
||||
virtual bool handle_exists(std::string& key) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<Store>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
std::map<std::string, std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>> kvs{};
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_KVS_IMPL_HPP
|
||||
10
tools/EVerest-main/modules/Misc/Store/manifest.yaml
Normal file
10
tools/EVerest-main/modules/Misc/Store/manifest.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
description: Simple implementation of a memory-backed key-value store
|
||||
provides:
|
||||
main:
|
||||
interface: kvs
|
||||
description: This implements a key-value store
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Kai-Uwe Hermann
|
||||
- Thilo Molitor
|
||||
37
tools/EVerest-main/modules/Misc/System/BUILD.bazel
Normal file
37
tools/EVerest-main/modules/Misc/System/BUILD.bazel
Normal file
@@ -0,0 +1,37 @@
|
||||
load("//modules:module.bzl", "cc_everest_module")
|
||||
|
||||
IMPLS = [
|
||||
"main",
|
||||
]
|
||||
|
||||
cc_everest_module(
|
||||
name = "System",
|
||||
impls = IMPLS,
|
||||
deps = [
|
||||
"//lib:run_application",
|
||||
"//lib/everest/timer:libtimer",
|
||||
"@com_github_HowardHinnant_date//:date",
|
||||
],
|
||||
)
|
||||
|
||||
# Install script files alongside the binary
|
||||
genrule(
|
||||
name = "system_scripts",
|
||||
srcs = [
|
||||
"constants.env",
|
||||
"diagnostics_uploader.sh",
|
||||
"firmware_updater.sh",
|
||||
"signed_firmware_downloader.sh",
|
||||
"signed_firmware_installer.sh",
|
||||
],
|
||||
outs = [
|
||||
"System/constants.env",
|
||||
"System/diagnostics_uploader.sh",
|
||||
"System/firmware_updater.sh",
|
||||
"System/signed_firmware_downloader.sh",
|
||||
"System/signed_firmware_installer.sh",
|
||||
],
|
||||
cmd = "mkdir -p $(RULEDIR)/System && " +
|
||||
"cp $(SRCS) $(RULEDIR)/System/",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
39
tools/EVerest-main/modules/Misc/System/CMakeLists.txt
Normal file
39
tools/EVerest-main/modules/Misc/System/CMakeLists.txt
Normal file
@@ -0,0 +1,39 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
date::date
|
||||
date::date-tz
|
||||
everest::timer
|
||||
everest::run_application
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/systemImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
target_compile_features(${MODULE_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
install(
|
||||
PROGRAMS
|
||||
constants.env
|
||||
diagnostics_uploader.sh
|
||||
firmware_updater.sh
|
||||
signed_firmware_downloader.sh
|
||||
signed_firmware_installer.sh
|
||||
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
|
||||
)
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
15
tools/EVerest-main/modules/Misc/System/System.cpp
Normal file
15
tools/EVerest-main/modules/Misc/System/System.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "System.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void System::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void System::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
67
tools/EVerest-main/modules/Misc/System/System.hpp
Normal file
67
tools/EVerest-main/modules/Misc/System/System.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SYSTEM_HPP
|
||||
#define SYSTEM_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/system/Implementation.hpp>
|
||||
|
||||
// headers for required interface implementations
|
||||
#include <generated/interfaces/kvs/Interface.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
double DefaultRetries;
|
||||
double DefaultRetryInterval;
|
||||
int ResetDelay;
|
||||
};
|
||||
|
||||
class System : public Everest::ModuleBase {
|
||||
public:
|
||||
System() = delete;
|
||||
System(const ModuleInfo& info, std::unique_ptr<systemImplBase> p_main,
|
||||
std::vector<std::unique_ptr<kvsIntf>> r_store, Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), r_store(std::move(r_store)), config(config){};
|
||||
|
||||
const std::unique_ptr<systemImplBase> p_main;
|
||||
const std::vector<std::unique_ptr<kvsIntf>> r_store;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // SYSTEM_HPP
|
||||
27
tools/EVerest-main/modules/Misc/System/constants.env
Normal file
27
tools/EVerest-main/modules/Misc/System/constants.env
Normal file
@@ -0,0 +1,27 @@
|
||||
CONNECTION_TIMEOUT=20
|
||||
|
||||
DOWNLOADED="Downloaded"
|
||||
DOWNLOADING="Downloading"
|
||||
DOWNLOAD_FAILED="DownloadFailed"
|
||||
DOWNLOAD_SCHEDULED="DownloadScheduled"
|
||||
DOWNLOAD_PAUSED="DownloadPaused"
|
||||
|
||||
IDLE="Idle"
|
||||
|
||||
INSTALLATION_FAILED="InstallationFailed"
|
||||
INSTALLING="Installing"
|
||||
INSTALLED="Installed"
|
||||
INSTALL_REBOOTING="InstallRebooting"
|
||||
INSTALL_SCHEDULED="InstallScheduled"
|
||||
INSTALL_VERIFICATION_FAILED="InstallVerificationFailed"
|
||||
|
||||
PERMISSION_DENIED="PermissionDenied"
|
||||
NOT_SUPPORTED_OPERATION="NotSupportedOperation"
|
||||
BAD_MESSAGE="BadMessage"
|
||||
|
||||
INVALID_SIGNATURE="InvalidSignature"
|
||||
SIGNATURE_VERIFIED="SignatureVerified"
|
||||
|
||||
UPLOADED="Uploaded"
|
||||
UPLOAD_FAILURE="UploadFailure"
|
||||
UPLOADING="Uploading"
|
||||
21
tools/EVerest-main/modules/Misc/System/diagnostics_uploader.sh
Executable file
21
tools/EVerest-main/modules/Misc/System/diagnostics_uploader.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
. "${1}"
|
||||
|
||||
echo "$UPLOADING"
|
||||
sleep 2
|
||||
curl --progress-bar --ssl --connect-timeout "$CONNECTION_TIMEOUT" -T "${4}" "${2}"
|
||||
curl_exit_code=$?
|
||||
if [[ $curl_exit_code -eq 0 ]]; then
|
||||
echo "$UPLOADED"
|
||||
elif [[ $curl_exit_code -eq 67 ]] || [[ $curl_exit_code -eq 35 ]] || [[ $curl_exit_code -eq 69 ]] ||
|
||||
[[ $curl_exit_code -eq 9 ]]; then
|
||||
echo "$PERMISSION_DENIED"
|
||||
elif [[ $curl_exit_code -eq 3 ]] || [[ $curl_exit_code -eq 6 ]] || [[ $curl_exit_code -eq 10 ]] ||
|
||||
[[ $curl_exit_code -eq 87 ]]; then
|
||||
echo "$BAD_MESSAGE"
|
||||
elif [[ $curl_exit_code -eq 1 ]]; then
|
||||
echo "$NOT_SUPPORTED_OPERATION"
|
||||
else
|
||||
echo "$UPLOAD_FAILURE"
|
||||
fi
|
||||
20
tools/EVerest-main/modules/Misc/System/docs/index.rst
Normal file
20
tools/EVerest-main/modules/Misc/System/docs/index.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
.. _everest_modules_handwritten_System:
|
||||
|
||||
.. ******
|
||||
.. System
|
||||
.. ******
|
||||
|
||||
This module implements system wide operations.
|
||||
|
||||
Currently this includes the following commands:
|
||||
|
||||
- Log Uploads
|
||||
- Firmware Updates
|
||||
- Setting of System time
|
||||
|
||||
Corresponding variables signal the state of Log Uploads and Firmware Updates.
|
||||
|
||||
Integration in EVerest
|
||||
======================
|
||||
|
||||
This module provides implementation for the system interface. It does not require any other modules.
|
||||
21
tools/EVerest-main/modules/Misc/System/firmware_updater.sh
Executable file
21
tools/EVerest-main/modules/Misc/System/firmware_updater.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
. "${1}"
|
||||
|
||||
echo "$DOWNLOADING"
|
||||
curl --progress-bar --ssl --connect-timeout "$CONNECTION_TIMEOUT" "${2}" -o "${3}"
|
||||
curl_exit_code=$?
|
||||
sleep 2
|
||||
if [[ $curl_exit_code -eq 0 ]]; then
|
||||
echo "$DOWNLOADED"
|
||||
else
|
||||
echo "$DOWNLOAD_FAILED"
|
||||
fi
|
||||
sleep 2
|
||||
|
||||
if [[ $curl_exit_code -eq 0 ]]; then
|
||||
echo "$INSTALLING"
|
||||
sleep 2
|
||||
echo "$INSTALLED"
|
||||
sleep 2
|
||||
fi
|
||||
475
tools/EVerest-main/modules/Misc/System/main/systemImpl.cpp
Normal file
475
tools/EVerest-main/modules/Misc/System/main/systemImpl.cpp
Normal file
@@ -0,0 +1,475 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "systemImpl.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <utils/date.hpp>
|
||||
|
||||
#include <everest/run_application/run_application.hpp>
|
||||
|
||||
using namespace everest::run_application;
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
const std::string CONSTANTS = "constants.env";
|
||||
const std::string DIAGNOSTICS_UPLOADER = "diagnostics_uploader.sh";
|
||||
const std::string FIRMWARE_UPDATER = "firmware_updater.sh";
|
||||
const std::string SIGNED_FIRMWARE_DOWNLOADER = "signed_firmware_downloader.sh";
|
||||
const std::string SIGNED_FIRMWARE_INSTALLER = "signed_firmware_installer.sh";
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// FIXME (aw): this function needs to be refactored into some kind of utility library
|
||||
fs::path create_temp_file(const fs::path& dir, const std::string& prefix) {
|
||||
const std::string fn_template = (dir / prefix).string() + "XXXXXX" + std::string(1, '\0');
|
||||
std::vector<char> fn_template_buffer{fn_template.begin(), fn_template.end()};
|
||||
|
||||
// mkstemp needs to have at least 6 XXXXXX at the end and it will replace these
|
||||
// with a valid file name
|
||||
auto fd = mkstemp(fn_template_buffer.data());
|
||||
|
||||
if (fd == -1) {
|
||||
EVLOG_error << "Failed to create temporary file at: " << fn_template;
|
||||
return {};
|
||||
}
|
||||
|
||||
// close the file descriptor
|
||||
close(fd);
|
||||
|
||||
return fn_template_buffer.data();
|
||||
}
|
||||
|
||||
void systemImpl::init() {
|
||||
this->scripts_path = mod->info.paths.libexec;
|
||||
this->log_upload_running = false;
|
||||
this->firmware_download_running = false;
|
||||
this->firmware_installation_running = false;
|
||||
this->standard_firmware_update_running = false;
|
||||
this->boot_reason_key = "ocpp_boot_reason";
|
||||
}
|
||||
|
||||
void systemImpl::ready() {
|
||||
}
|
||||
|
||||
void systemImpl::standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
||||
|
||||
this->standard_firmware_update_running = true;
|
||||
EVLOG_info << "Starting firmware update";
|
||||
// create temporary file
|
||||
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
|
||||
const auto firmware_file_path = create_temp_file(fs::temp_directory_path(), "firmware-" + date_time);
|
||||
|
||||
if (firmware_file_path.empty()) {
|
||||
EVLOG_error << "Firmware update ignored, cannot write temporary file.";
|
||||
publish_firmware_update_status({types::system::FirmwareUpdateStatusEnum::DownloadFailed});
|
||||
return;
|
||||
}
|
||||
|
||||
const auto constants = this->scripts_path / CONSTANTS;
|
||||
|
||||
this->update_firmware_thread = std::thread([this, firmware_update_request, firmware_file_path, constants]() {
|
||||
const auto firmware_updater = this->scripts_path / FIRMWARE_UPDATER;
|
||||
|
||||
const std::vector<std::string> args = {constants.string(), firmware_update_request.location,
|
||||
firmware_file_path.string()};
|
||||
int32_t retries = 0;
|
||||
const auto total_retries = firmware_update_request.retries.value_or(this->mod->config.DefaultRetries);
|
||||
const auto retry_interval =
|
||||
firmware_update_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
|
||||
|
||||
auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::DownloadFailed;
|
||||
types::system::FirmwareUpdateStatus firmware_status;
|
||||
firmware_status.request_id = -1;
|
||||
firmware_status.firmware_update_status = firmware_status_enum;
|
||||
|
||||
while (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
||||
retries < total_retries) {
|
||||
retries += 1;
|
||||
run_application(firmware_updater.string(), args, [this, &firmware_status](const std::string& output_line) {
|
||||
firmware_status.firmware_update_status =
|
||||
types::system::string_to_firmware_update_status_enum(output_line);
|
||||
this->publish_firmware_update_status(firmware_status);
|
||||
return CmdControl::Continue;
|
||||
});
|
||||
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
||||
retries < total_retries) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
|
||||
}
|
||||
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::Installed and
|
||||
!this->mod->r_store.empty()) {
|
||||
this->mod->r_store.at(0)->call_store(boot_reason_key,
|
||||
boot_reason_to_string(types::system::BootReason::FirmwareUpdate));
|
||||
}
|
||||
}
|
||||
this->standard_firmware_update_running = false;
|
||||
});
|
||||
this->update_firmware_thread.detach();
|
||||
}
|
||||
|
||||
types::system::UpdateFirmwareResponse
|
||||
systemImpl::handle_standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
||||
|
||||
if (!this->standard_firmware_update_running) {
|
||||
if (firmware_update_request.retrieve_timestamp.has_value() &&
|
||||
Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()) > date::utc_clock::now()) {
|
||||
const auto retrieve_timestamp =
|
||||
Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value());
|
||||
this->standard_update_firmware_timer.at(
|
||||
[this, retrieve_timestamp, firmware_update_request]() {
|
||||
this->standard_firmware_update(firmware_update_request);
|
||||
},
|
||||
retrieve_timestamp);
|
||||
EVLOG_info << "Download for firmware scheduled for: " << Everest::Date::to_rfc3339(retrieve_timestamp);
|
||||
} else {
|
||||
// start download immediately
|
||||
this->update_firmware_thread = std::thread(
|
||||
[this, firmware_update_request]() { this->standard_firmware_update(firmware_update_request); });
|
||||
this->update_firmware_thread.detach();
|
||||
}
|
||||
return types::system::UpdateFirmwareResponse::Accepted;
|
||||
} else {
|
||||
EVLOG_info << "Not starting firmware update because firmware update process already running";
|
||||
return types::system::UpdateFirmwareResponse::Rejected;
|
||||
}
|
||||
}
|
||||
|
||||
types::system::UpdateFirmwareResponse
|
||||
systemImpl::handle_signed_fimware_update(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
||||
|
||||
if (!firmware_update_request.signing_certificate.has_value()) {
|
||||
EVLOG_warning << "Signing certificate is missing in FirmwareUpdateRequest";
|
||||
return types::system::UpdateFirmwareResponse::Rejected;
|
||||
}
|
||||
if (!firmware_update_request.signature.has_value()) {
|
||||
EVLOG_warning << "Signature is missing in FirmwareUpdateRequest";
|
||||
return types::system::UpdateFirmwareResponse::Rejected;
|
||||
}
|
||||
|
||||
EVLOG_info << "Executing signed firmware update download callback";
|
||||
|
||||
if (firmware_update_request.retrieve_timestamp.has_value() &&
|
||||
Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value()) > date::utc_clock::now()) {
|
||||
const auto retrieve_timestamp = Everest::Date::from_rfc3339(firmware_update_request.retrieve_timestamp.value());
|
||||
this->signed_firmware_update_download_timer.at(
|
||||
[this, retrieve_timestamp, firmware_update_request]() {
|
||||
this->download_signed_firmware(firmware_update_request);
|
||||
},
|
||||
retrieve_timestamp);
|
||||
EVLOG_info << "Download for firmware scheduled for: " << Everest::Date::to_rfc3339(retrieve_timestamp);
|
||||
types::system::FirmwareUpdateStatus firmware_update_status;
|
||||
firmware_update_status.request_id = firmware_update_request.request_id;
|
||||
firmware_update_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::DownloadScheduled;
|
||||
this->publish_firmware_update_status(firmware_update_status);
|
||||
} else {
|
||||
// start download immediately
|
||||
this->update_firmware_thread =
|
||||
std::thread([this, firmware_update_request]() { this->download_signed_firmware(firmware_update_request); });
|
||||
this->update_firmware_thread.detach();
|
||||
}
|
||||
|
||||
if (this->firmware_download_running) {
|
||||
return types::system::UpdateFirmwareResponse::AcceptedCanceled;
|
||||
} else if (this->firmware_installation_running) {
|
||||
return types::system::UpdateFirmwareResponse::Rejected;
|
||||
} else {
|
||||
return types::system::UpdateFirmwareResponse::Accepted;
|
||||
}
|
||||
}
|
||||
|
||||
void systemImpl::download_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_request) {
|
||||
|
||||
if (!firmware_update_request.signing_certificate.has_value()) {
|
||||
EVLOG_warning << "Signing certificate is missing in FirmwareUpdateRequest";
|
||||
this->publish_firmware_update_status(
|
||||
{types::system::FirmwareUpdateStatusEnum::DownloadFailed, firmware_update_request.request_id});
|
||||
return;
|
||||
}
|
||||
if (!firmware_update_request.signature.has_value()) {
|
||||
EVLOG_warning << "Signature is missing in FirmwareUpdateRequest";
|
||||
this->publish_firmware_update_status(
|
||||
{types::system::FirmwareUpdateStatusEnum::DownloadFailed, firmware_update_request.request_id});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->firmware_download_running) {
|
||||
EVLOG_info
|
||||
<< "Received Firmware update request and firmware update already running - cancelling firmware update";
|
||||
this->interrupt_firmware_download.exchange(true);
|
||||
EVLOG_info << "Waiting for other firmware download to finish...";
|
||||
std::unique_lock<std::mutex> lk(this->firmware_update_mutex);
|
||||
this->firmware_update_cv.wait(lk, [this]() { return !this->firmware_download_running; });
|
||||
EVLOG_info << "Previous Firmware download finished!";
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lg(this->firmware_update_mutex);
|
||||
EVLOG_info << "Starting Firmware update";
|
||||
this->interrupt_firmware_download.exchange(false);
|
||||
this->firmware_download_running = true;
|
||||
|
||||
// // create temporary file
|
||||
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
const auto firmware_file_path = create_temp_file(fs::temp_directory_path(), "signed_firmware-" + date_time);
|
||||
|
||||
const auto firmware_downloader = this->scripts_path / SIGNED_FIRMWARE_DOWNLOADER;
|
||||
const auto constants = this->scripts_path / CONSTANTS;
|
||||
|
||||
const std::vector<std::string> download_args = {
|
||||
constants.string(), firmware_update_request.location, firmware_file_path.string(),
|
||||
firmware_update_request.signature.value(), firmware_update_request.signing_certificate.value()};
|
||||
int32_t retries = 0;
|
||||
const auto total_retries = firmware_update_request.retries.value_or(this->mod->config.DefaultRetries);
|
||||
const auto retry_interval =
|
||||
firmware_update_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
|
||||
|
||||
auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::DownloadFailed;
|
||||
types::system::FirmwareUpdateStatus firmware_status;
|
||||
firmware_status.request_id = firmware_update_request.request_id;
|
||||
firmware_status.firmware_update_status = firmware_status_enum;
|
||||
|
||||
while (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
||||
retries < total_retries && !this->interrupt_firmware_download) {
|
||||
run_application(
|
||||
firmware_downloader.string(), download_args, [this, &firmware_status](const std::string& output_line) {
|
||||
firmware_status.firmware_update_status =
|
||||
types::system::string_to_firmware_update_status_enum(output_line);
|
||||
this->publish_firmware_update_status(firmware_status);
|
||||
if (this->interrupt_firmware_download) {
|
||||
EVLOG_info << "Updating firmware was interrupted, terminating firmware update script, requestId: "
|
||||
<< firmware_status.request_id;
|
||||
return CmdControl::Terminate;
|
||||
}
|
||||
return CmdControl::Continue;
|
||||
});
|
||||
retries += 1;
|
||||
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::DownloadFailed &&
|
||||
retries < total_retries) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
|
||||
}
|
||||
}
|
||||
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::SignatureVerified) {
|
||||
this->initialize_firmware_installation(firmware_update_request, firmware_file_path);
|
||||
}
|
||||
|
||||
this->firmware_download_running = false;
|
||||
this->firmware_update_cv.notify_one();
|
||||
EVLOG_info << "Firmware update thread finished";
|
||||
}
|
||||
|
||||
void systemImpl::initialize_firmware_installation(const types::system::FirmwareUpdateRequest& firmware_update_request,
|
||||
const fs::path& firmware_file_path) {
|
||||
if (firmware_update_request.install_timestamp.has_value() &&
|
||||
Everest::Date::from_rfc3339(firmware_update_request.install_timestamp.value()) > date::utc_clock::now()) {
|
||||
const auto install_timestamp = Everest::Date::from_rfc3339(firmware_update_request.install_timestamp.value());
|
||||
this->signed_firmware_update_install_timer.at(
|
||||
[this, firmware_update_request, firmware_file_path]() {
|
||||
this->install_signed_firmware(firmware_update_request, firmware_file_path);
|
||||
},
|
||||
install_timestamp);
|
||||
EVLOG_info << "Installation for firmware scheduled for: " << Everest::Date::to_rfc3339(install_timestamp);
|
||||
types::system::FirmwareUpdateStatus firmware_update_status;
|
||||
firmware_update_status.request_id = firmware_update_request.request_id;
|
||||
firmware_update_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::InstallScheduled;
|
||||
this->publish_firmware_update_status(firmware_update_status);
|
||||
} else {
|
||||
// start installation immediately
|
||||
this->update_firmware_thread = std::thread([this, firmware_update_request, firmware_file_path]() {
|
||||
this->install_signed_firmware(firmware_update_request, firmware_file_path);
|
||||
});
|
||||
this->update_firmware_thread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
void systemImpl::install_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_request,
|
||||
const fs::path& firmware_file_path) {
|
||||
auto firmware_status_enum = types::system::FirmwareUpdateStatusEnum::Installing;
|
||||
types::system::FirmwareUpdateStatus firmware_status;
|
||||
firmware_status.request_id = firmware_update_request.request_id;
|
||||
firmware_status.firmware_update_status = firmware_status_enum;
|
||||
if (!this->firmware_installation_running) {
|
||||
this->firmware_installation_running = true;
|
||||
const auto firmware_installer = this->scripts_path / SIGNED_FIRMWARE_INSTALLER;
|
||||
const auto constants = this->scripts_path / CONSTANTS;
|
||||
const std::vector<std::string> install_args = {constants.string()};
|
||||
run_application(firmware_installer.string(), install_args,
|
||||
[this, &firmware_status](const std::string& output_line) {
|
||||
firmware_status.firmware_update_status =
|
||||
types::system::string_to_firmware_update_status_enum(output_line);
|
||||
this->publish_firmware_update_status(firmware_status);
|
||||
return CmdControl::Continue;
|
||||
});
|
||||
if (firmware_status.firmware_update_status == types::system::FirmwareUpdateStatusEnum::Installed) {
|
||||
if (!this->mod->r_store.empty()) {
|
||||
this->mod->r_store.at(0)->call_store(boot_reason_key,
|
||||
boot_reason_to_string(types::system::BootReason::FirmwareUpdate));
|
||||
}
|
||||
|
||||
auto reset_type = types::system::ResetType::Hard;
|
||||
bool firmware_installation_running_copy = this->firmware_installation_running;
|
||||
this->handle_reset(reset_type, firmware_installation_running_copy);
|
||||
}
|
||||
} else {
|
||||
firmware_status.firmware_update_status = types::system::FirmwareUpdateStatusEnum::InstallationFailed;
|
||||
this->publish_firmware_update_status(firmware_status);
|
||||
}
|
||||
}
|
||||
|
||||
types::system::UpdateFirmwareResponse
|
||||
systemImpl::handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) {
|
||||
if (firmware_update_request.request_id == -1) {
|
||||
return this->handle_standard_firmware_update(firmware_update_request);
|
||||
} else {
|
||||
return this->handle_signed_fimware_update(firmware_update_request);
|
||||
}
|
||||
};
|
||||
|
||||
void systemImpl::handle_allow_firmware_installation() {
|
||||
// TODO: implement me
|
||||
}
|
||||
|
||||
types::system::UploadLogsResponse
|
||||
systemImpl::handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) {
|
||||
|
||||
types::system::UploadLogsResponse response;
|
||||
|
||||
if (this->log_upload_running) {
|
||||
response.upload_logs_status = types::system::UploadLogsStatus::AcceptedCanceled;
|
||||
} else {
|
||||
response.upload_logs_status = types::system::UploadLogsStatus::Accepted;
|
||||
}
|
||||
|
||||
const auto date_time = Everest::Date::to_rfc3339(date::utc_clock::now());
|
||||
// TODO(piet): consider start time and end time
|
||||
const auto diagnostics_file_path = create_temp_file(fs::temp_directory_path(), "diagnostics-" + date_time);
|
||||
const auto diagnostics_file_name = diagnostics_file_path.filename().string();
|
||||
|
||||
response.file_name = diagnostics_file_name;
|
||||
|
||||
const auto fake_diagnostics_file = json({{"diagnostics", {{"key", "value"}}}});
|
||||
std::ofstream diagnostics_file(diagnostics_file_path.c_str());
|
||||
diagnostics_file << fake_diagnostics_file.dump();
|
||||
|
||||
this->upload_logs_thread = std::thread([this, upload_logs_request, diagnostics_file_name, diagnostics_file_path]() {
|
||||
if (this->log_upload_running) {
|
||||
EVLOG_info << "Received Log upload request and log upload already running - cancelling current upload";
|
||||
this->interrupt_log_upload.exchange(true);
|
||||
EVLOG_info << "Waiting for other log upload to finish...";
|
||||
std::unique_lock<std::mutex> lk(this->log_upload_mutex);
|
||||
this->log_upload_cv.wait(lk, [this]() { return !this->log_upload_running; });
|
||||
EVLOG_info << "Previous Log upload finished!";
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lg(this->log_upload_mutex);
|
||||
EVLOG_info << "Starting upload of log file";
|
||||
this->interrupt_log_upload.exchange(false);
|
||||
this->log_upload_running = true;
|
||||
const auto diagnostics_uploader = this->scripts_path / DIAGNOSTICS_UPLOADER;
|
||||
const auto constants = this->scripts_path / CONSTANTS;
|
||||
|
||||
std::vector<std::string> args = {constants.string(), upload_logs_request.location, diagnostics_file_name,
|
||||
diagnostics_file_path.string()};
|
||||
bool uploaded = false;
|
||||
int32_t retries = 0;
|
||||
const auto total_retries = upload_logs_request.retries.value_or(this->mod->config.DefaultRetries);
|
||||
const auto retry_interval =
|
||||
upload_logs_request.retry_interval_s.value_or(this->mod->config.DefaultRetryInterval);
|
||||
|
||||
types::system::LogStatus log_status;
|
||||
while (!uploaded && retries < total_retries && !this->interrupt_log_upload) {
|
||||
retries += 1;
|
||||
log_status.request_id = upload_logs_request.request_id.value_or(-1);
|
||||
run_application(diagnostics_uploader.string(), args, [this, &log_status](const std::string& output_line) {
|
||||
if (output_line == "Uploaded") {
|
||||
log_status.log_status = types::system::string_to_log_status_enum(output_line);
|
||||
} else if (output_line == "UploadFailure" || output_line == "PermissionDenied" ||
|
||||
output_line == "BadMessage" || output_line == "NotSupportedOperation") {
|
||||
log_status.log_status = types::system::LogStatusEnum::UploadFailure;
|
||||
} else {
|
||||
log_status.log_status = types::system::LogStatusEnum::Uploading;
|
||||
}
|
||||
this->publish_log_status(log_status);
|
||||
if (this->interrupt_log_upload) {
|
||||
return CmdControl::Terminate;
|
||||
}
|
||||
return CmdControl::Continue;
|
||||
});
|
||||
if (this->interrupt_log_upload) {
|
||||
EVLOG_info << "Uploading Logs was interrupted, terminating upload script, requestId: "
|
||||
<< log_status.request_id;
|
||||
// N01.FR.20
|
||||
log_status.log_status = types::system::LogStatusEnum::AcceptedCanceled;
|
||||
this->publish_log_status(log_status);
|
||||
} else if (log_status.log_status != types::system::LogStatusEnum::Uploaded && retries < total_retries) {
|
||||
// command finished, but neither interrupted nor uploaded
|
||||
std::this_thread::sleep_for(std::chrono::seconds(retry_interval));
|
||||
} else {
|
||||
uploaded = true;
|
||||
}
|
||||
}
|
||||
this->log_upload_running = false;
|
||||
this->log_upload_cv.notify_one();
|
||||
EVLOG_info << "Log upload thread finished";
|
||||
});
|
||||
this->upload_logs_thread.detach();
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
bool systemImpl::handle_is_reset_allowed(types::system::ResetType& type) {
|
||||
// Right now we dont want to reject a reset ever
|
||||
return true;
|
||||
}
|
||||
|
||||
void systemImpl::handle_reset(types::system::ResetType& type, bool& scheduled) {
|
||||
// let the actual work be done by a worker thread, which can also delay it
|
||||
// a little bit (if configured) to allow e.g. clean shutdown of communication
|
||||
// channels in parallel when this call returns
|
||||
std::thread([this, type, scheduled] {
|
||||
EVLOG_info << "Reset request received: " << type << ", " << (scheduled ? "" : "not ") << "scheduled";
|
||||
if (!this->mod->r_store.empty() and !this->mod->r_store.at(0)->call_exists(boot_reason_key)) {
|
||||
this->mod->r_store.at(0)->call_store(boot_reason_key,
|
||||
boot_reason_to_string(types::system::BootReason::RemoteReset));
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(this->mod->config.ResetDelay));
|
||||
|
||||
if (type == types::system::ResetType::Soft) {
|
||||
EVLOG_info << "Performing soft reset now.";
|
||||
kill(getpid(), SIGINT);
|
||||
} else {
|
||||
EVLOG_info << "Performing hard reset now.";
|
||||
kill(getpid(), SIGINT); // FIXME(piet): Define appropriate behavior for hard reset
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
bool systemImpl::handle_set_system_time(std::string& timestamp) {
|
||||
// your code for cmd set_system_time goes here
|
||||
return true;
|
||||
};
|
||||
|
||||
types::system::BootReason systemImpl::handle_get_boot_reason() {
|
||||
if (this->mod->r_store.empty()) {
|
||||
return types::system::BootReason::PowerUp;
|
||||
}
|
||||
auto reason_variant = this->mod->r_store.at(0)->call_load(boot_reason_key);
|
||||
auto* reason = std::get_if<std::string>(&reason_variant);
|
||||
std::string final_reason{boot_reason_to_string(types::system::BootReason::PowerUp)};
|
||||
if (reason != nullptr) {
|
||||
final_reason = *reason;
|
||||
}
|
||||
this->mod->r_store.at(0)->call_delete(boot_reason_key);
|
||||
return types::system::string_to_boot_reason(final_reason);
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
152
tools/EVerest-main/modules/Misc/System/main/systemImpl.hpp
Normal file
152
tools/EVerest-main/modules/Misc/System/main/systemImpl.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_SYSTEM_IMPL_HPP
|
||||
#define MAIN_SYSTEM_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/system/Implementation.hpp>
|
||||
|
||||
#include "../System.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
#include <filesystem>
|
||||
|
||||
#include <everest/timer.hpp>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class systemImpl : public systemImplBase {
|
||||
public:
|
||||
systemImpl() = delete;
|
||||
systemImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<System>& mod, Conf& config) :
|
||||
systemImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::system::UpdateFirmwareResponse
|
||||
handle_update_firmware(types::system::FirmwareUpdateRequest& firmware_update_request) override;
|
||||
virtual void handle_allow_firmware_installation() override;
|
||||
virtual types::system::UploadLogsResponse
|
||||
handle_upload_logs(types::system::UploadLogsRequest& upload_logs_request) override;
|
||||
virtual bool handle_is_reset_allowed(types::system::ResetType& type) override;
|
||||
virtual void handle_reset(types::system::ResetType& type, bool& scheduled) override;
|
||||
virtual bool handle_set_system_time(std::string& timestamp) override;
|
||||
virtual types::system::BootReason handle_get_boot_reason() override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<System>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
|
||||
std::filesystem::path scripts_path;
|
||||
|
||||
std::atomic<bool> interrupt_firmware_download;
|
||||
std::atomic<bool> interrupt_log_upload;
|
||||
|
||||
bool log_upload_running;
|
||||
bool standard_firmware_update_running;
|
||||
bool firmware_download_running;
|
||||
std::atomic<bool> firmware_installation_running;
|
||||
|
||||
std::condition_variable log_upload_cv;
|
||||
std::condition_variable firmware_update_cv;
|
||||
|
||||
std::mutex log_upload_mutex;
|
||||
std::mutex firmware_update_mutex;
|
||||
|
||||
std::thread update_firmware_thread;
|
||||
std::thread upload_logs_thread;
|
||||
|
||||
Everest::SteadyTimer standard_update_firmware_timer;
|
||||
Everest::SteadyTimer signed_firmware_update_download_timer;
|
||||
Everest::SteadyTimer signed_firmware_update_install_timer;
|
||||
|
||||
std::string boot_reason_key;
|
||||
|
||||
/**
|
||||
* @brief Executes a standard firmware update using the given \p firmware_update_request
|
||||
*
|
||||
* @param firmware_update_request
|
||||
*/
|
||||
void standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request);
|
||||
|
||||
/**
|
||||
* @brief Handles the given \p firmware_update_request . If firmware update is already running, the request will be
|
||||
* rejected. If the download should not be started in the future it starts the download and installation of the
|
||||
* firmware immediately, otherwise this method sets a timer for the download accordingly.
|
||||
*
|
||||
* @param firmware_update_request
|
||||
* @return types::system::UpdateFirmwareResponse
|
||||
*/
|
||||
types::system::UpdateFirmwareResponse
|
||||
handle_standard_firmware_update(const types::system::FirmwareUpdateRequest& firmware_update_request);
|
||||
|
||||
/**
|
||||
* @brief Handles the given \p firmware_update_request. If the download should not be started in the future it
|
||||
* starts the download and installation of the firmware immediately, otherwise this method sets a timer for the
|
||||
* download accordingly.
|
||||
*
|
||||
* @param firmware_update_request
|
||||
*/
|
||||
types::system::UpdateFirmwareResponse
|
||||
handle_signed_fimware_update(const types::system::FirmwareUpdateRequest& firmware_update_request);
|
||||
|
||||
/**
|
||||
* @brief Handles the download of the firmware specified in the given \p firmware_update_request . If a download is
|
||||
* already running, this method will interrupt the download process and restart it.
|
||||
*
|
||||
* @param firmware_update_request
|
||||
*/
|
||||
void download_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_request);
|
||||
|
||||
/**
|
||||
* @brief Initializes the firmware installation by starting it immediately or if specified in the \p
|
||||
* firmware_update_request it schedules it for the future.
|
||||
*
|
||||
* @param firmware_update_request
|
||||
* @param firmware_file_path
|
||||
*/
|
||||
void initialize_firmware_installation(const types::system::FirmwareUpdateRequest& firmware_update_request,
|
||||
const std::filesystem::path& firmware_file_path);
|
||||
|
||||
/**
|
||||
* @brief Executes the installation of the firmware specified in the given \p firmware_update_request .
|
||||
*
|
||||
* @param firmware_update_reqeust
|
||||
* @param firmware_file_path
|
||||
*/
|
||||
void install_signed_firmware(const types::system::FirmwareUpdateRequest& firmware_update_reqeust,
|
||||
const std::filesystem::path& firmware_file_path);
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_SYSTEM_IMPL_HPP
|
||||
33
tools/EVerest-main/modules/Misc/System/manifest.yaml
Normal file
33
tools/EVerest-main/modules/Misc/System/manifest.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
description: This module implements system wide operations
|
||||
config:
|
||||
DefaultRetries:
|
||||
description: Specifies how many times Charge Point tries to upload or download files on previous failure.
|
||||
type: number
|
||||
default: 1
|
||||
DefaultRetryInterval:
|
||||
description: >-
|
||||
Specifies in seconds after which time a retry of an upload or download on previous failure may be attempted.
|
||||
type: number
|
||||
default: 1
|
||||
ResetDelay:
|
||||
description: >-
|
||||
When receiving a reset request, then the actual execution can be delayed by this amount of time (given in seconds).
|
||||
This might be necessary, for example, when the reset request arrives via the network and the call acknowledgement
|
||||
should be given some time to travel the return path to the caller.
|
||||
Defaults to zero, which means that the reset is executed directly without delay.
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 0
|
||||
provides:
|
||||
main:
|
||||
description: Implements the system interface
|
||||
interface: system
|
||||
requires:
|
||||
store:
|
||||
interface: kvs
|
||||
min_connections: 0
|
||||
max_connections: 1
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Piet Gömpel
|
||||
30
tools/EVerest-main/modules/Misc/System/signed_firmware_downloader.sh
Executable file
30
tools/EVerest-main/modules/Misc/System/signed_firmware_downloader.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
|
||||
. "${1}"
|
||||
|
||||
SIGNATURE_VALIDATION_DIR=$(mktemp -d /tmp/signature_validation_XXXXX)
|
||||
sleep 2
|
||||
echo "$DOWNLOADING"
|
||||
|
||||
sleep 2
|
||||
curl --progress-bar --ssl --connect-timeout "$CONNECTION_TIMEOUT" "${2}" -o "${3}"
|
||||
curl_exit_code=$?
|
||||
sleep 2
|
||||
if [[ $curl_exit_code -eq 0 ]]; then
|
||||
echo "$DOWNLOADED"
|
||||
echo -e "${4}" >"$SIGNATURE_VALIDATION_DIR/firmware_signature.base64"
|
||||
echo -e "${5}" >"$SIGNATURE_VALIDATION_DIR/firmware_cert.pem"
|
||||
openssl x509 -pubkey -noout -in "$SIGNATURE_VALIDATION_DIR/firmware_cert.pem" >"$SIGNATURE_VALIDATION_DIR/pubkey.pem"
|
||||
openssl base64 -d -in "$SIGNATURE_VALIDATION_DIR/firmware_signature.base64" -out "$SIGNATURE_VALIDATION_DIR/firmware_signature.sha256"
|
||||
r=$(openssl dgst -sha256 -verify "$SIGNATURE_VALIDATION_DIR/pubkey.pem" -signature "$SIGNATURE_VALIDATION_DIR/firmware_signature.sha256" "${3}")
|
||||
|
||||
if [ "$r" = "Verified OK" ]; then
|
||||
echo "$SIGNATURE_VERIFIED"
|
||||
else
|
||||
echo "$INVALID_SIGNATURE"
|
||||
fi
|
||||
else
|
||||
echo "$DOWNLOAD_FAILED"
|
||||
fi
|
||||
|
||||
rm -rf "$SIGNATURE_VALIDATION_DIR"
|
||||
7
tools/EVerest-main/modules/Misc/System/signed_firmware_installer.sh
Executable file
7
tools/EVerest-main/modules/Misc/System/signed_firmware_installer.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
. "${1}"
|
||||
|
||||
echo "$INSTALLING"
|
||||
sleep 2
|
||||
echo "$INSTALLED"
|
||||
25
tools/EVerest-main/modules/Misc/YamlStore/CMakeLists.txt
Normal file
25
tools/EVerest-main/modules/Misc/YamlStore/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
everest::yaml
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/kvsImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
15
tools/EVerest-main/modules/Misc/YamlStore/YamlStore.cpp
Normal file
15
tools/EVerest-main/modules/Misc/YamlStore/YamlStore.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "YamlStore.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void YamlStore::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void YamlStore::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
60
tools/EVerest-main/modules/Misc/YamlStore/YamlStore.hpp
Normal file
60
tools/EVerest-main/modules/Misc/YamlStore/YamlStore.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef YAML_STORE_HPP
|
||||
#define YAML_STORE_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/kvs/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string file;
|
||||
};
|
||||
|
||||
class YamlStore : public Everest::ModuleBase {
|
||||
public:
|
||||
YamlStore() = delete;
|
||||
YamlStore(const ModuleInfo& info, std::unique_ptr<kvsImplBase> p_main, Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), config(config){};
|
||||
|
||||
const std::unique_ptr<kvsImplBase> p_main;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // YAML_STORE_HPP
|
||||
51
tools/EVerest-main/modules/Misc/YamlStore/main/kvsImpl.cpp
Normal file
51
tools/EVerest-main/modules/Misc/YamlStore/main/kvsImpl.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "kvsImpl.hpp"
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <utils/yaml_loader.hpp>
|
||||
#include <variant>
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
void kvsImpl::init() {
|
||||
auto kv_file_path = std::filesystem::path(mod->config.file);
|
||||
|
||||
try {
|
||||
data = Everest::load_yaml(kv_file_path);
|
||||
} catch (const std::exception& err) {
|
||||
EVLOG_error << "Error parsing YAML file at " << mod->config.file << ": " << err.what();
|
||||
}
|
||||
}
|
||||
|
||||
void kvsImpl::ready() {
|
||||
}
|
||||
|
||||
void kvsImpl::handle_store(std::string& key,
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string>& value) {
|
||||
// this is a read-only kvs - do nothing but prevent compiler warnings about unused parameters
|
||||
(void)key;
|
||||
(void)value;
|
||||
}
|
||||
|
||||
std::variant<std::nullptr_t, Array, Object, bool, double, int, std::string> kvsImpl::handle_load(std::string& key) {
|
||||
if (data.contains(key)) {
|
||||
std::string value{data[key]};
|
||||
return value;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void kvsImpl::handle_delete(std::string& key) {
|
||||
// this is a read-only kvs - do nothing but prevent compiler warnings about unused parameters
|
||||
(void)key;
|
||||
}
|
||||
|
||||
bool kvsImpl::handle_exists(std::string& key) {
|
||||
return data.contains(key);
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user