From 7e743b73433475df086fcec81be7b10c1d695a42 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Fri, 7 May 2021 02:18:07 +0300 Subject: initial --- src/inverterctl.cc | 490 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 490 insertions(+) create mode 100644 src/inverterctl.cc (limited to 'src/inverterctl.cc') diff --git a/src/inverterctl.cc b/src/inverterctl.cc new file mode 100644 index 0000000..882c425 --- /dev/null +++ b/src/inverterctl.cc @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "util.h" +#include "common.h" +#include "p18/client.h" +#include "p18/types.h" +#include "p18/defines.h" +#include "p18/exceptions.h" +#include "p18/commands.h" +#include "formatter/formatter.h" +#include "voltronic/device.h" +#include "voltronic/exceptions.h" +#include "hexdump/hexdump.h" + +const size_t MAX_RAW_COMMAND_LENGTH = 128; + +template +std::ostream& operator<<(std::ostream& os, const std::array& P) { + for (auto const& item: P) { + std::cout << item; + if (&item != &P.back()) + std::cout << "|"; + } + return os; +} + +static void short_usage(const char* progname) { + std::cout << "Usage: " << progname << " OPTIONS [COMMAND]\n" << + "\n" + "Options:\n" + " -h: Show this help\n" + " --help: Show full help (with all commands)\n" + " --raw : Execute arbitrary command and print response\n" + " --device : 'usb' (default), 'serial' or 'pseudo'\n" + " --timeout : Timeout in ms (default: " << voltronic::Device::TIMEOUT << ")\n" + " --verbose: Be verbose\n" + " --format : 'table' (default), 'simple-table' or 'json'\n" + "\n" + "To see list of supported commands, use --help.\n"; + exit(1); +} + +static void usage(const char* progname) { + std::ios_base::fmtflags f(std::cout.flags()); + std::cout << "Usage: " << progname << " OPTIONS [COMMAND]\n" << + "\n" + "Options:\n" + " -h: Show short help\n" + " --help: Show this help\n" + " --raw : Execute arbitrary command and print response\n" + " (example: ^P005PI)\n" + " --device : Device type to use. See below for list of supported\n" + " devices\n" + " --timeout : Device read/write timeout, in milliseconds\n" + " (default: " << voltronic::Device::TIMEOUT << ")\n" + " --verbose: Print debug information (including hex dumps of\n" + " device traffic)\n" + " --format : Output format for command responses\n" + "\n" + "Device types:\n" + " usb USB device\n" + " serial Serial device\n" + " pseudo Pseudo device (only useful for development/debugging purposes)\n" + "\n"; + std::cout << std::hex << std::setfill('0') << + "USB device options:\n" + " --usb-vendor-id : Vendor ID (default: " << std::setw(4) << voltronic::USBDevice::VENDOR_ID << ")\n" + " --usb-device-id : Device ID (default: " << std::setw(4) << voltronic::USBDevice::PRODUCT_ID << ")\n" + "\n"; + std::cout.flags(f); + std::cout << + "Serial device options:\n" + " --serial-name : Path to serial device (default: " << voltronic::SerialDevice::DEVICE_NAME << ")\n" + " --serial-baud-rate 110|300|1200|2400|4800|9600|19200|38400|57600|115200\n" + " --serial-data-bits 5|6|7|8\n" + " --serial-stop-bits 1|1.5|2\n" + " --serial-parity none|odd|even|mark|space\n" + "\n" + "Commands:\n" + " get-protocol-id\n" + " get-date-time\n" + " get-total-generated\n" + " get-year-generated \n" + " get-month-generated \n" + " get-day-generated
\n" + " get-series-number\n" + " get-cpu-version\n" + " get-rated\n" + " get-status\n" + " get-p-rated \n" + " id: Parallel machine ID\n" + "\n" + " get-p-status \n" + " id: Parallel machine ID\n" + "\n" + " get-mode\n" + " get-errors\n" + " get-flags\n" + " get-rated-defaults\n" + " get-allowed-charging-currents\n" + " get-allowed-ac-charging-currents\n" + " get-ac-charging-time\n" + " get-ac-loads-supply-time\n" + " set-loads-supply 0|1\n" + " set-flag 0|1\n" + " set-rated-defaults\n" + " set-max-charging-current \n" + " id: Parallel machine ID (use 0 for single model)\n" + " amps: Use get-allowed-charging-currents\n" + " to see a list of allowed values.\n" + "\n" + " set-max-ac-charging-current \n" + " id: Parallel machine ID (use 0 for single model)\n" + " amps: Use get-allowed-ac-charging-currents\n" + " to see a list of allowed values.\n" + "\n" + " set-ac-output-freq 50|60\n" + " set-max-charging-voltage \n" + " cv: Constant voltage (48.0 ~ 58.4).\n" + " fv: Float voltage (48.0 ~ 58.4).\n" + "\n" + " set-ac-output-voltage \n" + " v: " << p18::ac_output_rated_voltages << "\n" + "\n" + " set-output-source-priority SUB|SBU\n" + " 'SUB' means " << p18::OutputSourcePriority::SolarUtilityBattery << "\n" + " 'SBU' means " << p18::OutputSourcePriority::SolarBatteryUtility << "\n" + "\n" + " set-charging-thresholds \n" + " Set battery re-charging and re-discharging voltages when\n" + " utility is available.\n" + "\n" + " cv: re-charging voltage\n" + " For 12 V unit: " << p18::bat_ac_recharging_voltages_12v << "\n" + " For 24 V unit: " << p18::bat_ac_recharging_voltages_24v << "\n" + " For 48 V unit: " << p18::bat_ac_recharging_voltages_48v << "\n" + "\n" + " dv: re-discharging voltage\n" + " For 12 V unit: " << p18::bat_ac_redischarging_voltages_12v << "\n" + " For 24 V unit: " << p18::bat_ac_redischarging_voltages_24v << "\n" + " For 48 V unit: " << p18::bat_ac_redischarging_voltages_48v << "\n" + "\n" + " set-charging-source-priority \n" + " id: Parallel machine ID (use 0 for a single model)\n" + " priority: SF|SU|S\n" + " 'SF' means " << p18::ChargerSourcePriority::SolarFirst << ",\n" + " 'SU' means " << p18::ChargerSourcePriority::SolarAndUtility << "\n" + " 'S' means " << p18::ChargerSourcePriority::SolarOnly << "\n" + "\n" + " set-solar-power-priority BLU|LBU\n" + " 'BLU' means " << p18::SolarPowerPriority::BatteryLoadUtility << "\n" + " 'LBU' means " << p18::SolarPowerPriority::LoadBatteryUtility << "\n" + "\n" + " set-ac-input-voltage-range APPLIANCE|UPS\n" + " set-battery-type AGM|FLOODED|USER\n" + " set-output-model \n" + " id: Parallel machine ID (use 0 for a single model)\n" + " model: SM|P|P1|P2|P3\n" + " SM: " << p18::OutputModelSetting::SingleModule << "\n" + " P: " << p18::OutputModelSetting::ParallelOutput << "\n" + " P1: " << p18::OutputModelSetting::Phase1OfThreePhaseOutput << "\n" + " P2: " << p18::OutputModelSetting::Phase2OfThreePhaseOutput << "\n" + " P3: " << p18::OutputModelSetting::Phase3OfThreePhaseOutput << "\n" + "\n" + " set-battery-cut-off-voltage \n" + " v: Cut-off voltage (40.0~48.0)\n" + "\n" + " set-solar-configuration \n" + " id: Serial number\n" + "\n" + " clear-generated-data\n" + " Clear all recorded stats about generated energy.\n" + "\n" + " set-date-time
\n" + " YYYY: Year\n" + " MM: Month\n" + " DD: Day\n" + " hh: Hours\n" + " mm: Minutes\n" + " ss: Seconds\n" + "\n" + " set-ac-charging-time \n" + " start: Starting time, hh:mm format\n" + " end: Ending time, hh:mm format\n" + "\n" + " set-ac-loads-supply-time \n" + " start: Starting time, hh:mm format\n" + " end: Ending time, hh:mm format\n" + "\n" + "Flags:\n"; + for (const p18::Flag& flag: p18::flags) + std::cout << " " << flag.flag << ": " << flag.description << "\n"; + std::cout << + "\n" + "Formats:\n" + " table Human-readable table\n" + " simple-table Conveniently-parsable table\n" + " json JSON object or array\n"; + + exit(1); +} + +static void output_formatted_error(formatter::Format format, std::exception& e, std::string s = "") { + std::ostringstream buf; + if (!s.empty()) + buf << s << ": "; + buf << e.what(); + + auto err = p18::response_type::ErrorResponse(buf.str()); + auto output = err.format(format); + + if (format == formatter::Format::JSON) { + std::cout << *output; + } else { + std::cerr << *output << std::endl; + } +} + + +enum class Action { + ShortHelp, + FullHelp, + Raw, + Command, +}; + +int main(int argc, char *argv[]) { + if (argv[1] == nullptr) + short_usage(argv[0]); + + // common params + Action action = Action::Command; + u64 timeout = voltronic::Device::TIMEOUT; + bool verbose = false; + p18::CommandType commandType; + std::vector arguments; + + // format params + bool formatChanged = false; + formatter::Format format = formatter::Format::Table; + + // raw command param + std::string raw; + + // device params + DeviceType deviceType = DeviceType::USB; + + u16 usbVendorId = voltronic::USBDevice::VENDOR_ID; + u16 usbDeviceId = voltronic::USBDevice::PRODUCT_ID; + + std::string serialDeviceName(voltronic::SerialDevice::DEVICE_NAME); + voltronic::SerialBaudRate serialBaudRate = voltronic::SerialDevice::BAUD_RATE; + voltronic::SerialDataBits serialDataBits = voltronic::SerialDevice::DATA_BITS; + voltronic::SerialStopBits serialStopBits = voltronic::SerialDevice::STOP_BITS; + voltronic::SerialParity serialParity = voltronic::SerialDevice::PARITY; + + try { + int opt; + struct option long_options[] = { + {"help", no_argument, nullptr, LO_HELP}, + {"verbose", no_argument, nullptr, LO_VERBOSE}, + {"raw", required_argument, nullptr, LO_RAW}, + {"timeout", required_argument, nullptr, LO_TIMEOUT}, + {"format", required_argument, nullptr, LO_FORMAT}, + {"device", required_argument, nullptr, LO_DEVICE}, + {"usb-vendor-id", required_argument, nullptr, LO_USB_VENDOR_ID}, + {"usb-device-id", required_argument, nullptr, LO_USB_DEVICE_ID}, + {"serial-name", required_argument, nullptr, LO_SERIAL_NAME}, + {"serial-baud-rate", required_argument, nullptr, LO_SERIAL_BAUD_RATE}, + {"serial-data-bits", required_argument, nullptr, LO_SERIAL_DATA_BITS}, + {"serial-stop-bits", required_argument, nullptr, LO_SERIAL_STOP_BITS}, + {"serial-parity", required_argument, nullptr, LO_SERIAL_PARITY}, + {nullptr, 0, nullptr, 0} + }; + + bool getoptError = false; // FIXME + while ((opt = getopt_long(argc, argv, "h", long_options, nullptr)) != EOF) { + if (opt == '?') { + getoptError = true; + break; + } + + // simple options (flags), no arguments + switch (opt) { + case 'h': action = Action::ShortHelp; continue; + case LO_HELP: action = Action::FullHelp; continue; + case LO_VERBOSE: verbose = true; continue; + default: break; + } + + // options with arguments + std::string arg; + if (optarg) + arg = std::string(optarg); + + switch (opt) { + case LO_FORMAT: + format = format_from_string(arg); + formatChanged = true; + break; + + case LO_DEVICE: + if (arg == "usb") + deviceType = DeviceType::USB; + else if (arg == "serial") + deviceType = DeviceType::Serial; + else if (arg == "pseudo") + deviceType = DeviceType::Pseudo; + else + throw std::invalid_argument("invalid device"); + + break; + + case LO_RAW: + raw = arg; + if (raw.size() > MAX_RAW_COMMAND_LENGTH) + throw std::invalid_argument("command is too long"); + action = Action::Raw; + break; + + case LO_TIMEOUT: + timeout = std::stoull(arg); + break; + + case LO_USB_VENDOR_ID: + try { + if (arg.size() != 4) + throw std::invalid_argument("usb-vendor-id: invalid length"); + usbVendorId = static_cast(hextoul(arg)); + } catch (std::invalid_argument& e) { + throw std::invalid_argument(std::string("usb-vendor-id: invalid format: ") + e.what()); + } + break; + + case LO_USB_DEVICE_ID: + try { + if (arg.size() != 4) + throw std::invalid_argument("usb-device-id: invalid length"); + usbDeviceId = static_cast(hextoul(arg)); + } catch (std::invalid_argument& e) { + throw std::invalid_argument(std::string("usb-device-id: invalid format: ") + e.what()); + } + break; + + case LO_SERIAL_NAME: + serialDeviceName = arg; + break; + + case LO_SERIAL_BAUD_RATE: + serialBaudRate = static_cast(std::stoul(arg)); + if (!voltronic::is_serial_baud_rate_valid(serialBaudRate)) + throw std::invalid_argument("invalid serial baud rate"); + break; + + case LO_SERIAL_DATA_BITS: + serialDataBits = static_cast(std::stoul(arg)); + if (voltronic::is_serial_data_bits_valid(serialDataBits)) + throw std::invalid_argument("invalid serial data bits"); + break; + + case LO_SERIAL_STOP_BITS: + if (arg == "1") + serialStopBits = voltronic::SerialStopBits::One; + else if (arg == "1.5") + serialStopBits = voltronic::SerialStopBits::OneAndHalf; + else if (arg == "2") + serialStopBits = voltronic::SerialStopBits::Two; + else + throw std::invalid_argument("invalid serial stop bits"); + break; + + case LO_SERIAL_PARITY: + if (arg == "none") + serialParity = voltronic::SerialParity::None; + else if (arg == "odd") + serialParity = voltronic::SerialParity::Odd; + else if (arg == "even") + serialParity = voltronic::SerialParity::Even; + else if (arg == "mark") + serialParity = voltronic::SerialParity::Mark; + else if (arg == "space") + serialParity = voltronic::SerialParity::Space; + else + throw std::invalid_argument("invalid serial parity"); + break; + + default: + break; + } + } + + switch (action) { + case Action::ShortHelp: + short_usage(argv[0]); + break; + + case Action::FullHelp: + usage(argv[0]); + break; + + case Action::Command: { + if (argc <= optind) + throw std::invalid_argument("missing command"); + + std::string command = argv[optind++]; + + p18::CommandInput input{argc, argv}; + commandType = p18::validate_input(command, arguments, (void*)&input); + break; + } + + case Action::Raw: + if (formatChanged) + throw std::invalid_argument("--format is not allowed with --raw"); + break; + } + + if (optind < argc) + throw std::invalid_argument("extra parameter found"); + } catch (std::invalid_argument& e) { + output_formatted_error(format, e); + return 1; + } + + bool success = false; + try { + std::shared_ptr dev; + switch (deviceType) { + case DeviceType::USB: + dev = std::shared_ptr(new voltronic::USBDevice(usbVendorId, + usbDeviceId)); + break; + + case DeviceType::Pseudo: + dev = std::shared_ptr(new voltronic::PseudoDevice); + break; + + case DeviceType::Serial: + dev = std::shared_ptr(new voltronic::SerialDevice(serialDeviceName, + serialBaudRate, + serialDataBits, + serialStopBits, + serialParity)); + break; + } + + dev->setVerbose(verbose); + dev->setTimeout(timeout); + + p18::Client client; + client.setDevice(dev); + + if (action == Action::Raw) { + auto result = client.runOnDevice(raw); + if (verbose) + std::cerr << hexdump(result.first.get(), result.second); + std::cout << std::string(result.first.get(), result.second) << std::endl; + } else { + auto response = client.execute(commandType, arguments); + std::cout << *(response->format(format).get()) << std::endl; + } + + success = true; + } + catch (voltronic::DeviceError& e) { + output_formatted_error(format, e, "device error"); + } + catch (voltronic::TimeoutError& e) { + output_formatted_error(format, e, "timeout"); + } + catch (voltronic::InvalidDataError& e) { + output_formatted_error(format, e, "data is invalid"); + } + catch (p18::InvalidResponseError& e) { + output_formatted_error(format, e, "response is invalid"); + } + + return success ? 1 : 0; +} \ No newline at end of file -- cgit v1.2.3