aboutsummaryrefslogtreecommitdiff
path: root/p18.c
diff options
context:
space:
mode:
Diffstat (limited to 'p18.c')
-rw-r--r--p18.c1559
1 files changed, 1559 insertions, 0 deletions
diff --git a/p18.c b/p18.c
new file mode 100644
index 0000000..5ca71f2
--- /dev/null
+++ b/p18.c
@@ -0,0 +1,1559 @@
+/**
+ * Copyright (C) 2020 Evgeny Zinoviev
+ * This file is part of isv <https://github.com/gch1p/isv>.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <math.h>
+
+#include "p18.h"
+#include "util.h"
+
+const char *p18_query_cmds[] = {
+ "PI", /* Protocol ID */
+ "T", /* Current time */
+ "ET", /* Total generated energy */
+ "EY", /* Year generated energy */
+ "EM", /* Month generated energy */
+ "ED", /* Day generated energy */
+ "ID", /* Series number */
+ "VFW", /* CPU version */
+ "PIRI", /* Rated information */
+ "GS", /* General status */
+ "MOD", /* Working mode */
+ "FWS", /* Fault and warning status */
+ "FLAG", /* Enable/Disable flags statuses */
+ "DI", /* Default values of changeable parameters */
+ "MCHGCR", /* Max charging current selectable (allowed) values */
+ "MUCHGCR", /* Max AC charging current selectable (allowed) values */
+ "PRI", /* Parallel system rated information */
+ "PGS", /* Parallel system general status */
+ "ACCT", /* AC charge time bucket */
+ "ACLT", /* AC supply load time bucket */
+};
+
+const char *p18_set_cmds[] = {
+ "LON", /* Enable/disable machine supply power to the loads */
+ "P", /* Enable/disable flags */
+ "PF", /* Reset changeable params */
+ "MCHGC", /* Battery maximum charge current */
+ "MUCHGC", /* Battery maximum AC charge current */
+ /* The protocol documentation defines two commands, "F50" and "F60",
+ but it's identical as if there were just one "F" command with an argument. */
+ "F", /* Set AC output frequency to be 50Hz or 60Hz */
+ "MCHGV", /* Battery maximum charge voltage */
+ "V", /* AC output rated voltage */
+ "POP", /* Output source priority */
+ "BUCD", /* Battery re-charged and re-discharged voltage when utility is available */
+ "PCP", /* Charging source priority */
+ "PSP", /* Solar power priority */
+ "PGR", /* AC input voltage range */
+ "PBT", /* Battery type */
+ "POPM", /* Output model */
+ "PSDV", /* Battery cut-off voltage */
+ "ID", /* Set solar configuration */
+ "CLE", /* Clear all data of generated energy */
+ "DAT", /* Set date and time */
+ "ACCT", /* AC charge time bucket */
+ "ACLT", /* AC supply load time bucket */
+};
+
+const p18_fault_code_list_item_t 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 p18_flag_printable_list_item_t p18_flags_printable_list[9] = {
+ {"BUZZ", "A", "Silence buzzer or open buzzer"},
+ {"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)"},
+};
+
+const int p18_ac_output_rated_voltages[5] = {
+ 202,
+ 208,
+ 220,
+ 230,
+ 240,
+};
+
+const char *p18_battery_util_recharging_voltages_12v_unit[8] = {
+ "11",
+ "11.3",
+ "11.5",
+ "11.8",
+ "12",
+ "12.3",
+ "12.5",
+ "12.8",
+};
+
+const char *p18_battery_util_recharging_voltages_24v_unit[8] = {
+ "22",
+ "22.5",
+ "23",
+ "23.5",
+ "24",
+ "24.5",
+ "25",
+ "25.5",
+};
+const char *p18_battery_util_recharging_voltages_48v_unit[8] = {
+ "44",
+ "45",
+ "46",
+ "47",
+ "48",
+ "49",
+ "50",
+ "51",
+};
+
+const char *p18_battery_util_redischarging_voltages_12v_unit[12] = {
+ "0",
+ "12",
+ "12.3",
+ "12.5",
+ "12.8",
+ "13",
+ "13.3",
+ "13.5",
+ "13.8",
+ "14",
+ "14.3",
+ "14.5",
+};
+const char *p18_battery_util_redischarging_voltages_24v_unit[12] = {
+ "0",
+ "24",
+ "24.5",
+ "25",
+ "25.5",
+ "26",
+ "26.5",
+ "27",
+ "27.5",
+ "28",
+ "28.5",
+ "29"
+};
+const char *p18_battery_util_redischarging_voltages_48v_unit[12] = {
+ "0",
+ "48",
+ "49",
+ "50",
+ "51",
+ "52",
+ "53",
+ "54",
+ "55",
+ "56",
+ "57",
+ "58",
+};
+
+const char *p18_battery_types[] = {
+ "AGM",
+ "Flooded",
+ "User",
+};
+
+const char *p18_voltage_ranges[] = {
+ "Appliance",
+ "UPS"
+};
+
+const char *p18_output_source_priorities[] = {
+ "Solar-Utility-Battery",
+ "Solar-Battery-Utility"
+};
+
+const char *p18_charger_source_priorities[] = {
+ "Solar-First",
+ "Solar-and-Utility",
+ "Solar-Only"
+};
+
+const char *p18_machine_types[] = {
+ "Off-Grid-Tie",
+ "Grid-Tie"
+};
+
+const char *p18_topologies[] = {
+ "Transformerless",
+ "Transformer"
+};
+
+const char *p18_output_model_settings[] = {
+ "Single module",
+ "Parallel output",
+ "Phase 1 of three phase output",
+ "Phase 2 of three phase output",
+ "Phase 3 of three phase",
+};
+
+const char *p18_solar_power_priorities[] = {
+ "Battery-Load-Utility",
+ "Load-Battery-Utility"
+};
+
+const char *p18_mppt_charger_statuses[] = {
+ "Abnormal",
+ "Not charging",
+ "Charging"
+};
+
+const char *p18_battery_power_directions[] = {
+ "Do nothing",
+ "Charge",
+ "Discharge"
+};
+
+const char *p18_dc_ac_power_directions[] = {
+ "Do nothing",
+ "AC/DC",
+ "DC/AC"
+};
+
+const char *p18_line_power_directions[] = {
+ "Do nothing",
+ "Input",
+ "Output",
+};
+
+const char *p18_working_modes[] = {
+ "Power on mode",
+ "Standby mode",
+ "Bypass mode",
+ "Battery mode",
+ "Fault mode",
+ "Hybrid mode",
+};
+
+const char *p18_parallel_connection_statuses[] = {
+ "Non-existent",
+ "Existent"
+};
+
+const char *p18_unknown = "Unknown";
+
+/* ------------------------------------------ */
+
+static char *p18_append_arguments(int command, char *buf, const char **a)
+{
+ switch (command) {
+ case P18_QUERY_YEAR_GENERATED:
+ buf += sprintf(buf, "%s", a[0]);
+ break;
+
+ case P18_QUERY_MONTH_GENERATED:
+ buf += sprintf(buf, "%s%02d", a[0], atoi(a[1]));
+ break;
+
+ case P18_QUERY_DAY_GENERATED:
+ buf += sprintf(buf, "%s%02d%02d",
+ a[0], atoi(a[1]), atoi(a[2]));
+ break;
+
+ case P18_QUERY_PARALLEL_RATED_INFORMATION:
+ case P18_QUERY_PARALLEL_GENERAL_STATUS:
+ buf += sprintf(buf, "%d", atoi(a[0]));
+ break;
+
+ case P18_SET_LOADS:
+ buf += sprintf(buf, "%s", a[0]);
+ break;
+
+ case P18_SET_FLAG:
+ buf += sprintf(buf, "%c%s",
+ *a[1] == '1' ? 'E' : 'D',
+ a[0]);
+ break;
+
+ case P18_SET_BAT_MAX_CHARGE_CURRENT:
+ case P18_SET_BAT_MAX_AC_CHARGE_CURRENT:
+ buf += sprintf(buf, "%c,%03d", *a[0], atoi(a[1]));
+ break;
+
+ case P18_SET_AC_OUTPUT_FREQ:
+ buf += sprintf(buf, "%02d", atoi(a[0]));
+ break;
+
+ case P18_SET_BAT_MAX_CHARGE_VOLTAGE: {
+ double cv = atof(a[0]);
+ double fv = atof(a[1]);
+ buf += sprintf(buf, "%03d,%03d", (int)round(cv*10), (int)round(fv*10));
+ break;
+ }
+
+ case P18_SET_AC_OUTPUT_RATED_VOLTAGE: {
+ int v = atoi(a[0]);
+ buf += sprintf(buf, "%04d", v*10);
+ break;
+ }
+
+ case P18_SET_OUTPUT_SOURCE_PRIORITY:
+ case P18_SET_SOLAR_POWER_PRIORITY:
+ case P18_SET_AC_INPUT_VOLTAGE_RANGE:
+ case P18_SET_BAT_TYPE:
+ buf += sprintf(buf, "%c", *a[0]);
+ break;
+
+ case P18_SET_BAT_CHARGING_THRESHOLDS_WHEN_UTILITY_AVAIL: {
+ double cv = atof(a[0]);
+ double dv = atof(a[1]);
+ buf += sprintf(buf, "%03d,%03d", (int)round(cv*10), (int)round(dv*10));
+ break;
+ }
+
+ case P18_SET_CHARGING_SOURCE_PRIORITY:
+ case P18_SET_OUTPUT_MODEL:
+ buf += sprintf(buf, "%c,%c", *a[0], *a[1]);
+ break;
+
+ case P18_SET_BAT_CUTOFF_VOLTAGE: {
+ double v = atof(a[0]);
+ buf += sprintf(buf, "%03d", (int)round(v*10));
+ break;
+ }
+
+ case P18_SET_SOLAR_CONFIG: {
+ int len = (int)strlen(a[0]);
+ buf += sprintf(buf, "%02d", len);
+ buf += sprintf(buf, "%s", a[0]);
+ if (len < 20) {
+ for (int i = 0; i < 20-len; i++)
+ buf += sprintf(buf, "%c", '0');
+ }
+ break;
+ }
+
+ case P18_SET_DATE_TIME: {
+ int Y = atoi(a[0]) - 2000;
+ int M = atoi(a[1]);
+ int D = atoi(a[2]);
+ int h = atoi(a[3]);
+ int m = atoi(a[4]);
+ int s = atoi(a[5]);
+ buf += sprintf(buf, "%02d%02d%02d%02d%02d%02d", Y, M, D, h, m, s);
+ break;
+ }
+
+ case P18_SET_AC_CHARGE_TIME_BUCKET:
+ case P18_SET_AC_SUPPLY_LOAD_TIME_BUCKET:
+ buf += sprintf(buf, "%02d%02d,%02d%02d",
+ atoi(a[0]), atoi(a[1]), atoi(a[2]), atoi(a[3]));
+ break;
+ }
+
+ return buf;
+}
+
+bool p18_build_command(int command, const char **args, size_t args_size, char *buf)
+{
+ UNUSED(args_size);
+
+ bool set = command >= P18_SET_CMDS_ENUM_OFFSET;
+ int offset;
+ const char **list;
+ size_t list_size;
+ if (!set) {
+ list = p18_query_cmds;
+ list_size = ARRAY_SIZE(p18_query_cmds);
+ offset = P18_QUERY_CMDS_ENUM_OFFSET;
+ } else {
+ list = p18_set_cmds;
+ list_size = ARRAY_SIZE(p18_set_cmds);
+ offset = P18_SET_CMDS_ENUM_OFFSET;
+ }
+
+ int cmd_index = command - offset;
+ if (cmd_index < 0 || cmd_index >= (int)list_size) {
+ LOG("%s: invalid cmd_index %d\n", __func__, cmd_index);
+ return false;
+ }
+
+ (*buf++) = '^';
+ (*buf++) = set ? 'S' : 'P';
+
+ char *len_p = buf;
+ buf += sprintf(buf, "000");
+ buf += sprintf(buf, "%s", list[cmd_index]);
+
+ buf = p18_append_arguments(command, buf, args);
+
+ char len_buf[4];
+ sprintf(len_buf, "%03zu", (size_t)(buf-len_p));
+ memcpy(len_p, len_buf, 3);
+
+ return true;
+}
+
+bool p18_validate_query_response(const char *buf, size_t size, size_t *data_size)
+{
+ if (buf[0] != '^' || buf[1] != 'D')
+ return false;
+
+ char lenbuf[4];
+ memcpy(lenbuf, &buf[2], 3);
+ lenbuf[3] = '\0';
+
+ unsigned int len = atoi(lenbuf);
+ if (size < len+4)
+ return false;
+
+ if (data_size)
+ *data_size = len-3;
+ return true;
+}
+
+bool p18_set_result(const char *buf, size_t size)
+{
+ if (size < 2) {
+ ERROR("%s: response is too small\n", __func__);
+ return false;
+ }
+ if (buf[0] != '^') {
+ ERROR("%s: invalid character %c, ^ expected\n", __func__, *buf);
+ return false;
+ }
+ return buf[1] == '1';
+}
+
+static void p18_parse_list(
+ const char *data,
+ void *message_ptr,
+ int expected_items_count, /* -1 means no fixed limit */
+ p18_parse_cb_t callback)
+{
+ int data_size = (int)strlen(data);
+ static const int buf_size = 64;
+ char buf[buf_size];
+ char *c = (char *)data;
+
+ for (int i = 0, char_index = 0, item_index = 0;
+ i < data_size;
+ i++) {
+
+ if (*c != ',' && i < data_size-1) {
+ if (char_index < buf_size-1)
+ buf[char_index++] = *c;
+ c++;
+ continue;
+ }
+
+ /* last character */
+ if (*c != ',' && char_index < buf_size-1)
+ buf[char_index++] = *c;
+
+ if (expected_items_count != -1 && item_index >= expected_items_count)
+ ERROR("warning: item %d is not expected\n", item_index);
+ else {
+ buf[char_index] = '\0';
+ callback((const char *)buf, char_index, item_index, message_ptr);
+ }
+
+ char_index = 0;
+ c++;
+ item_index++;
+ }
+}
+
+static void p18_expect_listitem_length(const char *f,
+ int index,
+ size_t expected_len,
+ size_t actual_len) {
+ if (actual_len != expected_len) {
+ LOG("%s: length of item %d is %zu != %zu\n",
+ f, index, actual_len, expected_len);
+ }
+}
+
+/* ------------------------------------------ */
+/* Command-specific methods */
+
+P18_UNPACK_FN(protocol_id)
+{
+ char s[4];
+ strncpy(s, data, 2);
+ s[2] = '\0';
+
+ p18_protocol_id_msg_t m;
+ m.id = atoi(s);
+
+ return m;
+}
+
+P18_UNPACK_FN(current_time)
+{
+ char s[4];
+ int n;
+ p18_current_time_msg_t m;
+
+ substr_copy(s, data, 4);
+ m.year = atoi(s);
+
+ for (int i = 0; i < 5; i++) {
+ substr_copy(s, data + 4 + (i * 2), 2);
+ n = atoi(s);
+ switch (i) {
+ case 0: m.month = n; break;
+ case 1: m.day = n; break;
+ case 2: m.hour = n; break;
+ case 3: m.minute = n; break;
+ case 4: m.second = n; break;
+ default:
+ ERROR("%s: warning: unexpected code path, i=%d\n",
+ __func__, i);
+ break;
+ }
+ }
+
+ return m;
+}
+
+static long parse_generated(const char *buf)
+{
+ char s[16];
+ strncpy(s, buf, 8);
+ s[8] = '\0';
+ return atol(s);
+}
+
+P18_UNPACK_FN(total_generated)
+{
+ p18_total_generated_msg_t m = {
+ .kwh = parse_generated(data)
+ };
+ return m;
+}
+
+P18_UNPACK_FN(year_generated)
+{
+ p18_year_generated_msg_t m = {
+ .kwh = parse_generated(data)
+ };
+ return m;
+}
+
+P18_UNPACK_FN(month_generated)
+{
+ p18_month_generated_msg_t m = {
+ .kwh = parse_generated(data)
+ };
+ return m;
+}
+
+P18_UNPACK_FN(day_generated)
+{
+ p18_day_generated_msg_t m = {
+ .kwh = parse_generated(data)
+ };
+ return m;
+}
+
+P18_UNPACK_FN(series_number)
+{
+ p18_series_number_msg_t m;
+
+ /* read length, reuse the already available id datafer */
+ substr_copy(m.id, data, 2);
+ m.length = atoi(m.id);
+
+ /* read id of given length */
+ substr_copy(m.id, data + 2, m.length);
+
+ return m;
+}
+
+P18_PARSE_CB_FN(cpu_version)
+{
+ //LOG("%s: value=%s, value_len=%d\n", __func__, value, value_len);
+
+ p18_cpu_version_msg_t *m = (p18_cpu_version_msg_t *)message_ptr;
+ P18_EXPECT_LISTITEM_LENGTH(5);
+
+ switch (index) {
+ case 0:
+ substr_copy(m->main_cpu_version, value, 5);
+ break;
+
+ case 1:
+ substr_copy(m->slave1_cpu_version, value, 5);
+ break;
+
+ case 2:
+ substr_copy(m->slave2_cpu_version, value, 5);
+ break;
+ }
+}
+P18_UNPACK_FN(cpu_version)
+{
+ P18_PARSE_LIST_AND_RETURN(cpu_version, 3);
+}
+
+P18_PARSE_CB_FN(rated_information)
+{
+ p18_rated_information_msg_t *m = (p18_rated_information_msg_t *)message_ptr;
+ int num = atoi(value);
+ switch (index) {
+ case 0: /* AAAA */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_input_rating_voltage = num;
+ break;
+ case 1: /* BBB */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->ac_input_rating_current = num;
+ break;
+ case 2: /* CCCC */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_rating_voltage = num;
+ break;
+ case 3: /* DDD */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->ac_output_rating_freq = num;
+ break;
+ case 4: /* EEE */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->ac_output_rating_current = num;
+ break;
+ case 5: /* FFFF */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_rating_apparent_power = num;
+ break;
+ case 6: /* GGGG */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_rating_active_power = num;
+ break;
+ case 7: /* HHH */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_rating_voltage = num;
+ break;
+ case 8: /* III */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_recharge_voltage = num;
+ break;
+ case 9: /* JJJ */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_redischarge_voltage = num;
+ break;
+ case 10: /* KKK */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_under_voltage = num;
+ break;
+ case 11: /* LLL */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_bulk_voltage = num;
+ break;
+ case 12: /* MMM */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_float_voltage = num;
+ break;
+ case 13: /* N */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->battery_type = num;
+ break;
+ case 14: /* OO */
+ P18_EXPECT_LISTITEM_LENGTH(2);
+ m->max_ac_charging_current = num;
+ break;
+ case 15: /* PPP */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->max_charging_current = num;
+ break;
+ case 16: /* Q */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->input_voltage_range = num;
+ break;
+ case 17: /* R */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->output_source_priority = num;
+ break;
+ case 18: /* S */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->charger_source_priority = num;
+ break;
+ case 19: /* T */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->parallel_max_num = num;
+ break;
+ case 20: /* U */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->machine_type = num;
+ break;
+ case 21: /* V */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->topology = num;
+ break;
+ case 22: /* W */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->output_model_setting = num;
+ break;
+ case 23: /* Z */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->solar_power_priority = num;
+ break;
+ case 24: /* a */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ substr_copy(m->mppt, value, 1);
+ break;
+ }
+}
+P18_UNPACK_FN(rated_information)
+{
+ P18_PARSE_LIST_AND_RETURN(rated_information, 25)
+}
+
+P18_PARSE_CB_FN(general_status)
+{
+ p18_general_status_msg_t *m = (p18_general_status_msg_t *)message_ptr;
+ int num = atoi(value);
+ switch (index) {
+ case 0: /* AAAA */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->grid_voltage = num;
+ break;
+ case 1: /* BBB */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->grid_freq = num;
+ break;
+ case 2: /* CCCC */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_voltage = num;
+ break;
+ case 3: /* DDD */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->ac_output_freq = num;
+ break;
+ case 4: /* EEEE */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_apparent_power = num;
+ break;
+ case 5: /* FFFF */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_active_power = num;
+ break;
+ case 6: /* GGG */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->output_load_percent = num;
+ break;
+ case 7: /* HHH */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_voltage = num;
+ break;
+ case 8: /* III */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_voltage_scc = num;
+ break;
+ case 9: /* JJJ */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_voltage_scc2 = num;
+ break;
+ case 10: /* KKK */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_discharge_current = num;
+ break;
+ case 11: /* LLL */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_charging_current = num;
+ break;
+ case 12: /* MMM */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_capacity = num;
+ break;
+ case 13: /* NNN */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->inverter_heat_sink_temp = num;
+ break;
+ case 14: /* OOO */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->mppt1_charger_temp = num;
+ break;
+ case 15: /* PPP */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->mppt2_charger_temp = num;
+ break;
+ case 16: /* QQQQ */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv1_input_power = num;
+ break;
+ case 17: /* RRRR */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv2_input_power = num;
+ break;
+ case 18: /* SSSS */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv1_input_voltage = num;
+ break;
+ case 19: /* TTTT */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv2_input_voltage = num;
+ break;
+ case 20: /* U */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->settings_values_changed = num == 1;
+ break;
+ case 21: /* V */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->mppt1_charger_status = num;
+ break;
+ case 22: /* W */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->mppt2_charger_status = num;
+ break;
+ case 23: /* X */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->load_connected = num == 1;
+ break;
+ case 24: /* Y */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->battery_power_direction = num;
+ break;
+ case 25: /* Z */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->dc_ac_power_direction = num;
+ break;
+ case 26: /* a */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->line_power_direction = num;
+ break;
+ case 27: /* b */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->local_parallel_id = num;
+ break;
+ }
+}
+P18_UNPACK_FN(general_status)
+{
+ P18_PARSE_LIST_AND_RETURN(general_status, 28)
+}
+
+P18_UNPACK_FN(working_mode)
+{
+ char s[4];
+ strncpy(s, data, 2);
+ s[2] = '\0';
+
+ p18_working_mode_msg_t m;
+ m.mode = atoi(s);
+
+ return m;
+}
+
+P18_PARSE_CB_FN(faults_warnings)
+{
+ p18_faults_warnings_msg_t *m = (p18_faults_warnings_msg_t *)message_ptr;
+ int num = atoi(value);
+ P18_EXPECT_LISTITEM_LENGTH(index == 0 ? 2 : 1);
+ switch (index) {
+ case 0:
+ m->fault_code = num;
+ break;
+ case 1:
+ m->line_fail = num > 0;
+ break;
+ case 2:
+ m->output_circuit_short = num > 0;
+ break;
+ case 3:
+ m->inverter_over_temperature = num > 0;
+ break;
+ case 4:
+ m->fan_lock = num > 0;
+ break;
+ case 5:
+ m->battery_voltage_high = num > 0;
+ break;
+ case 6:
+ m->battery_low = num > 0;
+ break;
+ case 7:
+ m->battery_under = num > 0;
+ break;
+ case 8:
+ m->over_load = num > 0;
+ break;
+ case 9:
+ m->eeprom_fail = num > 0;
+ break;
+ case 10:
+ m->power_limit = num > 0;
+ break;
+ case 11:
+ m->pv1_voltage_high = num > 0;
+ break;
+ case 12:
+ m->pv2_voltage_high = num > 0;
+ break;
+ case 13:
+ m->mppt1_overload_warning = num > 0;
+ break;
+ case 14:
+ m->mppt2_overload_warning = num > 0;
+ break;
+ case 15:
+ m->battery_too_low_to_charge_for_scc1 = num > 0;
+ break;
+ case 16:
+ m->battery_too_low_to_charge_for_scc2 = num > 0;
+ break;
+ }
+}
+P18_UNPACK_FN(faults_warnings)
+{
+ P18_PARSE_LIST_AND_RETURN(faults_warnings, 17)
+}
+
+P18_PARSE_CB_FN(flags_statuses)
+{
+ p18_flags_statuses_msg_t *m = (p18_flags_statuses_msg_t *)message_ptr;
+ int num = atoi(value);
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ switch (index) {
+ case 0:
+ m->buzzer = num > 0;
+ break;
+ case 1:
+ m->overload_bypass = num > 0;
+ break;
+ case 2:
+ m->lcd_escape_to_default_page_after_1min_timeout = num > 0;
+ break;
+ case 3:
+ m->overload_restart = num > 0;
+ break;
+ case 4:
+ m->over_temp_restart = num > 0;
+ break;
+ case 5:
+ m->backlight_on = num > 0;
+ break;
+ case 6:
+ m->alarm_on_primary_source_interrupt = num > 0;
+ break;
+ case 7:
+ m->fault_code_record = num > 0;
+ break;
+ }
+}
+P18_UNPACK_FN(flags_statuses)
+{
+ P18_PARSE_LIST_AND_RETURN(flags_statuses, 9)
+}
+
+P18_PARSE_CB_FN(defaults)
+{
+ p18_defaults_msg_t *m = (p18_defaults_msg_t *)message_ptr;
+ int num = atoi(value);
+ switch (index) {
+ case 0: /* AAAA */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_voltage = num;
+ break;
+ case 1: /* BBB */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->ac_output_freq = num;
+ break;
+ case 2: /* C */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->ac_input_voltage_range = num;
+ break;
+ case 3: /* DDD */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_under_voltage = num;
+ break;
+ case 4: /* EEE */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->charging_float_voltage = num;
+ break;
+ case 5: /* FFF */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->charging_bulk_voltage = num;
+ break;
+ case 6: /* GGG */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_recharge_voltage = num;
+ break;
+ case 7: /* HHH */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_redischarge_voltage = num;
+ break;
+ case 8: /* III */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->max_charging_current = num;
+ break;
+ case 9: /* JJ */
+ P18_EXPECT_LISTITEM_LENGTH(2);
+ m->max_ac_charging_current = num;
+ break;
+ case 10: /* K */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->battery_type = num;
+ break;
+ case 11: /* L */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->output_source_priority = num;
+ break;
+ case 12: /* M */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->charger_source_priority = num;
+ break;
+ case 13: /* N */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->solar_power_priority = num;
+ break;
+ case 14: /* O */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->machine_type = num;
+ break;
+ case 15: /* P */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->output_model_setting = num;
+ break;
+ case 16: /* S */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_buzzer = num > 0;
+ break;
+ case 17: /* T */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_overload_restart = num > 0;
+ break;
+ case 18: /* U */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_over_temp_restart = num > 0;
+ break;
+ case 19: /* V */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_backlight_on = num > 0;
+ break;
+ case 20: /* W */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_alarm_on_primary_source_interrupt = num > 0;
+ break;
+ case 21: /* X */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_fault_code_record = num > 0;
+ break;
+ case 22: /* Y */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_overload_bypass = num > 0;
+ break;
+ case 23: /* Z */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->flag_lcd_escape_to_default_page_after_1min_timeout = num > 0;
+ break;
+ }
+}
+P18_UNPACK_FN(defaults)
+{
+ P18_PARSE_LIST_AND_RETURN(defaults, 24)
+}
+
+P18_PARSE_CB_FN(max_charging_current_selectable_values)
+{
+ UNUSED(value_len);
+ p18_max_charging_current_selectable_values_msg_t *m =
+ (p18_max_charging_current_selectable_values_msg_t *)message_ptr;
+ if (m->len >= ARRAY_SIZE(m->amps))
+ ERROR("warning: %dth item, ignoring as we accepting up to %zu values\n",
+ index, ARRAY_SIZE(m->amps));
+ else
+ m->amps[m->len++] = atoi(value);
+}
+P18_UNPACK_FN(max_charging_current_selectable_values)
+{
+ P18_PARSE_LIST_AND_RETURN(max_charging_current_selectable_values, -1)
+}
+
+P18_PARSE_CB_FN(max_ac_charging_current_selectable_values)
+{
+ UNUSED(value_len);
+ p18_max_ac_charging_current_selectable_values_msg_t *m =
+ (p18_max_ac_charging_current_selectable_values_msg_t *)message_ptr;
+ if (m->len >= ARRAY_SIZE(m->amps))
+ ERROR("warning: %dth item, ignoring as we accepting up to %zu values\n",
+ index, ARRAY_SIZE(m->amps));
+ else
+ m->amps[m->len++] = atoi(value);
+}
+P18_UNPACK_FN(max_ac_charging_current_selectable_values)
+{
+ P18_PARSE_LIST_AND_RETURN(max_ac_charging_current_selectable_values, -1)
+}
+
+P18_PARSE_CB_FN(parallel_rated_information)
+{
+ p18_parallel_rated_information_msg_t *m = (p18_parallel_rated_information_msg_t *)message_ptr;
+ switch (index) {
+ case 0: /* A */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->parallel_id_connection_status = atoi(value);
+ break;
+ case 1: /* BB */
+ P18_EXPECT_LISTITEM_LENGTH(2);
+ m->serial_number_valid_length = atoi(value);
+ break;
+ case 2: /* CCCCCCCCCCCCCCCCCCCC */
+ P18_EXPECT_LISTITEM_LENGTH(20);
+ substr_copy(m->serial_number, value, MIN(m->serial_number_valid_length, (int)ARRAY_SIZE(m->serial_number)-1));
+ break;
+ case 3: /* D */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->charger_source_priority = atoi(value);
+ break;
+ case 4: /* EEE */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->max_charging_current = atoi(value);
+ break;
+ case 5: /* FF */
+ P18_EXPECT_LISTITEM_LENGTH(2);
+ m->max_ac_charging_current = atoi(value);
+ break;
+ case 6: /* G */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->output_model_setting = atoi(value);
+ break;
+ }
+}
+P18_UNPACK_FN(parallel_rated_information)
+{
+ P18_PARSE_LIST_AND_RETURN(parallel_rated_information, 7)
+}
+
+P18_PARSE_CB_FN(parallel_general_status)
+{
+ p18_parallel_general_status_msg_t *m = (p18_parallel_general_status_msg_t *)message_ptr;
+ int num = atoi(value);
+ switch (index) {
+ case 0: /* A */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->parallel_id_connection_status = num;
+ break;
+ case 1: /* B */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->work_mode = num;
+ break;
+ case 2: /* CC */
+ P18_EXPECT_LISTITEM_LENGTH(2);
+ m->fault_code = num;
+ break;
+ case 3: /* DDDD */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->grid_voltage = num;
+ break;
+ case 4: /* EEE */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->grid_freq = num;
+ break;
+ case 5: /* FFFF */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_voltage = num;
+ break;
+ case 6: /* GGG */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->ac_output_freq = num;
+ break;
+ case 7: /* HHHH */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_apparent_power = num;
+ break;
+ case 8: /* IIII */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->ac_output_active_power = num;
+ break;
+ case 9: /* JJJJJ */
+ P18_EXPECT_LISTITEM_LENGTH(5);
+ m->total_ac_output_apparent_power = num;
+ break;
+ case 10: /* KKKKK */
+ P18_EXPECT_LISTITEM_LENGTH(5);
+ m->total_ac_output_active_power = num;
+ break;
+ case 11: /* LLL */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->output_load_percent = num;
+ break;
+ case 12: /* MMM */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->total_output_load_percent = num;
+ break;
+ case 13: /* NNN */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_voltage = num;
+ break;
+ case 14: /* OOO */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_discharge_current = num;
+ break;
+ case 15: /* PPP */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_charging_current = num;
+ break;
+ case 16: /* QQQ */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->total_battery_charging_current = num;
+ break;
+ case 17: /* MMM. It's not my mistake, it's as per the doc. */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->battery_capacity = num;
+ break;
+ case 18: /* RRRR */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv1_input_power = num;
+ break;
+ case 19: /* SSSS */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv2_input_power = num;
+ break;
+ case 20: /* TTTT */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv1_input_voltage = num == 1;
+ break;
+ case 21: /* UUUU */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+ m->pv2_input_voltage = num;
+ break;
+ case 22: /* V */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->mppt1_charger_status = num;
+ break;
+ case 23: /* W */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->mppt2_charger_status = num;
+ break;
+ case 24: /* X */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->load_connected = num == 1;
+ break;
+ case 25: /* Y */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->battery_power_direction = num;
+ break;
+ case 26: /* Z */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->dc_ac_power_direction = num;
+ break;
+ case 27: /* a */
+ P18_EXPECT_LISTITEM_LENGTH(1);
+ m->line_power_direction = num;
+ break;
+ case 28: /* bbb. It's marked in red in the doc, idk what it means. */
+ /* My guess is that this elem is not always present. */
+ P18_EXPECT_LISTITEM_LENGTH(3);
+ m->max_temp = num;
+ break;
+ }
+}
+P18_UNPACK_FN(parallel_general_status)
+{
+ P18_PARSE_LIST_AND_RETURN(parallel_general_status, 29)
+}
+
+P18_PARSE_CB_FN(ac_charge_time_bucket)
+{
+ p18_ac_charge_time_bucket_msg_t *m = (p18_ac_charge_time_bucket_msg_t *)message_ptr;
+ char buf[4];
+ switch (index) {
+ case 0: /* AAAA */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+
+ substr_copy(buf, value, 2);
+ m->start_h = atoi(buf);
+
+ substr_copy(buf, value+2, 2);
+ m->start_m = atoi(buf);
+
+ break;
+
+ case 1: /* BBBB */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+
+ substr_copy(buf, value, 2);
+ m->end_h = atoi(buf);
+
+ substr_copy(buf, value+2, 2);
+ m->end_m = atoi(buf);
+
+ break;
+ }
+}
+P18_UNPACK_FN(ac_charge_time_bucket)
+{
+ P18_PARSE_LIST_AND_RETURN(ac_charge_time_bucket, 2)
+}
+
+P18_PARSE_CB_FN(ac_supply_load_time_bucket)
+{
+ p18_ac_supply_load_time_bucket_msg_t *m = (p18_ac_supply_load_time_bucket_msg_t *)message_ptr;
+ char buf[4];
+ switch (index) {
+ case 0: /* AAAA */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+
+ substr_copy(buf, value, 2);
+ m->start_h = atoi(buf);
+
+ substr_copy(buf, value+2, 2);
+ m->start_m = atoi(buf);
+
+ break;
+
+ case 1: /* BBBB */
+ P18_EXPECT_LISTITEM_LENGTH(4);
+
+ substr_copy(buf, value, 2);
+ m->end_h = atoi(buf);
+
+ substr_copy(buf, value+2, 2);
+ m->end_m = atoi(buf);
+
+ break;
+ }
+}
+P18_UNPACK_FN(ac_supply_load_time_bucket)
+{
+ P18_PARSE_LIST_AND_RETURN(ac_supply_load_time_bucket, 2)
+}
+
+/* ------------------------------------------ */
+/* Label getters */
+
+const char *p18_battery_type_label(p18_battery_type_t type)
+{
+ switch (type) {
+ case P18_BT_AGM:
+ case P18_BT_USER:
+ case P18_BT_FLOODED:
+ return p18_battery_types[type];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_input_voltage_range_label(p18_input_voltage_range_t range)
+{
+ switch (range) {
+ case P18_IVR_APPLIANCE:
+ case P18_IVR_UPS:
+ return p18_voltage_ranges[range];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_output_source_priority_label(p18_output_source_priority_t priority)
+{
+ switch (priority) {
+ case P18_OSP_SOLAR_BATTERY_UTILITY:
+ case P18_OSP_SOLAR_UTILITY_BATTERY:
+ return p18_output_source_priorities[priority];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_charge_source_priority_label(p18_charger_source_priority_t priority)
+{
+ switch (priority) {
+ case P18_CSP_SOLAR_FIRST:
+ case P18_CSP_SOLAR_AND_UTILITY:
+ case P18_CSP_SOLAR_ONLY:
+ return p18_charger_source_priorities[priority];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_machine_type_label(p18_machine_type_t type)
+{
+ switch (type) {
+ case P18_MT_GRID_TIE:
+ case P18_MT_OFF_GRID_TIE:
+ return p18_machine_types[type];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_topology_label(p18_topology_t topology)
+{
+ switch (topology) {
+ case P18_TRANSFORMER:
+ case P18_TRANSFORMERLESS:
+ return p18_topologies[topology];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_output_model_setting_label(p18_output_model_setting_t setting)
+{
+ switch (setting) {
+ case P18_OMS_PARALLEL_OUTPUT:
+ case P18_OMS_PHASE1_OF_3PHASE_OUTPUT:
+ case P18_OMS_PHASE2_OF_3PHASE_OUTPUT:
+ case P18_OMS_PHASE3_OF_3PHASE_OUTPUT:
+ case P18_OMS_SINGLE_MODULE:
+ return p18_output_model_settings[setting];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_solar_power_priority_label(p18_solar_power_priority_t priority)
+{
+ switch (priority) {
+ case P18_SPP_BATTERY_LOAD_UTILITY:
+ case P18_SPP_LOAD_BATTERY_UTILITY:
+ return p18_solar_power_priorities[priority];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_mppt_charger_status_label(p18_mppt_charger_status_t status)
+{
+ switch (status) {
+ case P18_MPPT_CS_ABNORMAL:
+ case P18_MPPT_CS_CHARGED:
+ case P18_MPPT_CS_NOT_CHARGED:
+ return p18_mppt_charger_statuses[status];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_battery_power_direction_label(p18_battery_power_direction_t direction)
+{
+ switch (direction) {
+ case P18_BPD_CHARGE:
+ case P18_BPD_DISCHARGE:
+ case P18_BPD_DONOTHING:
+ return p18_battery_power_directions[direction];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_dc_ac_power_direction_label(p18_dc_ac_power_direction_t direction)
+{
+ switch (direction) {
+ case P18_DAPD_AC_DC:
+ case P18_DAPD_DC_AC:
+ case P18_DAPD_DONOTHING:
+ return p18_dc_ac_power_directions[direction];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_line_power_direction_label(p18_line_power_direction_t direction)
+{
+ switch (direction) {
+ case P18_LPD_DONOTHING:
+ case P18_LPD_INPUT:
+ case P18_LPD_OUTPUT:
+ return p18_line_power_directions[direction];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_working_mode_label(p18_working_mode_t mode)
+{
+ switch (mode) {
+ case P18_WM_BATTERY_MODE:
+ case P18_WM_BYPASS_MODE:
+ case P18_WM_FAULT_MODE:
+ case P18_WM_HYBRID_MODE:
+ case P18_WM_POWER_ON_MODE:
+ case P18_WM_STANDBY_MODE:
+ return p18_working_modes[mode];
+
+ default:
+ return p18_unknown;
+ }
+}
+
+const char *p18_fault_code_label(unsigned int code)
+{
+ int len = (int)ARRAY_SIZE(fault_codes);
+ for (int i = 0; i < len; i++) {
+ const p18_fault_code_list_item_t *fc = &fault_codes[i];
+ if (fc->id == code)
+ return fc->message;
+ }
+ return p18_unknown;
+}
+
+const char *p18_parallel_connection_status_label(p18_parallel_id_connection_status_t status)
+{
+ switch (status) {
+ case P18_PCS_NON_EXISTENT:
+ case P18_PCS_EXISTENT:
+ return p18_parallel_connection_statuses[status];
+
+ default:
+ return p18_unknown;
+ }
+} \ No newline at end of file