Compare commits

...

15 Commits

Author SHA1 Message Date
Evgeny Zinoviev
58a43d4648 src/numeric_types: include cstdint 2023-12-14 20:06:44 +03:00
Evgeny Zinoviev
25fdaac582 According to user report, some inverters may return 3 digits in 14th field when querying get-rated. Support that. 2023-12-14 20:05:07 +03:00
Evgeny Zinoviev
5d9e828903 usb: support specifying device path (e.g. /dev/hidrawX) 2023-01-12 02:01:36 +03:00
Evgeny Zinoviev
f95f3fe3d0 minor help message fixes 2022-09-02 12:54:59 +03:00
Evgeny Zinoviev
2cfe71d323 CMakeLists.txt: clean up 2022-09-02 12:49:11 +03:00
Evgeny Zinoviev
523d10d386 1.4.99 2022-09-02 12:48:19 +03:00
Evgeny Zinoviev
6df6012edf rename stuff
- Rename some commands
- Use more unified and correct terminology
2022-09-02 12:44:15 +03:00
Evgeny Zinoviev
9ba7a470ad p18: get-rated: fix parsing of invalid response
Sometimes (my guess is when AC input line is connected) get-rated
(^P007PIRI request) returns additional field in the end. On my device
it's two bytes, '00' (0x30 0x30).

It shouldn't be there as per documentation (but you know how
accurate these chinese docs can be, right?) and, as of now, I have no
clue what that means.
2022-08-31 23:41:06 +03:00
Evgeny Zinoviev
750297157d p18/get-rated: fix output_source_priority return type 2022-08-31 17:43:05 +03:00
Evgeny Zinoviev
d86ca1e596 readme: update list of tested devices 2021-11-30 01:07:27 +03:00
Evgeny Zinoviev
6003eaa675 p18/get-p-status: don't output max_temp if inverter doesn't return it 2021-11-29 16:52:27 +03:00
Evgeny Zinoviev
d74a2a3e78 readme: add inverter-http-proxy link 2021-11-29 02:36:53 +03:00
Evgeny Zinoviev
69596fa400 p18: fix serial number parsing in get-p-rated 2021-11-29 02:30:20 +03:00
Evgeny Zinoviev
0d7e12ce55 p18: fix variable field lengths in get-p-rated 2021-11-29 02:28:21 +03:00
Evgeny Zinoviev
46c308bc14 p18: fix get-p-status 2021-11-29 01:08:49 +03:00
16 changed files with 424 additions and 314 deletions

View File

@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.0)
set(CMAKE_CXX_STANDARD 17)
add_compile_options(-Wno-psabi)
project(inverter-tools VERSION 1.4.0)
project(inverter-tools VERSION 1.4.99)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX /usr/local/bin)
@ -96,11 +96,4 @@ target_include_directories(testserial PRIVATE .)
target_link_libraries(testserial ${LIBSERIALPORT_LIBRARY})
target_include_directories(testserial PRIVATE
${LIBSERIALPORT_INCLUDE_DIR}
third_party/hexdump)
# inverterd
#add_executable(inverterd
# src/inverterd.cc)
#target_link_libraries(inverterd ${HIDAPI_LIBRARY} ${LIBSERIALPORT_LIBRARY} m)
# TODO install
third_party/hexdump)

View File

@ -20,9 +20,10 @@ is planned.
- HIDAPI
- libserialport
## Supported devices
## Tested devices
As of time of writing, only InfiniSolar V 5KW was tested.
- [InfiniSolar V 5KW](https://ekoproekt-energo.ru/infinisolar-v-5k-48)
- [Crown Micro Elego 6KW](https://coollineenergy.com/product/crown-micro-elego-6kw-solar-hybrid-inverter/)
## Supported interfaces
@ -40,6 +41,8 @@ for all possible options and commands.
for querying inverterd server.
- [inverter-bot](https://github.com/gch1p/inverter-bot) - Telegram bot that uses inverterd
for querying data.
- [inverter-http-proxy](https://github.com/gch1p/inverter-http-proxy) - HTTP
"frontend" for inverterd
## License

View File

@ -24,6 +24,7 @@ enum {
LO_DEVICE_ERROR_LIMIT,
LO_USB_VENDOR_ID,
LO_USB_DEVICE_ID,
LO_USB_PATH,
LO_SERIAL_NAME,
LO_SERIAL_BAUD_RATE,
LO_SERIAL_DATA_BITS,

View File

@ -127,6 +127,10 @@ public:
explicit Table(Format format, std::vector<TableItem<T>> v)
: Formattable(format), v_(v) {}
void push(TableItem<T> item) {
v_.push_back(item);
}
std::ostream& writeSimpleTable(std::ostream& os) const override {
for (const auto& item: v_) {
os << item.key << " ";

View File

@ -78,6 +78,9 @@ static void usage(const char* progname) {
"USB device options:\n"
" --usb-vendor-id <ID>: Vendor ID (default: " << std::setw(4) << voltronic::USBDevice::VENDOR_ID << ")\n"
" --usb-device-id <ID>: Device ID (default: " << std::setw(4) << voltronic::USBDevice::PRODUCT_ID << ")\n"
"\n"
" Alternatively, you can specify device path (e.g., /dev/hidraw0):\n"
" --usb-path <PATH>: Device path\n"
"\n";
std::cout.flags(f);
std::cout <<
@ -95,7 +98,7 @@ static void usage(const char* progname) {
" get-year-generated <yyyy>\n"
" get-month-generated <yyyy> <mm>\n"
" get-day-generated <yyyy> <mm> <dd>\n"
" get-series-number\n"
" get-serial-number\n"
" get-cpu-version\n"
" get-rated\n"
" get-status\n"
@ -109,55 +112,55 @@ static void usage(const char* progname) {
" 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"
" get-allowed-charge-currents\n"
" get-allowed-ac-charge-currents\n"
" get-ac-charge-time\n"
" get-ac-supply-time\n"
" set-ac-supply 0|1\n"
" set-flag <flag> 0|1\n"
" set-rated-defaults\n"
" set-max-charging-current <id> <amps>\n"
" id: Parallel machine ID (use 0 for single model)\n"
" amps: Use get-allowed-charging-currents\n"
" set-max-charge-current <id> <amps>\n"
" id: Parallel machine ID\n"
" amps: Use get-allowed-charge-currents\n"
" to see a list of allowed values.\n"
"\n"
" set-max-ac-charging-current <id> <amps>\n"
" id: Parallel machine ID (use 0 for single model)\n"
" amps: Use get-allowed-ac-charging-currents\n"
" set-max-ac-charge-current <id> <amps>\n"
" id: Parallel machine ID\n"
" amps: Use get-allowed-ac-charge-currents\n"
" to see a list of allowed values.\n"
"\n"
" set-max-charge-voltage <cv> <fv>\n"
" cv: Constant voltage (48.0 ~ 58.4)\n"
" fv: Float voltage (48.0 ~ 58.4)\n"
"\n"
" set-ac-output-freq 50|60\n"
" set-max-charging-voltage <cv> <fv>\n"
" cv: Constant voltage (48.0 ~ 58.4).\n"
" fv: Float voltage (48.0 ~ 58.4).\n"
"\n"
" set-ac-output-voltage <v>\n"
" v: " << p18::ac_output_rated_voltages << "\n"
" v: " << p18::ac_output_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 <cv> <dv>\n"
" Set battery re-charging and re-discharging voltages when\n"
" utility is available.\n"
" set-charge-thresholds <cv> <dv>\n"
" Set battery re-charge and re-discharge voltages when\n"
" grid is connected.\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"
" cv: re-charge voltage\n"
" For 12 V unit: " << p18::bat_ac_recharge_voltages_12v << "\n"
" For 24 V unit: " << p18::bat_ac_recharge_voltages_24v << "\n"
" For 48 V unit: " << p18::bat_ac_recharge_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"
" dv: re-discharge voltage\n"
" For 12 V unit: " << p18::bat_ac_redischarge_voltages_12v << "\n"
" For 24 V unit: " << p18::bat_ac_redischarge_voltages_24v << "\n"
" For 48 V unit: " << p18::bat_ac_redischarge_voltages_48v << "\n"
"\n"
" set-charging-source-priority <id> <priority>\n"
" id: Parallel machine ID (use 0 for a single model)\n"
" set-charge-source-priority <id> <priority>\n"
" id: Parallel machine ID\n"
" priority: SF|SU|S\n"
" 'SF' means " << p18::ChargerSourcePriority::SolarFirst << ",\n"
" 'SU' means " << p18::ChargerSourcePriority::SolarAndUtility << "\n"
" 'S' means " << p18::ChargerSourcePriority::SolarOnly << "\n"
" 'SF' means " << p18::ChargeSourcePriority::SolarFirst << "\n"
" 'SU' means " << p18::ChargeSourcePriority::SolarAndUtility << "\n"
" 'S' means " << p18::ChargeSourcePriority::SolarOnly << "\n"
"\n"
" set-solar-power-priority BLU|LBU\n"
" 'BLU' means " << p18::SolarPowerPriority::BatteryLoadUtility << "\n"
@ -165,16 +168,16 @@ static void usage(const char* progname) {
"\n"
" set-ac-input-voltage-range APPLIANCE|UPS\n"
" set-battery-type AGM|FLOODED|USER\n"
" set-output-model <id> <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"
" set-output-mode <id> <mode>\n"
" id: Machine ID\n"
" mode: S|P|1|2|3\n"
" S: " << p18::OutputMode::SingleOutput << "\n"
" P: " << p18::OutputMode::ParallelOutput << "\n"
" 1: " << p18::OutputMode::Phase_1_of_3 << "\n"
" 2: " << p18::OutputMode::Phase_2_of_3 << "\n"
" 3: " << p18::OutputMode::Phase_3_of_3 << "\n"
"\n"
" set-battery-cut-off-voltage <v>\n"
" set-battery-cutoff-voltage <v>\n"
" v: Cut-off voltage (40.0~48.0)\n"
"\n"
" set-solar-configuration <id>\n"
@ -191,14 +194,16 @@ static void usage(const char* progname) {
" mm: Minutes\n"
" ss: Seconds\n"
"\n"
" set-ac-charging-time <start> <end>\n"
" set-ac-charge-time <start> <end>\n"
" start: Starting time, hh:mm format\n"
" end: Ending time, hh:mm format\n"
"\n"
" set-ac-loads-supply-time <start> <end>\n"
" set-ac-supply-time <start> <end>\n"
" start: Starting time, hh:mm format\n"
" end: Ending time, hh:mm format\n"
"\n"
"Note: use 0 as parallel machine ID for single machine.\n"
"\n"
"Flags:\n";
for (const p18::Flag& flag: p18::flags)
std::cout << " " << flag.flag << ": " << flag.description << "\n";
@ -260,6 +265,7 @@ int main(int argc, char *argv[]) {
u16 usbVendorId = voltronic::USBDevice::VENDOR_ID;
u16 usbDeviceId = voltronic::USBDevice::PRODUCT_ID;
std::string usbDevicePath {};
std::string serialDeviceName(voltronic::SerialDevice::DEVICE_NAME);
voltronic::SerialBaudRate serialBaudRate = voltronic::SerialDevice::BAUD_RATE;
@ -278,6 +284,7 @@ int main(int argc, char *argv[]) {
{"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},
{"usb-path", required_argument, nullptr, LO_USB_PATH},
{"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},
@ -355,6 +362,10 @@ int main(int argc, char *argv[]) {
}
break;
case LO_USB_PATH:
usbDevicePath = arg;
break;
case LO_SERIAL_NAME:
serialDeviceName = arg;
break;
@ -440,8 +451,12 @@ int main(int argc, char *argv[]) {
std::shared_ptr<voltronic::Device> dev;
switch (deviceType) {
case DeviceType::USB:
dev = std::shared_ptr<voltronic::Device>(new voltronic::USBDevice(usbVendorId,
usbDeviceId));
if (usbDevicePath.empty()) {
dev = std::shared_ptr<voltronic::Device>(new voltronic::USBDevice(usbVendorId,
usbDeviceId));
} else {
dev = std::shared_ptr<voltronic::Device>(new voltronic::USBDevice(usbDevicePath));
}
break;
case DeviceType::Pseudo:

View File

@ -42,7 +42,10 @@ static void usage(const char* progname) {
std::cout << std::hex << std::setfill('0') <<
"USB device options:\n"
" --usb-vendor-id <ID>: Vendor ID (default: " << std::setw(4) << voltronic::USBDevice::VENDOR_ID << ")\n"
" --usb-device-id <ID>: Device ID (default: " << std::setw(4) << voltronic::USBDevice::PRODUCT_ID << ")\n";
" --usb-device-id <ID>: Device ID (default: " << std::setw(4) << voltronic::USBDevice::PRODUCT_ID << ")\n"
"\n"
" Alternatively, you can specify device path (e.g., /dev/hidraw0):\n"
" --usb-path <PATH>: Device path\n";
std::cout.flags(f);
std::cout << "\n"
@ -73,6 +76,7 @@ int main(int argc, char *argv[]) {
unsigned short usbVendorId = voltronic::USBDevice::VENDOR_ID;
unsigned short usbDeviceId = voltronic::USBDevice::PRODUCT_ID;
std::string usbDevicePath {};
std::string serialDeviceName(voltronic::SerialDevice::DEVICE_NAME);
voltronic::SerialBaudRate serialBaudRate = voltronic::SerialDevice::BAUD_RATE;
@ -92,6 +96,7 @@ int main(int argc, char *argv[]) {
{"device-error-limit", required_argument, nullptr, LO_DEVICE_ERROR_LIMIT},
{"usb-vendor-id", required_argument, nullptr, LO_USB_VENDOR_ID},
{"usb-device-id", required_argument, nullptr, LO_USB_DEVICE_ID},
{"usb-path", required_argument, nullptr, LO_USB_PATH},
{"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},
@ -176,6 +181,10 @@ int main(int argc, char *argv[]) {
}
break;
case LO_USB_PATH:
usbDevicePath = arg;
break;
case LO_SERIAL_NAME:
serialDeviceName = arg;
break;
@ -243,7 +252,12 @@ int main(int argc, char *argv[]) {
try {
switch (deviceType) {
case DeviceType::USB:
dev = std::shared_ptr<voltronic::Device>(new voltronic::USBDevice(usbVendorId, usbDeviceId));
if (usbDevicePath.empty()) {
dev = std::shared_ptr<voltronic::Device>(new voltronic::USBDevice(usbVendorId,
usbDeviceId));
} else {
dev = std::shared_ptr<voltronic::Device>(new voltronic::USBDevice(usbDevicePath));
}
break;
case DeviceType::Pseudo:

View File

@ -3,6 +3,8 @@
#ifndef INVERTER_TOOLS_NUMERIC_TYPES_H
#define INVERTER_TOOLS_NUMERIC_TYPES_H
#include <cstdint>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;

View File

@ -65,42 +65,42 @@ std::shared_ptr<response_type::BaseResponse> Client::execute(p18::CommandType co
RESPONSE_CASE(YearGenerated)
RESPONSE_CASE(MonthGenerated)
RESPONSE_CASE(DayGenerated)
RESPONSE_CASE(SeriesNumber)
RESPONSE_CASE(SerialNumber)
RESPONSE_CASE(CPUVersion)
RESPONSE_CASE(RatedInformation)
RESPONSE_CASE(GeneralStatus)
RESPONSE_CASE(WorkingMode)
RESPONSE_CASE(FaultsAndWarnings)
RESPONSE_CASE(FlagsAndStatuses)
RESPONSE_CASE(Defaults)
RESPONSE_CASE(AllowedChargingCurrents)
RESPONSE_CASE(AllowedACChargingCurrents)
RESPONSE_CASE(RatedDefaults)
RESPONSE_CASE(AllowedChargeCurrents)
RESPONSE_CASE(AllowedACChargeCurrents)
RESPONSE_CASE(ParallelRatedInformation)
RESPONSE_CASE(ParallelGeneralStatus)
RESPONSE_CASE(ACChargingTimeBucket)
RESPONSE_CASE(ACLoadsSupplyTimeBucket)
RESPONSE_CASE(ACChargeTimeBucket)
RESPONSE_CASE(ACSupplyTimeBucket)
case CommandType::SetLoads:
case CommandType::SetACSupply:
case CommandType::SetFlag:
case CommandType::SetDefaults:
case CommandType::SetBatteryMaxChargingCurrent:
case CommandType::SetBatteryMaxACChargingCurrent:
case CommandType::SetBatteryMaxChargeCurrent:
case CommandType::SetBatteryMaxACChargeCurrent:
case CommandType::SetACOutputFreq:
case CommandType::SetBatteryMaxChargingVoltage:
case CommandType::SetACOutputRatedVoltage:
case CommandType::SetBatteryMaxChargeVoltage:
case CommandType::SetACOutputVoltage:
case CommandType::SetOutputSourcePriority:
case CommandType::SetBatteryChargingThresholds:
case CommandType::SetChargingSourcePriority:
case CommandType::SetBatteryChargeThresholds:
case CommandType::SetChargeSourcePriority:
case CommandType::SetSolarPowerPriority:
case CommandType::SetACInputVoltageRange:
case CommandType::SetBatteryType:
case CommandType::SetOutputModel:
case CommandType::SetOutputMode:
case CommandType::SetBatteryCutOffVoltage:
case CommandType::SetSolarConfig:
case CommandType::ClearGenerated:
case CommandType::SetDateTime:
case CommandType::SetACChargingTimeBucket:
case CommandType::SetACLoadsSupplyTimeBucket:
case CommandType::SetACChargeTimeBucket:
case CommandType::SetACSupplyTimeBucket:
response = MKRESPONSE(SetResponse);
break;
}
@ -132,7 +132,7 @@ std::string Client::packArguments(p18::CommandType commandType, std::vector<std:
case CommandType::SetSolarPowerPriority:
case CommandType::SetACInputVoltageRange:
case CommandType::SetBatteryType:
case CommandType::SetLoads:
case CommandType::SetACSupply:
buf << arguments[0];
break;
@ -153,8 +153,8 @@ std::string Client::packArguments(p18::CommandType commandType, std::vector<std:
buf << arguments[0];
break;
case CommandType::SetBatteryMaxChargingCurrent:
case CommandType::SetBatteryMaxACChargingCurrent:
case CommandType::SetBatteryMaxChargeCurrent:
case CommandType::SetBatteryMaxACChargeCurrent:
buf << arguments[0] << ",";
buf << std::setw(3) << std::stoi(arguments[1]);
break;
@ -163,8 +163,8 @@ std::string Client::packArguments(p18::CommandType commandType, std::vector<std:
buf << std::setw(2) << std::stoi(arguments[0]);
break;
case CommandType::SetBatteryMaxChargingVoltage:
case CommandType::SetBatteryChargingThresholds: {
case CommandType::SetBatteryMaxChargeVoltage:
case CommandType::SetBatteryChargeThresholds: {
for (int i = 0; i < 2; i++) {
double val = std::stod(arguments[i]);
buf << std::setw(3) << (int)round(val*10);
@ -174,13 +174,13 @@ std::string Client::packArguments(p18::CommandType commandType, std::vector<std:
break;
}
case CommandType::SetACOutputRatedVoltage: {
case CommandType::SetACOutputVoltage: {
buf << std::setw(4) << (std::stoi(arguments[0])*10);
break;
}
case CommandType::SetChargingSourcePriority:
case CommandType::SetOutputModel:
case CommandType::SetChargeSourcePriority:
case CommandType::SetOutputMode:
buf << arguments[0] << "," << arguments[1];
break;
@ -210,8 +210,8 @@ std::string Client::packArguments(p18::CommandType commandType, std::vector<std:
break;
}
case CommandType::SetACChargingTimeBucket:
case CommandType::SetACLoadsSupplyTimeBucket:
case CommandType::SetACChargeTimeBucket:
case CommandType::SetACSupplyTimeBucket:
for (int i = 0; i < 4; i++) {
buf << std::setw(2) << std::stoi(arguments[i]);
if (i == 1)

View File

@ -24,41 +24,41 @@ const std::map<std::string, p18::CommandType> client_commands = {
{"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-serial-number", p18::CommandType::GetSerialNumber},
{"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-rated-defaults", p18::CommandType::GetRatedDefaults},
{"get-allowed-charge-currents", p18::CommandType::GetAllowedChargeCurrents},
{"get-allowed-ac-charge-currents", p18::CommandType::GetAllowedACChargeCurrents},
{"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},
{"get-ac-charge-time", p18::CommandType::GetACChargeTimeBucket},
{"get-ac-supply-time", p18::CommandType::GetACSupplyTimeBucket},
{"set-ac-supply", p18::CommandType::SetACSupply},
{"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-max-charge-current", p18::CommandType::SetBatteryMaxChargeCurrent},
{"set-max-ac-charge-current", p18::CommandType::SetBatteryMaxACChargeCurrent},
{"set-ac-output-freq", p18::CommandType::SetACOutputFreq},
{"set-max-charging-voltage", p18::CommandType::SetBatteryMaxChargingVoltage},
{"set-ac-output-voltage", p18::CommandType::SetACOutputRatedVoltage},
{"set-max-charge-voltage", p18::CommandType::SetBatteryMaxChargeVoltage},
{"set-ac-output-voltage", p18::CommandType::SetACOutputVoltage},
{"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-charge-thresholds", p18::CommandType::SetBatteryChargeThresholds}, /* Battery re-charge and re-discharge voltage when utility is available */
{"set-charge-source-priority", p18::CommandType::SetChargeSourcePriority},
{"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-output-mode", p18::CommandType::SetOutputMode},
{"set-battery-cutoff-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},
{"set-ac-charge-time", p18::CommandType::SetACChargeTimeBucket},
{"set-ac-supply-time", p18::CommandType::SetACSupplyTimeBucket},
};
static void validate_date_args(const std::string* ys, const std::string* ms, const std::string* ds) {
@ -200,7 +200,7 @@ p18::CommandType validate_input(std::string& command,
throw std::invalid_argument("invalid argument");
break;
case p18::CommandType::SetLoads: {
case p18::CommandType::SetACSupply: {
GET_ARGS(1);
std::string &arg = arguments[0];
if (arg != "0" && arg != "1")
@ -229,8 +229,8 @@ p18::CommandType validate_input(std::string& command,
break;
}
case p18::CommandType::SetBatteryMaxChargingCurrent:
case p18::CommandType::SetBatteryMaxACChargingCurrent: {
case p18::CommandType::SetBatteryMaxChargeCurrent:
case p18::CommandType::SetBatteryMaxACChargeCurrent: {
GET_ARGS(2);
auto id = static_cast<unsigned>(std::stoul(arguments[0]));
@ -254,7 +254,7 @@ p18::CommandType validate_input(std::string& command,
break;
}
case p18::CommandType::SetBatteryMaxChargingVoltage: {
case p18::CommandType::SetBatteryMaxChargeVoltage: {
GET_ARGS(2);
float cv = std::stof(arguments[0]);
@ -269,13 +269,13 @@ p18::CommandType validate_input(std::string& command,
break;
}
case p18::CommandType::SetACOutputRatedVoltage: {
case p18::CommandType::SetACOutputVoltage: {
GET_ARGS(1);
auto v = static_cast<unsigned>(std::stoul(arguments[0]));
bool matchFound = false;
for (const auto &item: p18::ac_output_rated_voltages) {
for (const auto &item: p18::ac_output_voltages) {
if (v == item) {
matchFound = true;
break;
@ -301,26 +301,26 @@ p18::CommandType validate_input(std::string& command,
break;
}
case p18::CommandType::SetBatteryChargingThresholds: {
case p18::CommandType::SetBatteryChargeThresholds: {
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)
if (index_of(p18::bat_ac_recharge_voltages_12v, cv) == -1 &&
index_of(p18::bat_ac_recharge_voltages_24v, cv) == -1 &&
index_of(p18::bat_ac_recharge_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)
if (index_of(p18::bat_ac_redischarge_voltages_12v, dv) == -1 &&
index_of(p18::bat_ac_redischarge_voltages_24v, dv) == -1 &&
index_of(p18::bat_ac_redischarge_voltages_48v, dv) == -1)
throw std::invalid_argument("invalid DV");
break;
}
case p18::CommandType::SetChargingSourcePriority: {
case p18::CommandType::SetChargeSourcePriority: {
GET_ARGS(2);
auto id = static_cast<unsigned>(std::stoul(arguments[0]));
@ -370,14 +370,14 @@ p18::CommandType validate_input(std::string& command,
break;
}
case p18::CommandType::SetOutputModel: {
case p18::CommandType::SetOutputMode: {
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"});
std::array<std::string, 5> allowed({"S", "P", "1", "2", "3"});
long index = index_of(allowed, arguments[1]);
if (index == -1)
throw std::invalid_argument("invalid model");
@ -414,8 +414,8 @@ p18::CommandType validate_input(std::string& command,
break;
}
case p18::CommandType::SetACChargingTimeBucket:
case p18::CommandType::SetACLoadsSupplyTimeBucket: {
case p18::CommandType::SetACChargeTimeBucket:
case p18::CommandType::SetACSupplyTimeBucket: {
GET_ARGS(2);
std::vector<std::string> start = split(arguments[0], ':');

View File

@ -8,60 +8,60 @@
namespace p18 {
const std::map<CommandType, std::string> raw_commands = {
{CommandType::GetProtocolID, "PI"},
{CommandType::GetCurrentTime, "T"},
{CommandType::GetTotalGenerated, "ET"},
{CommandType::GetYearGenerated, "EY"},
{CommandType::GetMonthGenerated, "EM"},
{CommandType::GetDayGenerated, "ED"},
{CommandType::GetSeriesNumber, "ID"},
{CommandType::GetCPUVersion, "VFW"},
{CommandType::GetRatedInformation, "PIRI"},
{CommandType::GetGeneralStatus, "GS"},
{CommandType::GetWorkingMode, "MOD"},
{CommandType::GetFaultsAndWarnings, "FWS"},
{CommandType::GetFlagsAndStatuses, "FLAG"},
{CommandType::GetDefaults, "DI"},
{CommandType::GetAllowedChargingCurrents, "MCHGCR"},
{CommandType::GetAllowedACChargingCurrents, "MUCHGCR"},
{CommandType::GetParallelRatedInformation, "PRI"},
{CommandType::GetParallelGeneralStatus, "PGS"},
{CommandType::GetACChargingTimeBucket, "ACCT"},
{CommandType::GetACLoadsSupplyTimeBucket, "ACLT"},
{CommandType::SetLoads, "LON"},
{CommandType::SetFlag, "P"},
{CommandType::SetDefaults, "PF"},
{CommandType::SetBatteryMaxChargingCurrent, "MCHGC"},
{CommandType::SetBatteryMaxACChargingCurrent, "MUCHGC"},
{CommandType::GetProtocolID, "PI"},
{CommandType::GetCurrentTime, "T"},
{CommandType::GetTotalGenerated, "ET"},
{CommandType::GetYearGenerated, "EY"},
{CommandType::GetMonthGenerated, "EM"},
{CommandType::GetDayGenerated, "ED"},
{CommandType::GetSerialNumber, "ID"},
{CommandType::GetCPUVersion, "VFW"},
{CommandType::GetRatedInformation, "PIRI"},
{CommandType::GetGeneralStatus, "GS"},
{CommandType::GetWorkingMode, "MOD"},
{CommandType::GetFaultsAndWarnings, "FWS"},
{CommandType::GetFlagsAndStatuses, "FLAG"},
{CommandType::GetRatedDefaults, "DI"},
{CommandType::GetAllowedChargeCurrents, "MCHGCR"},
{CommandType::GetAllowedACChargeCurrents, "MUCHGCR"},
{CommandType::GetParallelRatedInformation, "PRI"},
{CommandType::GetParallelGeneralStatus, "PGS"},
{CommandType::GetACChargeTimeBucket, "ACCT"},
{CommandType::GetACSupplyTimeBucket, "ACLT"},
{CommandType::SetACSupply, "LON"},
{CommandType::SetFlag, "P"},
{CommandType::SetDefaults, "PF"},
{CommandType::SetBatteryMaxChargeCurrent, "MCHGC"},
{CommandType::SetBatteryMaxACChargeCurrent, "MUCHGC"},
/* The protocol documentation defines two commands, "F50" and "F60",
but it's identical as if there were just one "F" command with an argument. */
{CommandType::SetACOutputFreq, "F"},
{CommandType::SetBatteryMaxChargingVoltage, "MCHGV"},
{CommandType::SetACOutputRatedVoltage, "V"},
{CommandType::SetOutputSourcePriority, "POP"},
{CommandType::SetBatteryChargingThresholds, "BUCD"},
{CommandType::SetChargingSourcePriority, "PCP"},
{CommandType::SetSolarPowerPriority, "PSP"},
{CommandType::SetACInputVoltageRange, "PGR"},
{CommandType::SetBatteryType, "PBT"},
{CommandType::SetOutputModel, "POPM"},
{CommandType::SetBatteryCutOffVoltage, "PSDV"},
{CommandType::SetSolarConfig, "ID"},
{CommandType::ClearGenerated, "CLE"},
{CommandType::SetDateTime, "DAT"},
{CommandType::SetACChargingTimeBucket, "ACCT"},
{CommandType::SetACLoadsSupplyTimeBucket, "ACLT"},
{CommandType::SetACOutputFreq, "F"},
{CommandType::SetBatteryMaxChargeVoltage, "MCHGV"},
{CommandType::SetACOutputVoltage, "V"},
{CommandType::SetOutputSourcePriority, "POP"},
{CommandType::SetBatteryChargeThresholds, "BUCD"},
{CommandType::SetChargeSourcePriority, "PCP"},
{CommandType::SetSolarPowerPriority, "PSP"},
{CommandType::SetACInputVoltageRange, "PGR"},
{CommandType::SetBatteryType, "PBT"},
{CommandType::SetOutputMode, "POPM"},
{CommandType::SetBatteryCutOffVoltage, "PSDV"},
{CommandType::SetSolarConfig, "ID"},
{CommandType::ClearGenerated, "CLE"},
{CommandType::SetDateTime, "DAT"},
{CommandType::SetACChargeTimeBucket, "ACCT"},
{CommandType::SetACSupplyTimeBucket, "ACLT"},
};
const std::array<int, 5> ac_output_rated_voltages = {202, 208, 220, 230, 240};
const std::array<int, 5> ac_output_voltages = {202, 208, 220, 230, 240};
const std::array<float, 8> bat_ac_recharging_voltages_12v = {11, 11.3, 11.5, 11.8, 12, 12.3, 12.5, 12.8};
const std::array<float, 8> bat_ac_recharging_voltages_24v = {22, 22.5, 23, 23.5, 24, 24.5, 25, 25.5};
const std::array<float, 8> bat_ac_recharging_voltages_48v = {44, 45, 46, 47, 48, 49, 50, 51};
const std::array<float, 8> bat_ac_recharge_voltages_12v = {11, 11.3, 11.5, 11.8, 12, 12.3, 12.5, 12.8};
const std::array<float, 8> bat_ac_recharge_voltages_24v = {22, 22.5, 23, 23.5, 24, 24.5, 25, 25.5};
const std::array<float, 8> bat_ac_recharge_voltages_48v = {44, 45, 46, 47, 48, 49, 50, 51};
const std::array<float, 12> bat_ac_redischarging_voltages_12v = {0, 12, 12.3, 12.5, 12.8, 13, 13.3, 13.5, 13.8, 14, 14.3, 14.5};
const std::array<float, 12> bat_ac_redischarging_voltages_24v = {0, 24, 24.5, 25, 25.5, 26, 26.5, 27, 27.5, 28, 28.5, 29};
const std::array<float, 12> bat_ac_redischarging_voltages_48v = {0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58};
const std::array<float, 12> bat_ac_redischarge_voltages_12v = {0, 12, 12.3, 12.5, 12.8, 13, 13.3, 13.5, 13.8, 14, 14.3, 14.5};
const std::array<float, 12> bat_ac_redischarge_voltages_24v = {0, 24, 24.5, 25, 25.5, 26, 26.5, 27, 27.5, 28, 28.5, 29};
const std::array<float, 12> bat_ac_redischarge_voltages_48v = {0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58};
const std::map<int, std::string> fault_codes = {
{1, "Fan is locked"},
@ -133,13 +133,13 @@ ENUM_STR(OutputSourcePriority) {
ENUM_STR_DEFAULT;
}
ENUM_STR(ChargerSourcePriority) {
ENUM_STR(ChargeSourcePriority) {
switch (val) {
case ChargerSourcePriority::SolarFirst:
case ChargeSourcePriority::SolarFirst:
return os << "Solar-First";
case ChargerSourcePriority::SolarAndUtility:
case ChargeSourcePriority::SolarAndUtility:
return os << "Solar-and-Utility";
case ChargerSourcePriority::SolarOnly:
case ChargeSourcePriority::SolarOnly:
return os << "Solar-only";
}
ENUM_STR_DEFAULT;
@ -161,18 +161,18 @@ ENUM_STR(Topology) {
ENUM_STR_DEFAULT;
}
ENUM_STR(OutputModelSetting) {
ENUM_STR(OutputMode) {
switch (val) {
case OutputModelSetting::SingleModule:
return os << "Single module";
case OutputModelSetting::ParallelOutput:
case OutputMode::SingleOutput:
return os << "Single output";
case OutputMode::ParallelOutput:
return os << "Parallel output";
case OutputModelSetting::Phase1OfThreePhaseOutput:
return os << "Phase 1 of three phase output";
case OutputModelSetting::Phase2OfThreePhaseOutput:
return os << "Phase 2 of three phase output";
case OutputModelSetting::Phase3OfThreePhaseOutput:
return os << "Phase 3 of three phase";
case OutputMode::Phase_1_of_3:
return os << "Phase 1 of 3-phase output";
case OutputMode::Phase_2_of_3:
return os << "Phase 2 of 3-phase output";
case OutputMode::Phase_3_of_3:
return os << "Phase 3 of 3-phase";
}
ENUM_STR_DEFAULT;
}

View File

@ -13,15 +13,15 @@ namespace p18 {
extern const std::map<CommandType, std::string> raw_commands;
extern const std::array<int, 5> ac_output_rated_voltages;
extern const std::array<int, 5> ac_output_voltages;
extern const std::array<float, 8> bat_ac_recharging_voltages_12v;
extern const std::array<float, 8> bat_ac_recharging_voltages_24v;
extern const std::array<float, 8> bat_ac_recharging_voltages_48v;
extern const std::array<float, 8> bat_ac_recharge_voltages_12v;
extern const std::array<float, 8> bat_ac_recharge_voltages_24v;
extern const std::array<float, 8> bat_ac_recharge_voltages_48v;
extern const std::array<float, 12> bat_ac_redischarging_voltages_12v;
extern const std::array<float, 12> bat_ac_redischarging_voltages_24v;
extern const std::array<float, 12> bat_ac_redischarging_voltages_48v;
extern const std::array<float, 12> bat_ac_redischarge_voltages_12v;
extern const std::array<float, 12> bat_ac_redischarge_voltages_24v;
extern const std::array<float, 12> bat_ac_redischarge_voltages_48v;
extern const std::map<int, std::string> fault_codes;

View File

@ -27,6 +27,19 @@ typedef formatter::TableItem<VariantHolder> LINE;
using formatter::Unit;
/**
* Helpers
*/
std::ostream& operator<<(std::ostream& os, FieldLength fl) {
if (fl.min_ == fl.max_)
os << fl.min_;
else
os << "[" << fl.min_ << ", " << fl.max_ << "]";
return os;
}
/**
* Base responses
*/
@ -58,23 +71,32 @@ size_t GetResponse::getDataSize() const {
return rawSize_ - 5;
}
std::vector<std::string> GetResponse::getList(std::vector<size_t> itemLengths) const {
std::vector<std::string> GetResponse::getList(std::vector<FieldLength> itemLengths, int expectAtLeast) const {
std::string buf(getData(), getDataSize());
auto list = ::split(buf, ',');
if (expectAtLeast == -1)
expectAtLeast = (int)itemLengths.size();
if (!itemLengths.empty()) {
// check list length
if (list.size() < itemLengths.size()) {
if (list.size() < expectAtLeast) {
std::ostringstream error;
error << "while parsing " << demangle_type_name(typeid(*this).name());
error << ": list is expected to be " << itemLengths.size() << " items long, ";
error << ": list is expected to be " << expectAtLeast << " items long, ";
error << "got only " << list.size() << " items";
throw ParseError(error.str());
}
// check each item's length
for (int i = 0; i < itemLengths.size(); i++) {
if (list[i].size() != itemLengths[i]) {
for (int i = 0; i < list.size(); i++) {
if (i >= itemLengths.size()) {
myerr << "while parsing " << demangle_type_name(typeid(*this).name())
<< ": item " << i << " is not expected";
break;
}
if (!itemLengths[i].validate(list[i].size())) {
std::ostringstream error;
error << "while parsing " << demangle_type_name(typeid(*this).name());
error << ": item " << i << " is expected to be " << itemLengths[i] << " characters long, ";
@ -200,7 +222,7 @@ formattable_ptr TotalGenerated::format(formatter::Format format) {
}
void SeriesNumber::unpack() {
void SerialNumber::unpack() {
auto data = getData();
std::string buf(data, 2);
@ -209,9 +231,9 @@ void SeriesNumber::unpack() {
id = std::string(data+2, len);
}
formattable_ptr SeriesNumber::format(formatter::Format format) {
formattable_ptr SerialNumber::format(formatter::Format format) {
RETURN_TABLE({
LINE("sn", "Series number", id)
LINE("sn", "Serial number", id)
});
}
@ -249,7 +271,7 @@ void RatedInformation::unpack() {
3, // LLL
3, // MMM
1, // N
2, // OO
FieldLength(2, 3), // OO
3, // PPP
1, // O
1, // R
@ -276,15 +298,15 @@ void RatedInformation::unpack() {
battery_bulk_voltage = stou(list[11]);
battery_float_voltage = stou(list[12]);
battery_type = static_cast<BatteryType>(stou(list[13]));
max_ac_charging_current = stou(list[14]);
max_charging_current = stou(list[15]);
max_ac_charge_current = stou(list[14]);
max_charge_current = stou(list[15]);
input_voltage_range = static_cast<InputVoltageRange>(stou(list[16]));
output_source_priority = static_cast<OutputModelSetting>(stou(list[17]));
charger_source_priority = static_cast<ChargerSourcePriority>(stou(list[18]));
output_source_priority = static_cast<OutputSourcePriority>(stou(list[17]));
charge_source_priority = static_cast<ChargeSourcePriority>(stou(list[18]));
parallel_max_num = stou(list[19]);
machine_type = static_cast<MachineType>(stou(list[20]));
topology = static_cast<Topology>(stou(list[21]));
output_model_setting = static_cast<OutputModelSetting>(stou(list[22]));
output_mode = static_cast<OutputMode>(stou(list[22]));
solar_power_priority = static_cast<SolarPowerPriority>(stou(list[23]));
mppt = list[24];
}
@ -305,15 +327,15 @@ formattable_ptr RatedInformation::format(formatter::Format format) {
LINE("battery_bulk_voltage", "Battery bulk voltage", battery_bulk_voltage / 10.0, Unit::V),
LINE("battery_float_voltage", "Battery float voltage", battery_float_voltage / 10.0, Unit::V),
LINE("battery_type", "Battery type", battery_type),
LINE("max_charging_current", "Max charging current", max_charging_current, Unit::A),
LINE("max_ac_charging_current", "Max AC charging current", max_ac_charging_current, Unit::A),
LINE("max_charge_current", "Max charge current", max_charge_current, Unit::A),
LINE("max_ac_charge_current", "Max AC charge current", max_ac_charge_current, Unit::A),
LINE("input_voltage_range", "Input voltage range", input_voltage_range),
LINE("output_source_priority", "Output source priority", output_source_priority),
LINE("charge_source_priority", "Charge source priority", charger_source_priority),
LINE("charge_source_priority", "Charge source priority", charge_source_priority),
LINE("parallel_max_num", "Parallel max num", parallel_max_num),
LINE("machine_type", "Machine type", machine_type),
LINE("topology", "Topology", topology),
LINE("output_model_setting", "Output model setting", output_model_setting),
LINE("output_mode", "Output mode", output_mode),
LINE("solar_power_priority", "Solar power priority", solar_power_priority),
LINE("mppt", "MPPT string", mppt)
});
@ -363,7 +385,7 @@ void GeneralStatus::unpack() {
battery_voltage_scc = stou(list[8]);
battery_voltage_scc2 = stou(list[9]);
battery_discharge_current = stou(list[10]);
battery_charging_current = stou(list[11]);
battery_charge_current = stou(list[11]);
battery_capacity = stou(list[12]);
inverter_heat_sink_temp = stou(list[13]);
mppt1_charger_temp = stou(list[14]);
@ -394,8 +416,8 @@ formattable_ptr GeneralStatus::format(formatter::Format format) {
LINE("battery_voltage", "Battery voltage", battery_voltage / 10.0, Unit::V),
LINE("battery_voltage_scc", "Battery voltage from SCC", battery_voltage_scc / 10.0, Unit::V),
LINE("battery_voltage_scc2", "Battery voltage from SCC2", battery_voltage_scc2 / 10.0, Unit::V),
LINE("battery_discharging_current", "Battery discharging current", battery_discharge_current, Unit::A),
LINE("battery_charging_current", "Battery charging current", battery_charging_current, Unit::A),
LINE("battery_discharge_current", "Battery discharge current", battery_discharge_current, Unit::A),
LINE("battery_charge_current", "Battery charge current", battery_charge_current, Unit::A),
LINE("battery_capacity", "Battery capacity", battery_capacity, Unit::Percentage),
LINE("inverter_heat_sink_temp", "Inverter heat sink temperature", inverter_heat_sink_temp, Unit::Celsius),
LINE("mppt1_charger_temp", "MPPT1 charger temperature", mppt1_charger_temp, Unit::Celsius),
@ -524,7 +546,7 @@ formattable_ptr FlagsAndStatuses::format(formatter::Format format) {
}
void Defaults::unpack() {
void RatedDefaults::unpack() {
auto list = getList({
4, // AAAA
3, // BBB
@ -560,14 +582,14 @@ void Defaults::unpack() {
charging_bulk_voltage = stou(list[5]);
battery_recharge_voltage = stou(list[6]);
battery_redischarge_voltage = stou(list[7]);
max_charging_current = stou(list[8]);
max_ac_charging_current = stou(list[9]);
max_charge_current = stou(list[8]);
max_ac_charge_current = stou(list[9]);
battery_type = static_cast<BatteryType>(stou(list[10]));
output_source_priority = static_cast<OutputSourcePriority>(stou(list[11]));
charger_source_priority = static_cast<ChargerSourcePriority>(stou(list[12]));
charge_source_priority = static_cast<ChargeSourcePriority>(stou(list[12]));
solar_power_priority = static_cast<SolarPowerPriority>(stou(list[13]));
machine_type = static_cast<MachineType>(stou(list[14]));
output_model_setting = static_cast<OutputModelSetting>(stou(list[15]));
output_mode = static_cast<OutputMode>(stou(list[15]));
flag_buzzer = stou(list[16]) > 0;
flag_overload_restart = stou(list[17]) > 0;
flag_over_temp_restart = stou(list[18]) > 0;
@ -578,7 +600,7 @@ void Defaults::unpack() {
flag_lcd_escape_to_default_page_after_1min_timeout = stou(list[23]) > 0;
}
formattable_ptr Defaults::format(formatter::Format format) {
formattable_ptr RatedDefaults::format(formatter::Format format) {
RETURN_TABLE({
LINE("ac_output_voltage", "AC output voltage", ac_output_voltage / 10.0, Unit::V),
LINE("ac_output_freq", "AC output frequency", ac_output_freq / 10.0, Unit::Hz),
@ -586,16 +608,16 @@ formattable_ptr Defaults::format(formatter::Format format) {
LINE("battery_under_voltage", "Battery under voltage", battery_under_voltage / 10.0, Unit::V),
LINE("battery_bulk_voltage", "Charging bulk voltage", charging_bulk_voltage / 10.0, Unit::V),
LINE("battery_float_voltage", "Charging float voltage", charging_float_voltage / 10.0, Unit::V),
LINE("battery_recharging_voltage", "Battery re-charging voltage", battery_recharge_voltage / 10.0, Unit::V),
LINE("battery_redischarging_voltage", "Battery re-discharging voltage", battery_redischarge_voltage / 10.0, Unit::V),
LINE("max_charging_current", "Max charging current", max_charging_current, Unit::A),
LINE("max_ac_charging_current", "Max AC charging current", max_ac_charging_current, Unit::A),
LINE("battery_recharge_voltage", "Battery re-charge voltage", battery_recharge_voltage / 10.0, Unit::V),
LINE("battery_redischarge_voltage", "Battery re-discharge voltage", battery_redischarge_voltage / 10.0, Unit::V),
LINE("max_charge_current", "Max charge current", max_charge_current, Unit::A),
LINE("max_ac_charge_current", "Max AC charge current", max_ac_charge_current, Unit::A),
LINE("battery_type", "Battery type", battery_type),
LINE("output_source_priority", "Output source priority", output_source_priority),
LINE("charger_source_priority", "Charger source priority", charger_source_priority),
LINE("charge_source_priority", "Charge source priority", charge_source_priority),
LINE("solar_power_priority", "Solar power priority", solar_power_priority),
LINE("machine_type", "Machine type", machine_type),
LINE("output_model_setting", "Output model setting", output_model_setting),
LINE("output_mode", "Output mode", output_mode),
LINE("buzzer_flag", "Buzzer flag", flag_buzzer),
LINE("overload_bypass_flag", "Overload bypass function flag", flag_overload_bypass),
LINE("escape_to_default_screen_after_1min_timeout_flag", "Escape to default screen after 1min timeout flag", flag_lcd_escape_to_default_page_after_1min_timeout),
@ -607,14 +629,14 @@ formattable_ptr Defaults::format(formatter::Format format) {
})
}
void AllowedChargingCurrents::unpack() {
void AllowedChargeCurrents::unpack() {
auto list = getList({});
for (const std::string& i: list) {
amps.emplace_back(stou(i));
}
}
formattable_ptr AllowedChargingCurrents::format(formatter::Format format) {
formattable_ptr AllowedChargeCurrents::format(formatter::Format format) {
std::vector<formatter::ListItem<VariantHolder>> v;
for (const auto& n: amps)
v.emplace_back(n);
@ -632,27 +654,32 @@ void ParallelRatedInformation::unpack() {
20, // CCCCCCCCCCCCCCCCCCCC
1, // D
3, // EEE
2, // FF
// FF
// note: protocol documentation says that the following field is 2 bytes long,
// but actual tests of the 6kw unit shows it can be 3 bytes long
FieldLength(2, 3),
1 // G
});
parallel_id_connection_status = static_cast<ParallelConnectionStatus>(stou(list[0]));
parallel_connection_status = static_cast<ParallelConnectionStatus>(stou(list[0]));
serial_number_valid_length = stou(list[1]);
serial_number = std::string(list[2], serial_number_valid_length);
charger_source_priority = static_cast<ChargerSourcePriority>(stou(list[3]));
max_charging_current = stou(list[4]);
max_ac_charging_current = stou(list[5]);
output_model_setting = static_cast<OutputModelSetting>(stou(list[6]));
serial_number = std::string(list[2], 0, serial_number_valid_length);
charge_source_priority = static_cast<ChargeSourcePriority>(stou(list[3]));
max_charge_current = stou(list[4]);
max_ac_charge_current = stou(list[5]);
output_mode = static_cast<OutputMode>(stou(list[6]));
}
formattable_ptr ParallelRatedInformation::format(formatter::Format format) {
RETURN_TABLE({
LINE("parallel_id_connection_status", "Parallel ID connection status", parallel_id_connection_status),
LINE("parallel_connection_status", "Parallel connection status", parallel_connection_status),
LINE("serial_number", "Serial number", serial_number),
LINE("charger_source_priority", "Charger source priority", charger_source_priority),
LINE("max_charging_current", "Max charging current", max_charging_current, Unit::A),
LINE("max_ac_charging_current", "Max AC charging current", max_ac_charging_current, Unit::A),
LINE("output_model_setting", "Output model setting", output_model_setting),
LINE("charge_source_priority", "Charge source priority", charge_source_priority),
LINE("max_charge_current", "Max charge current", max_charge_current, Unit::A),
LINE("max_ac_charge_current", "Max AC charge current", max_ac_charge_current, Unit::A),
LINE("output_mode", "Output mode", output_mode),
})
}
@ -682,15 +709,19 @@ void ParallelGeneralStatus::unpack() {
4, // TTTT
4, // UUUU
1, // V
// FIXME: marked red in the docs
1, // W
// FIXME: marked red in the docs
1, // X
1, // Y
1, // Z
1, // a
3, // bbb. Note: this one is marked in red in the doc. I don't know what that means.
});
3, // bbb. Note: this one is marked in red in the doc. Apparently it means
// that it may be missing on some models, see
// https://github.com/gch1p/inverter-tools/issues/1#issuecomment-981158688
}, 28);
parallel_id_connection_status = static_cast<ParallelConnectionStatus>(stou(list[0]));
parallel_connection_status = static_cast<ParallelConnectionStatus>(stou(list[0]));
work_mode = static_cast<p18::WorkingMode>(stou(list[1]));
fault_code = stou(list[2]);
grid_voltage = stou(list[3]);
@ -705,8 +736,8 @@ void ParallelGeneralStatus::unpack() {
total_output_load_percent = stou(list[12]);
battery_voltage = stou(list[13]);
battery_discharge_current = stou(list[14]);
battery_charging_current = stou(list[15]);
total_battery_charging_current = stou(list[16]);
battery_charge_current = stou(list[15]);
total_battery_charge_current = stou(list[16]);
battery_capacity = stou(list[17]);
pv1_input_power = stou(list[18]);
pv2_input_power = stou(list[19]);
@ -718,12 +749,15 @@ void ParallelGeneralStatus::unpack() {
battery_power_direction = static_cast<BatteryPowerDirection>(stou(list[25]));
dc_ac_power_direction = static_cast<DC_AC_PowerDirection>(stou(list[26]));
line_power_direction = static_cast<LinePowerDirection>(stou(list[27]));
max_temp = stou(list[28]);
if (list.size() >= 29) {
max_temp_present = true;
max_temp = stou(list[28]);
}
}
formattable_ptr ParallelGeneralStatus::format(formatter::Format format) {
RETURN_TABLE({
LINE("parallel_id_connection_status", "Parallel ID connection status", parallel_id_connection_status),
auto table = new formatter::Table<VariantHolder>(format, {
LINE("parallel_connection_status", "Parallel connection status", parallel_connection_status),
LINE("mode", "Working mode", work_mode),
LINE("fault_code", "Fault code", fault_code),
LINE("grid_voltage", "Grid voltage", grid_voltage / 10.0, Unit::V),
@ -738,23 +772,32 @@ formattable_ptr ParallelGeneralStatus::format(formatter::Format format) {
LINE("total_output_load_percent", "Total output load percent", total_output_load_percent, Unit::Percentage),
LINE("battery_voltage", "Battery voltage", battery_voltage / 10.0, Unit::V),
LINE("battery_discharge_current", "Battery discharge current", battery_discharge_current, Unit::A),
LINE("battery_charging_current", "Battery charging current", battery_charging_current, Unit::A),
LINE("pv1_input_power", "PV1 Input power", pv1_input_power, Unit::Wh),
LINE("pv2_input_power", "PV2 Input power", pv2_input_power, Unit::Wh),
LINE("pv1_input_voltage", "PV1 Input voltage", pv1_input_voltage / 10.0, Unit::V),
LINE("pv2_input_voltage", "PV2 Input voltage", pv2_input_voltage / 10.0, Unit::V),
LINE("battery_charge_current", "Battery charge current", battery_charge_current, Unit::A),
LINE("total_battery_charge_current", "Total battery charge current", total_battery_charge_current, Unit::A),
LINE("battery_capacity", "Battery capacity", battery_capacity, Unit::Percentage),
LINE("pv1_input_power", "PV1 input power", pv1_input_power, Unit::Wh),
LINE("pv2_input_power", "PV2 input power", pv2_input_power, Unit::Wh),
LINE("pv1_input_voltage", "PV1 input voltage", pv1_input_voltage / 10.0, Unit::V),
LINE("pv2_input_voltage", "PV2 input voltage", pv2_input_voltage / 10.0, Unit::V),
LINE("mppt1_charger_status", "MPPT1 charger status", mppt1_charger_status),
LINE("mppt2_charger_status", "MPPT2 charger status", mppt2_charger_status),
LINE("load_connected", "Load connection", load_connected),
LINE("battery_power_direction", "Battery power direction", battery_power_direction),
LINE("dc_ac_power_direction", "DC/AC power direction", dc_ac_power_direction),
LINE("line_power_direction", "Line power direction", line_power_direction),
LINE("max_temp", "Max. temperature", max_temp),
})
});
if (max_temp_present) {
table->push(
LINE("max_temp", "Max. temperature", max_temp)
);
}
return std::shared_ptr<formatter::Table<VariantHolder>>(table);
}
void ACChargingTimeBucket::unpack() {
void ACChargeTimeBucket::unpack() {
auto list = getList({4 /* AAAA */, 4 /* BBBB */});
start_h = stouh(list[0].substr(0, 2));
@ -771,7 +814,7 @@ static inline std::string get_time(unsigned short h, unsigned short m) {
return buf.str();
}
formattable_ptr ACChargingTimeBucket::format(formatter::Format format) {
formattable_ptr ACChargeTimeBucket::format(formatter::Format format) {
RETURN_TABLE({
LINE("start_time", "Start time", get_time(start_h, start_m)),
LINE("end_time", "End time", get_time(end_h, end_m)),

View File

@ -32,7 +32,7 @@ typedef std::variant<
std::string,
p18::BatteryType,
p18::BatteryPowerDirection,
p18::ChargerSourcePriority,
p18::ChargeSourcePriority,
p18::DC_AC_PowerDirection,
p18::InputVoltageRange,
p18::LinePowerDirection,
@ -40,7 +40,7 @@ typedef std::variant<
p18::MPPTChargerStatus,
p18::Topology,
p18::OutputSourcePriority,
p18::OutputModelSetting,
p18::OutputMode,
p18::ParallelConnectionStatus,
p18::SolarPowerPriority,
p18::WorkingMode,
@ -62,7 +62,7 @@ public:
VariantHolder(std::string v) : v_(v) {}
VariantHolder(p18::BatteryType v) : v_(v) {}
VariantHolder(p18::BatteryPowerDirection v) : v_(v) {}
VariantHolder(p18::ChargerSourcePriority v) : v_(v) {}
VariantHolder(p18::ChargeSourcePriority v) : v_(v) {}
VariantHolder(p18::DC_AC_PowerDirection v) : v_(v) {}
VariantHolder(p18::InputVoltageRange v) : v_(v) {}
VariantHolder(p18::LinePowerDirection v) : v_(v) {}
@ -70,7 +70,7 @@ public:
VariantHolder(p18::MPPTChargerStatus v) : v_(v) {}
VariantHolder(p18::Topology v) : v_(v) {}
VariantHolder(p18::OutputSourcePriority v) : v_(v) {}
VariantHolder(p18::OutputModelSetting v) : v_(v) {}
VariantHolder(p18::OutputMode v) : v_(v) {}
VariantHolder(p18::ParallelConnectionStatus v) : v_(v) {}
VariantHolder(p18::SolarPowerPriority v) : v_(v) {}
VariantHolder(p18::WorkingMode v) : v_(v) {}
@ -89,7 +89,7 @@ public:
bool isEnum =
std::holds_alternative<p18::BatteryType>(v_) ||
std::holds_alternative<p18::BatteryPowerDirection>(v_) ||
std::holds_alternative<p18::ChargerSourcePriority>(v_) ||
std::holds_alternative<p18::ChargeSourcePriority>(v_) ||
std::holds_alternative<p18::DC_AC_PowerDirection>(v_) ||
std::holds_alternative<p18::InputVoltageRange>(v_) ||
std::holds_alternative<p18::LinePowerDirection>(v_) ||
@ -97,7 +97,7 @@ public:
std::holds_alternative<p18::MPPTChargerStatus>(v_) ||
std::holds_alternative<p18::Topology>(v_) ||
std::holds_alternative<p18::OutputSourcePriority>(v_) ||
std::holds_alternative<p18::OutputModelSetting>(v_) ||
std::holds_alternative<p18::OutputMode>(v_) ||
std::holds_alternative<p18::ParallelConnectionStatus>(v_) ||
std::holds_alternative<p18::SolarPowerPriority>(v_) ||
std::holds_alternative<p18::WorkingMode>(v_) ||
@ -124,6 +124,26 @@ public:
};
/**
* Some helpers
*/
class FieldLength {
protected:
size_t min_;
size_t max_;
public:
FieldLength(size_t n) : min_(n), max_(n) {}
FieldLength(size_t min, size_t max) : min_(min), max_(max) {}
[[nodiscard]] bool validate(size_t len) const {
return len >= min_ && len <= max_;
}
friend std::ostream& operator<<(std::ostream& os, FieldLength fl);
};
/**
* Base responses
*/
@ -145,7 +165,7 @@ class GetResponse : public BaseResponse {
protected:
const char* getData() const;
size_t getDataSize() const;
std::vector<std::string> getList(std::vector<size_t> itemLengths) const;
std::vector<std::string> getList(std::vector<FieldLength> itemLengths, int expectAtLeast = -1) const;
public:
using BaseResponse::BaseResponse;
@ -229,7 +249,7 @@ public:
using TotalGenerated::TotalGenerated;
};
class SeriesNumber : public GetResponse {
class SerialNumber : public GetResponse {
public:
using GetResponse::GetResponse;
void unpack() override;
@ -269,15 +289,15 @@ public:
unsigned battery_bulk_voltage; /* unit: 0.1V */
unsigned battery_float_voltage; /* unit: 0.1V */
p18::BatteryType battery_type;
unsigned max_ac_charging_current; /* unit: A */
unsigned max_charging_current; /* unit: A */
unsigned max_ac_charge_current; /* unit: A */
unsigned max_charge_current; /* unit: A */
p18::InputVoltageRange input_voltage_range;
p18::OutputModelSetting output_source_priority;
p18::ChargerSourcePriority charger_source_priority;
p18::OutputSourcePriority output_source_priority;
p18::ChargeSourcePriority charge_source_priority;
unsigned parallel_max_num;
p18::MachineType machine_type;
p18::Topology topology;
p18::OutputModelSetting output_model_setting;
p18::OutputMode output_mode;
p18::SolarPowerPriority solar_power_priority;
std::string mppt;
};
@ -299,7 +319,7 @@ public:
unsigned battery_voltage_scc; /* unit: 0.1V */
unsigned battery_voltage_scc2; /* unit: 0.1V */
unsigned battery_discharge_current; /* unit: A */
unsigned battery_charging_current; /* unit: A */
unsigned battery_charge_current; /* unit: A */
unsigned battery_capacity; /* unit: % */
unsigned inverter_heat_sink_temp; /* unit: C */
unsigned mppt1_charger_temp; /* unit: C */
@ -369,7 +389,7 @@ public:
char reserved = '0';
};
class Defaults : public GetResponse {
class RatedDefaults : public GetResponse {
public:
using GetResponse::GetResponse;
void unpack() override;
@ -383,14 +403,14 @@ public:
unsigned charging_bulk_voltage = 0;
unsigned battery_recharge_voltage = 0;
unsigned battery_redischarge_voltage = 0;
unsigned max_charging_current = 0;
unsigned max_ac_charging_current = 0;
unsigned max_charge_current = 0;
unsigned max_ac_charge_current = 0;
p18::BatteryType battery_type = static_cast<BatteryType>(0);
p18::OutputSourcePriority output_source_priority = static_cast<OutputSourcePriority>(0);
p18::ChargerSourcePriority charger_source_priority = static_cast<ChargerSourcePriority>(0);
p18::ChargeSourcePriority charge_source_priority = static_cast<ChargeSourcePriority>(0);
p18::SolarPowerPriority solar_power_priority = static_cast<SolarPowerPriority>(0);
p18::MachineType machine_type = static_cast<MachineType>(0);
p18::OutputModelSetting output_model_setting = static_cast<OutputModelSetting>(0);
p18::OutputMode output_mode = static_cast<OutputMode>(0);
bool flag_buzzer = false;
bool flag_overload_restart = false;
bool flag_over_temp_restart = false;
@ -401,7 +421,7 @@ public:
bool flag_lcd_escape_to_default_page_after_1min_timeout = false;
};
class AllowedChargingCurrents : public GetResponse {
class AllowedChargeCurrents : public GetResponse {
public:
using GetResponse::GetResponse;
void unpack() override;
@ -410,9 +430,9 @@ public:
std::vector<unsigned> amps;
};
class AllowedACChargingCurrents : public AllowedChargingCurrents {
class AllowedACChargeCurrents : public AllowedChargeCurrents {
public:
using AllowedChargingCurrents::AllowedChargingCurrents;
using AllowedChargeCurrents::AllowedChargeCurrents;
};
class ParallelRatedInformation : public GetResponse {
@ -421,13 +441,13 @@ public:
void unpack() override;
formattable_ptr format(formatter::Format format) override;
p18::ParallelConnectionStatus parallel_id_connection_status = static_cast<ParallelConnectionStatus>(0);
p18::ParallelConnectionStatus parallel_connection_status = static_cast<ParallelConnectionStatus>(0);
unsigned serial_number_valid_length = 0;
std::string serial_number;
p18::ChargerSourcePriority charger_source_priority = static_cast<ChargerSourcePriority>(0);
unsigned max_ac_charging_current = 0; // unit: A
unsigned max_charging_current = 0; // unit: A
p18::OutputModelSetting output_model_setting = static_cast<OutputModelSetting>(0);
p18::ChargeSourcePriority charge_source_priority = static_cast<ChargeSourcePriority>(0);
unsigned max_ac_charge_current = 0; // unit: A
unsigned max_charge_current = 0; // unit: A
p18::OutputMode output_mode = static_cast<OutputMode>(0);
};
class ParallelGeneralStatus : public GetResponse {
@ -436,7 +456,7 @@ public:
void unpack() override;
formattable_ptr format(formatter::Format format) override;
p18::ParallelConnectionStatus parallel_id_connection_status;
p18::ParallelConnectionStatus parallel_connection_status;
p18::WorkingMode work_mode;
unsigned fault_code;
unsigned grid_voltage; /* unit: 0.1V */
@ -451,8 +471,8 @@ public:
unsigned total_output_load_percent; /* unit: % */
unsigned battery_voltage; /* unit: 0.1V */
unsigned battery_discharge_current; /* unit: A */
unsigned battery_charging_current; /* unit: A */
unsigned total_battery_charging_current; /* unit: A */
unsigned battery_charge_current; /* unit: A */
unsigned total_battery_charge_current; /* unit: A */
unsigned battery_capacity; /* unit: % */
unsigned pv1_input_power; /* unit: W */
unsigned pv2_input_power; /* unit: W */
@ -464,10 +484,12 @@ public:
p18::BatteryPowerDirection battery_power_direction;
p18::DC_AC_PowerDirection dc_ac_power_direction;
p18::LinePowerDirection line_power_direction;
bool max_temp_present = false;
unsigned max_temp; /* unit: C */
};
class ACChargingTimeBucket : public GetResponse {
class ACChargeTimeBucket : public GetResponse {
public:
using GetResponse::GetResponse;
void unpack() override;
@ -479,9 +501,9 @@ public:
unsigned short end_m = 0;
};
class ACLoadsSupplyTimeBucket : public ACChargingTimeBucket {
class ACSupplyTimeBucket : public ACChargeTimeBucket {
public:
using ACChargingTimeBucket::ACChargingTimeBucket;
using ACChargeTimeBucket::ACChargeTimeBucket;
};
} // namespace p18

View File

@ -17,41 +17,41 @@ enum class CommandType {
GetYearGenerated,
GetMonthGenerated,
GetDayGenerated,
GetSeriesNumber,
GetSerialNumber,
GetCPUVersion,
GetRatedInformation,
GetGeneralStatus,
GetWorkingMode,
GetFaultsAndWarnings,
GetFlagsAndStatuses,
GetDefaults,
GetAllowedChargingCurrents,
GetAllowedACChargingCurrents,
GetRatedDefaults,
GetAllowedChargeCurrents,
GetAllowedACChargeCurrents,
GetParallelRatedInformation,
GetParallelGeneralStatus,
GetACChargingTimeBucket,
GetACLoadsSupplyTimeBucket,
SetLoads = 100,
GetACChargeTimeBucket,
GetACSupplyTimeBucket,
SetACSupply = 100,
SetFlag,
SetDefaults,
SetBatteryMaxChargingCurrent,
SetBatteryMaxACChargingCurrent,
SetBatteryMaxChargeCurrent,
SetBatteryMaxACChargeCurrent,
SetACOutputFreq,
SetBatteryMaxChargingVoltage,
SetACOutputRatedVoltage,
SetBatteryMaxChargeVoltage,
SetACOutputVoltage,
SetOutputSourcePriority,
SetBatteryChargingThresholds, /* Battery re-charging and re-discharing voltage when utility is available */
SetChargingSourcePriority,
SetBatteryChargeThresholds, /* Battery re-charge and re-discharge voltage when utility is available */
SetChargeSourcePriority,
SetSolarPowerPriority,
SetACInputVoltageRange,
SetBatteryType,
SetOutputModel,
SetOutputMode,
SetBatteryCutOffVoltage,
SetSolarConfig,
ClearGenerated,
SetDateTime,
SetACChargingTimeBucket,
SetACLoadsSupplyTimeBucket,
SetACChargeTimeBucket,
SetACSupplyTimeBucket,
};
enum class BatteryType {
@ -73,12 +73,12 @@ enum class OutputSourcePriority {
};
ENUM_STR(OutputSourcePriority);
enum class ChargerSourcePriority {
enum class ChargeSourcePriority {
SolarFirst = 0,
SolarAndUtility = 1,
SolarOnly = 2,
};
ENUM_STR(ChargerSourcePriority);
ENUM_STR(ChargeSourcePriority);
enum class MachineType {
OffGridTie = 0,
@ -92,14 +92,14 @@ enum class Topology {
};
ENUM_STR(Topology);
enum class OutputModelSetting {
SingleModule = 0,
ParallelOutput = 1,
Phase1OfThreePhaseOutput = 2,
Phase2OfThreePhaseOutput = 3,
Phase3OfThreePhaseOutput = 4,
enum class OutputMode {
SingleOutput = 0,
ParallelOutput = 1,
Phase_1_of_3 = 2,
Phase_2_of_3 = 3,
Phase_3_of_3 = 4,
};
ENUM_STR(OutputModelSetting);
ENUM_STR(OutputMode);
enum class SolarPowerPriority {
BatteryLoadUtility = 0,

View File

@ -72,8 +72,11 @@ public:
static u16 GET_HID_REPORT_SIZE(size_t size);
USBDevice(u16 vendorId, u16 productId);
explicit USBDevice(const std::string& path);
~USBDevice();
static inline void init();
size_t read(u8* buf, size_t bufSize) override;
size_t write(const u8* data, size_t dataSize) override;
};

View File

@ -12,14 +12,24 @@
namespace voltronic {
USBDevice::USBDevice(u16 vendorId, u16 productId) {
if (hid_init() != 0)
throw DeviceError("hidapi initialization failure");
init();
device_ = hid_open(vendorId, productId, nullptr);
if (!device_)
throw DeviceError("failed to create hidapi device");
}
USBDevice::USBDevice(const std::string& path) {
init();
device_ = hid_open_path(path.c_str());
if (!device_)
throw DeviceError("failed to create hidapi device");
}
void USBDevice::init() {
if (hid_init() != 0)
throw DeviceError("hidapi initialization failure");
}
USBDevice::~USBDevice() {
if (device_)
hid_close(device_);