summaryrefslogtreecommitdiff
path: root/src/protocol_18
diff options
context:
space:
mode:
Diffstat (limited to 'src/protocol_18')
-rw-r--r--src/protocol_18/client.cc216
-rw-r--r--src/protocol_18/client.h32
-rw-r--r--src/protocol_18/defines.cc262
-rw-r--r--src/protocol_18/defines.h32
-rw-r--r--src/protocol_18/functions.cc12
-rw-r--r--src/protocol_18/functions.h12
-rw-r--r--src/protocol_18/input.cc341
-rw-r--r--src/protocol_18/input.h21
-rw-r--r--src/protocol_18/response.cc808
-rw-r--r--src/protocol_18/response.cc.orig812
-rw-r--r--src/protocol_18/response.h496
-rw-r--r--src/protocol_18/response.h.orig500
-rw-r--r--src/protocol_18/types.h174
13 files changed, 3718 insertions, 0 deletions
diff --git a/src/protocol_18/client.cc b/src/protocol_18/client.cc
new file mode 100644
index 0000000..d42c24a
--- /dev/null
+++ b/src/protocol_18/client.cc
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <memory>
+#include <utility>
+#include <sstream>
+#include <iomanip>
+#include <cmath>
+#include <stdexcept>
+
+#include "client.h"
+#include "types.h"
+#include "defines.h"
+#include "../protocol/exceptions.h"
+#include "response.h"
+#include "../voltronic/crc.h"
+
+#define MKRESPONSE(type) std::shared_ptr<response_type::BaseResponse>(new response_type::type(raw, rawSize))
+
+#define RESPONSE_CASE(type) \
+ case CommandType::Get ## type: \
+ response = MKRESPONSE(type); \
+ break; \
+
+
+namespace p18 {
+
+std::shared_ptr<response_type::BaseResponse> Client::execute(int commandType, std::vector<std::string>& arguments) {
+ auto type = static_cast<CommandType>(commandType);
+
+ std::ostringstream buf;
+ buf << std::setfill('0');
+
+ bool isSetCommand = commandType >= 100;
+
+ auto pos = raw_commands.find(type);
+ if (pos == raw_commands.end())
+ throw std::runtime_error("packedCommand " + std::to_string(commandType) + " not found");
+
+ std::string packedCommand = pos->second;
+ std::string packedArguments = packArguments(type, arguments);
+
+ size_t len = sizeof(voltronic::CRC) + 1 + packedCommand.size() + packedArguments.size();
+
+ buf << "^";
+ buf << (isSetCommand ? "S" : "P");
+ buf << std::setw(3) << len;
+ buf << packedCommand;
+ buf << packedArguments;
+
+ std::string packed = buf.str();
+
+ auto result = runOnDevice(packed);
+ std::shared_ptr<response_type::BaseResponse> response;
+
+ const auto raw = result.first;
+ const auto rawSize = result.second;
+
+ switch (type) {
+ RESPONSE_CASE(ProtocolID)
+ RESPONSE_CASE(CurrentTime)
+ RESPONSE_CASE(TotalGenerated)
+ RESPONSE_CASE(YearGenerated)
+ RESPONSE_CASE(MonthGenerated)
+ RESPONSE_CASE(DayGenerated)
+ RESPONSE_CASE(SeriesNumber)
+ 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(ParallelRatedInformation)
+ RESPONSE_CASE(ParallelGeneralStatus)
+ RESPONSE_CASE(ACChargingTimeBucket)
+ RESPONSE_CASE(ACLoadsSupplyTimeBucket)
+
+ case CommandType::SetLoads:
+ case CommandType::SetFlag:
+ case CommandType::SetDefaults:
+ case CommandType::SetBatteryMaxChargingCurrent:
+ case CommandType::SetBatteryMaxACChargingCurrent:
+ case CommandType::SetACOutputFreq:
+ case CommandType::SetBatteryMaxChargingVoltage:
+ case CommandType::SetACOutputRatedVoltage:
+ case CommandType::SetOutputSourcePriority:
+ case CommandType::SetBatteryChargingThresholds:
+ case CommandType::SetChargingSourcePriority:
+ case CommandType::SetSolarPowerPriority:
+ case CommandType::SetACInputVoltageRange:
+ case CommandType::SetBatteryType:
+ case CommandType::SetOutputModel:
+ case CommandType::SetBatteryCutOffVoltage:
+ case CommandType::SetSolarConfig:
+ case CommandType::ClearGenerated:
+ case CommandType::SetDateTime:
+ case CommandType::SetACChargingTimeBucket:
+ case CommandType::SetACLoadsSupplyTimeBucket:
+ response = MKRESPONSE(SetResponse);
+ break;
+ }
+
+ if (!response->validate())
+ throw protocol::InvalidResponseError("validate() failed");
+
+ response->unpack();
+ return std::move(response);
+}
+
+std::string Client::packArguments(p18::CommandType commandType, std::vector<std::string>& arguments) {
+ std::ostringstream buf;
+ buf << std::setfill('0');
+
+ switch (commandType) {
+ case CommandType::GetYearGenerated:
+ case CommandType::SetOutputSourcePriority:
+ case CommandType::SetSolarPowerPriority:
+ case CommandType::SetACInputVoltageRange:
+ case CommandType::SetBatteryType:
+ case CommandType::SetLoads:
+ buf << arguments[0];
+ break;
+
+ case CommandType::GetMonthGenerated:
+ case CommandType::GetDayGenerated:
+ buf << arguments[0];
+ for (int i = 1; i <= (commandType == CommandType::GetMonthGenerated ? 1 : 2); i++)
+ buf << std::setw(2) << std::stoi(arguments[i]);
+ break;
+
+ case CommandType::GetParallelGeneralStatus:
+ case CommandType::GetParallelRatedInformation:
+ buf << std::stoi(arguments[0]);
+ break;
+
+ case CommandType::SetFlag:
+ buf << (arguments[1] == "1" ? "E" : "D");
+ buf << arguments[0];
+ break;
+
+ case CommandType::SetBatteryMaxChargingCurrent:
+ case CommandType::SetBatteryMaxACChargingCurrent:
+ buf << arguments[0] << ",";
+ buf << std::setw(3) << std::stoi(arguments[1]);
+ break;
+
+ case CommandType::SetACOutputFreq:
+ buf << std::setw(2) << std::stoi(arguments[0]);
+ break;
+
+ case CommandType::SetBatteryMaxChargingVoltage:
+ case CommandType::SetBatteryChargingThresholds: {
+ for (int i = 0; i < 2; i++) {
+ double val = std::stod(arguments[i]);
+ buf << std::setw(3) << (int)round(val*10);
+ if (i == 0)
+ buf << ",";
+ }
+ break;
+ }
+
+ case CommandType::SetACOutputRatedVoltage: {
+ buf << std::setw(4) << (std::stoi(arguments[0])*10);
+ break;
+ }
+
+ case CommandType::SetChargingSourcePriority:
+ case CommandType::SetOutputModel:
+ buf << arguments[0] << "," << arguments[1];
+ break;
+
+ case CommandType::SetBatteryCutOffVoltage: {
+ double v = std::stod(arguments[0]);
+ buf << std::setw(3) << ((int)round(v*10));
+ break;
+ }
+
+ case CommandType::SetSolarConfig: {
+ size_t len = arguments[0].size();
+ buf << std::setw(2) << len << arguments[0];
+ if (len < 20) {
+ for (int i = 0; i < 20-len; i++)
+ buf << "0";
+ }
+ break;
+ }
+
+ case CommandType::SetDateTime: {
+ for (int i = 0; i < 6; i++) {
+ int val = std::stoi(arguments[i]);
+ if (i == 0)
+ val -= 2000;
+ buf << std::setw(2) << val;
+ }
+ break;
+ }
+
+ case CommandType::SetACChargingTimeBucket:
+ case CommandType::SetACLoadsSupplyTimeBucket:
+ for (int i = 0; i < 4; i++) {
+ buf << std::setw(2) << std::stoi(arguments[i]);
+ if (i == 1)
+ buf << ",";
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return buf.str();
+}
+
+} \ No newline at end of file
diff --git a/src/protocol_18/client.h b/src/protocol_18/client.h
new file mode 100644
index 0000000..3fb96fc
--- /dev/null
+++ b/src/protocol_18/client.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef INVERTER_TOOLS_P18_CLIENT_H_
+#define INVERTER_TOOLS_P18_CLIENT_H_
+
+#include "../voltronic/device.h"
+#include "types.h"
+#include "response.h"
+#include "../protocol/client.h"
+
+#include <memory>
+#include <vector>
+#include <string>
+
+
+namespace p18 {
+
+using protocol::BaseClient;
+using protocol::BaseResponse;
+
+class Client : public BaseClient {
+private:
+ static std::string packArguments(p18::CommandType commandType, std::vector<std::string>& arguments);
+
+public:
+ std::shared_ptr<BaseResponse> execute(int commandType, std::vector<std::string>& arguments) override;
+};
+
+}
+
+
+#endif //INVERTER_TOOLS_P18_CLIENT_H_
diff --git a/src/protocol_18/defines.cc b/src/protocol_18/defines.cc
new file mode 100644
index 0000000..207d909
--- /dev/null
+++ b/src/protocol_18/defines.cc
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <iostream>
+
+#include "defines.h"
+#include "types.h"
+
+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"},
+ /* 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"},
+};
+
+const std::array<int, 5> ac_output_rated_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, 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::map<int, std::string> fault_codes = {
+ {1, "Fan is locked"},
+ {2, "Over temperature"},
+ {3, "Battery voltage is too high"},
+ {4, "Battery voltage is too low"},
+ {5, "Output short circuited or Over temperature"},
+ {6, "Output voltage is too high"},
+ {7, "Over load time out"},
+ {8, "Bus voltage is too high"},
+ {9, "Bus soft start failed"},
+ {11, "Main relay failed"},
+ {51, "Over current inverter"},
+ {52, "Bus soft start failed"},
+ {53, "Inverter soft start failed"},
+ {54, "Self-test failed"},
+ {55, "Over DC voltage on output of inverter"},
+ {56, "Battery connection is open"},
+ {57, "Current sensor failed"},
+ {58, "Output voltage is too low"},
+ {60, "Inverter negative power"},
+ {71, "Parallel version different"},
+ {72, "Output circuit failed"},
+ {80, "CAN communication failed"},
+ {81, "Parallel host line lost"},
+ {82, "Parallel synchronized signal lost"},
+ {83, "Parallel battery voltage detect different"},
+ {84, "Parallel LINE voltage or frequency detect different"},
+ {85, "Parallel LINE input current unbalanced"},
+ {86, "Parallel output setting different"},
+};
+
+const std::array<Flag, 9> flags = {{
+ {"BUZZ", 'A', "Mute buzzer beep"},
+ {"OLBP", 'B', "Overload bypass function"},
+ {"LCDE", 'C', "LCD display escape to default page after 1min timeout"},
+ {"OLRS", 'D', "Overload restart"},
+ {"OTRS", 'E', "Overload temperature restart"},
+ {"BLON", 'F', "Backlight on"},
+ {"ALRM", 'G', "Alarm on primary source interrupt"},
+ {"FTCR", 'H', "Fault code record"},
+ {"MTYP", 'I', "Machine type (1=Grid-Tie, 0=Off-Grid-Tie)"},
+}};
+
+ENUM_STR(BatteryType) {
+ switch (val) {
+ case BatteryType::AGM: return os << "AGM" ;
+ case BatteryType::Flooded: return os << "Flooded";
+ case BatteryType::User: return os << "User";
+ };
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(InputVoltageRange) {
+ switch (val) {
+ case InputVoltageRange::Appliance: return os << "Appliance";
+ case InputVoltageRange::USP: return os << "USP";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(OutputSourcePriority) {
+ switch (val) {
+ case OutputSourcePriority::SolarUtilityBattery:
+ return os << "Solar-Utility-Battery";
+ case OutputSourcePriority::SolarBatteryUtility:
+ return os << "Solar-Battery-Utility";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(ChargerSourcePriority) {
+ switch (val) {
+ case ChargerSourcePriority::SolarFirst:
+ return os << "Solar-First";
+ case ChargerSourcePriority::SolarAndUtility:
+ return os << "Solar-and-Utility";
+ case ChargerSourcePriority::SolarOnly:
+ return os << "Solar-only";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(MachineType) {
+ switch (val) {
+ case MachineType::OffGridTie: return os << "Off-Grid-Tie";
+ case MachineType::GridTie: return os << "Grid-Tie";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(Topology) {
+ switch (val) {
+ case Topology::TransformerLess: return os << "Transformer-less";
+ case Topology::Transformer: return os << "Transformer";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(OutputModelSetting) {
+ switch (val) {
+ case OutputModelSetting::SingleModule:
+ return os << "Single module";
+ case OutputModelSetting::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";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(SolarPowerPriority) {
+ switch (val) {
+ case SolarPowerPriority::BatteryLoadUtility:
+ return os << "Battery-Load-Utility";
+ case SolarPowerPriority::LoadBatteryUtility:
+ return os << "Load-Battery-Utility";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(MPPTChargerStatus) {
+ switch (val) {
+ case MPPTChargerStatus::Abnormal: return os << "Abnormal";
+ case MPPTChargerStatus::NotCharging: return os << "Not charging";
+ case MPPTChargerStatus::Charging: return os << "Charging";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(BatteryPowerDirection) {
+ switch (val) {
+ case BatteryPowerDirection::DoNothing: return os << "Do nothing";
+ case BatteryPowerDirection::Charge: return os << "Charge";
+ case BatteryPowerDirection::Discharge: return os << "Discharge";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(DC_AC_PowerDirection) {
+ switch (val) {
+ case DC_AC_PowerDirection::DoNothing: return os << "Do nothing";
+ case DC_AC_PowerDirection::AC_DC: return os << "AC/DC";
+ case DC_AC_PowerDirection::DC_AC: return os << "DC/AC";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(LinePowerDirection) {
+ switch (val) {
+ case LinePowerDirection::DoNothing: return os << "Do nothing";
+ case LinePowerDirection::Input: return os << "Input";
+ case LinePowerDirection::Output: return os << "Output";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(WorkingMode) {
+ switch (val) {
+ case WorkingMode::PowerOnMode: return os << "Power on mode";
+ case WorkingMode::StandbyMode: return os << "Standby mode";
+ case WorkingMode::BypassMode: return os << "Bypass mode";
+ case WorkingMode::BatteryMode: return os << "Battery mode";
+ case WorkingMode::FaultMode: return os << "Fault mode";
+ case WorkingMode::HybridMode: return os << "Hybrid mode";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(ParallelConnectionStatus) {
+ switch (val) {
+ case ParallelConnectionStatus::NotExistent: return os << "Non-existent";
+ case ParallelConnectionStatus::Existent: return os << "Existent";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(LoadConnectionStatus) {
+ switch (val) {
+ case LoadConnectionStatus::Disconnected: return os << "Disconnected";
+ case LoadConnectionStatus::Connected: return os << "Connected";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+ENUM_STR(ConfigurationStatus) {
+ switch (val) {
+ case ConfigurationStatus::Default: return os << "Default";
+ case ConfigurationStatus::Changed: return os << "Changed";
+ }
+ ENUM_STR_DEFAULT;
+}
+
+} \ No newline at end of file
diff --git a/src/protocol_18/defines.h b/src/protocol_18/defines.h
new file mode 100644
index 0000000..83728f8
--- /dev/null
+++ b/src/protocol_18/defines.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef INVERTER_TOOLS_P18_DEFINES_H
+#define INVERTER_TOOLS_P18_DEFINES_H
+
+#include <map>
+#include <string>
+#include <array>
+
+#include "types.h"
+
+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<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, 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::map<int, std::string> fault_codes;
+
+extern const std::array<Flag, 9> flags;
+
+}
+
+#endif //INVERTER_TOOLS_P18_DEFINES_H
diff --git a/src/protocol_18/functions.cc b/src/protocol_18/functions.cc
new file mode 100644
index 0000000..9799fc0
--- /dev/null
+++ b/src/protocol_18/functions.cc
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "functions.h"
+
+namespace p18 {
+
+bool is_valid_parallel_id(unsigned id)
+{
+ return id >= 0 && id <= 6;
+}
+
+} \ No newline at end of file
diff --git a/src/protocol_18/functions.h b/src/protocol_18/functions.h
new file mode 100644
index 0000000..1f15dcc
--- /dev/null
+++ b/src/protocol_18/functions.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef INVERTER_TOOLS_P18_FUNCTIONS_H
+#define INVERTER_TOOLS_P18_FUNCTIONS_H
+
+namespace p18 {
+
+bool is_valid_parallel_id(unsigned id);
+
+}
+
+#endif //INVERTER_TOOLS_P18_FUNCTIONS_H
diff --git a/src/protocol_18/input.cc b/src/protocol_18/input.cc
new file mode 100644
index 0000000..5647ee6
--- /dev/null
+++ b/src/protocol_18/input.cc
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "input.h"
+#include "defines.h"
+#include "functions.h"
+#include "../util.h"
+
+namespace p18 {
+
+using namespace protocol;
+
+const std::map<std::string, CommandType> client_commands = {
+ {"get-protocol-id", CommandType::GetProtocolID},
+ {"get-date-time", CommandType::GetCurrentTime},
+ {"get-total-generated", CommandType::GetTotalGenerated},
+ {"get-year-generated", CommandType::GetYearGenerated},
+ {"get-month-generated", CommandType::GetMonthGenerated},
+ {"get-day-generated", CommandType::GetDayGenerated},
+ {"get-series-number", CommandType::GetSeriesNumber},
+ {"get-cpu-version", CommandType::GetCPUVersion},
+ {"get-rated", CommandType::GetRatedInformation},
+ {"get-status", CommandType::GetGeneralStatus},
+ {"get-mode", CommandType::GetWorkingMode},
+ {"get-errors", CommandType::GetFaultsAndWarnings},
+ {"get-flags", CommandType::GetFlagsAndStatuses},
+ {"get-rated-defaults", CommandType::GetDefaults},
+ {"get-allowed-charging-currents", CommandType::GetAllowedChargingCurrents},
+ {"get-allowed-ac-charging-currents", CommandType::GetAllowedACChargingCurrents},
+ {"get-p-rated", CommandType::GetParallelRatedInformation},
+ {"get-p-status", CommandType::GetParallelGeneralStatus},
+ {"get-ac-charging-time", CommandType::GetACChargingTimeBucket},
+ {"get-ac-loads-supply-time", CommandType::GetACLoadsSupplyTimeBucket},
+ {"set-loads-supply", CommandType::SetLoads},
+ {"set-flag", CommandType::SetFlag},
+ {"set-rated-defaults", CommandType::SetDefaults},
+ {"set-max-charging-current", CommandType::SetBatteryMaxChargingCurrent},
+ {"set-max-ac-charging-current", CommandType::SetBatteryMaxACChargingCurrent},
+ {"set-ac-output-freq", CommandType::SetACOutputFreq},
+ {"set-max-charging-voltage", CommandType::SetBatteryMaxChargingVoltage},
+ {"set-ac-output-voltage", CommandType::SetACOutputRatedVoltage},
+ {"set-output-source-priority", CommandType::SetOutputSourcePriority},
+ {"set-charging-thresholds", CommandType::SetBatteryChargingThresholds}, /* Battery re-charging and re-discharging voltage when utility is available */
+ {"set-charging-source-priority", CommandType::SetChargingSourcePriority},
+ {"set-solar-power-priority", CommandType::SetSolarPowerPriority},
+ {"set-ac-input-voltage-range", CommandType::SetACInputVoltageRange},
+ {"set-battery-type", CommandType::SetBatteryType},
+ {"set-output-model", CommandType::SetOutputModel},
+ {"set-battery-cut-off-voltage", CommandType::SetBatteryCutOffVoltage},
+ {"set-solar-configuration", CommandType::SetSolarConfig},
+ {"clear-generated-data", CommandType::ClearGenerated},
+ {"set-date-time", CommandType::SetDateTime},
+ {"set-ac-charging-time", CommandType::SetACChargingTimeBucket},
+ {"set-ac-loads-supply-time", CommandType::SetACLoadsSupplyTimeBucket},
+};
+
+CommandType validate_input(std::string& command,
+ std::vector<std::string>& arguments,
+ void* input) {
+ auto it = client_commands.find(command);
+ if (it == client_commands.end())
+ throw std::invalid_argument("invalid command");
+
+ auto commandType = it->second;
+ switch (commandType) {
+ case CommandType::GetYearGenerated:
+ GET_ARGS(1);
+ validate_date_args(&arguments[0], nullptr, nullptr);
+ break;
+
+ case CommandType::GetMonthGenerated:
+ GET_ARGS(2);
+ validate_date_args(&arguments[0], &arguments[1], nullptr);
+ break;
+
+ case CommandType::GetDayGenerated:
+ GET_ARGS(3);
+ validate_date_args(&arguments[0], &arguments[1], &arguments[2]);
+ break;
+
+ case CommandType::GetParallelRatedInformation:
+ case CommandType::GetParallelGeneralStatus:
+ GET_ARGS(1);
+ if (!is_numeric(arguments[0]) || arguments[0].size() > 1)
+ throw std::invalid_argument("invalid argument");
+ break;
+
+ case 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 CommandType::SetFlag: {
+ GET_ARGS(2);
+
+ bool match_found = false;
+ for (auto const& item: 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 CommandType::SetBatteryMaxChargingCurrent:
+ case CommandType::SetBatteryMaxACChargingCurrent: {
+ GET_ARGS(2);
+
+ auto id = static_cast<unsigned>(std::stoul(arguments[0]));
+ auto amps = static_cast<unsigned>(std::stoul(arguments[1]));
+
+ if (!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 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 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 CommandType::SetACOutputRatedVoltage: {
+ GET_ARGS(1);
+
+ auto v = static_cast<unsigned>(std::stoul(arguments[0]));
+
+ bool matchFound = false;
+ for (const auto &item: ac_output_rated_voltages) {
+ if (v == item) {
+ matchFound = true;
+ break;
+ }
+ }
+
+ if (!matchFound)
+ throw std::invalid_argument("invalid voltage");
+
+ break;
+ }
+
+ case CommandType::SetOutputSourcePriority: {
+ GET_ARGS(1);
+
+ std::array<std::string, 2> priorities({"SUB", "SBU"});
+
+ long index = index_of(priorities, arguments[0]);
+ if (index == -1)
+ throw std::invalid_argument("invalid argument");
+
+ arguments[0] = std::to_string(index);
+ break;
+ }
+
+ case CommandType::SetBatteryChargingThresholds: {
+ GET_ARGS(2);
+
+ float cv = std::stof(arguments[0]);
+ float dv = std::stof(arguments[1]);
+
+ if (index_of(bat_ac_recharging_voltages_12v, cv) == -1 &&
+ index_of(bat_ac_recharging_voltages_24v, cv) == -1 &&
+ index_of(bat_ac_recharging_voltages_48v, cv) == -1)
+ throw std::invalid_argument("invalid CV");
+
+ if (index_of(bat_ac_redischarging_voltages_12v, dv) == -1 &&
+ index_of(bat_ac_redischarging_voltages_24v, dv) == -1 &&
+ index_of(bat_ac_redischarging_voltages_48v, dv) == -1)
+ throw std::invalid_argument("invalid DV");
+
+ break;
+ }
+
+ case CommandType::SetChargingSourcePriority: {
+ GET_ARGS(2);
+
+ auto id = static_cast<unsigned>(std::stoul(arguments[0]));
+ if (!is_valid_parallel_id(id))
+ throw std::invalid_argument("invalid id");
+
+ std::array<std::string, 3> priorities({"SF", "SU", "S"});
+ long index = index_of(priorities, arguments[1]);
+ if (index == -1)
+ throw std::invalid_argument("invalid argument");
+
+ arguments[1] = std::to_string(index);
+ break;
+ }
+
+ case CommandType::SetSolarPowerPriority: {
+ GET_ARGS(1);
+
+ std::array<std::string, 2> allowed({"BLU", "LBU"});
+ long index = index_of(allowed, arguments[0]);
+ if (index == -1)
+ throw std::invalid_argument("invalid priority");
+
+ arguments[0] = std::to_string(index);
+ break;
+ }
+
+ case CommandType::SetACInputVoltageRange: {
+ GET_ARGS(1);
+ std::array<std::string, 2> allowed({"APPLIANCE", "UPS"});
+ long index = index_of(allowed, arguments[0]);
+ if (index == -1)
+ throw std::invalid_argument("invalid argument");
+ arguments[0] = std::to_string(index);
+ break;
+ }
+
+ case CommandType::SetBatteryType: {
+ GET_ARGS(1);
+
+ std::array<std::string, 3> allowed({"AGM", "FLOODED", "USER"});
+ long index = index_of(allowed, arguments[0]);
+ if (index == -1)
+ throw std::invalid_argument("invalid type");
+ arguments[0] = std::to_string(index);
+
+ break;
+ }
+
+ case CommandType::SetOutputModel: {
+ GET_ARGS(2);
+
+ auto id = static_cast<unsigned>(std::stoul(arguments[0]));
+ if (!is_valid_parallel_id(id))
+ throw std::invalid_argument("invalid id");
+
+ std::array<std::string, 5> allowed({"SM", "P", "P1", "P2", "P3"});
+ long index = index_of(allowed, arguments[1]);
+ if (index == -1)
+ throw std::invalid_argument("invalid model");
+ arguments[1] = std::to_string(index);
+
+ break;
+ }
+
+ case 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 CommandType::SetSolarConfig: {
+ GET_ARGS(1);
+
+ if (!is_numeric(arguments[0]) || arguments[0].size() > 20)
+ throw std::invalid_argument("invalid argument");
+
+ break;
+ }
+
+ case 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 CommandType::SetACChargingTimeBucket:
+ case CommandType::SetACLoadsSupplyTimeBucket: {
+ GET_ARGS(2);
+
+ std::vector<std::string> start = split(arguments[0], ':');
+ if (start.size() != 2)
+ throw std::invalid_argument("invalid start time");
+
+ std::vector<std::string> end = split(arguments[1], ':');
+ if (end.size() != 2)
+ throw std::invalid_argument("invalid end time");
+
+ auto startHour = static_cast<unsigned short>(std::stoul(start[0]));
+ auto startMinute = static_cast<unsigned short>(std::stoul(start[1]));
+ if (startHour > 23 || startMinute > 59)
+ throw std::invalid_argument("invalid start time");
+
+ auto endHour = static_cast<unsigned short>(std::stoul(end[0]));
+ auto endMinute = static_cast<unsigned short>(std::stoul(end[1]));
+ if (endHour > 23 || endMinute > 59)
+ throw std::invalid_argument("invalid end time");
+
+ arguments.clear();
+
+ arguments.emplace_back(std::to_string(startHour));
+ arguments.emplace_back(std::to_string(startMinute));
+
+ arguments.emplace_back(std::to_string(endHour));
+ arguments.emplace_back(std::to_string(endMinute));
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return commandType;
+}
+
+} \ No newline at end of file
diff --git a/src/protocol_18/input.h b/src/protocol_18/input.h
new file mode 100644
index 0000000..939497e
--- /dev/null
+++ b/src/protocol_18/input.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef INVERTER_TOOLS_P18_INPUT_H
+#define INVERTER_TOOLS_P18_INPUT_H
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "../protocol/input.h"
+#include "types.h"
+
+namespace p18 {
+
+extern const std::map<std::string, CommandType> client_commands;
+
+CommandType validate_input(std::string& command, std::vector<std::string>& arguments, void* input);
+
+}
+
+#endif //INVERTER_TOOLS_P18_INPUT_H
diff --git a/src/protocol_18/response.cc b/src/protocol_18/response.cc
new file mode 100644
index 0000000..4062b37
--- /dev/null
+++ b/src/protocol_18/response.cc
@@ -0,0 +1,808 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <utility>
+#include <cstring>
+#include <sstream>
+#include <iomanip>
+#include <typeinfo>
+
+#include "response.h"
+#include "../protocol/exceptions.h"
+#include "../logging.h"
+
+#define RETURN_TABLE(...) \
+ return std::shared_ptr<formatter::Table<VariantHolder>>( \
+ new formatter::Table<VariantHolder>(format, __VA_ARGS__) \
+ );
+
+#define RETURN_STATUS(...) \
+ return std::shared_ptr<formatter::Status>( \
+ new formatter::Status(format, __VA_ARGS__) \
+ );
+
+
+namespace p18::response_type {
+
+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
+ */
+
+
+bool GetResponse::validate() {
+ if (rawSize_ < 5)
+ return false;
+
+ const char* raw = raw_.get();
+ if (raw[0] != '^' || raw[1] != 'D')
+ return false;
+
+ char lenbuf[4];
+ memcpy(lenbuf, &raw[2], 3);
+ lenbuf[3] = '\0';
+
+ auto len = static_cast<size_t>(std::stoul(lenbuf));
+ return rawSize_ >= len-5 /* exclude ^Dxxx*/;
+}
+
+const char* GetResponse::getData() const {
+ return raw_.get() + 5;
+}
+
+size_t GetResponse::getDataSize() const {
+ return rawSize_ - 5;
+}
+
+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() < expectAtLeast) {
+ std::ostringstream error;
+ error << "while parsing " << demangle_type_name(typeid(*this).name());
+ error << ": list is expected to be " << expectAtLeast << " items long, ";
+ error << "got only " << list.size() << " items";
+ throw protocol::ParseError(error.str());
+ }
+
+ // check each item's length
+ for (int i = 0; i < list.size(); i++) {
+ 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, ";
+ error << "got " << list[i].size() << " characters";
+ throw protocol::ParseError(error.str());
+ }
+ }
+ }
+
+ return list;
+}
+
+bool SetResponse::validate() {
+ if (rawSize_ < 2)
+ return false;
+
+ const char* raw = raw_.get();
+ return raw[0] == '^' && (raw[1] == '0' || raw[1] == '1');
+}
+
+bool SetResponse::get() {
+ return raw_.get()[1] == '1';
+}
+
+void SetResponse::unpack() {}
+
+formattable_ptr SetResponse::format(formatter::Format format) {
+ RETURN_STATUS(get(), "");
+}
+
+
+/**
+ * Actual typed responses
+ */
+
+void ProtocolID::unpack() {
+ auto data = getData();
+
+ char s[4];
+ strncpy(s, data, 2);
+ s[2] = '\0';
+
+ id = stou(s);
+}
+
+formattable_ptr ProtocolID::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("id", "Protocol ID", id),
+ });
+}
+
+
+void CurrentTime::unpack() {
+ auto data = getData();
+
+ std::string buf;
+ buf = std::string(data, 4);
+
+ year = stou(buf);
+
+ for (int i = 0; i < 5; i++) {
+ buf = std::string(data + 4 + (i * 2), 2);
+ auto n = stou(buf);
+
+ switch (i) {
+ case 0:
+ month = n;
+ break;
+
+ case 1:
+ day = n;
+ break;
+
+ case 2:
+ hour = n;
+ break;
+
+ case 3:
+ minute = n;
+ break;
+
+ case 4:
+ second = n;
+ break;
+
+ default:
+ std::ostringstream error;
+ error << "unexpected value while parsing CurrentTime (i = " << i << ")";
+ throw protocol::ParseError(error.str());
+ }
+ }
+}
+
+formattable_ptr CurrentTime::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("year", "Year", year),
+ LINE("month", "Month", month),
+ LINE("day", "Day", day),
+ LINE("hour", "Hour", hour),
+ LINE("minute", "Minute", minute),
+ LINE("second", "Second", second),
+ });
+}
+
+
+void TotalGenerated::unpack() {
+ auto data = getData();
+
+ std::string buf(data, 8);
+ wh = stou(buf);
+}
+
+formattable_ptr TotalGenerated::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("wh", "Wh", wh)
+ });
+}
+
+
+void SeriesNumber::unpack() {
+ auto data = getData();
+
+ std::string buf(data, 2);
+ size_t len = std::stoul(buf);
+
+ id = std::string(data+2, len);
+}
+
+formattable_ptr SeriesNumber::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("sn", "Series number", id)
+ });
+}
+
+
+void CPUVersion::unpack() {
+ auto list = getList({5, 5, 5});
+
+ main_cpu_version = list[0];
+ slave1_cpu_version = list[1];
+ slave2_cpu_version = list[2];
+}
+
+formattable_ptr CPUVersion::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("main_v", "Main CPU version", main_cpu_version),
+ LINE("slave1_v", "Slave 1 CPU version", slave1_cpu_version),
+ LINE("slave2_v", "Slave 2 CPU version", slave2_cpu_version)
+ });
+}
+
+
+void RatedInformation::unpack() {
+ auto list = getList({
+ 4, // AAAA
+ 3, // BBB
+ 4, // CCCC
+ 3, // DDD
+ 3, // EEE
+ 4, // FFFF
+ 4, // GGGG
+ 3, // HHH
+ 3, // III
+ 3, // JJJ
+ 3, // KKK
+ 3, // LLL
+ 3, // MMM
+ 1, // N
+ 2, // OO
+ 3, // PPP
+ 1, // O
+ 1, // R
+ 1, // S
+ 1, // T
+ 1, // U
+ 1, // V
+ 1, // W
+ 1, // Z
+ 1, // a
+ });
+
+ ac_input_rating_voltage = stou(list[0]);
+ ac_input_rating_current = stou(list[1]);
+ ac_output_rating_voltage = stou(list[2]);
+ ac_output_rating_freq = stou(list[3]);
+ ac_output_rating_current = stou(list[4]);
+ ac_output_rating_apparent_power = stou(list[5]);
+ ac_output_rating_active_power = stou(list[6]);
+ battery_rating_voltage = stou(list[7]);
+ battery_recharge_voltage = stou(list[8]);
+ battery_redischarge_voltage = stou(list[9]);
+ battery_under_voltage = stou(list[10]);
+ 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]);
+ 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]));
+ 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]));
+ solar_power_priority = static_cast<SolarPowerPriority>(stou(list[23]));
+ mppt = list[24];
+}
+
+formattable_ptr RatedInformation::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("ac_input_rating_voltage", "AC input rating voltage", ac_input_rating_voltage / 10.0, Unit::V),
+ LINE("ac_input_rating_current", "AC input rating current", ac_input_rating_current / 10.0, Unit::A),
+ LINE("ac_output_rating_voltage", "AC output rating voltage", ac_output_rating_voltage / 10.0, Unit::V),
+ LINE("ac_output_rating_freq", "AC output rating frequency", ac_output_rating_freq / 10.0, Unit::Hz),
+ LINE("ac_output_rating_current", "AC output rating current", ac_output_rating_current / 10.0, Unit::A),
+ LINE("ac_output_rating_apparent_power", "AC output rating apparent power", ac_output_rating_apparent_power, Unit::VA),
+ LINE("ac_output_rating_active_power", "AC output rating active power", ac_output_rating_active_power, Unit::Wh),
+ LINE("battery_rating_voltage", "Battery rating voltage", battery_rating_voltage / 10.0, Unit::V),
+ 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("battery_under_voltage", "Battery under voltage", battery_under_voltage / 10.0, Unit::V),
+ 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("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("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("solar_power_priority", "Solar power priority", solar_power_priority),
+ LINE("mppt", "MPPT string", mppt)
+ });
+}
+
+
+void GeneralStatus::unpack() {
+ auto list = getList({
+ 4, // AAAA
+ 3, // BBB
+ 4, // CCCC
+ 3, // DDD
+ 4, // EEEE
+ 4, // FFFF
+ 3, // GGG
+ 3, // HHH
+ 3, // III
+ 3, // JJJ
+ 3, // KKK
+ 3, // LLL
+ 3, // MMM
+ 3, // NNN
+ 3, // OOO
+ 3, // PPP
+ 4, // QQQQ
+ 4, // RRRR
+ 4, // SSSS
+ 4, // TTTT
+ 1, // U
+ 1, // V
+ 1, // W
+ 1, // X
+ 1, // Y
+ 1, // Z
+ 1, // a
+ 1, // b
+ });
+
+ grid_voltage = stou(list[0]);
+ grid_freq = stou(list[1]);
+ ac_output_voltage = stou(list[2]);
+ ac_output_freq = stou(list[3]);
+ ac_output_apparent_power = stou(list[4]);
+ ac_output_active_power = stou(list[5]);
+ output_load_percent = stou(list[6]);
+ battery_voltage = stou(list[7]);
+ 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_capacity = stou(list[12]);
+ inverter_heat_sink_temp = stou(list[13]);
+ mppt1_charger_temp = stou(list[14]);
+ mppt2_charger_temp = stou(list[15]);
+ pv1_input_power = stou(list[16]);
+ pv2_input_power = stou(list[17]);
+ pv1_input_voltage = stou(list[18]);
+ pv2_input_voltage = stou(list[19]);
+ configuration_status = static_cast<ConfigurationStatus>(stou(list[20]));
+ mppt1_charger_status = static_cast<MPPTChargerStatus>(stou(list[21]));
+ mppt2_charger_status = static_cast<MPPTChargerStatus>(stou(list[22]));
+ load_connected = static_cast<LoadConnectionStatus>(stou(list[23]));
+ battery_power_direction = static_cast<BatteryPowerDirection>(stou(list[24]));
+ dc_ac_power_direction = static_cast<DC_AC_PowerDirection>(stou(list[25]));
+ line_power_direction = static_cast<LinePowerDirection>(stou(list[26]));
+ local_parallel_id = stou(list[27]);
+}
+
+formattable_ptr GeneralStatus::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("grid_voltage", "Grid voltage", grid_voltage / 10.0, Unit::V),
+ LINE("grid_freq", "Grid frequency", grid_freq / 10.0, Unit::Hz),
+ 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),
+ LINE("ac_output_apparent_power", "AC output apparent power", ac_output_apparent_power, Unit::VA),
+ LINE("ac_output_active_power", "AC output active power", ac_output_active_power, Unit::Wh),
+ LINE("output_load_percent", "Output load percent", output_load_percent, Unit::Percentage),
+ 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_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),
+ LINE("mppt2_charger_temp", "MPPT2 charger temperature", mppt2_charger_temp, Unit::Celsius),
+ 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("configuration_status", "Configuration state", configuration_status),
+ 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("local_parallel_id", "Local parallel ID", local_parallel_id),
+ });
+}
+
+
+void WorkingMode::unpack() {
+ auto data = getData();
+ mode = static_cast<p18::WorkingMode>(stou(std::string(data, 2)));
+}
+
+formattable_ptr WorkingMode::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("mode", "Working mode", mode)
+ })
+}
+
+
+void FaultsAndWarnings::unpack() {
+ auto list = getList({2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1});
+
+ fault_code = stou(list[0]);
+ line_fail = stou(list[1]) > 0;
+ output_circuit_short = stou(list[2]) > 0;
+ inverter_over_temperature = stou(list[3]) > 0;
+ fan_lock = stou(list[4]) > 0;
+ battery_voltage_high = stou(list[5]) > 0;
+ battery_low = stou(list[6]) > 0;
+ battery_under = stou(list[7]) > 0;
+ over_load = stou(list[8]) > 0;
+ eeprom_fail = stou(list[9]) > 0;
+ power_limit = stou(list[10]) > 0;
+ pv1_voltage_high = stou(list[11]) > 0;
+ pv2_voltage_high = stou(list[12]) > 0;
+ mppt1_overload_warning = stou(list[13]) > 0;
+ mppt2_overload_warning = stou(list[14]) > 0;
+ battery_too_low_to_charge_for_scc1 = stou(list[15]) > 0;
+ battery_too_low_to_charge_for_scc2 = stou(list[16]) > 0;
+}
+
+formattable_ptr FaultsAndWarnings::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("fault_code", "Fault code", fault_code),
+ LINE("line_fail", "Line fail", line_fail),
+ LINE("output_circuit_short", "Output circuit short", output_circuit_short),
+ LINE("inverter_over_temperature", "Inverter over temperature", inverter_over_temperature),
+ LINE("fan_lock", "Fan lock", fan_lock),
+ LINE("battery_voltage_high", "Battery voltage high", battery_voltage_high),
+ LINE("battery_low", "Battery low", battery_low),
+ LINE("battery_under", "Battery under", battery_under),
+ LINE("over_load", "Over load", over_load),
+ LINE("eeprom_fail", "EEPROM fail", eeprom_fail),
+ LINE("power_limit", "Power limit", power_limit),
+ LINE("pv1_voltage_high", "PV1 voltage high", pv1_voltage_high),
+ LINE("pv2_voltage_high", "PV2 voltage high", pv2_voltage_high),
+ LINE("mppt1_overload_warning", "MPPT1 overload warning", mppt1_overload_warning),
+ LINE("mppt2_overload_warning", "MPPT2 overload warning", mppt2_overload_warning),
+ LINE("battery_too_low_to_charge_for_scc1", "Battery too low to charge for SCC1", battery_too_low_to_charge_for_scc1),
+ LINE("battery_too_low_to_charge_for_scc2", "Battery too low to charge for SCC2", battery_too_low_to_charge_for_scc2),
+ })
+}
+
+
+void FlagsAndStatuses::unpack() {
+ auto list = getList({1, 1, 1, 1, 1, 1, 1, 1, 1});
+
+ buzzer = stou(list[0]) > 0;
+ overload_bypass = stou(list[1]) > 0;
+ lcd_escape_to_default_page_after_1min_timeout = stou(list[2]) > 0;
+ overload_restart = stou(list[3]) > 0;
+ over_temp_restart = stou(list[4]) > 0;
+ backlight_on = stou(list[5]) > 0;
+ alarm_on_primary_source_interrupt = stou(list[6]) > 0;
+ fault_code_record = stou(list[7]) > 0;
+ reserved = *list[8].c_str();
+}
+
+formattable_ptr FlagsAndStatuses::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("buzzer",
+ "Buzzer",
+ buzzer),
+
+ LINE("overload_bypass",
+ "Overload bypass function",
+ overload_bypass),
+
+ LINE("escape_to_default_screen_after_1min_timeout",
+ "Escape to default screen after 1min timeout",
+ lcd_escape_to_default_page_after_1min_timeout),
+
+ LINE("overload_restart",
+ "Overload restart",
+ overload_restart),
+
+ LINE("over_temp_restart",
+ "Over temperature restart",
+ over_temp_restart),
+
+ LINE("backlight_on",
+ "Backlight on",
+ backlight_on),
+
+ LINE("alarm_on_on_primary_source_interrupt",
+ "Alarm on on primary source interrupt",
+ alarm_on_primary_source_interrupt),
+
+ LINE("fault_code_record",
+ "Fault code record",
+ fault_code_record)
+ })
+}
+
+
+void Defaults::unpack() {
+ auto list = getList({
+ 4, // AAAA
+ 3, // BBB
+ 1, // C
+ 3, // DDD
+ 3, // EEE
+ 3, // FFF
+ 3, // GGG
+ 3, // HHH
+ 3, // III
+ 2, // JJ
+ 1, // K
+ 1, // L
+ 1, // M
+ 1, // N
+ 1, // O
+ 1, // P
+ 1, // S
+ 1, // T
+ 1, // U
+ 1, // V
+ 1, // W
+ 1, // X
+ 1, // Y
+ 1, // Z
+ });
+
+ ac_output_voltage = stou(list[0]);
+ ac_output_freq = stou(list[1]);
+ ac_input_voltage_range = static_cast<InputVoltageRange>(stou(list[2]));
+ battery_under_voltage = stou(list[3]);
+ charging_float_voltage = stou(list[4]);
+ 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]);
+ 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]));
+ 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]));
+ flag_buzzer = stou(list[16]) > 0;
+ flag_overload_restart = stou(list[17]) > 0;
+ flag_over_temp_restart = stou(list[18]) > 0;
+ flag_backlight_on = stou(list[19]) > 0;
+ flag_alarm_on_primary_source_interrupt = stou(list[20]) > 0;
+ flag_fault_code_record = stou(list[21]) > 0;
+ flag_overload_bypass = stou(list[22]) > 0;
+ flag_lcd_escape_to_default_page_after_1min_timeout = stou(list[23]) > 0;
+}
+
+formattable_ptr Defaults::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),
+ LINE("ac_input_voltage_range", "AC input voltage range", ac_input_voltage_range),
+ 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_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("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("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),
+ LINE("overload_restart_flag", "Overload restart flag", flag_overload_restart),
+ LINE("over_temp_restart_flag", "Over temperature restart flag", flag_over_temp_restart),
+ LINE("backlight_on_flag", "Backlight on flag", flag_backlight_on),
+ LINE("alarm_on_on_primary_source_interrupt_flag", "Alarm on on primary source interrupt flag", flag_alarm_on_primary_source_interrupt),
+ LINE("fault_code_record_flag", "Fault code record flag", flag_fault_code_record),
+ })
+}
+
+void AllowedChargingCurrents::unpack() {
+ auto list = getList({});
+ for (const std::string& i: list) {
+ amps.emplace_back(stou(i));
+ }
+}
+
+formattable_ptr AllowedChargingCurrents::format(formatter::Format format) {
+ std::vector<formatter::ListItem<VariantHolder>> v;
+ for (const auto& n: amps)
+ v.emplace_back(n);
+
+ return std::shared_ptr<formatter::List<VariantHolder>>(
+ new formatter::List<VariantHolder>(format, v)
+ );
+}
+
+
+void ParallelRatedInformation::unpack() {
+ auto list = getList({
+ 1, // A
+ 2, // BB
+ 20, // CCCCCCCCCCCCCCCCCCCC
+ 1, // D
+ 3, // EEE
+
+ // 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]));
+ serial_number_valid_length = stou(list[1]);
+ serial_number = std::string(list[2], 0, 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]));
+}
+
+formattable_ptr ParallelRatedInformation::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("parallel_id_connection_status", "Parallel ID connection status", parallel_id_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),
+ })
+}
+
+
+void ParallelGeneralStatus::unpack() {
+ auto list = getList({
+ 1, // A
+ 1, // B
+ 2, // CC
+ 4, // DDDD
+ 3, // EEE
+ 4, // FFFF
+ 3, // GGG
+ 4, // HHHH
+ 4, // IIII
+ 5, // JJJJJ
+ 5, // KKKKK
+ 3, // LLL
+ 3, // MMM
+ 3, // NNN
+ 3, // OOO
+ 3, // PPP
+ 3, // QQQ
+ 3, // MMM. It's not my mistake, it's per the doc.
+ 4, // RRRR
+ 4, // SSSS
+ 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. 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]));
+ work_mode = static_cast<p18::WorkingMode>(stou(list[1]));
+ fault_code = stou(list[2]);
+ grid_voltage = stou(list[3]);
+ grid_freq = stou(list[4]);
+ ac_output_voltage = stou(list[5]);
+ ac_output_freq = stou(list[6]);
+ ac_output_apparent_power = stou(list[7]);
+ ac_output_active_power = stou(list[8]);
+ total_ac_output_apparent_power = stou(list[9]);
+ total_ac_output_active_power = stou(list[10]);
+ output_load_percent = stou(list[11]);
+ 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_capacity = stou(list[17]);
+ pv1_input_power = stou(list[18]);
+ pv2_input_power = stou(list[19]);
+ pv1_input_voltage = stou(list[20]);
+ pv2_input_voltage = stou(list[21]);
+ mppt1_charger_status = static_cast<MPPTChargerStatus>(stou(list[22]));
+ mppt2_charger_status = static_cast<MPPTChargerStatus>(stou(list[23]));
+ load_connected = static_cast<LoadConnectionStatus>(stou(list[24]));
+ 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]));
+ if (list.size() >= 29) {
+ max_temp_present = true;
+ max_temp = stou(list[28]);
+ }
+}
+
+formattable_ptr ParallelGeneralStatus::format(formatter::Format format) {
+ auto table = new formatter::Table<VariantHolder>(format, {
+ LINE("parallel_id_connection_status", "Parallel ID connection status", parallel_id_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),
+ LINE("grid_freq", "Grid frequency", grid_freq / 10.0, Unit::Hz),
+ 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),
+ LINE("ac_output_apparent_power", "AC output apparent power", ac_output_apparent_power, Unit::VA),
+ LINE("ac_output_active_power", "AC output active power", ac_output_active_power, Unit::Wh),
+ LINE("total_ac_output_apparent_power", "Total AC output apparent power", total_ac_output_apparent_power, Unit::VA),
+ LINE("total_ac_output_active_power", "Total AC output active power", total_ac_output_active_power, Unit::Wh),
+ LINE("output_load_percent", "Output load percent", output_load_percent, Unit::Percentage),
+ 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("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),
+ });
+
+ if (max_temp_present) {
+ table->push(
+ LINE("max_temp", "Max. temperature", max_temp)
+ );
+ }
+
+ return std::shared_ptr<formatter::Table<VariantHolder>>(table);
+}
+
+
+void ACChargingTimeBucket::unpack() {
+ auto list = getList({4 /* AAAA */, 4 /* BBBB */});
+
+ start_h = stouh(list[0].substr(0, 2));
+ start_m = stouh(list[0].substr(2, 2));
+
+ end_h = stouh(list[1].substr(0, 2));
+ end_m = stouh(list[1].substr(2, 2));
+}
+
+static inline std::string get_time(unsigned short h, unsigned short m) {
+ std::ostringstream buf;
+ buf << std::setfill('0');
+ buf << std::setw(2) << h << ":" << std::setw(2) << m;
+ return buf.str();
+}
+
+formattable_ptr ACChargingTimeBucket::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)),
+ })
+}
+
+}
diff --git a/src/protocol_18/response.cc.orig b/src/protocol_18/response.cc.orig
new file mode 100644
index 0000000..902fb95
--- /dev/null
+++ b/src/protocol_18/response.cc.orig
@@ -0,0 +1,812 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include <utility>
+#include <cstring>
+#include <sstream>
+#include <iomanip>
+#include <typeinfo>
+
+#include "response.h"
+<<<<<<< HEAD:src/p18/response.cc
+#include "exceptions.h"
+=======
+#include "../protocol/exceptions.h"
+#include "../logging.h"
+>>>>>>> bf1b75a (wip):src/protocol_18/response.cc
+
+#define RETURN_TABLE(...) \
+ return std::shared_ptr<formatter::Table<VariantHolder>>( \
+ new formatter::Table<VariantHolder>(format, __VA_ARGS__) \
+ );
+
+#define RETURN_STATUS(...) \
+ return std::shared_ptr<formatter::Status>( \
+ new formatter::Status(format, __VA_ARGS__) \
+ );
+
+
+namespace p18::response_type {
+
+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
+ */
+
+
+bool GetResponse::validate() {
+ if (rawSize_ < 5)
+ return false;
+
+ const char* raw = raw_.get();
+ if (raw[0] != '^' || raw[1] != 'D')
+ return false;
+
+ char lenbuf[4];
+ memcpy(lenbuf, &raw[2], 3);
+ lenbuf[3] = '\0';
+
+ auto len = static_cast<size_t>(std::stoul(lenbuf));
+ return rawSize_ >= len-5 /* exclude ^Dxxx*/;
+}
+
+const char* GetResponse::getData() const {
+ return raw_.get() + 5;
+}
+
+size_t GetResponse::getDataSize() const {
+ return rawSize_ - 5;
+}
+
+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() < expectAtLeast) {
+ std::ostringstream error;
+ error << "while parsing " << demangle_type_name(typeid(*this).name());
+ error << ": list is expected to be " << expectAtLeast << " items long, ";
+ error << "got only " << list.size() << " items";
+ throw protocol::ParseError(error.str());
+ }
+
+ // check each item's length
+ for (int i = 0; i < list.size(); i++) {
+ 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, ";
+ error << "got " << list[i].size() << " characters";
+ throw protocol::ParseError(error.str());
+ }
+ }
+ }
+
+ return list;
+}
+
+bool SetResponse::validate() {
+ if (rawSize_ < 2)
+ return false;
+
+ const char* raw = raw_.get();
+ return raw[0] == '^' && (raw[1] == '0' || raw[1] == '1');
+}
+
+bool SetResponse::get() {
+ return raw_.get()[1] == '1';
+}
+
+void SetResponse::unpack() {}
+
+formattable_ptr SetResponse::format(formatter::Format format) {
+ RETURN_STATUS(get(), "");
+}
+
+
+/**
+ * Actual typed responses
+ */
+
+void ProtocolID::unpack() {
+ auto data = getData();
+
+ char s[4];
+ strncpy(s, data, 2);
+ s[2] = '\0';
+
+ id = stou(s);
+}
+
+formattable_ptr ProtocolID::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("id", "Protocol ID", id),
+ });
+}
+
+
+void CurrentTime::unpack() {
+ auto data = getData();
+
+ std::string buf;
+ buf = std::string(data, 4);
+
+ year = stou(buf);
+
+ for (int i = 0; i < 5; i++) {
+ buf = std::string(data + 4 + (i * 2), 2);
+ auto n = stou(buf);
+
+ switch (i) {
+ case 0:
+ month = n;
+ break;
+
+ case 1:
+ day = n;
+ break;
+
+ case 2:
+ hour = n;
+ break;
+
+ case 3:
+ minute = n;
+ break;
+
+ case 4:
+ second = n;
+ break;
+
+ default:
+ std::ostringstream error;
+ error << "unexpected value while parsing CurrentTime (i = " << i << ")";
+ throw protocol::ParseError(error.str());
+ }
+ }
+}
+
+formattable_ptr CurrentTime::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("year", "Year", year),
+ LINE("month", "Month", month),
+ LINE("day", "Day", day),
+ LINE("hour", "Hour", hour),
+ LINE("minute", "Minute", minute),
+ LINE("second", "Second", second),
+ });
+}
+
+
+void TotalGenerated::unpack() {
+ auto data = getData();
+
+ std::string buf(data, 8);
+ wh = stou(buf);
+}
+
+formattable_ptr TotalGenerated::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("wh", "Wh", wh)
+ });
+}
+
+
+void SeriesNumber::unpack() {
+ auto data = getData();
+
+ std::string buf(data, 2);
+ size_t len = std::stoul(buf);
+
+ id = std::string(data+2, len);
+}
+
+formattable_ptr SeriesNumber::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("sn", "Series number", id)
+ });
+}
+
+
+void CPUVersion::unpack() {
+ auto list = getList({5, 5, 5});
+
+ main_cpu_version = list[0];
+ slave1_cpu_version = list[1];
+ slave2_cpu_version = list[2];
+}
+
+formattable_ptr CPUVersion::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("main_v", "Main CPU version", main_cpu_version),
+ LINE("slave1_v", "Slave 1 CPU version", slave1_cpu_version),
+ LINE("slave2_v", "Slave 2 CPU version", slave2_cpu_version)
+ });
+}
+
+
+void RatedInformation::unpack() {
+ auto list = getList({
+ 4, // AAAA
+ 3, // BBB
+ 4, // CCCC
+ 3, // DDD
+ 3, // EEE
+ 4, // FFFF
+ 4, // GGGG
+ 3, // HHH
+ 3, // III
+ 3, // JJJ
+ 3, // KKK
+ 3, // LLL
+ 3, // MMM
+ 1, // N
+ 2, // OO
+ 3, // PPP
+ 1, // O
+ 1, // R
+ 1, // S
+ 1, // T
+ 1, // U
+ 1, // V
+ 1, // W
+ 1, // Z
+ 1, // a
+ });
+
+ ac_input_rating_voltage = stou(list[0]);
+ ac_input_rating_current = stou(list[1]);
+ ac_output_rating_voltage = stou(list[2]);
+ ac_output_rating_freq = stou(list[3]);
+ ac_output_rating_current = stou(list[4]);
+ ac_output_rating_apparent_power = stou(list[5]);
+ ac_output_rating_active_power = stou(list[6]);
+ battery_rating_voltage = stou(list[7]);
+ battery_recharge_voltage = stou(list[8]);
+ battery_redischarge_voltage = stou(list[9]);
+ battery_under_voltage = stou(list[10]);
+ 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]);
+ 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]));
+ 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]));
+ solar_power_priority = static_cast<SolarPowerPriority>(stou(list[23]));
+ mppt = list[24];
+}
+
+formattable_ptr RatedInformation::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("ac_input_rating_voltage", "AC input rating voltage", ac_input_rating_voltage / 10.0, Unit::V),
+ LINE("ac_input_rating_current", "AC input rating current", ac_input_rating_current / 10.0, Unit::A),
+ LINE("ac_output_rating_voltage", "AC output rating voltage", ac_output_rating_voltage / 10.0, Unit::V),
+ LINE("ac_output_rating_freq", "AC output rating frequency", ac_output_rating_freq / 10.0, Unit::Hz),
+ LINE("ac_output_rating_current", "AC output rating current", ac_output_rating_current / 10.0, Unit::A),
+ LINE("ac_output_rating_apparent_power", "AC output rating apparent power", ac_output_rating_apparent_power, Unit::VA),
+ LINE("ac_output_rating_active_power", "AC output rating active power", ac_output_rating_active_power, Unit::Wh),
+ LINE("battery_rating_voltage", "Battery rating voltage", battery_rating_voltage / 10.0, Unit::V),
+ 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("battery_under_voltage", "Battery under voltage", battery_under_voltage / 10.0, Unit::V),
+ 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("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("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("solar_power_priority", "Solar power priority", solar_power_priority),
+ LINE("mppt", "MPPT string", mppt)
+ });
+}
+
+
+void GeneralStatus::unpack() {
+ auto list = getList({
+ 4, // AAAA
+ 3, // BBB
+ 4, // CCCC
+ 3, // DDD
+ 4, // EEEE
+ 4, // FFFF
+ 3, // GGG
+ 3, // HHH
+ 3, // III
+ 3, // JJJ
+ 3, // KKK
+ 3, // LLL
+ 3, // MMM
+ 3, // NNN
+ 3, // OOO
+ 3, // PPP
+ 4, // QQQQ
+ 4, // RRRR
+ 4, // SSSS
+ 4, // TTTT
+ 1, // U
+ 1, // V
+ 1, // W
+ 1, // X
+ 1, // Y
+ 1, // Z
+ 1, // a
+ 1, // b
+ });
+
+ grid_voltage = stou(list[0]);
+ grid_freq = stou(list[1]);
+ ac_output_voltage = stou(list[2]);
+ ac_output_freq = stou(list[3]);
+ ac_output_apparent_power = stou(list[4]);
+ ac_output_active_power = stou(list[5]);
+ output_load_percent = stou(list[6]);
+ battery_voltage = stou(list[7]);
+ 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_capacity = stou(list[12]);
+ inverter_heat_sink_temp = stou(list[13]);
+ mppt1_charger_temp = stou(list[14]);
+ mppt2_charger_temp = stou(list[15]);
+ pv1_input_power = stou(list[16]);
+ pv2_input_power = stou(list[17]);
+ pv1_input_voltage = stou(list[18]);
+ pv2_input_voltage = stou(list[19]);
+ configuration_status = static_cast<ConfigurationStatus>(stou(list[20]));
+ mppt1_charger_status = static_cast<MPPTChargerStatus>(stou(list[21]));
+ mppt2_charger_status = static_cast<MPPTChargerStatus>(stou(list[22]));
+ load_connected = static_cast<LoadConnectionStatus>(stou(list[23]));
+ battery_power_direction = static_cast<BatteryPowerDirection>(stou(list[24]));
+ dc_ac_power_direction = static_cast<DC_AC_PowerDirection>(stou(list[25]));
+ line_power_direction = static_cast<LinePowerDirection>(stou(list[26]));
+ local_parallel_id = stou(list[27]);
+}
+
+formattable_ptr GeneralStatus::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("grid_voltage", "Grid voltage", grid_voltage / 10.0, Unit::V),
+ LINE("grid_freq", "Grid frequency", grid_freq / 10.0, Unit::Hz),
+ 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),
+ LINE("ac_output_apparent_power", "AC output apparent power", ac_output_apparent_power, Unit::VA),
+ LINE("ac_output_active_power", "AC output active power", ac_output_active_power, Unit::Wh),
+ LINE("output_load_percent", "Output load percent", output_load_percent, Unit::Percentage),
+ 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_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),
+ LINE("mppt2_charger_temp", "MPPT2 charger temperature", mppt2_charger_temp, Unit::Celsius),
+ 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("configuration_status", "Configuration state", configuration_status),
+ 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("local_parallel_id", "Local parallel ID", local_parallel_id),
+ });
+}
+
+
+void WorkingMode::unpack() {
+ auto data = getData();
+ mode = static_cast<p18::WorkingMode>(stou(std::string(data, 2)));
+}
+
+formattable_ptr WorkingMode::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("mode", "Working mode", mode)
+ })
+}
+
+
+void FaultsAndWarnings::unpack() {
+ auto list = getList({2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1});
+
+ fault_code = stou(list[0]);
+ line_fail = stou(list[1]) > 0;
+ output_circuit_short = stou(list[2]) > 0;
+ inverter_over_temperature = stou(list[3]) > 0;
+ fan_lock = stou(list[4]) > 0;
+ battery_voltage_high = stou(list[5]) > 0;
+ battery_low = stou(list[6]) > 0;
+ battery_under = stou(list[7]) > 0;
+ over_load = stou(list[8]) > 0;
+ eeprom_fail = stou(list[9]) > 0;
+ power_limit = stou(list[10]) > 0;
+ pv1_voltage_high = stou(list[11]) > 0;
+ pv2_voltage_high = stou(list[12]) > 0;
+ mppt1_overload_warning = stou(list[13]) > 0;
+ mppt2_overload_warning = stou(list[14]) > 0;
+ battery_too_low_to_charge_for_scc1 = stou(list[15]) > 0;
+ battery_too_low_to_charge_for_scc2 = stou(list[16]) > 0;
+}
+
+formattable_ptr FaultsAndWarnings::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("fault_code", "Fault code", fault_code),
+ LINE("line_fail", "Line fail", line_fail),
+ LINE("output_circuit_short", "Output circuit short", output_circuit_short),
+ LINE("inverter_over_temperature", "Inverter over temperature", inverter_over_temperature),
+ LINE("fan_lock", "Fan lock", fan_lock),
+ LINE("battery_voltage_high", "Battery voltage high", battery_voltage_high),
+ LINE("battery_low", "Battery low", battery_low),
+ LINE("battery_under", "Battery under", battery_under),
+ LINE("over_load", "Over load", over_load),
+ LINE("eeprom_fail", "EEPROM fail", eeprom_fail),
+ LINE("power_limit", "Power limit", power_limit),
+ LINE("pv1_voltage_high", "PV1 voltage high", pv1_voltage_high),
+ LINE("pv2_voltage_high", "PV2 voltage high", pv2_voltage_high),
+ LINE("mppt1_overload_warning", "MPPT1 overload warning", mppt1_overload_warning),
+ LINE("mppt2_overload_warning", "MPPT2 overload warning", mppt2_overload_warning),
+ LINE("battery_too_low_to_charge_for_scc1", "Battery too low to charge for SCC1", battery_too_low_to_charge_for_scc1),
+ LINE("battery_too_low_to_charge_for_scc2", "Battery too low to charge for SCC2", battery_too_low_to_charge_for_scc2),
+ })
+}
+
+
+void FlagsAndStatuses::unpack() {
+ auto list = getList({1, 1, 1, 1, 1, 1, 1, 1, 1});
+
+ buzzer = stou(list[0]) > 0;
+ overload_bypass = stou(list[1]) > 0;
+ lcd_escape_to_default_page_after_1min_timeout = stou(list[2]) > 0;
+ overload_restart = stou(list[3]) > 0;
+ over_temp_restart = stou(list[4]) > 0;
+ backlight_on = stou(list[5]) > 0;
+ alarm_on_primary_source_interrupt = stou(list[6]) > 0;
+ fault_code_record = stou(list[7]) > 0;
+ reserved = *list[8].c_str();
+}
+
+formattable_ptr FlagsAndStatuses::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("buzzer",
+ "Buzzer",
+ buzzer),
+
+ LINE("overload_bypass",
+ "Overload bypass function",
+ overload_bypass),
+
+ LINE("escape_to_default_screen_after_1min_timeout",
+ "Escape to default screen after 1min timeout",
+ lcd_escape_to_default_page_after_1min_timeout),
+
+ LINE("overload_restart",
+ "Overload restart",
+ overload_restart),
+
+ LINE("over_temp_restart",
+ "Over temperature restart",
+ over_temp_restart),
+
+ LINE("backlight_on",
+ "Backlight on",
+ backlight_on),
+
+ LINE("alarm_on_on_primary_source_interrupt",
+ "Alarm on on primary source interrupt",
+ alarm_on_primary_source_interrupt),
+
+ LINE("fault_code_record",
+ "Fault code record",
+ fault_code_record)
+ })
+}
+
+
+void Defaults::unpack() {
+ auto list = getList({
+ 4, // AAAA
+ 3, // BBB
+ 1, // C
+ 3, // DDD
+ 3, // EEE
+ 3, // FFF
+ 3, // GGG
+ 3, // HHH
+ 3, // III
+ 2, // JJ
+ 1, // K
+ 1, // L
+ 1, // M
+ 1, // N
+ 1, // O
+ 1, // P
+ 1, // S
+ 1, // T
+ 1, // U
+ 1, // V
+ 1, // W
+ 1, // X
+ 1, // Y
+ 1, // Z
+ });
+
+ ac_output_voltage = stou(list[0]);
+ ac_output_freq = stou(list[1]);
+ ac_input_voltage_range = static_cast<InputVoltageRange>(stou(list[2]));
+ battery_under_voltage = stou(list[3]);
+ charging_float_voltage = stou(list[4]);
+ 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]);
+ 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]));
+ 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]));
+ flag_buzzer = stou(list[16]) > 0;
+ flag_overload_restart = stou(list[17]) > 0;
+ flag_over_temp_restart = stou(list[18]) > 0;
+ flag_backlight_on = stou(list[19]) > 0;
+ flag_alarm_on_primary_source_interrupt = stou(list[20]) > 0;
+ flag_fault_code_record = stou(list[21]) > 0;
+ flag_overload_bypass = stou(list[22]) > 0;
+ flag_lcd_escape_to_default_page_after_1min_timeout = stou(list[23]) > 0;
+}
+
+formattable_ptr Defaults::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),
+ LINE("ac_input_voltage_range", "AC input voltage range", ac_input_voltage_range),
+ 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_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("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("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),
+ LINE("overload_restart_flag", "Overload restart flag", flag_overload_restart),
+ LINE("over_temp_restart_flag", "Over temperature restart flag", flag_over_temp_restart),
+ LINE("backlight_on_flag", "Backlight on flag", flag_backlight_on),
+ LINE("alarm_on_on_primary_source_interrupt_flag", "Alarm on on primary source interrupt flag", flag_alarm_on_primary_source_interrupt),
+ LINE("fault_code_record_flag", "Fault code record flag", flag_fault_code_record),
+ })
+}
+
+void AllowedChargingCurrents::unpack() {
+ auto list = getList({});
+ for (const std::string& i: list) {
+ amps.emplace_back(stou(i));
+ }
+}
+
+formattable_ptr AllowedChargingCurrents::format(formatter::Format format) {
+ std::vector<formatter::ListItem<VariantHolder>> v;
+ for (const auto& n: amps)
+ v.emplace_back(n);
+
+ return std::shared_ptr<formatter::List<VariantHolder>>(
+ new formatter::List<VariantHolder>(format, v)
+ );
+}
+
+
+void ParallelRatedInformation::unpack() {
+ auto list = getList({
+ 1, // A
+ 2, // BB
+ 20, // CCCCCCCCCCCCCCCCCCCC
+ 1, // D
+ 3, // EEE
+
+ // 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]));
+ serial_number_valid_length = stou(list[1]);
+ serial_number = std::string(list[2], 0, 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]));
+}
+
+formattable_ptr ParallelRatedInformation::format(formatter::Format format) {
+ RETURN_TABLE({
+ LINE("parallel_id_connection_status", "Parallel ID connection status", parallel_id_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),
+ })
+}
+
+
+void ParallelGeneralStatus::unpack() {
+ auto list = getList({
+ 1, // A
+ 1, // B
+ 2, // CC
+ 4, // DDDD
+ 3, // EEE
+ 4, // FFFF
+ 3, // GGG
+ 4, // HHHH
+ 4, // IIII
+ 5, // JJJJJ
+ 5, // KKKKK
+ 3, // LLL
+ 3, // MMM
+ 3, // NNN
+ 3, // OOO
+ 3, // PPP
+ 3, // QQQ
+ 3, // MMM. It's not my mistake, it's per the doc.
+ 4, // RRRR
+ 4, // SSSS
+ 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. 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]));
+ work_mode = static_cast<p18::WorkingMode>(stou(list[1]));
+ fault_code = stou(list[2]);
+ grid_voltage = stou(list[3]);
+ grid_freq = stou(list[4]);
+ ac_output_voltage = stou(list[5]);
+ ac_output_freq = stou(list[6]);
+ ac_output_apparent_power = stou(list[7]);
+ ac_output_active_power = stou(list[8]);
+ total_ac_output_apparent_power = stou(list[9]);
+ total_ac_output_active_power = stou(list[10]);
+ output_load_percent = stou(list[11]);
+ 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_capacity = stou(list[17]);
+ pv1_input_power = stou(list[18]);
+ pv2_input_power = stou(list[19]);
+ pv1_input_voltage = stou(list[20]);
+ pv2_input_voltage = stou(list[21]);
+ mppt1_charger_status = static_cast<MPPTChargerStatus>(stou(list[22]));
+ mppt2_charger_status = static_cast<MPPTChargerStatus>(stou(list[23]));
+ load_connected = static_cast<LoadConnectionStatus>(stou(list[24]));
+ 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]));
+ if (list.size() >= 29) {
+ max_temp_present = true;
+ max_temp = stou(list[28]);
+ }
+}
+
+formattable_ptr ParallelGeneralStatus::format(formatter::Format format) {
+ auto table = new formatter::Table<VariantHolder>(format, {
+ LINE("parallel_id_connection_status", "Parallel ID connection status", parallel_id_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),
+ LINE("grid_freq", "Grid frequency", grid_freq / 10.0, Unit::Hz),
+ 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),
+ LINE("ac_output_apparent_power", "AC output apparent power", ac_output_apparent_power, Unit::VA),
+ LINE("ac_output_active_power", "AC output active power", ac_output_active_power, Unit::Wh),
+ LINE("total_ac_output_apparent_power", "Total AC output apparent power", total_ac_output_apparent_power, Unit::VA),
+ LINE("total_ac_output_active_power", "Total AC output active power", total_ac_output_active_power, Unit::Wh),
+ LINE("output_load_percent", "Output load percent", output_load_percent, Unit::Percentage),
+ 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("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),
+ });
+
+ if (max_temp_present) {
+ table->push(
+ LINE("max_temp", "Max. temperature", max_temp)
+ );
+ }
+
+ return std::shared_ptr<formatter::Table<VariantHolder>>(table);
+}
+
+
+void ACChargingTimeBucket::unpack() {
+ auto list = getList({4 /* AAAA */, 4 /* BBBB */});
+
+ start_h = stouh(list[0].substr(0, 2));
+ start_m = stouh(list[0].substr(2, 2));
+
+ end_h = stouh(list[1].substr(0, 2));
+ end_m = stouh(list[1].substr(2, 2));
+}
+
+static inline std::string get_time(unsigned short h, unsigned short m) {
+ std::ostringstream buf;
+ buf << std::setfill('0');
+ buf << std::setw(2) << h << ":" << std::setw(2) << m;
+ return buf.str();
+}
+
+formattable_ptr ACChargingTimeBucket::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)),
+ })
+}
+
+} \ No newline at end of file
diff --git a/src/protocol_18/response.h b/src/protocol_18/response.h
new file mode 100644
index 0000000..239c474
--- /dev/null
+++ b/src/protocol_18/response.h
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef INVERTER_TOOLS_P18_RESPONSE_H
+#define INVERTER_TOOLS_P18_RESPONSE_H
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <variant>
+#include <nlohmann/json.hpp>
+
+#include "types.h"
+#include "../formatter/formatter.h"
+#include "../protocol/response.h"
+
+namespace p18::response_type {
+
+using protocol::BaseResponse;
+using nlohmann::json;
+
+typedef std::shared_ptr<formatter::Formattable> formattable_ptr;
+
+
+/**
+ * Value holder for the formatter module
+ */
+
+typedef std::variant<
+ unsigned,
+ unsigned short,
+ unsigned long,
+ bool,
+ double,
+ std::string,
+ p18::BatteryType,
+ p18::BatteryPowerDirection,
+ p18::ChargerSourcePriority,
+ p18::DC_AC_PowerDirection,
+ p18::InputVoltageRange,
+ p18::LinePowerDirection,
+ p18::MachineType,
+ p18::MPPTChargerStatus,
+ p18::Topology,
+ p18::OutputSourcePriority,
+ p18::OutputModelSetting,
+ p18::ParallelConnectionStatus,
+ p18::SolarPowerPriority,
+ p18::WorkingMode,
+ p18::LoadConnectionStatus,
+ p18::ConfigurationStatus
+> Variant;
+
+class VariantHolder {
+private:
+ Variant v_;
+
+public:
+ VariantHolder(unsigned v) : v_(v) {}
+ VariantHolder(unsigned short v) : v_(v) {}
+ VariantHolder(unsigned long v) : v_(v) {}
+ VariantHolder(bool v) : v_(v) {}
+ VariantHolder(double v) : v_(v) {}
+ 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::DC_AC_PowerDirection v) : v_(v) {}
+ VariantHolder(p18::InputVoltageRange v) : v_(v) {}
+ VariantHolder(p18::LinePowerDirection v) : v_(v) {}
+ VariantHolder(p18::MachineType v) : v_(v) {}
+ 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::ParallelConnectionStatus v) : v_(v) {}
+ VariantHolder(p18::SolarPowerPriority v) : v_(v) {}
+ VariantHolder(p18::WorkingMode v) : v_(v) {}
+ VariantHolder(p18::LoadConnectionStatus v) : v_(v) {}
+ VariantHolder(p18::ConfigurationStatus v) : v_(v) {}
+
+ friend std::ostream &operator<<(std::ostream &os, VariantHolder const& ref) {
+ std::visit([&os](const auto& elem) {
+ os << elem;
+ }, ref.v_);
+ return os;
+ }
+
+ inline json toJSON() const {
+ json j;
+ bool isEnum =
+ std::holds_alternative<p18::BatteryType>(v_) ||
+ std::holds_alternative<p18::BatteryPowerDirection>(v_) ||
+ std::holds_alternative<p18::ChargerSourcePriority>(v_) ||
+ std::holds_alternative<p18::DC_AC_PowerDirection>(v_) ||
+ std::holds_alternative<p18::InputVoltageRange>(v_) ||
+ std::holds_alternative<p18::LinePowerDirection>(v_) ||
+ std::holds_alternative<p18::MachineType>(v_) ||
+ 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::ParallelConnectionStatus>(v_) ||
+ std::holds_alternative<p18::SolarPowerPriority>(v_) ||
+ std::holds_alternative<p18::WorkingMode>(v_) ||
+ std::holds_alternative<p18::LoadConnectionStatus>(v_) ||
+ std::holds_alternative<p18::ConfigurationStatus>(v_);
+
+ std::visit([&j, &isEnum](const auto& elem) {
+ if (isEnum)
+ j = formatter::to_str(elem);
+ else
+ j = elem;
+ }, v_);
+
+ return j;
+ }
+
+ inline json toSimpleJSON() const {
+ json j;
+ std::visit([&j](const auto& elem) {
+ j = elem;
+ }, v_);
+ return j;
+ }
+};
+
+
+/**
+ * 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
+ */
+
+class BaseResponse {
+protected:
+ std::shared_ptr<char> raw_;
+ size_t rawSize_;
+
+public:
+ BaseResponse(std::shared_ptr<char> raw, size_t rawSize);
+ virtual ~BaseResponse() = default;
+ virtual bool validate() = 0;
+ virtual void unpack() = 0;
+ virtual formattable_ptr format(formatter::Format format) = 0;
+};
+
+class GetResponse : public BaseResponse {
+protected:
+ const char* getData() const;
+ size_t getDataSize() const;
+ std::vector<std::string> getList(std::vector<FieldLength> itemLengths, int expectAtLeast = -1) const;
+
+public:
+ using BaseResponse::BaseResponse;
+ bool validate() override;
+};
+
+class SetResponse : public BaseResponse {
+public:
+ using BaseResponse::BaseResponse;
+ void unpack() override;
+ bool validate() override;
+ formattable_ptr format(formatter::Format format) override;
+ bool get();
+};
+
+
+/**
+ * Actual typed responses
+ */
+
+class ProtocolID : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned id = 0;
+};
+
+class CurrentTime : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned year = 0;
+ unsigned short month = 0;
+ unsigned short day = 0;
+ unsigned short hour = 0;
+ unsigned short minute = 0;
+ unsigned short second = 0;
+};
+
+class TotalGenerated : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned long wh = 0;
+};
+
+class YearGenerated : public TotalGenerated {
+public:
+ using TotalGenerated::TotalGenerated;
+};
+
+class MonthGenerated : public TotalGenerated {
+public:
+ using TotalGenerated::TotalGenerated;
+};
+
+class DayGenerated : public TotalGenerated {
+public:
+ using TotalGenerated::TotalGenerated;
+};
+
+class SeriesNumber : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ std::string id;
+};
+
+class CPUVersion : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ std::string main_cpu_version;
+ std::string slave1_cpu_version;
+ std::string slave2_cpu_version;
+};
+
+class RatedInformation : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned ac_input_rating_voltage; /* unit: 0.1V */
+ unsigned ac_input_rating_current; /* unit: 0.1A */
+ unsigned ac_output_rating_voltage; /* unit: 0.1A */
+ unsigned ac_output_rating_freq; /* unit: 0.1Hz */
+ unsigned ac_output_rating_current; /* unit: 0.1A */
+ unsigned ac_output_rating_apparent_power; /* unit: VA */
+ unsigned ac_output_rating_active_power; /* unit: W */
+ unsigned battery_rating_voltage; /* unit: 0.1V */
+ unsigned battery_recharge_voltage; /* unit: 0.1V */
+ unsigned battery_redischarge_voltage; /* unit: 0.1V */
+ unsigned battery_under_voltage; /* unit: 0.1V */
+ 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 */
+ p18::InputVoltageRange input_voltage_range;
+ p18::OutputModelSetting output_source_priority;
+ p18::ChargerSourcePriority charger_source_priority;
+ unsigned parallel_max_num;
+ p18::MachineType machine_type;
+ p18::Topology topology;
+ p18::OutputModelSetting output_model_setting;
+ p18::SolarPowerPriority solar_power_priority;
+ std::string mppt;
+};
+
+class GeneralStatus : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned grid_voltage; /* unit: 0.1V */
+ unsigned grid_freq; /* unit: 0.1Hz */
+ unsigned ac_output_voltage; /* unit: 0.1V */
+ unsigned ac_output_freq; /* unit: 0.1Hz */
+ unsigned ac_output_apparent_power; /* unit: VA */
+ unsigned ac_output_active_power; /* unit: W */
+ unsigned output_load_percent; /* unit: % */
+ unsigned battery_voltage; /* unit: 0.1V */
+ 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_capacity; /* unit: % */
+ unsigned inverter_heat_sink_temp; /* unit: C */
+ unsigned mppt1_charger_temp; /* unit: C */
+ unsigned mppt2_charger_temp; /* unit: C */
+ unsigned pv1_input_power; /* unit: W */
+ unsigned pv2_input_power; /* unit: W */
+ unsigned pv1_input_voltage; /* unit: 0.1V */
+ unsigned pv2_input_voltage; /* unit: 0.1V */
+ p18::ConfigurationStatus configuration_status;
+ p18::MPPTChargerStatus mppt1_charger_status;
+ p18::MPPTChargerStatus mppt2_charger_status;
+ p18::LoadConnectionStatus load_connected;
+ p18::BatteryPowerDirection battery_power_direction;
+ p18::DC_AC_PowerDirection dc_ac_power_direction;
+ p18::LinePowerDirection line_power_direction;
+ unsigned local_parallel_id; /* 0 .. (parallel number - 1) */
+};
+
+class WorkingMode : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ p18::WorkingMode mode = static_cast<p18::WorkingMode>(0);
+};
+
+class FaultsAndWarnings : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned fault_code = 0;
+ bool line_fail = false;
+ bool output_circuit_short = false;
+ bool inverter_over_temperature = false;
+ bool fan_lock = false;
+ bool battery_voltage_high = false;
+ bool battery_low = false;
+ bool battery_under = false;
+ bool over_load = false;
+ bool eeprom_fail = false;
+ bool power_limit = false;
+ bool pv1_voltage_high = false;
+ bool pv2_voltage_high = false;
+ bool mppt1_overload_warning = false;
+ bool mppt2_overload_warning = false;
+ bool battery_too_low_to_charge_for_scc1 = false;
+ bool battery_too_low_to_charge_for_scc2 = false;
+};
+
+class FlagsAndStatuses : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ bool buzzer = false;
+ bool overload_bypass = false;
+ bool lcd_escape_to_default_page_after_1min_timeout = false;
+ bool overload_restart = false;
+ bool over_temp_restart = false;
+ bool backlight_on = false;
+ bool alarm_on_primary_source_interrupt = false;
+ bool fault_code_record = false;
+ char reserved = '0';
+};
+
+class Defaults : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned ac_output_voltage = 0; /* unit: 0.1V */
+ unsigned ac_output_freq = 0;
+ p18::InputVoltageRange ac_input_voltage_range = static_cast<InputVoltageRange>(0);
+ unsigned battery_under_voltage = 0;
+ unsigned charging_float_voltage = 0;
+ 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;
+ 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::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);
+ bool flag_buzzer = false;
+ bool flag_overload_restart = false;
+ bool flag_over_temp_restart = false;
+ bool flag_backlight_on = false;
+ bool flag_alarm_on_primary_source_interrupt = false;
+ bool flag_fault_code_record = false;
+ bool flag_overload_bypass = false;
+ bool flag_lcd_escape_to_default_page_after_1min_timeout = false;
+};
+
+class AllowedChargingCurrents : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ std::vector<unsigned> amps;
+};
+
+class AllowedACChargingCurrents : public AllowedChargingCurrents {
+public:
+ using AllowedChargingCurrents::AllowedChargingCurrents;
+};
+
+class ParallelRatedInformation : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ p18::ParallelConnectionStatus parallel_id_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);
+};
+
+class ParallelGeneralStatus : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ p18::ParallelConnectionStatus parallel_id_connection_status;
+ p18::WorkingMode work_mode;
+ unsigned fault_code;
+ unsigned grid_voltage; /* unit: 0.1V */
+ unsigned grid_freq; /* unit: 0.1Hz */
+ unsigned ac_output_voltage; /* unit: 0.1V */
+ unsigned ac_output_freq; /* unit: 0.1Hz */
+ unsigned ac_output_apparent_power; /* unit: VA */
+ unsigned ac_output_active_power; /* unit: W */
+ unsigned total_ac_output_apparent_power; /* unit: VA */
+ unsigned total_ac_output_active_power; /* unit: W */
+ unsigned output_load_percent; /* unit: % */
+ 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_capacity; /* unit: % */
+ unsigned pv1_input_power; /* unit: W */
+ unsigned pv2_input_power; /* unit: W */
+ unsigned pv1_input_voltage; /* unit: 0.1V */
+ unsigned pv2_input_voltage; /* unit: 0.1V */
+ p18::MPPTChargerStatus mppt1_charger_status;
+ p18::MPPTChargerStatus mppt2_charger_status;
+ p18::LoadConnectionStatus load_connected;
+ 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 {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned short start_h = 0;
+ unsigned short start_m = 0;
+ unsigned short end_h = 0;
+ unsigned short end_m = 0;
+};
+
+class ACLoadsSupplyTimeBucket : public ACChargingTimeBucket {
+public:
+ using ACChargingTimeBucket::ACChargingTimeBucket;
+};
+
+} // namespace protocol_18
+
+#endif //INVERTER_TOOLS_P18_RESPONSE_H
diff --git a/src/protocol_18/response.h.orig b/src/protocol_18/response.h.orig
new file mode 100644
index 0000000..9295943
--- /dev/null
+++ b/src/protocol_18/response.h.orig
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef INVERTER_TOOLS_P18_RESPONSE_H
+#define INVERTER_TOOLS_P18_RESPONSE_H
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <variant>
+#include <nlohmann/json.hpp>
+
+#include "types.h"
+#include "../formatter/formatter.h"
+#include "../protocol/response.h"
+
+namespace p18::response_type {
+
+using protocol::BaseResponse;
+using nlohmann::json;
+
+typedef std::shared_ptr<formatter::Formattable> formattable_ptr;
+
+
+/**
+ * Value holder for the formatter module
+ */
+
+typedef std::variant<
+ unsigned,
+ unsigned short,
+ unsigned long,
+ bool,
+ double,
+ std::string,
+ p18::BatteryType,
+ p18::BatteryPowerDirection,
+ p18::ChargerSourcePriority,
+ p18::DC_AC_PowerDirection,
+ p18::InputVoltageRange,
+ p18::LinePowerDirection,
+ p18::MachineType,
+ p18::MPPTChargerStatus,
+ p18::Topology,
+ p18::OutputSourcePriority,
+ p18::OutputModelSetting,
+ p18::ParallelConnectionStatus,
+ p18::SolarPowerPriority,
+ p18::WorkingMode,
+ p18::LoadConnectionStatus,
+ p18::ConfigurationStatus
+> Variant;
+
+class VariantHolder {
+private:
+ Variant v_;
+
+public:
+ // implicit conversion constructors
+ VariantHolder(unsigned v) : v_(v) {}
+ VariantHolder(unsigned short v) : v_(v) {}
+ VariantHolder(unsigned long v) : v_(v) {}
+ VariantHolder(bool v) : v_(v) {}
+ VariantHolder(double v) : v_(v) {}
+ 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::DC_AC_PowerDirection v) : v_(v) {}
+ VariantHolder(p18::InputVoltageRange v) : v_(v) {}
+ VariantHolder(p18::LinePowerDirection v) : v_(v) {}
+ VariantHolder(p18::MachineType v) : v_(v) {}
+ 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::ParallelConnectionStatus v) : v_(v) {}
+ VariantHolder(p18::SolarPowerPriority v) : v_(v) {}
+ VariantHolder(p18::WorkingMode v) : v_(v) {}
+ VariantHolder(p18::LoadConnectionStatus v) : v_(v) {}
+ VariantHolder(p18::ConfigurationStatus v) : v_(v) {}
+
+ friend std::ostream &operator<<(std::ostream &os, VariantHolder const& ref) {
+ std::visit([&os](const auto& elem) {
+ os << elem;
+ }, ref.v_);
+ return os;
+ }
+
+ inline json toJSON() const {
+ json j;
+ bool isEnum =
+ std::holds_alternative<p18::BatteryType>(v_) ||
+ std::holds_alternative<p18::BatteryPowerDirection>(v_) ||
+ std::holds_alternative<p18::ChargerSourcePriority>(v_) ||
+ std::holds_alternative<p18::DC_AC_PowerDirection>(v_) ||
+ std::holds_alternative<p18::InputVoltageRange>(v_) ||
+ std::holds_alternative<p18::LinePowerDirection>(v_) ||
+ std::holds_alternative<p18::MachineType>(v_) ||
+ 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::ParallelConnectionStatus>(v_) ||
+ std::holds_alternative<p18::SolarPowerPriority>(v_) ||
+ std::holds_alternative<p18::WorkingMode>(v_) ||
+ std::holds_alternative<p18::LoadConnectionStatus>(v_) ||
+ std::holds_alternative<p18::ConfigurationStatus>(v_);
+
+ std::visit([&j, &isEnum](const auto& elem) {
+ if (isEnum)
+ j = formatter::to_str(elem);
+ else
+ j = elem;
+ }, v_);
+
+ return j;
+ }
+
+ inline json toSimpleJSON() const {
+ json j;
+ std::visit([&j](const auto& elem) {
+ j = elem;
+ }, v_);
+ return j;
+ }
+};
+
+
+<<<<<<< HEAD:src/p18/response.h
+/**
+ * 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
+ */
+
+class BaseResponse {
+protected:
+ std::shared_ptr<char> raw_;
+ size_t rawSize_;
+
+public:
+ BaseResponse(std::shared_ptr<char> raw, size_t rawSize);
+ virtual ~BaseResponse() = default;
+ virtual bool validate() = 0;
+ virtual void unpack() = 0;
+ virtual formattable_ptr format(formatter::Format format) = 0;
+};
+
+=======
+>>>>>>> bf1b75a (wip):src/protocol_18/response.h
+class GetResponse : public BaseResponse {
+protected:
+ const char* getData() const;
+ size_t getDataSize() const;
+ std::vector<std::string> getList(std::vector<FieldLength> itemLengths, int expectAtLeast = -1) const;
+
+public:
+ using BaseResponse::BaseResponse;
+ bool validate() override;
+};
+
+class SetResponse : public BaseResponse {
+public:
+ using BaseResponse::BaseResponse;
+ void unpack() override;
+ bool validate() override;
+ formattable_ptr format(formatter::Format format) override;
+ bool get();
+};
+
+
+/**
+ * Actual typed responses
+ */
+
+class ProtocolID : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned id = 0;
+};
+
+class CurrentTime : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned year = 0;
+ unsigned short month = 0;
+ unsigned short day = 0;
+ unsigned short hour = 0;
+ unsigned short minute = 0;
+ unsigned short second = 0;
+};
+
+class TotalGenerated : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned long wh = 0;
+};
+
+class YearGenerated : public TotalGenerated {
+public:
+ using TotalGenerated::TotalGenerated;
+};
+
+class MonthGenerated : public TotalGenerated {
+public:
+ using TotalGenerated::TotalGenerated;
+};
+
+class DayGenerated : public TotalGenerated {
+public:
+ using TotalGenerated::TotalGenerated;
+};
+
+class SeriesNumber : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ std::string id;
+};
+
+class CPUVersion : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ std::string main_cpu_version;
+ std::string slave1_cpu_version;
+ std::string slave2_cpu_version;
+};
+
+class RatedInformation : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned ac_input_rating_voltage; /* unit: 0.1V */
+ unsigned ac_input_rating_current; /* unit: 0.1A */
+ unsigned ac_output_rating_voltage; /* unit: 0.1A */
+ unsigned ac_output_rating_freq; /* unit: 0.1Hz */
+ unsigned ac_output_rating_current; /* unit: 0.1A */
+ unsigned ac_output_rating_apparent_power; /* unit: VA */
+ unsigned ac_output_rating_active_power; /* unit: W */
+ unsigned battery_rating_voltage; /* unit: 0.1V */
+ unsigned battery_recharge_voltage; /* unit: 0.1V */
+ unsigned battery_redischarge_voltage; /* unit: 0.1V */
+ unsigned battery_under_voltage; /* unit: 0.1V */
+ 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 */
+ p18::InputVoltageRange input_voltage_range;
+ p18::OutputModelSetting output_source_priority;
+ p18::ChargerSourcePriority charger_source_priority;
+ unsigned parallel_max_num;
+ p18::MachineType machine_type;
+ p18::Topology topology;
+ p18::OutputModelSetting output_model_setting;
+ p18::SolarPowerPriority solar_power_priority;
+ std::string mppt;
+};
+
+class GeneralStatus : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned grid_voltage; /* unit: 0.1V */
+ unsigned grid_freq; /* unit: 0.1Hz */
+ unsigned ac_output_voltage; /* unit: 0.1V */
+ unsigned ac_output_freq; /* unit: 0.1Hz */
+ unsigned ac_output_apparent_power; /* unit: VA */
+ unsigned ac_output_active_power; /* unit: W */
+ unsigned output_load_percent; /* unit: % */
+ unsigned battery_voltage; /* unit: 0.1V */
+ 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_capacity; /* unit: % */
+ unsigned inverter_heat_sink_temp; /* unit: C */
+ unsigned mppt1_charger_temp; /* unit: C */
+ unsigned mppt2_charger_temp; /* unit: C */
+ unsigned pv1_input_power; /* unit: W */
+ unsigned pv2_input_power; /* unit: W */
+ unsigned pv1_input_voltage; /* unit: 0.1V */
+ unsigned pv2_input_voltage; /* unit: 0.1V */
+ p18::ConfigurationStatus configuration_status;
+ p18::MPPTChargerStatus mppt1_charger_status;
+ p18::MPPTChargerStatus mppt2_charger_status;
+ p18::LoadConnectionStatus load_connected;
+ p18::BatteryPowerDirection battery_power_direction;
+ p18::DC_AC_PowerDirection dc_ac_power_direction;
+ p18::LinePowerDirection line_power_direction;
+ unsigned local_parallel_id; /* 0 .. (parallel number - 1) */
+};
+
+class WorkingMode : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ p18::WorkingMode mode = static_cast<p18::WorkingMode>(0);
+};
+
+class FaultsAndWarnings : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned fault_code = 0;
+ bool line_fail = false;
+ bool output_circuit_short = false;
+ bool inverter_over_temperature = false;
+ bool fan_lock = false;
+ bool battery_voltage_high = false;
+ bool battery_low = false;
+ bool battery_under = false;
+ bool over_load = false;
+ bool eeprom_fail = false;
+ bool power_limit = false;
+ bool pv1_voltage_high = false;
+ bool pv2_voltage_high = false;
+ bool mppt1_overload_warning = false;
+ bool mppt2_overload_warning = false;
+ bool battery_too_low_to_charge_for_scc1 = false;
+ bool battery_too_low_to_charge_for_scc2 = false;
+};
+
+class FlagsAndStatuses : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ bool buzzer = false;
+ bool overload_bypass = false;
+ bool lcd_escape_to_default_page_after_1min_timeout = false;
+ bool overload_restart = false;
+ bool over_temp_restart = false;
+ bool backlight_on = false;
+ bool alarm_on_primary_source_interrupt = false;
+ bool fault_code_record = false;
+ char reserved = '0';
+};
+
+class Defaults : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned ac_output_voltage = 0; /* unit: 0.1V */
+ unsigned ac_output_freq = 0;
+ p18::InputVoltageRange ac_input_voltage_range = static_cast<InputVoltageRange>(0);
+ unsigned battery_under_voltage = 0;
+ unsigned charging_float_voltage = 0;
+ 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;
+ 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::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);
+ bool flag_buzzer = false;
+ bool flag_overload_restart = false;
+ bool flag_over_temp_restart = false;
+ bool flag_backlight_on = false;
+ bool flag_alarm_on_primary_source_interrupt = false;
+ bool flag_fault_code_record = false;
+ bool flag_overload_bypass = false;
+ bool flag_lcd_escape_to_default_page_after_1min_timeout = false;
+};
+
+class AllowedChargingCurrents : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ std::vector<unsigned> amps;
+};
+
+class AllowedACChargingCurrents : public AllowedChargingCurrents {
+public:
+ using AllowedChargingCurrents::AllowedChargingCurrents;
+};
+
+class ParallelRatedInformation : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ p18::ParallelConnectionStatus parallel_id_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);
+};
+
+class ParallelGeneralStatus : public GetResponse {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ p18::ParallelConnectionStatus parallel_id_connection_status;
+ p18::WorkingMode work_mode;
+ unsigned fault_code;
+ unsigned grid_voltage; /* unit: 0.1V */
+ unsigned grid_freq; /* unit: 0.1Hz */
+ unsigned ac_output_voltage; /* unit: 0.1V */
+ unsigned ac_output_freq; /* unit: 0.1Hz */
+ unsigned ac_output_apparent_power; /* unit: VA */
+ unsigned ac_output_active_power; /* unit: W */
+ unsigned total_ac_output_apparent_power; /* unit: VA */
+ unsigned total_ac_output_active_power; /* unit: W */
+ unsigned output_load_percent; /* unit: % */
+ 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_capacity; /* unit: % */
+ unsigned pv1_input_power; /* unit: W */
+ unsigned pv2_input_power; /* unit: W */
+ unsigned pv1_input_voltage; /* unit: 0.1V */
+ unsigned pv2_input_voltage; /* unit: 0.1V */
+ p18::MPPTChargerStatus mppt1_charger_status;
+ p18::MPPTChargerStatus mppt2_charger_status;
+ p18::LoadConnectionStatus load_connected;
+ 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 {
+public:
+ using GetResponse::GetResponse;
+ void unpack() override;
+ formattable_ptr format(formatter::Format format) override;
+
+ unsigned short start_h = 0;
+ unsigned short start_m = 0;
+ unsigned short end_h = 0;
+ unsigned short end_m = 0;
+};
+
+class ACLoadsSupplyTimeBucket : public ACChargingTimeBucket {
+public:
+ using ACChargingTimeBucket::ACChargingTimeBucket;
+};
+
+} // namespace protocol_18
+
+#endif //INVERTER_TOOLS_P18_RESPONSE_H
diff --git a/src/protocol_18/types.h b/src/protocol_18/types.h
new file mode 100644
index 0000000..d3574e9
--- /dev/null
+++ b/src/protocol_18/types.h
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef INVERTER_TOOLS_P18_TYPES_H
+#define INVERTER_TOOLS_P18_TYPES_H
+
+#include <string>
+
+#define ENUM_STR(enum_type) std::ostream& operator<< (std::ostream& os, enum_type val)
+#define ENUM_STR_DEFAULT return os << val
+
+namespace p18 {
+
+enum class CommandType: int {
+ GetProtocolID = 0,
+ GetCurrentTime,
+ GetTotalGenerated,
+ GetYearGenerated,
+ GetMonthGenerated,
+ GetDayGenerated,
+ GetSeriesNumber,
+ GetCPUVersion,
+ GetRatedInformation,
+ GetGeneralStatus,
+ GetWorkingMode,
+ GetFaultsAndWarnings,
+ GetFlagsAndStatuses,
+ GetDefaults,
+ GetAllowedChargingCurrents,
+ GetAllowedACChargingCurrents,
+ GetParallelRatedInformation,
+ GetParallelGeneralStatus,
+ GetACChargingTimeBucket,
+ GetACLoadsSupplyTimeBucket,
+ SetLoads = 100,
+ SetFlag,
+ SetDefaults,
+ SetBatteryMaxChargingCurrent,
+ SetBatteryMaxACChargingCurrent,
+ SetACOutputFreq,
+ SetBatteryMaxChargingVoltage,
+ SetACOutputRatedVoltage,
+ SetOutputSourcePriority,
+ SetBatteryChargingThresholds, /* Battery re-charging and re-discharing voltage when utility is available */
+ SetChargingSourcePriority,
+ SetSolarPowerPriority,
+ SetACInputVoltageRange,
+ SetBatteryType,
+ SetOutputModel,
+ SetBatteryCutOffVoltage,
+ SetSolarConfig,
+ ClearGenerated,
+ SetDateTime,
+ SetACChargingTimeBucket,
+ SetACLoadsSupplyTimeBucket,
+};
+
+enum class BatteryType {
+ AGM = 0,
+ Flooded = 1,
+ User = 2,
+};
+ENUM_STR(BatteryType);
+
+enum class InputVoltageRange {
+ Appliance = 0,
+ USP = 1,
+};
+ENUM_STR(InputVoltageRange);
+
+enum class OutputSourcePriority {
+ SolarUtilityBattery = 0,
+ SolarBatteryUtility = 1,
+};
+ENUM_STR(OutputSourcePriority);
+
+enum class ChargerSourcePriority {
+ SolarFirst = 0,
+ SolarAndUtility = 1,
+ SolarOnly = 2,
+};
+ENUM_STR(ChargerSourcePriority);
+
+enum class MachineType {
+ OffGridTie = 0,
+ GridTie = 1,
+};
+ENUM_STR(MachineType);
+
+enum class Topology {
+ TransformerLess = 0,
+ Transformer = 1,
+};
+ENUM_STR(Topology);
+
+enum class OutputModelSetting {
+ SingleModule = 0,
+ ParallelOutput = 1,
+ Phase1OfThreePhaseOutput = 2,
+ Phase2OfThreePhaseOutput = 3,
+ Phase3OfThreePhaseOutput = 4,
+};
+ENUM_STR(OutputModelSetting);
+
+enum class SolarPowerPriority {
+ BatteryLoadUtility = 0,
+ LoadBatteryUtility = 1,
+};
+ENUM_STR(SolarPowerPriority);
+
+enum class MPPTChargerStatus {
+ Abnormal = 0,
+ NotCharging = 1,
+ Charging = 2,
+};
+ENUM_STR(MPPTChargerStatus);
+
+enum class BatteryPowerDirection {
+ DoNothing = 0,
+ Charge = 1,
+ Discharge = 2,
+};
+ENUM_STR(BatteryPowerDirection);
+
+enum class DC_AC_PowerDirection {
+ DoNothing = 0,
+ AC_DC = 1,
+ DC_AC = 2,
+};
+ENUM_STR(DC_AC_PowerDirection);
+
+enum class LinePowerDirection {
+ DoNothing = 0,
+ Input = 1,
+ Output = 2,
+};
+ENUM_STR(LinePowerDirection);
+
+enum class WorkingMode {
+ PowerOnMode = 0,
+ StandbyMode = 1,
+ BypassMode = 2,
+ BatteryMode = 3,
+ FaultMode = 4,
+ HybridMode = 5,
+};
+ENUM_STR(WorkingMode);
+
+enum class ParallelConnectionStatus {
+ NotExistent = 0,
+ Existent = 1,
+};
+ENUM_STR(ParallelConnectionStatus);
+
+enum class LoadConnectionStatus {
+ Disconnected = 0,
+ Connected = 1,
+};
+ENUM_STR(LoadConnectionStatus);
+
+enum class ConfigurationStatus {
+ Default = 0,
+ Changed = 1,
+};
+ENUM_STR(ConfigurationStatus);
+
+struct Flag {
+ std::string flag;
+ char letter;
+ std::string description;
+};
+
+}
+
+#endif //INVERTER_TOOLS_P18_TYPES_H