diff options
Diffstat (limited to 'p18.c')
-rw-r--r-- | p18.c | 1559 |
1 files changed, 1559 insertions, 0 deletions
@@ -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 |