// SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include #include #include #include #include // common stuff #include "logging.h" #include "util.h" #include "common.h" #include "formatter/formatter.h" #include "voltronic/device.h" #include "voltronic/exceptions.h" #include "hexdump/hexdump.h" #include "protocol/input.h" #include "protocol/exceptions.h" #include "protocol/response.h" // p18 #include "protocol_18/client.h" #include "protocol_18/types.h" #include "protocol_18/defines.h" #include "protocol_18/input.h" // p17 //#include "protocol_17/client.h" #include "protocol_17/types.h" #include "protocol_17/defines.h" #include "protocol_17/input.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" " -p, --protocol 17 or 18 (default)\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', 'json' or\n" " 'simple-json'\n" "\n" "To see list of supported commands, use --help.\n"; exit(1); } static void usage_p17() { std::cout << "P17 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-secondary-cpu-version\n" ; } static void usage_p18() { std::cout << "P18 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"; } static void usage(const char* progname, Protocol p) { 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" " -p, --protocol Protocol ID (default: 18)\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" "Protocols:\n" " 17\n" " 18\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"; switch (p) { case Protocol::P17: usage_p17(); break; case Protocol::P18: usage_p18(); break; } std::cout << "\n" "Formats:\n" " table Human-readable table\n" " simple-table Conveniently-parsable table\n" " json JSON object or array\n" " simple-json no units, enumerations represented as numbers\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 = protocol::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; int commandType; std::vector arguments; Protocol protocol = Protocol::P18; // 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}, {"protocol", required_argument, nullptr, LO_PROTOCOL}, {"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, "hp:", long_options, nullptr)) != EOF) { if (opt == '?') { getoptError = true; break; } // simple options (flags), no arguments switch (opt) { case 'h': action = Action::ShortHelp; continue; case 'p': { std::string s(optarg); protocol = protocol_from_string(s); break; } 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_PROTOCOL: protocol = protocol_from_string(arg); 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], protocol); break; case Action::Command: { if (argc <= optind) throw std::invalid_argument("missing command"); std::string command = argv[optind++]; protocol::CommandInput input{argc, argv}; switch (protocol) { case Protocol::P17: commandType = static_cast(p17::validate_input(command, arguments, (void*)&input)); break; case Protocol::P18: commandType = static_cast(p18::validate_input(command, arguments, (void*)&input)); break; } 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->setFlags(voltronic::FLAG_READ_CRC | voltronic::FLAG_VERIFY_CRC); 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 (protocol::InvalidResponseError& e) { output_formatted_error(format, e, "response is invalid"); } return success ? 1 : 0; }