diff options
author | Evgeny Zinoviev <me@ch1p.io> | 2021-05-07 02:18:07 +0300 |
---|---|---|
committer | Evgeny Zinoviev <me@ch1p.io> | 2021-05-07 02:18:07 +0300 |
commit | 7e743b73433475df086fcec81be7b10c1d695a42 (patch) | |
tree | 1737c5f9bdad2a40f740e9a655e510641331b9e2 /src/p18/commands.cc |
initial
Diffstat (limited to 'src/p18/commands.cc')
-rw-r--r-- | src/p18/commands.cc | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/src/p18/commands.cc b/src/p18/commands.cc new file mode 100644 index 0000000..0d37566 --- /dev/null +++ b/src/p18/commands.cc @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include <stdexcept> +#include <sstream> +#include <vector> +#include <string> + +#ifdef INVERTERCTL +#include <getopt.h> +#endif + +#include "commands.h" +#include "defines.h" +#include "functions.h" +#include "../util.h" +#include "../logging.h" + +namespace p18 { + +const std::map<std::string, p18::CommandType> client_commands = { + {"get-protocol-id", p18::CommandType::GetProtocolID}, + {"get-date-time", p18::CommandType::GetCurrentTime}, + {"get-total-generated", p18::CommandType::GetTotalGenerated}, + {"get-year-generated", p18::CommandType::GetYearGenerated}, + {"get-month-generated", p18::CommandType::GetMonthGenerated}, + {"get-day-generated", p18::CommandType::GetDayGenerated}, + {"get-series-number", p18::CommandType::GetSeriesNumber}, + {"get-cpu-version", p18::CommandType::GetCPUVersion}, + {"get-rated", p18::CommandType::GetRatedInformation}, + {"get-status", p18::CommandType::GetGeneralStatus}, + {"get-mode", p18::CommandType::GetWorkingMode}, + {"get-errors", p18::CommandType::GetFaultsAndWarnings}, + {"get-flags", p18::CommandType::GetFlagsAndStatuses}, + {"get-rated-defaults", p18::CommandType::GetDefaults}, + {"get-allowed-charging-currents", p18::CommandType::GetAllowedChargingCurrents}, + {"get-allowed-ac-charging-currents", p18::CommandType::GetAllowedACChargingCurrents}, + {"get-p-rated", p18::CommandType::GetParallelRatedInformation}, + {"get-p-status", p18::CommandType::GetParallelGeneralStatus}, + {"get-ac-charging-time", p18::CommandType::GetACChargingTimeBucket}, + {"get-ac-loads-supply-time", p18::CommandType::GetACLoadsSupplyTimeBucket}, + {"set-loads-supply", p18::CommandType::SetLoads}, + {"set-flag", p18::CommandType::SetFlag}, + {"set-rated-defaults", p18::CommandType::SetDefaults}, + {"set-max-charging-current", p18::CommandType::SetBatteryMaxChargingCurrent}, + {"set-max-ac-charging-current", p18::CommandType::SetBatteryMaxACChargingCurrent}, + {"set-ac-output-freq", p18::CommandType::SetACOutputFreq}, + {"set-max-charging-voltage", p18::CommandType::SetBatteryMaxChargingVoltage}, + {"set-ac-output-voltage", p18::CommandType::SetACOutputRatedVoltage}, + {"set-output-source-priority", p18::CommandType::SetOutputSourcePriority}, + {"set-charging-thresholds", p18::CommandType::SetBatteryChargingThresholds}, /* Battery re-charging and re-discharging voltage when utility is available */ + {"set-charging-source-priority", p18::CommandType::SetChargingSourcePriority}, + {"set-solar-power-priority", p18::CommandType::SetSolarPowerPriority}, + {"set-ac-input-voltage-range", p18::CommandType::SetACInputVoltageRange}, + {"set-battery-type", p18::CommandType::SetBatteryType}, + {"set-output-model", p18::CommandType::SetOutputModel}, + {"set-battery-cut-off-voltage", p18::CommandType::SetBatteryCutOffVoltage}, + {"set-solar-configuration", p18::CommandType::SetSolarConfig}, + {"clear-generated-data", p18::CommandType::ClearGenerated}, + {"set-date-time", p18::CommandType::SetDateTime}, + {"set-ac-charging-time", p18::CommandType::SetACChargingTimeBucket}, + {"set-ac-loads-supply-time", p18::CommandType::SetACLoadsSupplyTimeBucket}, +}; + +static void validate_date_args(const std::string* ys, const std::string* ms, const std::string* ds) { + static const std::string err_year = "invalid year"; + static const std::string err_month = "invalid month"; + static const std::string err_day = "invalid day"; + + int y, m = 0, d = 0; + + // validate year + if (!is_numeric(*ys) || ys->size() != 4) + throw std::invalid_argument(err_year); + + y = std::stoi(*ys); + if (y < 2000 || y > 2099) + throw std::invalid_argument(err_year); + + // validate month + if (ms != nullptr) { + if (!is_numeric(*ms) || ms->size() > 2) + throw std::invalid_argument(err_month); + + m = std::stoi(*ms); + if (m < 1 || m > 12) + throw std::invalid_argument(err_month); + } + + // validate day + if (ds != nullptr) { + if (!is_numeric(*ds) || ds->size() > 2) + throw std::invalid_argument(err_day); + + d = std::stoi(*ds); + if (d < 1 || d > 31) + throw std::invalid_argument(err_day); + } + + if (y != 0 && m != 0 && d != 0) { + if (!is_date_valid(y, m, d)) + throw std::invalid_argument("invalid date"); + } +} + +static void validate_time_args(const std::string* hs, const std::string* ms, const std::string* ss) { + static const std::string err_hour = "invalid hour"; + static const std::string err_minute = "invalid minute"; + static const std::string err_second = "invalid second"; + + unsigned h, m, s; + + if (!is_numeric(*hs) || hs->size() > 2) + throw std::invalid_argument(err_hour); + + h = static_cast<unsigned>(std::stoul(*hs)); + if (h > 23) + throw std::invalid_argument(err_hour); + + if (!is_numeric(*ms) || ms->size() > 2) + throw std::invalid_argument(err_minute); + + m = static_cast<unsigned>(std::stoul(*ms)); + if (m > 59) + throw std::invalid_argument(err_minute); + + if (!is_numeric(*ss) || ss->size() > 2) + throw std::invalid_argument(err_second); + + s = static_cast<unsigned>(std::stoul(*ss)); + if (s > 59) + throw std::invalid_argument(err_second); +} + + +#define GET_ARGS(__len__) get_args((CommandInput*)input, arguments, (__len__)) + +#ifdef INVERTERCTL +static void get_args(CommandInput* input, + std::vector<std::string>& arguments, + size_t count) { + for (size_t i = 0; i < count; i++) { + if (optind < input->argc && *input->argv[optind] != '-') + arguments.emplace_back(input->argv[optind++]); + else { + std::ostringstream error; + error << "this command requires " << count << " argument"; + if (count > 1) + error << "s"; + throw std::invalid_argument(error.str()); + } + } +} +#endif + +#ifdef INVERTERD +static void get_args(CommandInput* input, + std::vector<std::string>& arguments, + size_t count) { + if (input->argv->size() < count) { + std::ostringstream error; + error << "this command requires " << count << " argument"; + if (count > 1) + error << "s"; + throw std::invalid_argument(error.str()); + } + + for (size_t i = 0; i < count; i++) + arguments.emplace_back((*input->argv)[i]); +} +#endif + +p18::CommandType validate_input(std::string& command, + std::vector<std::string>& arguments, + void* input) { + auto it = p18::client_commands.find(command); + if (it == p18::client_commands.end()) + throw std::invalid_argument("invalid command"); + + auto commandType = it->second; + switch (commandType) { + case p18::CommandType::GetYearGenerated: + GET_ARGS(1); + validate_date_args(&arguments[0], nullptr, nullptr); + break; + + case p18::CommandType::GetMonthGenerated: + GET_ARGS(2); + validate_date_args(&arguments[0], &arguments[1], nullptr); + break; + + case p18::CommandType::GetDayGenerated: + GET_ARGS(3); + validate_date_args(&arguments[0], &arguments[1], &arguments[2]); + break; + + case p18::CommandType::GetParallelRatedInformation: + case p18::CommandType::GetParallelGeneralStatus: + GET_ARGS(1); + if (!is_numeric(arguments[0]) || arguments[0].size() > 1) + throw std::invalid_argument("invalid argument"); + break; + + case p18::CommandType::SetLoads: { + GET_ARGS(1); + std::string &arg = arguments[0]; + if (arg != "0" && arg != "1") + throw std::invalid_argument("invalid argument, only 0 or 1 allowed"); + break; + } + + case p18::CommandType::SetFlag: { + GET_ARGS(2); + + bool match_found = false; + for (auto const& item: p18::flags) { + if (arguments[0] == item.flag) { + arguments[0] = item.letter; + match_found = true; + break; + } + } + + if (!match_found) + throw std::invalid_argument("invalid flag"); + + if (arguments[1] != "0" && arguments[1] != "1") + throw std::invalid_argument("invalid flag state, only 0 or 1 allowed"); + + break; + } + + case p18::CommandType::SetBatteryMaxChargingCurrent: + case p18::CommandType::SetBatteryMaxACChargingCurrent: { + GET_ARGS(2); + + auto id = static_cast<unsigned>(std::stoul(arguments[0])); + auto amps = static_cast<unsigned>(std::stoul(arguments[1])); + + if (!p18::is_valid_parallel_id(id)) + throw std::invalid_argument("invalid id"); + + // 3 characters max + if (amps > 999) + throw std::invalid_argument("invalid amps"); + + break; + } + + case p18::CommandType::SetACOutputFreq: { + GET_ARGS(1); + std::string &freq = arguments[0]; + if (freq != "50" && freq != "60") + throw std::invalid_argument("invalid frequency, only 50 or 60 allowed"); + break; + } + + case p18::CommandType::SetBatteryMaxChargingVoltage: { + GET_ARGS(2); + + float cv = std::stof(arguments[0]); + float fv = std::stof(arguments[1]); + + if (cv < 48.0 || cv > 58.4) + throw std::invalid_argument("invalid CV"); + + if (fv < 48.0 || fv > 58.4) + throw std::invalid_argument("invalid FV"); + + break; + } + + case p18::CommandType::SetACOutputRatedVoltage: { + GET_ARGS(1); + + auto v = static_cast<unsigned>(std::stoul(arguments[0])); + + bool matchFound = false; + for (const auto &item: p18::ac_output_rated_voltages) { + if (v == item) { + matchFound = true; + break; + } + } + + if (!matchFound) + throw std::invalid_argument("invalid voltage"); + + break; + } + + case p18::CommandType::SetOutputSourcePriority: { + GET_ARGS(1); + + std::array<std::string, 2> priorities({"SUB", "SBU"}); + + long index = index_of(priorities, arguments[0]); + if (index == -1) + throw std::invalid_argument("invalid argument"); + + arguments[0] = std::to_string(index); + break; + } + + case p18::CommandType::SetBatteryChargingThresholds: { + GET_ARGS(2); + + float cv = std::stof(arguments[0]); + float dv = std::stof(arguments[1]); + + if (index_of(p18::bat_ac_recharging_voltages_12v, cv) == -1 || + index_of(p18::bat_ac_recharging_voltages_24v, cv) == -1 || + index_of(p18::bat_ac_recharging_voltages_48v, cv) == -1) + throw std::invalid_argument("invalid CV"); + + if (index_of(p18::bat_ac_redischarging_voltages_12v, dv) == -1 || + index_of(p18::bat_ac_redischarging_voltages_24v, dv) == -1 || + index_of(p18::bat_ac_redischarging_voltages_48v, dv) == -1) + throw std::invalid_argument("invalid DV"); + + break; + } + + case p18::CommandType::SetChargingSourcePriority: { + GET_ARGS(2); + + auto id = static_cast<unsigned>(std::stoul(arguments[0])); + if (!p18::is_valid_parallel_id(id)) + throw std::invalid_argument("invalid id"); + + std::array<std::string, 3> priorities({"SF", "SU", "S"}); + long index = index_of(priorities, arguments[0]); + if (index == -1) + throw std::invalid_argument("invalid argument"); + + arguments[1] = std::to_string(index); + break; + } + + case p18::CommandType::SetSolarPowerPriority: { + GET_ARGS(1); + + std::array<std::string, 2> allowed({"BLU", "LBU"}); + long index = index_of(allowed, arguments[0]); + if (index == -1) + throw std::invalid_argument("invalid priority"); + + arguments[0] = std::to_string(index); + break; + } + + case p18::CommandType::SetACInputVoltageRange: { + GET_ARGS(1); + std::array<std::string, 2> allowed({"APPLIANCE", "UPS"}); + long index = index_of(allowed, arguments[0]); + if (index == -1) + throw std::invalid_argument("invalid argument"); + arguments[0] = std::to_string(index); + break; + } + + case p18::CommandType::SetBatteryType: { + GET_ARGS(1); + + std::array<std::string, 3> allowed({"AGM", "FLOODED", "USER"}); + long index = index_of(allowed, arguments[0]); + if (index == -1) + throw std::invalid_argument("invalid type"); + arguments[0] = std::to_string(index); + + break; + } + + case p18::CommandType::SetOutputModel: { + GET_ARGS(2); + + auto id = static_cast<unsigned>(std::stoul(arguments[0])); + if (!p18::is_valid_parallel_id(id)) + throw std::invalid_argument("invalid id"); + + std::array<std::string, 5> allowed({"SM", "P", "P1", "P2", "P3"}); + long index = index_of(allowed, arguments[0]); + if (index == -1) + throw std::invalid_argument("invalid model"); + arguments[1] = std::to_string(index); + + break; + } + + case p18::CommandType::SetBatteryCutOffVoltage: { + GET_ARGS(1); + + float v = std::stof(arguments[0]); + if (v < 40.0 || v > 48.0) + throw std::invalid_argument("invalid voltage"); + + break; + } + + case p18::CommandType::SetSolarConfig: { + GET_ARGS(1); + + if (!is_numeric(arguments[0]) || arguments[0].size() > 20) + throw std::invalid_argument("invalid argument"); + + break; + } + + case p18::CommandType::SetDateTime: { + GET_ARGS(6); + + validate_date_args(&arguments[0], &arguments[1], &arguments[2]); + validate_time_args(&arguments[3], &arguments[4], &arguments[5]); + + break; + } + + case p18::CommandType::SetACChargingTimeBucket: + case p18::CommandType::SetACLoadsSupplyTimeBucket: { + GET_ARGS(2); + + std::vector<std::string> start = split(arguments[0], ':'); + if (start.size() != 2) + throw std::invalid_argument("invalid start time"); + + std::vector<std::string> end = split(arguments[1], ':'); + if (end.size() != 2) + throw std::invalid_argument("invalid end time"); + + auto startHour = static_cast<unsigned short>(std::stoul(start[0])); + auto startMinute = static_cast<unsigned short>(std::stoul(start[1])); + if (startHour > 23 || startMinute > 59) + throw std::invalid_argument("invalid start time"); + + auto endHour = static_cast<unsigned short>(std::stoul(end[0])); + auto endMinute = static_cast<unsigned short>(std::stoul(end[1])); + if (endHour > 23 || endMinute > 59) + throw std::invalid_argument("invalid end time"); + + arguments[0] = std::to_string(startHour); + arguments[1] = std::to_string(startMinute); + + arguments[2] = std::to_string(endHour); + arguments[3] = std::to_string(endMinute); + + break; + } + + default: + break; + } + + return commandType; +} + +}
\ No newline at end of file |