// SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #ifdef INVERTERCTL #include #endif #include "commands.h" #include "defines.h" #include "functions.h" #include "../util.h" #include "../logging.h" namespace p18 { const std::map 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(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(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(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& 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& 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& 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(std::stoul(arguments[0])); auto amps = static_cast(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(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 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(std::stoul(arguments[0])); if (!p18::is_valid_parallel_id(id)) throw std::invalid_argument("invalid id"); std::array priorities({"SF", "SU", "S"}); long index = index_of(priorities, arguments[1]); 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 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 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 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(std::stoul(arguments[0])); if (!p18::is_valid_parallel_id(id)) throw std::invalid_argument("invalid id"); std::array 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 start = split(arguments[0], ':'); if (start.size() != 2) throw std::invalid_argument("invalid start time"); std::vector end = split(arguments[1], ':'); if (end.size() != 2) throw std::invalid_argument("invalid end time"); auto startHour = static_cast(std::stoul(start[0])); auto startMinute = static_cast(std::stoul(start[1])); if (startHour > 23 || startMinute > 59) throw std::invalid_argument("invalid start time"); auto endHour = static_cast(std::stoul(end[0])); auto endMinute = static_cast(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; } }