initial commit
This commit is contained in:
commit
557a8f9de0
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.o
|
||||
isv
|
||||
.idea
|
41
Makefile
Normal file
41
Makefile
Normal file
@ -0,0 +1,41 @@
|
||||
CC := gcc
|
||||
|
||||
PROGRAM = isv
|
||||
|
||||
OS = $(shell uname -s)
|
||||
ifeq ($(OS),Linux)
|
||||
HIDAPI = hidapi-hidraw
|
||||
endif
|
||||
ifeq ($(OS),Darwin)
|
||||
HIDAPI = hidapi
|
||||
endif
|
||||
|
||||
CFLAGS = -O2 -std=c99
|
||||
CFLAGS += -Wall -W
|
||||
CFLAGS += `pkg-config --cflags $(HIDAPI)`
|
||||
LDFLAGS = -lm
|
||||
LDFLAGS += `pkg-config --libs $(HIDAPI)`
|
||||
|
||||
INSTALL = /usr/bin/env install
|
||||
PREFIX = /usr/local
|
||||
|
||||
OBJS = isv.o util.o p18.o print.o variant.o
|
||||
OBJS += libvoltronic/voltronic_dev_usb_hidapi.o
|
||||
OBJS += libvoltronic/voltronic_crc.o
|
||||
OBJS += libvoltronic/voltronic_dev.o
|
||||
|
||||
all: $(PROGRAM)
|
||||
|
||||
$(PROGRAM): $(OBJS)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
install: $(PROGRAM)
|
||||
$(INSTALL) $(PROGRAM) $(PREFIX)/bin
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(PROGRAM)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $^ -I. -o $@
|
||||
|
||||
.PHONY: all install clean distclean
|
312
README.md
Normal file
312
README.md
Normal file
@ -0,0 +1,312 @@
|
||||
# isv
|
||||
|
||||
**isv** is utility for controlling Voltronic hybrid solar inverters that use P18 protocol. **isv** has full P18
|
||||
support with all known methods implemented. It was written for use with InfiniSolar V 5kW inverter and it's the only
|
||||
inverter it has been tested with so far, but it should work with other inverters using P18 protocol as well. Adding
|
||||
support for other protocols (such as P16 or P17) by splitting them into separate modules is possible in future.
|
||||
|
||||
For now, only USB connection is supported and tested (I just don't have the RS-232 cable with this weird RJ-style plug
|
||||
lol), but RS-232 support will be added eventually.
|
||||
|
||||
It's written in pure C99 with almost zero dependencies. It uses [libvoltronic](https://github.com/jvandervyver/libvoltronic)
|
||||
for underlying device interaction. but you don't need to download and build it separately as **isv** comes with its own
|
||||
slightly modified libvoltronic version.
|
||||
|
||||
It can output data in different formats (human-readable tables, conveniently-parsable tables and even JSON) so you can
|
||||
easily integrate it in your project.
|
||||
|
||||
For now only Linux and macOS are supported and tested. Other operating systems will be supported later.
|
||||
|
||||
## Requirements
|
||||
|
||||
- `pkg-config`
|
||||
- `hidapi`
|
||||
- On Linux, you should be able to install it from your distro's package manager
|
||||
- On macOS, `brew install hidapi`
|
||||
|
||||
## Building
|
||||
|
||||
Just run `make`. If you want to install it, `make install` will do the job.
|
||||
|
||||
## Usage
|
||||
|
||||
Run `isv` without arguments to see the full options list. For the sake of good readmes it's also written here.
|
||||
|
||||
### Common options
|
||||
|
||||
- **`-r`** `COMMAND`<br>
|
||||
**`--raw`** `COMMAND` - execute arbitrary command and print inverter's response.<br>
|
||||
Command example: `^P005PI`
|
||||
|
||||
- **`-t `** `TIMEOUT`<br>
|
||||
**`--timeout`** `TIMEOUT` - device read timeout, in milliseconds.<br>Example: `-t 5000`
|
||||
|
||||
- **`-v`**, **`--verbose`** - print debug information, like hexdumps of communication traffic with inverter
|
||||
|
||||
- **`-p`**, **`--pretend`** - do not actually execute anything on inverter, but output some debug info. Little use for
|
||||
normal people. Doesn't work with `--raw`.
|
||||
|
||||
- **`-f`** `FORMAT`<br>
|
||||
**`--format`** `FORMAT` - output format for `--get-*` and `--set-*` options, you can find list of supported
|
||||
formats below.
|
||||
|
||||
### Get options
|
||||
|
||||
- **`--get-protocol-id`** - returns protocol id. Should be always `18` as it's the only one supported.
|
||||
|
||||
- **`--get-date-time`** - returns date and time from inverter
|
||||
|
||||
- **`--get-total-generated`** - returns total generated energy, in kWatts (or Watts?). The documentation says it should be
|
||||
kilowatts, but my inverter says that we generated almost 200,000 for the last two months... It's must be Watts. My
|
||||
guess is that it reports Watts first and when it reaches some kind of integer limit it switches to kWatts.
|
||||
|
||||
- **`--get-year-generated`** `YYYY` - returns generated energy for specified year, in kWatts (or Watts? see above)
|
||||
|
||||
- **`--get-month-generated`** `YYYY` `MM` - returns generated energy for specified month, in kWatts (or Watts? see above)
|
||||
|
||||
- **`--get-day-generated`** `YYYY` `MM` `DD` - returns generated energy for specified day, in Watts.
|
||||
|
||||
- **`--get-series-number`** - returns series number. Or maybe serial number. The documentation is written by Chinese in
|
||||
bad english.
|
||||
|
||||
- **`--get-cpu-version`** - returns main and slave CPU versions.
|
||||
|
||||
- **`--get-rated-information`** - returns rated information.
|
||||
|
||||
- **`--get-general-status`** - returns general status, many cool stuff here. Usually this is what you want to read.
|
||||
|
||||
- **`--get-working-mode`** - returns working mode.
|
||||
|
||||
- **`--get-faults-warnings`** - returns fault and warning status.
|
||||
|
||||
- **`--get-flags`** - returns state of a set of flags, or toggles, like backlight or buzzer ON or OFF, etc.
|
||||
|
||||
- **`--get-defaults`** - returns default values of some changeable parameters and default flags values.
|
||||
|
||||
- **`--get-max-charging-current-selectable-values`**
|
||||
|
||||
- **`--get-max-ac-charging-current-selectable-values`**
|
||||
|
||||
- **`--get-parallel-rated-information`** `ID`<br>
|
||||
`ID` - parallel machine ID
|
||||
|
||||
- **`--get-parallel-general-status`** `ID`<br>
|
||||
`ID` - parallel machine ID
|
||||
|
||||
- **`--get-ac-charge-time-bucket`**
|
||||
|
||||
- **`--get-ac-supply-load-time-bucket`**
|
||||
|
||||
### Set options
|
||||
|
||||
- **`--set-loads-supply`** `0|1`
|
||||
|
||||
- **`--set-flag`** `FLAG` `0|1`
|
||||
|
||||
List of flags:
|
||||
|
||||
- `BUZZ` - Silence buzzer or open buzzer
|
||||
- `OLBP` - Overload bypass function
|
||||
- `LCDE` - LCD display escape to default page after 1min timeout
|
||||
- `OLRS` - Overload restart
|
||||
- `OTRS` - Overload temperature restart
|
||||
- `BLON` - Backlight on
|
||||
- `ALRM` - Alarm on primary source interrupt
|
||||
- `FTCR` - Fault code record
|
||||
- `MTYP` - Machine type (1=Grid-Tie, 0=Off-Grid-Tie)
|
||||
|
||||
- **`--set-defaults`**<br>
|
||||
Reset changeable parameters to their default values.
|
||||
|
||||
- **`--set-battery-max-charging-current`** `ID` `AMPS`<br>
|
||||
`ID` - parallel machine ID (use 0 for a single model)<br>
|
||||
`AMPS` - use `--get-max-charging-current-selectable-values` to see a list of allowed currents
|
||||
|
||||
- **`--set-battery-max-ac-charging-current`** `ID` `AMPS`<br>
|
||||
`ID` - parallel machine ID (use 0 for a single model)<br>
|
||||
`AMPS` - use `--get-max-ac-charging-current-selectable-values` to see a list of allowed currents
|
||||
|
||||
- **`--set-ac-output-freq`** `50|60`
|
||||
|
||||
- **`--set-battery-max-charging-voltage`** `CV` `FV`<br>
|
||||
`CV` - constant voltage (48.0 ~ 58.4)<br>
|
||||
`FV` - float voltage (48.0 ~ 58.4)
|
||||
|
||||
- **`--set-ac-output-rated-voltage`** `V`<br>
|
||||
`V` - voltage. Allowed voltages are `202`, `208`, `220`, `230` and `240`
|
||||
|
||||
- **`--set-output-source-priority`** `PRIORITY`
|
||||
|
||||
List of priorities:
|
||||
|
||||
- `SUB` is for *Solar-Utility-Battery*<br>
|
||||
- `SBU` is for *Solar-Battery-Utility*
|
||||
|
||||
- **`--set-battery-charging-thresholds`** `CV` `DV`<br>
|
||||
Sets battery re-charging and re-discharigng voltages when utility is available.
|
||||
|
||||
`CV` - re-charging voltage<br>
|
||||
*For 12V unit:* `11`, `11.3`, `11.5`, `11.8`, `12`, `12.3`, `12.5` or `12.8`<br>
|
||||
*For 24V unit:* `22`, `22.5`, `23`, `23.5`, `24`, `24.5`, `25` or `25.5`<br>
|
||||
*For 48V unit:* `44`, `45`, `46`, `47`, `48`, `49`, `50` or `51`
|
||||
|
||||
`DV` - re-discharging voltage<br>
|
||||
*For 12V unit:* `0`, `12`, `12.3`, `12.5`, `12.8`, `13`, `13.3`, `13.5`, `13.8`, `14`, `14.3` or `14.5`<br>
|
||||
*For 24V unit:* `0`, `24`, `24.5`, `25`, `25.5`, `26`, `26.5`, `27`, `27.5`, `28`, `28.5` or `29`<br>
|
||||
*For 48V unit:* `0`, `48`, `49`, `50`, `51`, `52`, `53`, `54`, `55`, `56`, `57` or `58`<br>
|
||||
|
||||
- **`--set-charging-source-priority`** `ID` `PRIORITY`<br>
|
||||
`ID` - parallel machine ID (use 0 for a single model).
|
||||
|
||||
List of priorities:
|
||||
|
||||
- `SF` for *Solar-First*<br>
|
||||
- `SU` for *Solar-and-Utility*<br>
|
||||
- `S` for *Solar-Only*
|
||||
|
||||
- **`--set-solar-power-priority`** `PRIORITY`
|
||||
|
||||
List of priorities:
|
||||
|
||||
- `BLU` for *Battery-Load-Utility*<br>
|
||||
- `LBU` for *Load-Battery-Utility*
|
||||
|
||||
- **`--set-ac-input-voltage-range`** `RANGE`
|
||||
|
||||
List of ranges:
|
||||
|
||||
- `APPLIANCE`
|
||||
- `UPS`
|
||||
|
||||
- **`--set-battery-type`** `AGM|FLOODED|USER`
|
||||
|
||||
- **`--set-output-model`** `ID` `MODEL`<br>
|
||||
`ID` - parallel machine ID (use 0 for a single model).
|
||||
|
||||
List of allowed models:
|
||||
|
||||
- `SM` - Single module
|
||||
- `P` - Parallel output
|
||||
- `P1` - Phase 1 of three phase output
|
||||
- `P2` - Phase 2 of three phase output
|
||||
- `P3` - Phase 3 of three phase
|
||||
|
||||
- **`--set-battery-cutoff-voltage`** `V`<br>
|
||||
`V` - cut-off voltage (40.0 ~ 48.0)
|
||||
|
||||
- **`--set-solar-configuration`** `ID`<br>
|
||||
`ID` - serial number
|
||||
|
||||
- **`--clear-generated-data`**<br>
|
||||
Clears all data of generated energy.
|
||||
|
||||
- **`--set-date-time`** `YYYY` `MM` `DD` `hh` `mm` `ss`<br>
|
||||
`YYYY` - year<br>
|
||||
`MM` - month<br>
|
||||
`DD` - day<br>
|
||||
`hh` - hours<br>
|
||||
`mm` - minutes<br>
|
||||
`ss` - seconds
|
||||
|
||||
- **`--set-ac-charge-time-bucket`** `START` `END`<br>
|
||||
`START` - starting time, `hh:mm` format<br>
|
||||
`END` - ending time, `hh:mm` format
|
||||
|
||||
- **`--set-ac-supply-load-time-bucket`** `START` `END`<br>
|
||||
`START` - starting time, `hh:mm` format<br>
|
||||
`END` - ending time, `hh:mm` format
|
||||
|
||||
### Formats
|
||||
- `table` - human-readable table. This is used by default.
|
||||
|
||||
Output example:
|
||||
```
|
||||
Grid voltage: 0.0 V
|
||||
Grid frequency: 0.0 Hz
|
||||
AC output voltage: 230.1 V
|
||||
AC output frequency: 50.0 Hz
|
||||
AC output apparent power: 114 VA
|
||||
AC output active power: 69 Wh
|
||||
Output load percent: 2%
|
||||
Battery voltage: 49.5 V
|
||||
Battery voltage from SCC: 0.0 V
|
||||
Battery voltage from SCC2: 0.0 V
|
||||
Battery discharge current: 1 A
|
||||
Battery charging current: 0 A
|
||||
Battery capacity: 73%
|
||||
Inverter heat sink temperature: 32 °C
|
||||
MPPT1 charger temperature: 0 °C
|
||||
MPPT2 charger temperature: 0 °C
|
||||
PV1 Input power: 0.00 Wh
|
||||
PV2 Input power: 0.00 Wh
|
||||
PV1 Input voltage: 0.0 V
|
||||
PV2 Input voltage: 0.0 V
|
||||
Setting value configuration state: Something changed
|
||||
MPPT1 charger status: Abnormal
|
||||
MPPT2 charger status: Abnormal
|
||||
Load connection: Connected
|
||||
Battery power direction: Discharge
|
||||
DC/AC power direction: DC/AC
|
||||
Line power direction: Do nothing
|
||||
Local parallel ID: 0
|
||||
```
|
||||
|
||||
- `parsable-table`
|
||||
|
||||
Output example:
|
||||
|
||||
```
|
||||
grid_voltage 0.0 V
|
||||
grid_freq 0.0 Hz
|
||||
ac_output_voltage 230.0 V
|
||||
ac_output_freq 50.0 Hz
|
||||
ac_output_apparent_power 92 VA
|
||||
ac_output_active_power 52 Wh
|
||||
output_load_percent 1 %
|
||||
battery_voltage 49.5 V
|
||||
battery_voltage_scc 0.0 V
|
||||
battery_voltage_scc2 0.0 V
|
||||
battery_discharge_current 1 A
|
||||
battery_charging_current 0 A
|
||||
battery_capacity 73 %
|
||||
inverter_heat_sink_temp 32 °C
|
||||
mppt1_charger_temp 0 °C
|
||||
mppt2_charger_temp 0 °C
|
||||
pv1_input_power 0.00 Wh
|
||||
pv2_input_power 0.00 Wh
|
||||
pv1_input_voltage 0.0 V
|
||||
pv2_input_voltage 0.0 V
|
||||
settings_values_changed "Something changed"
|
||||
mppt1_charger_status Abnormal
|
||||
mppt2_charger_status Abnormal
|
||||
load_connected Connected
|
||||
battery_power_direction Discharge
|
||||
dc_ac_power_direction DC/AC
|
||||
line_power_direction "Do nothing"
|
||||
local_parallel_id 0
|
||||
```
|
||||
|
||||
- `json` - JSON.
|
||||
|
||||
Output example:
|
||||
|
||||
```
|
||||
{"grid_voltage":0.00,"grid_freq":0.00,"ac_output_voltage":229.90,"ac_output_freq":49.90,"ac_output_apparent_power":91,"ac_output_active_power":47,"output_load_percent":1,"battery_voltage":49.50,"battery_voltage_scc":0.00,"battery_voltage_scc2":0.00,"battery_discharge_current":1,"battery_charging_current":0,"battery_capacity":73,"inverter_heat_sink_temp":32,"mppt1_charger_temp":0,"mppt2_charger_temp":0,"pv1_input_power":0.00,"pv2_input_power":0.00,"pv1_input_voltage":0.00,"pv2_input_voltage":0.00,"settings_values_changed":"Something changed","mppt1_charger_status":"Abnormal","mppt2_charger_status":"Abnormal","load_connected":"Connected","battery_power_direction":"Discharge","dc_ac_power_direction":"DC/AC","line_power_direction":"Do nothing","local_parallel_id":0}
|
||||
```
|
||||
|
||||
- `json-w-units` - JSON with units.
|
||||
|
||||
Output example:
|
||||
|
||||
```
|
||||
{"grid_voltage":[0.00,"V"],"grid_freq":[0.00,"Hz"],"ac_output_voltage":[230.10,"V"],"ac_output_freq":[50.00,"Hz"],"ac_output_apparent_power":[92,"VA"],"ac_output_active_power":[53,"Wh"],"output_load_percent":[1,"%"],"battery_voltage":[49.50,"V"],"battery_voltage_scc":[0.00,"V"],"battery_voltage_scc2":[0.00,"V"],"battery_discharge_current":[1,"A"],"battery_charging_current":[0,"A"],"battery_capacity":[73,"%"],"inverter_heat_sink_temp":[32,"°C"],"mppt1_charger_temp":[0,"°C"],"mppt2_charger_temp":[0,"°C"],"pv1_input_power":[0.00,"Wh"],"pv2_input_power":[0.00,"Wh"],"pv1_input_voltage":[0.00,"V"],"pv2_input_voltage":[0.00,"V"],"settings_values_changed":"Something changed","mppt1_charger_status":"Abnormal","mppt2_charger_status":"Abnormal","load_connected":"Connected","battery_power_direction":"Discharge","dc_ac_power_direction":"DC/AC","line_power_direction":"Do nothing","local_parallel_id":0}
|
||||
```
|
||||
|
||||
### Return codes
|
||||
|
||||
**isv** returns `0` on success, `1` on some input error (e.g. invalid argument) and `2` on communication failure (e.g.
|
||||
failed to read response from inverter, or response is invalid).
|
||||
|
||||
## License
|
||||
|
||||
GPLv3
|
861
isv.c
Normal file
861
isv.c
Normal file
@ -0,0 +1,861 @@
|
||||
/**
|
||||
* 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "variant.h"
|
||||
#include "p18.h"
|
||||
#include "util.h"
|
||||
#include "print.h"
|
||||
#include "libvoltronic/voltronic_dev_usb.h"
|
||||
|
||||
#define COMMAND_BUF_LENGTH 128
|
||||
#define RESPONSE_BUF_LENGTH 128
|
||||
|
||||
#define PRINT(msg_type) \
|
||||
{ \
|
||||
P18_MSG_T(msg_type) m = P18_UNPACK_FN_NAME(msg_type)(buffer+5); \
|
||||
PRINT_FN_NAME(msg_type)(&m, g_format); \
|
||||
}
|
||||
|
||||
#define GET_ARGS(len) \
|
||||
get_args(argc, (const char **)argv, a, (len))
|
||||
|
||||
bool g_verbose = false;
|
||||
print_format_t g_format = PRINT_FORMAT_TABLE;
|
||||
|
||||
static void usageintlist(const int *list, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
printf("%d", list[i]);
|
||||
if (i < size-1)
|
||||
printf(", ");
|
||||
}
|
||||
}
|
||||
|
||||
static void usagestrlist(const char **list, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
printf("%s", list[i]);
|
||||
if (i < size-1)
|
||||
printf(", ");
|
||||
}
|
||||
}
|
||||
|
||||
static void write_num(char *buf, int n)
|
||||
{
|
||||
assert(n <= 9);
|
||||
*buf++ = n + '0';
|
||||
*buf++ = '\0';
|
||||
}
|
||||
|
||||
static void usage(const char *progname)
|
||||
{
|
||||
printf("Usage: %s OPTIONS\n", progname);
|
||||
printf("\n"
|
||||
"Options:\n"
|
||||
" -h, --help: print this help\n"
|
||||
" -r <COMMAND>,\n"
|
||||
" --raw <COMMAND>: execute arbitrary command and print inverter's\n"
|
||||
" response. Command example: ^P005PI\n"
|
||||
" -t <TIMEOUT>,\n"
|
||||
" --timeout <TIMEOUT>: device read timeout, in milliseconds\n"
|
||||
" -v, --verbose: print debug information, like hexdumps of\n"
|
||||
" communication traffic with inverter\n"
|
||||
" -p, --pretend: do not actually execute command on inverter,\n"
|
||||
" but output some debug info\n"
|
||||
" -f <FORMAT>,\n"
|
||||
" --format <FORMAT>: output format for --get and --set options, see below\n"
|
||||
"\n"
|
||||
"Options to get data from inverter:\n"
|
||||
" --get-protocol-id\n"
|
||||
" --get-date-time\n"
|
||||
" --get-total-generated\n"
|
||||
" --get-year-generated <YYYY>\n"
|
||||
" --get-month-generated <YYYY> <MM>\n"
|
||||
" --get-day-generated <YYYY> <MM> <DD>\n"
|
||||
" --get-series-number\n"
|
||||
" --get-cpu-version\n"
|
||||
" --get-rated-information\n"
|
||||
" --get-general-status\n"
|
||||
" --get-working-mode\n"
|
||||
" --get-faults-warnings\n"
|
||||
" --get-flags\n"
|
||||
" --get-defaults\n"
|
||||
" --get-max-charging-current-selectable-values\n"
|
||||
" --get-max-ac-charging-current-selectable-values\n"
|
||||
" --get-parallel-rated-information <ID>\n"
|
||||
" ID: parallel machine ID\n"
|
||||
"\n"
|
||||
" --get-parallel-general-status <ID>\n"
|
||||
" ID: parallel machine ID\n"
|
||||
"\n"
|
||||
" --get-ac-charge-time-bucket\n"
|
||||
" --get-ac-supply-load-time-bucket\n"
|
||||
"\n"
|
||||
"Options to set inverter's configuration:\n"
|
||||
" --set-loads-supply 0|1\n"
|
||||
" --set-flag <FLAG> 0|1\n"
|
||||
" --set-defaults\n"
|
||||
" --set-battery-max-charging-current <ID> <AMPS>\n"
|
||||
" ID: parallel machine ID (use 0 for a single model)\n"
|
||||
" AMPS: use --get-max-charging-current-selectable-values\n"
|
||||
" to see a list of allowed current values\n"
|
||||
"\n"
|
||||
" --set-battery-max-ac-charging-current <ID> <AMPS>\n"
|
||||
" ID: parallel machine ID (use 0 for a single model)\n"
|
||||
" AMPS: use --get-max-ac-charging-current-selectable-values\n"
|
||||
" to see a list of allowed current values\n"
|
||||
"\n"
|
||||
" --set-ac-output-freq 50|60\n"
|
||||
" --set-battery-max-charging-voltage <CV> <FV>\n"
|
||||
" CV: constant voltage (48.0~58.4)\n"
|
||||
" FV: float voltage (48.0~58.4)\n"
|
||||
"\n"
|
||||
" --set-ac-output-rated-voltage <V>\n"
|
||||
" V: one of: "
|
||||
);
|
||||
usageintlist(p18_ac_output_rated_voltages,
|
||||
ARRAY_SIZE(p18_ac_output_rated_voltages));
|
||||
printf("\n\n"
|
||||
" --set-output-source-priority SUB|SBU\n"
|
||||
" SUB for %s\n"
|
||||
" SBU for %s\n",
|
||||
p18_output_source_priority_label(P18_OSP_SOLAR_UTILITY_BATTERY),
|
||||
p18_output_source_priority_label(P18_OSP_SOLAR_BATTERY_UTILITY));
|
||||
printf("\n"
|
||||
" --set-battery-charging-thresholds <CV> <DV>\n"
|
||||
" Sets battery re-charging and re-discharigng voltages when\n"
|
||||
" utility is available.\n"
|
||||
"\n"
|
||||
" CV: re-charging voltage\n"
|
||||
" for 12V unit, one of: ");
|
||||
usagestrlist(p18_battery_util_recharging_voltages_12v_unit,
|
||||
ARRAY_SIZE(p18_battery_util_recharging_voltages_12v_unit));
|
||||
printf("\n"
|
||||
" for 24V unit, one of: ");
|
||||
usagestrlist(p18_battery_util_recharging_voltages_24v_unit,
|
||||
ARRAY_SIZE(p18_battery_util_recharging_voltages_24v_unit));
|
||||
printf("\n"
|
||||
" for 48V unit, one of: ");
|
||||
usagestrlist(p18_battery_util_recharging_voltages_48v_unit,
|
||||
ARRAY_SIZE(p18_battery_util_recharging_voltages_48v_unit));
|
||||
printf("\n"
|
||||
" DV: re-discharging voltage\n"
|
||||
" for 12V unit, one of: ");
|
||||
usagestrlist(p18_battery_util_redischarging_voltages_12v_unit,
|
||||
ARRAY_SIZE(p18_battery_util_redischarging_voltages_12v_unit));
|
||||
printf("\n"
|
||||
" for 24V unit, one of: ");
|
||||
usagestrlist(p18_battery_util_redischarging_voltages_24v_unit,
|
||||
ARRAY_SIZE(p18_battery_util_redischarging_voltages_24v_unit));
|
||||
printf("\n"
|
||||
" for 48V unit, one of: ");
|
||||
usagestrlist(p18_battery_util_redischarging_voltages_48v_unit,
|
||||
ARRAY_SIZE(p18_battery_util_redischarging_voltages_48v_unit));
|
||||
printf("\n\n"
|
||||
" --set-charging-source-priority <ID> <PRIORITY>\n"
|
||||
" ID: parallel machine ID (use 0 for a single model)\n"
|
||||
" PRIORITY:\n"
|
||||
" SF: %s,\n"
|
||||
" SU: %s,\n"
|
||||
" S: %s\n",
|
||||
p18_charge_source_priority_label(P18_CSP_SOLAR_FIRST),
|
||||
p18_charge_source_priority_label(P18_CSP_SOLAR_AND_UTILITY),
|
||||
p18_charge_source_priority_label(P18_CSP_SOLAR_ONLY));
|
||||
printf("\n"
|
||||
" --set-solar-power-priority BLU|LBU\n"
|
||||
" BLU: %s\n"
|
||||
" LBU: %s\n",
|
||||
p18_solar_power_priority_label(P18_SPP_BATTERY_LOAD_UTILITY),
|
||||
p18_solar_power_priority_label(P18_SPP_LOAD_BATTERY_UTILITY));
|
||||
printf("\n"
|
||||
" --set-ac-input-voltage-range APPLIANCE|UPS\n"
|
||||
" --set-battery-type AGM|FLOODED|USER\n"
|
||||
" --set-output-model <ID> <MODEL>\n"
|
||||
" ID: parallel machine ID (use 0 for a single model)\n"
|
||||
" MODEL:\n"
|
||||
" SM: %s\n"
|
||||
" P: %s\n"
|
||||
" P1: %s\n"
|
||||
" P2: %s\n"
|
||||
" P3: %s\n",
|
||||
p18_output_model_setting_label(P18_OMS_SINGLE_MODULE),
|
||||
p18_output_model_setting_label(P18_OMS_PARALLEL_OUTPUT),
|
||||
p18_output_model_setting_label(P18_OMS_PHASE1_OF_3PHASE_OUTPUT),
|
||||
p18_output_model_setting_label(P18_OMS_PHASE2_OF_3PHASE_OUTPUT),
|
||||
p18_output_model_setting_label(P18_OMS_PHASE3_OF_3PHASE_OUTPUT));
|
||||
printf("\n"
|
||||
" --set-battery-cutoff-voltage <V>\n"
|
||||
" V: cut-off voltage (40.0~48.0)\n"
|
||||
"\n"
|
||||
" --set-solar-configuration <ID>\n"
|
||||
" ID: serial number\n"
|
||||
"\n"
|
||||
" --clear-generated-data\n"
|
||||
" Clears all data of generated energy.\n"
|
||||
"\n"
|
||||
" --set-date-time <YYYY> <MM> <DD> <hh> <mm> <ss>\n"
|
||||
" YYYY: year\n"
|
||||
" MM: month\n"
|
||||
" DD: day\n"
|
||||
" hh: hours\n"
|
||||
" mm: minutes\n"
|
||||
" ss: seconds\n"
|
||||
"\n"
|
||||
" --set-ac-charge-time-bucket <START> <END>\n"
|
||||
" START: starting time, hh:mm format\n"
|
||||
" END: ending time, hh:mm format\n"
|
||||
"\n"
|
||||
" --set-ac-supply-load-time-bucket <START> <END>\n"
|
||||
" START: starting time, hh:mm format\n"
|
||||
" END: ending time, hh:mm format\n"
|
||||
"\n"
|
||||
);
|
||||
printf("Flags:\n");
|
||||
|
||||
size_t len = ARRAY_SIZE(p18_flags_printable_list);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
p18_flag_printable_list_item_t item = p18_flags_printable_list[i];
|
||||
printf(" %s: %s\n", item.key, item.title);
|
||||
}
|
||||
|
||||
printf("\n"
|
||||
"Formats:\n"
|
||||
" table human-readable table\n"
|
||||
" parsable-table conveniently-parsable table\n"
|
||||
" json JSON object, like {\"ac_output_voltage\":230}\n"
|
||||
" json-w-units JSON object with units, like:\n"
|
||||
" {\"ac_output_voltage\":[230,\"V\"]}\n"
|
||||
);
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void exit_with_error(int code, char *fmt, ...)
|
||||
{
|
||||
static const size_t buf_size = 256;
|
||||
char buf[buf_size];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
size_t len = vsnprintf(buf, buf_size, fmt, args);
|
||||
va_end(args);
|
||||
buf[MIN(len, buf_size-1)] = '\0';
|
||||
ERROR("error: %s\n", buf);
|
||||
if (g_format == PRINT_FORMAT_JSON || g_format == PRINT_FORMAT_JSON_W_UNITS) {
|
||||
print_item_t items[] = {
|
||||
{.key= "error", .value= variant_string(buf)}
|
||||
};
|
||||
print_json(items, 1, false);
|
||||
}
|
||||
exit(code);
|
||||
}
|
||||
|
||||
static void get_args(int argc, const char **argv, const char **arguments_dst, size_t count)
|
||||
{
|
||||
size_t i = 0;
|
||||
arguments_dst[i++] = optarg;
|
||||
for (; i < count; i++) {
|
||||
if (optind < argc && *argv[optind] != '-')
|
||||
arguments_dst[i] = argv[optind++];
|
||||
else
|
||||
exit_with_error(1, "option %s requires %zu arguments\n",
|
||||
argv[optind-i-1], count);
|
||||
}
|
||||
}
|
||||
|
||||
static void execute_raw(voltronic_dev_t dev, const char *command, int timeout)
|
||||
{
|
||||
char buffer[RESPONSE_BUF_LENGTH];
|
||||
int result = voltronic_dev_execute(dev, 0, command, strlen(command),
|
||||
buffer, sizeof(buffer), NULL, timeout);
|
||||
if (result <= 0)
|
||||
exit_with_error(2, "failed to execute %s: %s\n", command, strerror(errno));
|
||||
printf("%s\n", buffer);
|
||||
}
|
||||
|
||||
static void query(voltronic_dev_t dev,
|
||||
int command_key,
|
||||
int timeout,
|
||||
const char **args,
|
||||
size_t args_size,
|
||||
bool pretend)
|
||||
{
|
||||
char buffer[RESPONSE_BUF_LENGTH];
|
||||
char command[COMMAND_BUF_LENGTH];
|
||||
|
||||
if (!p18_build_command(command_key, args, args_size, command))
|
||||
exit_with_error(1, "invalid query command %d\n", command_key);
|
||||
|
||||
if (pretend) {
|
||||
size_t command_len = strlen(command);
|
||||
LOG("would write %zu+3 %s:\n",
|
||||
command_len, (command_len > 1 ? "bytes" : "byte"));
|
||||
HEXDUMP(command, command_len);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t received;
|
||||
int result = voltronic_dev_execute(dev, 0, command, strlen(command),
|
||||
buffer, sizeof(buffer), &received,
|
||||
timeout);
|
||||
if (result <= 0)
|
||||
exit_with_error(2, "failed to execute %s: %s\n", command, strerror(errno));
|
||||
|
||||
if (command_key < P18_SET_CMDS_ENUM_OFFSET) {
|
||||
size_t data_size;
|
||||
if (!p18_validate_query_response(buffer, received, &data_size))
|
||||
exit_with_error(2, "invalid response\n");
|
||||
|
||||
if (command_key == P18_QUERY_PROTOCOL_ID)
|
||||
PRINT(protocol_id)
|
||||
else if (command_key == P18_QUERY_CURRENT_TIME)
|
||||
PRINT(current_time)
|
||||
else if (command_key == P18_QUERY_TOTAL_GENERATED)
|
||||
PRINT(total_generated)
|
||||
else if (command_key == P18_QUERY_YEAR_GENERATED)
|
||||
PRINT(year_generated)
|
||||
else if (command_key == P18_QUERY_MONTH_GENERATED)
|
||||
PRINT(month_generated)
|
||||
else if (command_key == P18_QUERY_DAY_GENERATED)
|
||||
PRINT(day_generated)
|
||||
else if (command_key == P18_QUERY_SERIES_NUMBER)
|
||||
PRINT(series_number)
|
||||
else if (command_key == P18_QUERY_CPU_VERSION)
|
||||
PRINT(cpu_version)
|
||||
else if (command_key == P18_QUERY_RATED_INFORMATION)
|
||||
PRINT(rated_information)
|
||||
else if (command_key == P18_QUERY_GENERAL_STATUS)
|
||||
PRINT(general_status)
|
||||
else if (command_key == P18_QUERY_WORKING_MODE)
|
||||
PRINT(working_mode)
|
||||
else if (command_key == P18_QUERY_FAULTS_WARNINGS)
|
||||
PRINT(faults_warnings)
|
||||
else if (command_key == P18_QUERY_FLAGS_STATUSES)
|
||||
PRINT(flags_statuses)
|
||||
else if (command_key == P18_QUERY_DEFAULTS)
|
||||
PRINT(defaults)
|
||||
else if (command_key == P18_QUERY_MAX_CHARGING_CURRENT_SELECTABLE_VALUES)
|
||||
PRINT(max_charging_current_selectable_values)
|
||||
else if (command_key == P18_QUERY_MAX_AC_CHARGING_CURRENT_SELECTABLE_VALUES)
|
||||
PRINT(max_ac_charging_current_selectable_values)
|
||||
else if (command_key == P18_QUERY_PARALLEL_RATED_INFORMATION)
|
||||
PRINT(parallel_rated_information)
|
||||
else if (command_key == P18_QUERY_PARALLEL_GENERAL_STATUS)
|
||||
PRINT(parallel_general_status)
|
||||
else if (command_key == P18_QUERY_AC_CHARGE_TIME_BUCKET)
|
||||
PRINT(ac_charge_time_bucket)
|
||||
else if (command_key == P18_QUERY_AC_SUPPLY_LOAD_TIME_BUCKET)
|
||||
PRINT(ac_supply_load_time_bucket)
|
||||
} else {
|
||||
bool success = p18_set_result(buffer, received);
|
||||
print_set_result(success, g_format);
|
||||
if (!success)
|
||||
exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
static void validate_date_args(const char *ys, const char *ms, const char *ds)
|
||||
{
|
||||
static char *err_year = "invalid year";
|
||||
static char *err_month = "invalid month";
|
||||
static char *err_day = "invalid day";
|
||||
|
||||
int y, m = 0, d = 0;
|
||||
|
||||
/* validate year */
|
||||
if (!isnumeric(ys) || strlen(ys) != 4)
|
||||
exit_with_error(1, err_year);
|
||||
y = (int)strtoul(ys, NULL, 10);
|
||||
if (y < 2000 || y > 2099) {
|
||||
ERROR("%s\n", err_year);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* validate month */
|
||||
if (ms != NULL) {
|
||||
if (!isnumeric(ms) || strlen(ms) > 2)
|
||||
exit_with_error(1, err_month);
|
||||
m = (int)strtoul(ms, NULL, 10);
|
||||
if (m < 1 || m > 12)
|
||||
exit_with_error(1, err_month);
|
||||
}
|
||||
|
||||
/* validate day */
|
||||
if (ds != NULL) {
|
||||
if (!isnumeric(ds) || strlen(ds) > 2)
|
||||
exit_with_error(1, err_day);
|
||||
d = (int) strtoul(ds, NULL, 10);
|
||||
if (d < 1 || d > 31)
|
||||
exit_with_error(1, err_day);
|
||||
}
|
||||
|
||||
if (y != 0 && m != 0 && d != 0) {
|
||||
if (!isdatevalid(y, m, d))
|
||||
exit_with_error(1, "invalid date");
|
||||
}
|
||||
}
|
||||
|
||||
static void validate_time_args(const char *hs, const char *ms, const char *ss)
|
||||
{
|
||||
static char *err_hour = "invalid hour";
|
||||
static char *err_minute = "invalid minute";
|
||||
static char *err_second = "invalid second";
|
||||
|
||||
unsigned int h, m, s;
|
||||
|
||||
if (!isnumeric(hs) || strlen(hs) > 2)
|
||||
exit_with_error(1, err_hour);
|
||||
h = (unsigned int)strtoul(hs, NULL, 10);
|
||||
if (h > 23)
|
||||
exit_with_error(1, err_hour);
|
||||
|
||||
if (!isnumeric(ms) || strlen(ms) > 2)
|
||||
exit_with_error(1, err_minute);
|
||||
m = (unsigned int)strtoul(ms, NULL, 10);
|
||||
if (m > 59)
|
||||
exit_with_error(1, err_minute);
|
||||
|
||||
if (!isnumeric(ss) || strlen(ss) > 2)
|
||||
exit_with_error(1, err_second);
|
||||
s = (unsigned int)strtoul(ss, NULL, 10);
|
||||
if (s > 59)
|
||||
exit_with_error(1, err_second);
|
||||
}
|
||||
|
||||
static bool get_float(const char *s, float *fptr)
|
||||
{
|
||||
char *endptr;
|
||||
float f = strtof(s, &endptr);
|
||||
if (endptr == s)
|
||||
return false;
|
||||
if (fptr != NULL)
|
||||
*fptr = f;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_uint(const char *s, unsigned int *iptr)
|
||||
{
|
||||
char *endptr;
|
||||
unsigned int i = (unsigned int)strtoul(s, &endptr, 10);
|
||||
if (endptr == s)
|
||||
return false;
|
||||
if (iptr != NULL)
|
||||
*iptr = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_valid_parallel_id(const char *s)
|
||||
{
|
||||
return isnumeric(s) && strlen(s) == 1;
|
||||
}
|
||||
|
||||
enum action {
|
||||
ACTION_HELP,
|
||||
ACTION_DUMP,
|
||||
ACTION_EXECUTE,
|
||||
ACTION_QUERY,
|
||||
};
|
||||
|
||||
enum {
|
||||
OPT_HELP = 'h',
|
||||
OPT_DUMP = 'd',
|
||||
OPT_VERBOSE = 'v',
|
||||
OPT_RAW = 'r',
|
||||
OPT_PREDENT = 'p',
|
||||
OPT_TIMEOUT = 't',
|
||||
OPT_FORMAT = 'f',
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argv[1] == NULL)
|
||||
usage(argv[0]);
|
||||
|
||||
enum action act = ACTION_HELP;
|
||||
int opt;
|
||||
int command_no = 0, timeout = 1000;
|
||||
bool pretend = false;
|
||||
const char *a[6] = {0}; /* p18 command arguments */
|
||||
static struct option long_options[] = {
|
||||
{"help", no_argument, 0, OPT_HELP},
|
||||
{"dump", no_argument, 0, OPT_DUMP},
|
||||
{"verbose", no_argument, 0, OPT_VERBOSE},
|
||||
{"raw", required_argument, 0, OPT_RAW},
|
||||
{"pretend", required_argument, 0, OPT_PREDENT},
|
||||
{"timeout", required_argument, 0, OPT_TIMEOUT},
|
||||
{"format", required_argument, 0, OPT_FORMAT},
|
||||
|
||||
/* get queries */
|
||||
{"get-protocol-id", no_argument, 0, P18_QUERY_PROTOCOL_ID},
|
||||
{"get-date-time", no_argument, 0, P18_QUERY_CURRENT_TIME},
|
||||
{"get-total-generated", no_argument, 0, P18_QUERY_TOTAL_GENERATED},
|
||||
{"get-year-generated", required_argument, 0, P18_QUERY_YEAR_GENERATED},
|
||||
{"get-month-generated", required_argument, 0, P18_QUERY_MONTH_GENERATED},
|
||||
{"get-day-generated", required_argument, 0, P18_QUERY_DAY_GENERATED},
|
||||
{"get-series-number", no_argument, 0, P18_QUERY_SERIES_NUMBER},
|
||||
{"get-cpu-version", no_argument, 0, P18_QUERY_CPU_VERSION},
|
||||
{"get-rated-information", no_argument, 0, P18_QUERY_RATED_INFORMATION},
|
||||
{"get-general-status", no_argument, 0, P18_QUERY_GENERAL_STATUS},
|
||||
{"get-working-mode", no_argument, 0, P18_QUERY_WORKING_MODE},
|
||||
{"get-faults-warnings", no_argument, 0, P18_QUERY_FAULTS_WARNINGS},
|
||||
{"get-flags", no_argument, 0, P18_QUERY_FLAGS_STATUSES},
|
||||
{"get-defaults", no_argument, 0, P18_QUERY_DEFAULTS},
|
||||
{"get-max-charging-current-selectable-values", no_argument, 0, P18_QUERY_MAX_CHARGING_CURRENT_SELECTABLE_VALUES},
|
||||
{"get-max-ac-charging-current-selectable-values", no_argument, 0, P18_QUERY_MAX_AC_CHARGING_CURRENT_SELECTABLE_VALUES},
|
||||
{"get-parallel-rated-information", required_argument, 0, P18_QUERY_PARALLEL_RATED_INFORMATION},
|
||||
{"get-parallel-general-status", required_argument, 0, P18_QUERY_PARALLEL_GENERAL_STATUS},
|
||||
{"get-ac-charge-time-bucket", no_argument, 0, P18_QUERY_AC_CHARGE_TIME_BUCKET},
|
||||
{"get-ac-supply-load-time-bucket", no_argument, 0, P18_QUERY_AC_SUPPLY_LOAD_TIME_BUCKET},
|
||||
|
||||
/* set queries */
|
||||
{"set-loads-supply", required_argument, 0, P18_SET_LOADS},
|
||||
{"set-flag", required_argument, 0, P18_SET_FLAG},
|
||||
{"set-defaults", no_argument, 0, P18_SET_DEFAULTS},
|
||||
{"set-battery-max-charging-current", required_argument, 0, P18_SET_BAT_MAX_CHARGE_CURRENT},
|
||||
{"set-battery-max-ac-charging-current", required_argument, 0, P18_SET_BAT_MAX_AC_CHARGE_CURRENT},
|
||||
{"set-ac-output-freq", required_argument, 0, P18_SET_AC_OUTPUT_FREQ},
|
||||
{"set-battery-max-charging-voltage", required_argument, 0, P18_SET_BAT_MAX_CHARGE_VOLTAGE},
|
||||
{"set-ac-output-rated-voltage", required_argument, 0, P18_SET_AC_OUTPUT_RATED_VOLTAGE},
|
||||
{"set-output-source-priority", required_argument, 0, P18_SET_OUTPUT_SOURCE_PRIORITY},
|
||||
{"set-battery-charging-thresholds", required_argument, 0, P18_SET_BAT_CHARGING_THRESHOLDS_WHEN_UTILITY_AVAIL},
|
||||
{"set-charging-source-priority", required_argument, 0, P18_SET_CHARGING_SOURCE_PRIORITY},
|
||||
{"set-solar-power-priority", required_argument, 0, P18_SET_SOLAR_POWER_PRIORITY},
|
||||
{"set-ac-input-voltage-range", required_argument, 0, P18_SET_AC_INPUT_VOLTAGE_RANGE},
|
||||
{"set-battery-type", required_argument, 0, P18_SET_BAT_TYPE},
|
||||
{"set-output-model", required_argument, 0, P18_SET_OUTPUT_MODEL},
|
||||
{"set-battery-cutoff-voltage", required_argument, 0, P18_SET_BAT_CUTOFF_VOLTAGE},
|
||||
{"set-solar-configuration", required_argument, 0, P18_SET_SOLAR_CONFIG},
|
||||
{"clear-generated-data", no_argument , 0, P18_SET_CLEAR_GENERATED},
|
||||
{"set-date-time", required_argument, 0, P18_SET_DATE_TIME},
|
||||
{"set-ac-charge-time-bucket", required_argument, 0, P18_SET_AC_CHARGE_TIME_BUCKET},
|
||||
{"set-ac-supply-load-time-bucket", required_argument, 0, P18_SET_AC_SUPPLY_LOAD_TIME_BUCKET},
|
||||
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
bool getopt_err = false;
|
||||
while ((opt = getopt_long(argc, argv, "hdvr:pt:f:",
|
||||
long_options, NULL)) != EOF) {
|
||||
if (opt == '?') {
|
||||
getopt_err = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (opt == OPT_HELP)
|
||||
act = ACTION_HELP;
|
||||
|
||||
else if (opt == OPT_DUMP)
|
||||
act = ACTION_DUMP;
|
||||
|
||||
else if (opt == OPT_VERBOSE)
|
||||
g_verbose = true;
|
||||
|
||||
else if (opt == OPT_PREDENT)
|
||||
pretend = true;
|
||||
|
||||
else if (opt == OPT_FORMAT) {
|
||||
if (!strcmp(optarg, "json"))
|
||||
g_format = PRINT_FORMAT_JSON;
|
||||
else if (!strcmp(optarg, "json-w-units"))
|
||||
g_format = PRINT_FORMAT_JSON_W_UNITS;
|
||||
else if (!strcmp(optarg, "table"))
|
||||
g_format = PRINT_FORMAT_TABLE;
|
||||
else if (!strcmp(optarg, "parsable-table"))
|
||||
g_format = PRINT_FORMAT_PARSABLE_TABLE;
|
||||
else
|
||||
exit_with_error(1, "invalid format");
|
||||
}
|
||||
|
||||
else if (opt == OPT_RAW) {
|
||||
if (strlen(optarg) > COMMAND_BUF_LENGTH - 1)
|
||||
exit_with_error(1, "command is too long");
|
||||
a[0] = optarg;
|
||||
act = ACTION_EXECUTE;
|
||||
}
|
||||
|
||||
else if (opt == OPT_TIMEOUT) {
|
||||
timeout = atoi(optarg);
|
||||
if (timeout <= 0 || timeout > 60000)
|
||||
exit_with_error(1, "invalid timeout");
|
||||
}
|
||||
|
||||
else if (opt >= P18_QUERY_CMDS_ENUM_OFFSET) {
|
||||
if (act == ACTION_QUERY)
|
||||
exit_with_error(1, "one query at a time, please");
|
||||
|
||||
if (opt >= P18_QUERY_CMDS_ENUM_OFFSET) {
|
||||
act = ACTION_QUERY;
|
||||
command_no = opt;
|
||||
}
|
||||
|
||||
switch (opt) {
|
||||
case P18_QUERY_YEAR_GENERATED:
|
||||
GET_ARGS(1);
|
||||
validate_date_args(a[0], NULL, NULL);
|
||||
break;
|
||||
|
||||
case P18_QUERY_MONTH_GENERATED:
|
||||
GET_ARGS(2);
|
||||
validate_date_args(a[0], a[1], NULL);
|
||||
break;
|
||||
|
||||
case P18_QUERY_DAY_GENERATED:
|
||||
GET_ARGS(3);
|
||||
validate_date_args(a[0], a[1], a[2]);
|
||||
break;
|
||||
|
||||
case P18_QUERY_PARALLEL_RATED_INFORMATION:
|
||||
case P18_QUERY_PARALLEL_GENERAL_STATUS:
|
||||
GET_ARGS(1);
|
||||
if (!isnumeric(a[0]) || strlen(a[0]) > 1)
|
||||
exit_with_error(1, "invalid argument");
|
||||
break;
|
||||
|
||||
case P18_SET_LOADS:
|
||||
GET_ARGS(1);
|
||||
if (strcmp(a[0], "0") != 0 && strcmp(a[0], "1") != 0)
|
||||
exit_with_error(1, "invalid argument, only 0 or 1 allowed");
|
||||
break;
|
||||
|
||||
case P18_SET_FLAG: {
|
||||
GET_ARGS(2);
|
||||
bool matchfound = false;
|
||||
FOREACH (const p18_flag_printable_list_item_t *item,
|
||||
p18_flags_printable_list) {
|
||||
if (!strcmp(item->key, a[0])) {
|
||||
a[0] = item->p18_key;
|
||||
matchfound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchfound)
|
||||
exit_with_error(1, "invalid flag");
|
||||
if (strcmp(a[1], "0") != 0 &&
|
||||
strcmp(a[1], "1") != 0)
|
||||
exit_with_error(1, "invalid flag state, only 0 or 1 allowed");
|
||||
break;
|
||||
}
|
||||
|
||||
case P18_SET_BAT_MAX_CHARGE_CURRENT:
|
||||
case P18_SET_BAT_MAX_AC_CHARGE_CURRENT:
|
||||
GET_ARGS(2);
|
||||
if (!is_valid_parallel_id(a[0]))
|
||||
exit_with_error(1, "invalid id");
|
||||
if (!get_uint(a[1], NULL) || strlen(a[1]) > 3)
|
||||
exit_with_error(1, "invalid argument");
|
||||
break;
|
||||
|
||||
case P18_SET_AC_OUTPUT_FREQ:
|
||||
GET_ARGS(1);
|
||||
if (strcmp(a[0], "50") != 0 && strcmp(a[0], "60") != 0)
|
||||
exit_with_error(1, "invalid frequency, only 50 or 60 allowed");
|
||||
break;
|
||||
|
||||
case P18_SET_BAT_MAX_CHARGE_VOLTAGE:
|
||||
GET_ARGS(2);
|
||||
float cv, fv;
|
||||
bool cvr = get_float(a[0], &cv);
|
||||
bool fvr = get_float(a[1], &fv);
|
||||
if (!cvr || cv < 48.0 || cv > 58.4)
|
||||
exit_with_error(1, "invalid CV");
|
||||
if (!fvr || fv < 48.0 || fv > 58.4)
|
||||
exit_with_error(1, "invalid FV");
|
||||
break;
|
||||
|
||||
case P18_SET_AC_OUTPUT_RATED_VOLTAGE: {
|
||||
GET_ARGS(1);
|
||||
unsigned int v;
|
||||
if (!get_uint(a[0], &v))
|
||||
exit_with_error(1, "invalid argument");
|
||||
bool matchfound = false;
|
||||
FOREACH (const int *allowed_v, p18_ac_output_rated_voltages) {
|
||||
if ((unsigned int)*allowed_v == v) {
|
||||
matchfound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchfound)
|
||||
exit_with_error(1, "invalid voltage");
|
||||
break;
|
||||
|
||||
case P18_SET_OUTPUT_SOURCE_PRIORITY:
|
||||
GET_ARGS(1);
|
||||
const char *allowed[] = {"SUB", "SBU"};
|
||||
int index;
|
||||
if (!instrarray(a[0], allowed, ARRAY_SIZE(allowed), &index))
|
||||
exit_with_error(1, "invalid argument");
|
||||
write_num((char *)a[0], index);
|
||||
break;
|
||||
|
||||
case P18_SET_BAT_CHARGING_THRESHOLDS_WHEN_UTILITY_AVAIL:
|
||||
GET_ARGS(2);
|
||||
if ( !instrarray(a[0], p18_battery_util_recharging_voltages_12v_unit, ARRAY_SIZE(p18_battery_util_recharging_voltages_12v_unit), NULL)
|
||||
&& !instrarray(a[0], p18_battery_util_recharging_voltages_24v_unit, ARRAY_SIZE(p18_battery_util_recharging_voltages_24v_unit), NULL)
|
||||
&& !instrarray(a[0], p18_battery_util_recharging_voltages_48v_unit, ARRAY_SIZE(p18_battery_util_recharging_voltages_48v_unit), NULL))
|
||||
exit_with_error(1, "invalid CV");
|
||||
if ( !instrarray(a[1], p18_battery_util_redischarging_voltages_12v_unit, ARRAY_SIZE(p18_battery_util_recharging_voltages_12v_unit), NULL)
|
||||
&& !instrarray(a[1], p18_battery_util_redischarging_voltages_24v_unit, ARRAY_SIZE(p18_battery_util_recharging_voltages_24v_unit), NULL)
|
||||
&& !instrarray(a[1], p18_battery_util_redischarging_voltages_48v_unit, ARRAY_SIZE(p18_battery_util_recharging_voltages_48v_unit), NULL))
|
||||
exit_with_error(1, "invalid DV");
|
||||
break;
|
||||
}
|
||||
|
||||
case P18_SET_CHARGING_SOURCE_PRIORITY: {
|
||||
GET_ARGS(2);
|
||||
if (!is_valid_parallel_id(a[0]))
|
||||
exit_with_error(1, "invalid id");
|
||||
const char *allowed[] = {"SF", "SU", "S"};
|
||||
int index;
|
||||
if (!instrarray(a[1], allowed, ARRAY_SIZE(allowed), &index))
|
||||
exit_with_error(1, "invalid priority");
|
||||
write_num((char *)a[1], index);
|
||||
break;
|
||||
}
|
||||
|
||||
case P18_SET_SOLAR_POWER_PRIORITY: {
|
||||
GET_ARGS(1);
|
||||
const char *allowed[] = {"BLU", "LBU"};
|
||||
int index;
|
||||
if (!instrarray(a[0], allowed, ARRAY_SIZE(allowed), &index))
|
||||
exit_with_error(1, "invalid priority");
|
||||
write_num((char *)a[0], index);
|
||||
break;
|
||||
}
|
||||
|
||||
case P18_SET_AC_INPUT_VOLTAGE_RANGE: {
|
||||
GET_ARGS(1);
|
||||
const char *allowed[] = {"APPLIANCE", "UPS"};
|
||||
int index;
|
||||
if (!instrarray(a[0], allowed, ARRAY_SIZE(allowed), &index))
|
||||
exit_with_error(1, "invalid argument");
|
||||
write_num((char *)a[0], index);
|
||||
break;
|
||||
}
|
||||
|
||||
case P18_SET_BAT_TYPE: {
|
||||
GET_ARGS(1);
|
||||
const char *allowed[] = {"AGM", "FLOODED", "USER"};
|
||||
int index;
|
||||
if (!instrarray(a[0], allowed, ARRAY_SIZE(allowed), &index))
|
||||
exit_with_error(1, "invalid type");
|
||||
write_num((char *)a[0], index);
|
||||
break;
|
||||
}
|
||||
|
||||
case P18_SET_OUTPUT_MODEL: {
|
||||
GET_ARGS(2);
|
||||
if (!is_valid_parallel_id(a[0]))
|
||||
exit_with_error(1, "invalid id");
|
||||
const char *allowed[] = {"SM", "P", "P1", "P2", "P3"};
|
||||
int index;
|
||||
if (!instrarray(a[1], allowed, ARRAY_SIZE(allowed), &index))
|
||||
exit_with_error(1, "invalid model");
|
||||
write_num((char *)a[1], index);
|
||||
break;
|
||||
}
|
||||
|
||||
case P18_SET_BAT_CUTOFF_VOLTAGE:
|
||||
GET_ARGS(1);
|
||||
float v;
|
||||
bool vr = get_float(a[0], &v);
|
||||
if (!vr || v < 40.0 || v > 48.0)
|
||||
exit_with_error(1, "invalid voltage");
|
||||
break;
|
||||
|
||||
case P18_SET_SOLAR_CONFIG:
|
||||
GET_ARGS(1);
|
||||
if (!isnumeric(a[0]) || strlen(a[0]) > 20)
|
||||
exit_with_error(1, "invalid argument");
|
||||
break;
|
||||
|
||||
case P18_SET_DATE_TIME:
|
||||
GET_ARGS(6);
|
||||
validate_date_args(a[0], a[1], a[2]);
|
||||
validate_time_args(a[3], a[4], a[5]);
|
||||
break;
|
||||
|
||||
case P18_SET_AC_CHARGE_TIME_BUCKET:
|
||||
case P18_SET_AC_SUPPLY_LOAD_TIME_BUCKET:
|
||||
GET_ARGS(2);
|
||||
unsigned short start_h, start_m, end_h, end_m;
|
||||
int results;
|
||||
|
||||
results = sscanf(a[0], "%hu:%hu", &start_h, &start_m);
|
||||
if (results != 2 || start_h > 23 || start_m > 59)
|
||||
exit_with_error(1, "invalid start time");
|
||||
|
||||
results = sscanf(a[1], "%hu:%hu", &end_h, &end_m);
|
||||
if (results != 2 || end_h > 23 || end_m > 59)
|
||||
exit_with_error(1, "invalid end time");
|
||||
|
||||
char *start_col = strchr(a[0], ':');
|
||||
char *end_col = strchr(a[1], ':');
|
||||
*start_col = '\0';
|
||||
*end_col = '\0';
|
||||
|
||||
char *start_m_ptr = start_col+1;
|
||||
char *end_h_ptr = (char *)a[1];
|
||||
char *end_m_ptr = end_col+1;
|
||||
|
||||
a[1] = start_m_ptr;
|
||||
a[2] = end_h_ptr;
|
||||
a[3] = end_m_ptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
exit_with_error(1, "extra parameter found");
|
||||
|
||||
if (getopt_err)
|
||||
exit(1);
|
||||
|
||||
if (act == ACTION_HELP)
|
||||
usage(argv[0]);
|
||||
|
||||
voltronic_dev_t dev = voltronic_usb_create(0x0665, 0x5161);
|
||||
|
||||
if (!pretend && !dev)
|
||||
exit_with_error(1, "could not open USB device: %s", strerror(errno));
|
||||
|
||||
switch (act) {
|
||||
case ACTION_EXECUTE:
|
||||
execute_raw(dev, a[0], timeout);
|
||||
break;
|
||||
|
||||
case ACTION_QUERY:
|
||||
query(dev, command_no, timeout, a, sizeof(a), pretend);
|
||||
break;
|
||||
|
||||
default:
|
||||
exit_with_error(1, "unexpected act %d", act);
|
||||
}
|
||||
|
||||
if (dev)
|
||||
voltronic_dev_close(dev);
|
||||
|
||||
return 0;
|
||||
}
|
99
libvoltronic/voltronic_crc.c
Normal file
99
libvoltronic/voltronic_crc.c
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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 "voltronic_crc.h"
|
||||
|
||||
#define IS_INTEGER_EQUAL(_ch__a_, _ch__b_) \
|
||||
((_ch__a_) == (_ch__b_))
|
||||
|
||||
#define IS_RESERVED_BYTE(_ch_) ( \
|
||||
IS_INTEGER_EQUAL((_ch_), 0x28) || \
|
||||
IS_INTEGER_EQUAL((_ch_), 0x0D) || \
|
||||
IS_INTEGER_EQUAL((_ch_), 0x0A))
|
||||
|
||||
#define CRC_SIZE \
|
||||
(sizeof(voltronic_crc_t))
|
||||
|
||||
int write_voltronic_crc(
|
||||
const voltronic_crc_t crc,
|
||||
char* cstring_buffer) {
|
||||
|
||||
if (cstring_buffer != 0) {
|
||||
unsigned char* buffer =
|
||||
(unsigned char*) cstring_buffer;
|
||||
|
||||
buffer[0] = (crc >> 8) & 0xFF;
|
||||
buffer[1] = crc & 0xFF;
|
||||
}
|
||||
|
||||
return CRC_SIZE;
|
||||
}
|
||||
|
||||
voltronic_crc_t read_voltronic_crc(
|
||||
const char* cstring_buffer) {
|
||||
|
||||
const unsigned char* buffer =
|
||||
(const unsigned char*) cstring_buffer;
|
||||
|
||||
voltronic_crc_t crc = 0;
|
||||
|
||||
crc |= (voltronic_crc_t) buffer[0] << 8;
|
||||
crc |= (voltronic_crc_t) buffer[1];
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
voltronic_crc_t calculate_voltronic_crc(
|
||||
const char* cstring_buffer,
|
||||
size_t buffer_length) {
|
||||
|
||||
voltronic_crc_t crc = 0;
|
||||
if (buffer_length > 0) {
|
||||
|
||||
static const voltronic_crc_t crc_table[16] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063,
|
||||
0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B,
|
||||
0xC18C, 0xD1AD, 0xE1CE, 0xF1EF
|
||||
};
|
||||
|
||||
const unsigned char* buffer =
|
||||
(const unsigned char*) cstring_buffer;
|
||||
|
||||
unsigned char byte;
|
||||
do {
|
||||
byte = *buffer;
|
||||
|
||||
crc = crc_table[(crc >> 12) ^ (byte >> 4)] ^ (crc << 4);
|
||||
crc = crc_table[(crc >> 12) ^ (byte & 0x0F)] ^ (crc << 4);
|
||||
|
||||
buffer += sizeof(unsigned char);
|
||||
} while(--buffer_length);
|
||||
|
||||
byte = crc;
|
||||
if (IS_RESERVED_BYTE(byte)) {
|
||||
crc += 1;
|
||||
}
|
||||
|
||||
byte = crc >> 8;
|
||||
if (IS_RESERVED_BYTE(byte)) {
|
||||
crc += 1 << 8;
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
71
libvoltronic/voltronic_crc.h
Normal file
71
libvoltronic/voltronic_crc.h
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __VOLTRONIC__CRC__H__
|
||||
#define __VOLTRONIC__CRC__H__
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* The underlying numeric type used to store the CRC
|
||||
*/
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
#include "windows.h"
|
||||
|
||||
typedef unsigned __int16 voltronic_crc_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint16_t voltronic_crc_t;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Write a voltronic_crc_t to a buffer
|
||||
*
|
||||
* crc - CRC to write
|
||||
* buffer - Buffer to write CRC to or NULL/0
|
||||
*
|
||||
* Returns the size of the CRC
|
||||
*/
|
||||
int write_voltronic_crc(
|
||||
const voltronic_crc_t crc,
|
||||
char* buffer);
|
||||
|
||||
/**
|
||||
* Read a voltronic_crc_t from a buffer
|
||||
*
|
||||
* buffer - Buffer to read CRC from;
|
||||
* If the buffer is smaller than the size
|
||||
* returned by write_voltronic_crc behavoir is undefined
|
||||
*
|
||||
* Returns the CRC read from the buffer
|
||||
*/
|
||||
voltronic_crc_t read_voltronic_crc(const char* buffer);
|
||||
|
||||
/**
|
||||
* Calculate the Voltronic CRC by reading a buffer and calculating the CRC from the bytes
|
||||
*
|
||||
* buffer - Buffer to read from
|
||||
* buffer_length - Number of bytes in the buffer
|
||||
*
|
||||
* Returns the CRC created from the bytes in the buffer
|
||||
*/
|
||||
voltronic_crc_t calculate_voltronic_crc(
|
||||
const char* buffer,
|
||||
size_t buffer_length);
|
||||
|
||||
#endif
|
430
libvoltronic/voltronic_dev.c
Normal file
430
libvoltronic/voltronic_dev.c
Normal file
@ -0,0 +1,430 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
* Copyright (C) 2020 Evgeny Zinoviev
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "voltronic_dev.h"
|
||||
#include "voltronic_dev_impl.h"
|
||||
#include "voltronic_crc.h"
|
||||
#include "../util.h"
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
|
||||
#include "windows.h"
|
||||
|
||||
typedef DWORD millisecond_timestamp_t;
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
typedef uint64_t millisecond_timestamp_t;
|
||||
|
||||
#elif defined(ARDUINO)
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
typedef unsigned long millisecond_timestamp_t;
|
||||
|
||||
#else
|
||||
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
typedef uint64_t millisecond_timestamp_t;
|
||||
|
||||
#endif
|
||||
|
||||
#define END_OF_INPUT '\r'
|
||||
#define END_OF_INPUT_SIZE sizeof(char)
|
||||
#define NON_DATA_SIZE (sizeof(voltronic_crc_t) + END_OF_INPUT_SIZE)
|
||||
|
||||
#define GET_IMPL_DEV(_voltronic_dev_t_) \
|
||||
((void*) (_voltronic_dev_t_))
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
|
||||
#define SET_TIMEOUT_REACHED() SET_LAST_ERROR(WAIT_TIMEOUT)
|
||||
#define SET_BUFFER_OVERFLOW() SET_LAST_ERROR(ERROR_INSUFFICIENT_BUFFER)
|
||||
#define SET_CRC_ERROR() SET_LAST_ERROR(ERROR_CRC)
|
||||
#define SYSTEM_NOT_SUPPORTED() SET_LAST_ERROR(ERROR_CALL_NOT_IMPLEMENTED)
|
||||
|
||||
#else
|
||||
|
||||
#define SET_TIMEOUT_REACHED() SET_LAST_ERROR(ETIMEDOUT)
|
||||
#define SET_BUFFER_OVERFLOW() SET_LAST_ERROR(ENOBUFS)
|
||||
#define SET_CRC_ERROR() SET_LAST_ERROR(EBADMSG)
|
||||
#define SYSTEM_NOT_SUPPORTED() SET_LAST_ERROR(ENOSYS)
|
||||
|
||||
#endif
|
||||
|
||||
static millisecond_timestamp_t get_millisecond_timestamp(void);
|
||||
static int is_platform_supported_by_libvoltronic(void);
|
||||
|
||||
int voltronic_dev_read(
|
||||
const voltronic_dev_t dev,
|
||||
char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
if (buffer_size > 0) {
|
||||
const int result = voltronic_dev_impl_read(
|
||||
GET_IMPL_DEV(dev),
|
||||
buffer,
|
||||
buffer_size,
|
||||
timeout_milliseconds);
|
||||
|
||||
return result >= 0 ? result : -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int voltronic_dev_write(
|
||||
const voltronic_dev_t dev,
|
||||
const char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
if (buffer_size > 0) {
|
||||
const int result = voltronic_dev_impl_write(
|
||||
GET_IMPL_DEV(dev),
|
||||
buffer,
|
||||
buffer_size,
|
||||
timeout_milliseconds);
|
||||
|
||||
return result >= 0 ? result : -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int voltronic_dev_close(voltronic_dev_t dev) {
|
||||
if (dev != 0) {
|
||||
const int result = voltronic_dev_impl_close(GET_IMPL_DEV(dev));
|
||||
return result > 0 ? 1 : 0;
|
||||
} else {
|
||||
SET_INVALID_INPUT();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int voltronic_read_data_loop(
|
||||
const voltronic_dev_t dev,
|
||||
char* buffer,
|
||||
size_t buffer_length,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
unsigned int size = 0;
|
||||
|
||||
const millisecond_timestamp_t start_time = get_millisecond_timestamp();
|
||||
millisecond_timestamp_t elapsed = 0;
|
||||
|
||||
while(1) {
|
||||
int bytes_read = voltronic_dev_read(
|
||||
dev,
|
||||
buffer,
|
||||
buffer_length,
|
||||
timeout_milliseconds - elapsed);
|
||||
|
||||
if (bytes_read >= 0) {
|
||||
while(bytes_read) {
|
||||
--bytes_read;
|
||||
++size;
|
||||
|
||||
if (*buffer == END_OF_INPUT) {
|
||||
return size;
|
||||
}
|
||||
|
||||
buffer += sizeof(char);
|
||||
--buffer_length;
|
||||
}
|
||||
|
||||
elapsed = get_millisecond_timestamp() - start_time;
|
||||
if (elapsed >= timeout_milliseconds) {
|
||||
SET_TIMEOUT_REACHED();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buffer_length <= 0) {
|
||||
SET_BUFFER_OVERFLOW();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return bytes_read;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int voltronic_receive_data(
|
||||
const voltronic_dev_t dev,
|
||||
const unsigned int options,
|
||||
char *buffer,
|
||||
const size_t buffer_length,
|
||||
size_t *received,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
const int result = voltronic_read_data_loop(
|
||||
dev,
|
||||
buffer,
|
||||
buffer_length,
|
||||
timeout_milliseconds);
|
||||
|
||||
if (result >= 0) {
|
||||
if (received)
|
||||
*received = result;
|
||||
|
||||
LOG("%s: got %d %s:\n",
|
||||
__func__, result, (result > 1 ? "bytes" : "byte"));
|
||||
HEXDUMP(buffer, result);
|
||||
|
||||
if ((options & DISABLE_PARSE_VOLTRONIC_CRC) == 0) {
|
||||
if (((size_t) result) >= NON_DATA_SIZE) {
|
||||
const size_t data_size = result - NON_DATA_SIZE;
|
||||
const voltronic_crc_t read_crc = read_voltronic_crc(&buffer[data_size]);
|
||||
const voltronic_crc_t calculated_crc = calculate_voltronic_crc(buffer, data_size);
|
||||
buffer[data_size] = 0;
|
||||
|
||||
if (((options & DISABLE_VERIFY_VOLTRONIC_CRC) == 0) ||
|
||||
(read_crc == calculated_crc)) {
|
||||
|
||||
return data_size;
|
||||
}
|
||||
}
|
||||
|
||||
SET_CRC_ERROR();
|
||||
} else {
|
||||
if (((size_t) result) >= END_OF_INPUT_SIZE) {
|
||||
const size_t data_size = result - END_OF_INPUT_SIZE;
|
||||
buffer[data_size] = 0;
|
||||
return data_size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (received)
|
||||
*received = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int voltronic_write_data_loop(
|
||||
const voltronic_dev_t dev,
|
||||
const char* buffer,
|
||||
size_t buffer_length,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
const millisecond_timestamp_t start_time = get_millisecond_timestamp();
|
||||
millisecond_timestamp_t elapsed = 0;
|
||||
|
||||
int bytes_left = buffer_length;
|
||||
while(1) {
|
||||
const int write_result = voltronic_dev_write(dev, buffer, bytes_left, timeout_milliseconds);
|
||||
|
||||
if (write_result >= 0) {
|
||||
bytes_left -= write_result;
|
||||
if (bytes_left > 0) {
|
||||
buffer = &buffer[write_result];
|
||||
} else {
|
||||
return buffer_length;
|
||||
}
|
||||
|
||||
elapsed = get_millisecond_timestamp() - start_time;
|
||||
if (elapsed >= timeout_milliseconds) {
|
||||
SET_TIMEOUT_REACHED();
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return write_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int voltronic_send_data(
|
||||
const voltronic_dev_t dev,
|
||||
const unsigned int options,
|
||||
const char* buffer,
|
||||
const size_t buffer_length,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
size_t copy_length;
|
||||
char* copy;
|
||||
if ((options & DISABLE_WRITE_VOLTRONIC_CRC) == 0) {
|
||||
const voltronic_crc_t crc = calculate_voltronic_crc(buffer, buffer_length);
|
||||
|
||||
copy_length = buffer_length + NON_DATA_SIZE;
|
||||
copy = (char*) ALLOCATE_MEMORY(copy_length * sizeof(char));
|
||||
|
||||
write_voltronic_crc(crc, ©[buffer_length]);
|
||||
} else {
|
||||
copy_length = buffer_length + END_OF_INPUT_SIZE;
|
||||
copy = (char*) ALLOCATE_MEMORY(copy_length * sizeof(char));
|
||||
}
|
||||
|
||||
COPY_MEMORY(copy, buffer, buffer_length * sizeof(char));
|
||||
copy[copy_length - 1] = END_OF_INPUT;
|
||||
|
||||
LOG("%s: writing %zu %s:\n",
|
||||
__func__, copy_length, (copy_length > 1 ? "bytes" : "byte"));
|
||||
HEXDUMP(copy, copy_length);
|
||||
|
||||
const int result = voltronic_write_data_loop(
|
||||
dev,
|
||||
copy,
|
||||
copy_length,
|
||||
timeout_milliseconds);
|
||||
|
||||
FREE_MEMORY(copy);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
int voltronic_dev_execute(
|
||||
const voltronic_dev_t dev,
|
||||
const unsigned int options,
|
||||
const char *send_buffer,
|
||||
size_t send_buffer_length,
|
||||
char *receive_buffer,
|
||||
size_t receive_buffer_length,
|
||||
size_t *received,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
const millisecond_timestamp_t start_time = get_millisecond_timestamp();
|
||||
millisecond_timestamp_t elapsed = 0;
|
||||
int result;
|
||||
|
||||
result = voltronic_send_data(
|
||||
dev,
|
||||
options,
|
||||
send_buffer,
|
||||
send_buffer_length,
|
||||
timeout_milliseconds);
|
||||
|
||||
if (result > 0) {
|
||||
elapsed = get_millisecond_timestamp() - start_time;
|
||||
if (elapsed < timeout_milliseconds) {
|
||||
result = voltronic_receive_data(
|
||||
dev,
|
||||
options,
|
||||
receive_buffer,
|
||||
receive_buffer_length,
|
||||
received,
|
||||
timeout_milliseconds - elapsed);
|
||||
|
||||
if (result > 0) {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
SET_TIMEOUT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
voltronic_dev_t voltronic_dev_internal_create(void* impl_ptr) {
|
||||
if (is_platform_supported_by_libvoltronic()) {
|
||||
if (impl_ptr != 0) {
|
||||
return ((voltronic_dev_t) (impl_ptr));
|
||||
}
|
||||
}
|
||||
|
||||
if (impl_ptr != 0) {
|
||||
voltronic_dev_impl_close(impl_ptr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
|
||||
static millisecond_timestamp_t get_millisecond_timestamp(void) {
|
||||
return (millisecond_timestamp_t) GetTickCount();
|
||||
}
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
static millisecond_timestamp_t get_millisecond_timestamp(void) {
|
||||
return (millisecond_timestamp_t) mach_absolute_time();
|
||||
}
|
||||
|
||||
#elif defined(ARDUINO)
|
||||
|
||||
static millisecond_timestamp_t get_millisecond_timestamp(void) {
|
||||
return (millisecond_timestamp_t) millis();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static millisecond_timestamp_t get_millisecond_timestamp(void) {
|
||||
millisecond_timestamp_t milliseconds = 0;
|
||||
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
|
||||
static int monotonic_clock_error = 0;
|
||||
if (monotonic_clock_error == 0) {
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
||||
milliseconds = (millisecond_timestamp_t) ts.tv_sec;
|
||||
milliseconds *= 1000;
|
||||
milliseconds += (millisecond_timestamp_t) (ts.tv_nsec / 1000000);
|
||||
return milliseconds;
|
||||
} else {
|
||||
monotonic_clock_error = 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
struct timeval tv;
|
||||
if (gettimeofday(&tv, 0) == 0) {
|
||||
milliseconds = (millisecond_timestamp_t) tv.tv_sec;
|
||||
milliseconds *= 1000;
|
||||
milliseconds += (millisecond_timestamp_t) (tv.tv_usec / 1000);
|
||||
}
|
||||
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int is_platform_supported_by_libvoltronic(void) {
|
||||
/**
|
||||
* Operating system/cpu architecture validations
|
||||
* If any of these fail, things don't behave the code expects
|
||||
*/
|
||||
if ((sizeof(char) == sizeof(unsigned char)) &&
|
||||
(sizeof(unsigned char) == 1) &&
|
||||
(sizeof(int) >= 2) &&
|
||||
(sizeof(unsigned int) >= 2) &&
|
||||
(sizeof(voltronic_crc_t) == 2) &&
|
||||
(sizeof(millisecond_timestamp_t) >= 4)) {
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
SYSTEM_NOT_SUPPORTED();
|
||||
return 0;
|
||||
}
|
||||
}
|
109
libvoltronic/voltronic_dev.h
Normal file
109
libvoltronic/voltronic_dev.h
Normal file
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __VOLTRONIC__DEV__H__
|
||||
#define __VOLTRONIC__DEV__H__
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* Opaque pointer to a voltronic device
|
||||
*/
|
||||
typedef void* voltronic_dev_t;
|
||||
|
||||
/**
|
||||
* Read bytes from a voltronic device
|
||||
*
|
||||
* dev -> Opaque device pointer
|
||||
* buffer -> Buffer where read bytes are written to
|
||||
* buffer_size -> Maximum number of bytes to read
|
||||
* timeout_milliseconds -> Number of milliseconds to wait for a single byte before giving up
|
||||
*
|
||||
* Returns the number of bytes read
|
||||
* Returns 0 on timeout
|
||||
* Returns -1 on error
|
||||
*
|
||||
* Function sets errno (POSIX)/LastError (Windows) to approriate error on failure
|
||||
*/
|
||||
int voltronic_dev_read(
|
||||
const voltronic_dev_t dev,
|
||||
char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds);
|
||||
|
||||
/**
|
||||
* Write bytes to a voltronic device
|
||||
*
|
||||
* dev -> Opaque device pointer
|
||||
* buffer -> Buffer of bytes to write to device
|
||||
* buffer_size -> Number of bytes to write
|
||||
* timeout_milliseconds -> Number of milliseconds to wait before giving up
|
||||
*
|
||||
* Returns the number of bytes written
|
||||
* Returns 0 on timeout
|
||||
* Returns -1 on error
|
||||
*
|
||||
* Function sets errno (POSIX)/LastError (Windows) to approriate error on failure
|
||||
*/
|
||||
int voltronic_dev_write(
|
||||
const voltronic_dev_t dev,
|
||||
const char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds);
|
||||
|
||||
/**
|
||||
* Options for voltronic_dev_execute
|
||||
*/
|
||||
#define VOLTRONIC_EXECUTE_DEFAULT_OPTIONS (0)
|
||||
#define DISABLE_WRITE_VOLTRONIC_CRC (1 << 0)
|
||||
#define DISABLE_PARSE_VOLTRONIC_CRC (1 << 1)
|
||||
#define DISABLE_VERIFY_VOLTRONIC_CRC (1 << 2)
|
||||
|
||||
/**
|
||||
* Write a command to the device and wait for a response from the device
|
||||
*
|
||||
* dev -> Opaque device pointer
|
||||
* send_buffer -> Buffer of bytes to write to device
|
||||
* send_buffer_length -> Number of bytes to write
|
||||
* receive_buffer -> Buffer where read bytes are written to
|
||||
* receive_buffer_length -> Maximum number of bytes to read
|
||||
* timeout_milliseconds -> Number of milliseconds to wait before giving up
|
||||
* options -> See options above
|
||||
*
|
||||
* Returns 1 on success and 0 on failure
|
||||
*
|
||||
* Function sets errno (POSIX)/LastError (Windows) to approriate error on failure
|
||||
*/
|
||||
int voltronic_dev_execute(
|
||||
const voltronic_dev_t dev,
|
||||
const unsigned int options,
|
||||
const char *send_buffer,
|
||||
size_t send_buffer_length,
|
||||
char *receive_buffer,
|
||||
size_t receive_buffer_length,
|
||||
size_t *received,
|
||||
const unsigned int timeout_milliseconds);
|
||||
|
||||
/**
|
||||
* Close the connection to the device
|
||||
*
|
||||
* dev -> Opaque device pointer
|
||||
*/
|
||||
int voltronic_dev_close(
|
||||
voltronic_dev_t dev);
|
||||
|
||||
#endif
|
125
libvoltronic/voltronic_dev_impl.h
Normal file
125
libvoltronic/voltronic_dev_impl.h
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __VOLTRONIC__DEV__IMPL__H__
|
||||
#define __VOLTRONIC__DEV__IMPL__H__
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------
|
||||
* ------------------------------------------------------------------
|
||||
* Used internally by implementations of voltronic_dev.h
|
||||
* Don't include unless you are building an implementation
|
||||
* ------------------------------------------------------------------
|
||||
* ------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "voltronic_dev.h"
|
||||
|
||||
/**
|
||||
* Read up to buffer_size bytes from the device
|
||||
*
|
||||
* impl_ptr -> The underlying implementation's device pointer
|
||||
* buffer -> The buffer to store data from the device
|
||||
* buffer_size -> The maximum number of bytes to read
|
||||
* timeout_milliseconds -> Number of milliseconds before giving up
|
||||
*
|
||||
* Return the number of bytes successfully read from the device.
|
||||
* On failure returns < 0
|
||||
*
|
||||
* On failure set the appropriate error using SET_LAST_ERROR
|
||||
*/
|
||||
int voltronic_dev_impl_read(
|
||||
void* impl_ptr,
|
||||
char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds);
|
||||
|
||||
/**
|
||||
* Write the provided buffer data to the device
|
||||
*
|
||||
* impl_ptr -> The underlying implementation's device pointer
|
||||
* buffer -> The data to write to the device
|
||||
* buffer_size -> Number of bytes to write to the device
|
||||
* timeout_milliseconds -> Number of milliseconds before giving up
|
||||
*
|
||||
* Return the number of bytes successfully written to the device.
|
||||
* On failure returns < 0
|
||||
*
|
||||
* On failure set the appropriate error using SET_LAST_ERROR
|
||||
*/
|
||||
int voltronic_dev_impl_write(
|
||||
void* impl_ptr,
|
||||
const char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds);
|
||||
|
||||
/**
|
||||
* Accept the implementation pointer and close the underlying device connection
|
||||
*
|
||||
* Returns 0 on failure, anything else is considered success
|
||||
*
|
||||
* On failure set the appropriate error using SET_LAST_ERROR
|
||||
*/
|
||||
int voltronic_dev_impl_close(
|
||||
void* impl_ptr);
|
||||
|
||||
/**
|
||||
* Create the opaque pointer representing a connection to a physical voltronic device
|
||||
*
|
||||
* impl_ptr -> The underlying implementation's device pointer
|
||||
*
|
||||
* On failure sets the appropriate error using SET_LAST_ERROR
|
||||
*/
|
||||
voltronic_dev_t voltronic_dev_internal_create(
|
||||
void* impl_ptr);
|
||||
|
||||
/**
|
||||
* May change if operating system requires it.
|
||||
*/
|
||||
#define ALLOCATE_MEMORY(__size__) \
|
||||
malloc(((size_t) (__size__)))
|
||||
|
||||
#define COPY_MEMORY(__destination__, __source__, __size__) \
|
||||
memcpy(((void*) (__destination__)), \
|
||||
((const void*) (__source__)), \
|
||||
((size_t) (__size__)))
|
||||
|
||||
#define FREE_MEMORY(__ptr__) \
|
||||
free(((void*) (__ptr__)))
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
#include "windows.h"
|
||||
|
||||
typedef DWORD last_error_t;
|
||||
|
||||
#define SET_LAST_ERROR(_last_error_value_) SetLastError((_last_error_value_))
|
||||
#define GET_LAST_ERROR() GetLastError()
|
||||
#define SET_INVALID_INPUT() SET_LAST_ERROR(ERROR_INVALID_DATA)
|
||||
|
||||
#else
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
typedef int last_error_t;
|
||||
|
||||
#define SET_LAST_ERROR(_errno__value_) errno = (_errno__value_)
|
||||
#define GET_LAST_ERROR() (errno)
|
||||
#define SET_INVALID_INPUT() SET_LAST_ERROR(EINVAL)
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
78
libvoltronic/voltronic_dev_serial.h
Normal file
78
libvoltronic/voltronic_dev_serial.h
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __VOLTRONIC__DEV__SERIAL__H__
|
||||
#define __VOLTRONIC__DEV__SERIAL__H__
|
||||
|
||||
#include "voltronic_dev.h"
|
||||
|
||||
/**
|
||||
* The serial port baud rate configuration
|
||||
*/
|
||||
typedef unsigned int baud_rate_t;
|
||||
|
||||
/**
|
||||
* The serial port data bits configuration
|
||||
*/
|
||||
typedef enum {
|
||||
DATA_BITS_FIVE,
|
||||
DATA_BITS_SIX,
|
||||
DATA_BITS_SEVEN,
|
||||
DATA_BITS_EIGHT
|
||||
} data_bits_t;
|
||||
|
||||
/**
|
||||
* The serial port stop bits configuration
|
||||
*/
|
||||
typedef enum {
|
||||
STOP_BITS_ONE,
|
||||
STOP_BITS_ONE_AND_ONE_HALF,
|
||||
STOP_BITS_TWO
|
||||
} stop_bits_t;
|
||||
|
||||
/**
|
||||
* The serial port parity configuration
|
||||
*/
|
||||
typedef enum {
|
||||
SERIAL_PARITY_NONE,
|
||||
SERIAL_PARITY_ODD,
|
||||
SERIAL_PARITY_EVEN,
|
||||
SERIAL_PARITY_MARK,
|
||||
SERIAL_PARITY_SPACE
|
||||
} serial_parity_t;
|
||||
|
||||
/**
|
||||
* Create an opaque pointer to a voltronic device connected over serial
|
||||
*
|
||||
* name - The device name, ie. COM1; /dev/usb.serial; etc.
|
||||
* baud_rate - Baud rate configuration, ie. 2400
|
||||
* data_bits - Data bits configuration, ie. DATA_BITS_EIGHT
|
||||
* stop_bits - Stop bits configuration, ie. STOP_BITS_ONE
|
||||
* parity - Parity configuration, ie. SERIAL_PARITY_NONE
|
||||
*
|
||||
* Returns an opaque pointer to a voltronic device or 0 if an error occurred
|
||||
*
|
||||
* Function sets errno (POSIX)/LastError (Windows) to approriate error on failure
|
||||
*/
|
||||
voltronic_dev_t voltronic_serial_create(
|
||||
const char* name,
|
||||
const baud_rate_t baud_rate,
|
||||
const data_bits_t data_bits,
|
||||
const stop_bits_t stop_bits,
|
||||
const serial_parity_t parity);
|
||||
|
||||
#endif
|
196
libvoltronic/voltronic_dev_serial_libserialport.c
Normal file
196
libvoltronic/voltronic_dev_serial_libserialport.c
Normal file
@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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 "voltronic_dev_impl.h"
|
||||
#include "voltronic_dev_serial.h"
|
||||
#include <libserialport.h>
|
||||
|
||||
#define VOLTRONIC_DEV_SP(_impl_ptr_) ((struct sp_port*) (_impl_ptr_))
|
||||
|
||||
static int voltronic_dev_serial_configure(
|
||||
struct sp_port* port,
|
||||
const baud_rate_t baud_rate,
|
||||
const data_bits_t data_bits,
|
||||
const stop_bits_t stop_bits,
|
||||
const serial_parity_t parity);
|
||||
|
||||
voltronic_dev_t voltronic_serial_create(
|
||||
const char* name,
|
||||
const baud_rate_t baud_rate,
|
||||
const data_bits_t data_bits,
|
||||
const stop_bits_t stop_bits,
|
||||
const serial_parity_t parity) {
|
||||
|
||||
enum sp_return sp_result;
|
||||
struct sp_port* port = 0;
|
||||
if (name != 0) {
|
||||
struct sp_port* tmp_port = { 0 };
|
||||
SET_LAST_ERROR(0);
|
||||
if ((sp_result = sp_get_port_by_name(name, &tmp_port)) == SP_OK) {
|
||||
port = tmp_port;
|
||||
}
|
||||
}
|
||||
|
||||
if (port != 0) {
|
||||
SET_LAST_ERROR(0);
|
||||
if ((sp_result = sp_open(port, SP_MODE_READ_WRITE)) == SP_OK) {
|
||||
SET_LAST_ERROR(0);
|
||||
if (voltronic_dev_serial_configure(port, baud_rate, data_bits, stop_bits, parity) > 0) {
|
||||
sp_flush(port, SP_BUF_BOTH);
|
||||
SET_LAST_ERROR(0);
|
||||
return voltronic_dev_internal_create((void*) port);
|
||||
}
|
||||
}
|
||||
|
||||
const last_error_t last_error = GET_LAST_ERROR();
|
||||
sp_free_port(port);
|
||||
SET_LAST_ERROR(last_error);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int voltronic_dev_impl_read(
|
||||
void* impl_ptr,
|
||||
char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
SET_LAST_ERROR(0);
|
||||
return (int) sp_blocking_read_next(
|
||||
VOLTRONIC_DEV_SP(impl_ptr),
|
||||
(void*) buffer,
|
||||
buffer_size,
|
||||
(unsigned int) timeout_milliseconds);
|
||||
}
|
||||
|
||||
inline int voltronic_dev_impl_write(
|
||||
void* impl_ptr,
|
||||
const char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
SET_LAST_ERROR(0);
|
||||
return (int) sp_blocking_write(
|
||||
VOLTRONIC_DEV_SP(impl_ptr),
|
||||
(const void*) buffer,
|
||||
buffer_size,
|
||||
(unsigned int) timeout_milliseconds);
|
||||
}
|
||||
|
||||
inline int voltronic_dev_impl_close(void* impl_ptr) {
|
||||
struct sp_port* sp_port = VOLTRONIC_DEV_SP(impl_ptr);
|
||||
SET_LAST_ERROR(0);
|
||||
const enum sp_return result = sp_close(sp_port);
|
||||
if (result == SP_OK) {
|
||||
sp_free_port(sp_port);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int voltronic_dev_baud_rate(
|
||||
const baud_rate_t baud_rate) {
|
||||
|
||||
return (int) baud_rate;
|
||||
}
|
||||
|
||||
static inline int voltronic_dev_data_bits(
|
||||
const data_bits_t data_bits) {
|
||||
|
||||
switch(data_bits){
|
||||
case DATA_BITS_FIVE: return 5;
|
||||
case DATA_BITS_SIX: return 6;
|
||||
case DATA_BITS_SEVEN: return 7;
|
||||
case DATA_BITS_EIGHT: return 8;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int voltronic_dev_stop_bits(
|
||||
const stop_bits_t stop_bits) {
|
||||
|
||||
switch(stop_bits){
|
||||
case STOP_BITS_ONE: return 1;
|
||||
case STOP_BITS_TWO: return 2;
|
||||
case STOP_BITS_ONE_AND_ONE_HALF: return 3;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static inline enum sp_parity voltronic_dev_serial_parity(
|
||||
const serial_parity_t parity) {
|
||||
|
||||
switch(parity){
|
||||
case SERIAL_PARITY_NONE: return SP_PARITY_NONE;
|
||||
case SERIAL_PARITY_ODD: return SP_PARITY_ODD;
|
||||
case SERIAL_PARITY_EVEN: return SP_PARITY_EVEN;
|
||||
case SERIAL_PARITY_MARK: return SP_PARITY_MARK;
|
||||
case SERIAL_PARITY_SPACE: return SP_PARITY_SPACE;
|
||||
default: return SP_PARITY_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static int voltronic_dev_serial_configure(
|
||||
struct sp_port* port,
|
||||
const baud_rate_t baud_rate,
|
||||
const data_bits_t data_bits,
|
||||
const stop_bits_t stop_bits,
|
||||
const serial_parity_t parity) {
|
||||
|
||||
const int sp_baud_rate = voltronic_dev_baud_rate(baud_rate);
|
||||
const int sp_data_bits = voltronic_dev_data_bits(data_bits);
|
||||
const int sp_stop_bits = voltronic_dev_stop_bits(stop_bits);
|
||||
const enum sp_parity sp_sp_parity = voltronic_dev_serial_parity(parity);
|
||||
|
||||
if ((sp_data_bits == -1) ||
|
||||
(sp_data_bits == -1) ||
|
||||
(sp_stop_bits == -1) ||
|
||||
(sp_sp_parity == SP_PARITY_INVALID)) {
|
||||
|
||||
SET_INVALID_INPUT();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
struct sp_port_config *config;
|
||||
|
||||
SET_LAST_ERROR(0);
|
||||
if (sp_new_config(&config) == SP_OK) {
|
||||
SET_LAST_ERROR(0);
|
||||
if (sp_get_config(port, config) == SP_OK) {
|
||||
SET_LAST_ERROR(0);
|
||||
if ((sp_set_config_baudrate (config, sp_baud_rate) == SP_OK) &&
|
||||
(sp_set_config_bits(config, sp_data_bits) == SP_OK) &&
|
||||
(sp_set_config_stopbits(config, sp_stop_bits) == SP_OK) &&
|
||||
(sp_set_config_parity(config, sp_sp_parity) == SP_OK)) {
|
||||
|
||||
SET_LAST_ERROR(0);
|
||||
if (sp_set_config(port, config) == SP_OK) {
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const last_error_t last_error = GET_LAST_ERROR();
|
||||
sp_free_config(config);
|
||||
SET_LAST_ERROR(last_error);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
37
libvoltronic/voltronic_dev_usb.h
Normal file
37
libvoltronic/voltronic_dev_usb.h
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __VOLTRONIC__DEV__USB__H__
|
||||
#define __VOLTRONIC__DEV__USB__H__
|
||||
|
||||
#include "voltronic_dev.h"
|
||||
|
||||
/**
|
||||
* Create an opaque pointer to a voltronic device connected over USB
|
||||
*
|
||||
* vendor_id - Device vendor id to search for. ie. 0x0665
|
||||
* product_id - Device product id to search for. ie. 0x5161
|
||||
*
|
||||
* Returns an opaque pointer to a voltronic device or 0 if an error occurred
|
||||
*
|
||||
* Function sets errno (POSIX)/LastError (Windows) to approriate error on failure
|
||||
*/
|
||||
voltronic_dev_t voltronic_usb_create(
|
||||
const unsigned int vendor_id,
|
||||
const unsigned int product_id);
|
||||
|
||||
#endif
|
106
libvoltronic/voltronic_dev_usb_hidapi.c
Normal file
106
libvoltronic/voltronic_dev_usb_hidapi.c
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (C) 2019 Johan van der Vyver
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
#include "voltronic_dev_impl.h"
|
||||
#include "voltronic_dev_usb.h"
|
||||
#include "hidapi.h"
|
||||
|
||||
#define HID_REPORT_SIZE 8
|
||||
|
||||
#define VOLTRONIC_DEV_USB(_impl_ptr_) \
|
||||
((hid_device*) (_impl_ptr_))
|
||||
|
||||
#define GET_REPORT_SIZE(_val_) \
|
||||
(((_val_) > HID_REPORT_SIZE) ? HID_REPORT_SIZE : (_val_))
|
||||
|
||||
static inline void voltronic_usb_init_hidapi(void);
|
||||
|
||||
voltronic_dev_t voltronic_usb_create(
|
||||
const unsigned int vendor_id,
|
||||
const unsigned int product_id) {
|
||||
|
||||
voltronic_usb_init_hidapi();
|
||||
|
||||
SET_LAST_ERROR(0);
|
||||
hid_device* dev = hid_open(
|
||||
(unsigned short) vendor_id,
|
||||
(unsigned short) product_id,
|
||||
(const wchar_t*) 0);
|
||||
|
||||
if (dev != 0) {
|
||||
SET_LAST_ERROR(0);
|
||||
return voltronic_dev_internal_create((void*) dev);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int voltronic_dev_impl_read(
|
||||
void* impl_ptr,
|
||||
char* buffer,
|
||||
const size_t buffer_size,
|
||||
const unsigned int timeout_milliseconds) {
|
||||
|
||||
SET_LAST_ERROR(0);
|
||||
return hid_read_timeout(
|
||||
VOLTRONIC_DEV_USB(impl_ptr),
|
||||
(unsigned char*) buffer,
|
||||
GET_REPORT_SIZE(buffer_size),
|
||||
(int) timeout_milliseconds);
|
||||
}
|
||||
|
||||
inline int voltronic_dev_impl_write(
|
||||
void* impl_ptr,
|
||||
const char* buffer,
|
||||
const size_t buffer_size,
|
||||
unsigned int timeout_milliseconds) {
|
||||
|
||||
++timeout_milliseconds; // stop unused warnings
|
||||
const int write_size = GET_REPORT_SIZE(buffer_size);
|
||||
unsigned char write_buffer[HID_REPORT_SIZE + 1] = { 0 };
|
||||
COPY_MEMORY(&write_buffer[1], buffer, write_size);
|
||||
|
||||
SET_LAST_ERROR(0);
|
||||
const int bytes_written = hid_write(
|
||||
VOLTRONIC_DEV_USB(impl_ptr),
|
||||
(const unsigned char*) write_buffer,
|
||||
(size_t) (HID_REPORT_SIZE + 1));
|
||||
|
||||
return GET_REPORT_SIZE(bytes_written);
|
||||
}
|
||||
|
||||
inline int voltronic_dev_impl_close(void* impl_ptr) {
|
||||
hid_close(VOLTRONIC_DEV_USB(impl_ptr));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline void voltronic_usb_exit_hidapi(void) {
|
||||
hid_exit();
|
||||
}
|
||||
|
||||
static inline void voltronic_usb_init_hidapi(void) {
|
||||
static int hidapi_init_complete = 0;
|
||||
|
||||
if (hidapi_init_complete == 0) {
|
||||
if (hid_init() == 0) {
|
||||
atexit(voltronic_usb_exit_hidapi);
|
||||
hidapi_init_complete = 1;
|
||||
}
|
||||
}
|
||||
}
|
505
p18.h
Normal file
505
p18.h
Normal file
@ -0,0 +1,505 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ISV_P18_H
|
||||
#define ISV_P18_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "util.h"
|
||||
|
||||
#define P18_QUERY_CMDS_ENUM_OFFSET 1000
|
||||
#define P18_SET_CMDS_ENUM_OFFSET 1100
|
||||
|
||||
#define MK_P18_UNPACK_FN_NAME(msg_type) p18_unpack_ ## msg_type ## _msg
|
||||
#define MK_P18_PARSE_CB_FN_NAME(msg_type) p18_parse_cb_ ## msg_type ## _msg
|
||||
#define MK_P18_MSG_T(msg_type) p18_ ## msg_type ## _msg_t
|
||||
|
||||
#define P18_MSG_T(msg_type) MK_P18_MSG_T(msg_type)
|
||||
#define P18_UNPACK_FN_NAME(msg_type) MK_P18_UNPACK_FN_NAME(msg_type)
|
||||
#define P18_PARSE_CB_FN_NAME(msg_type) MK_P18_PARSE_CB_FN_NAME(msg_type)
|
||||
|
||||
#define P18_UNPACK_FN(msg_type) \
|
||||
P18_MSG_T(msg_type) P18_UNPACK_FN_NAME(msg_type)(const char *data)
|
||||
|
||||
#define P18_PARSE_CB_FN(msg_type) \
|
||||
static void P18_PARSE_CB_FN_NAME(msg_type)(const char *value, \
|
||||
size_t value_len, \
|
||||
unsigned int index, \
|
||||
void *message_ptr)
|
||||
|
||||
#define P18_PARSE_LIST_AND_RETURN(msg_type, exp_items_count) \
|
||||
P18_MSG_T(msg_type) m = {0}; \
|
||||
p18_parse_list(data, &m, (exp_items_count), P18_PARSE_CB_FN_NAME(msg_type)); \
|
||||
return m;
|
||||
|
||||
#define P18_EXPECT_LISTITEM_LENGTH(len) \
|
||||
p18_expect_listitem_length((const char *)__func__, index, (len), value_len)
|
||||
|
||||
typedef void (*p18_parse_cb_t)(const char *, size_t, unsigned int, void *);
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* Commands list */
|
||||
|
||||
enum p18_query_command {
|
||||
P18_QUERY_PROTOCOL_ID = P18_QUERY_CMDS_ENUM_OFFSET,
|
||||
P18_QUERY_CURRENT_TIME,
|
||||
P18_QUERY_TOTAL_GENERATED,
|
||||
P18_QUERY_YEAR_GENERATED,
|
||||
P18_QUERY_MONTH_GENERATED,
|
||||
P18_QUERY_DAY_GENERATED,
|
||||
P18_QUERY_SERIES_NUMBER,
|
||||
P18_QUERY_CPU_VERSION,
|
||||
P18_QUERY_RATED_INFORMATION,
|
||||
P18_QUERY_GENERAL_STATUS,
|
||||
P18_QUERY_WORKING_MODE,
|
||||
P18_QUERY_FAULTS_WARNINGS,
|
||||
P18_QUERY_FLAGS_STATUSES,
|
||||
P18_QUERY_DEFAULTS,
|
||||
P18_QUERY_MAX_CHARGING_CURRENT_SELECTABLE_VALUES,
|
||||
P18_QUERY_MAX_AC_CHARGING_CURRENT_SELECTABLE_VALUES,
|
||||
P18_QUERY_PARALLEL_RATED_INFORMATION,
|
||||
P18_QUERY_PARALLEL_GENERAL_STATUS,
|
||||
P18_QUERY_AC_CHARGE_TIME_BUCKET,
|
||||
P18_QUERY_AC_SUPPLY_LOAD_TIME_BUCKET,
|
||||
};
|
||||
|
||||
enum p18_set_command {
|
||||
P18_SET_LOADS = P18_SET_CMDS_ENUM_OFFSET,
|
||||
P18_SET_FLAG,
|
||||
P18_SET_DEFAULTS,
|
||||
P18_SET_BAT_MAX_CHARGE_CURRENT,
|
||||
P18_SET_BAT_MAX_AC_CHARGE_CURRENT,
|
||||
P18_SET_AC_OUTPUT_FREQ,
|
||||
P18_SET_BAT_MAX_CHARGE_VOLTAGE,
|
||||
P18_SET_AC_OUTPUT_RATED_VOLTAGE,
|
||||
P18_SET_OUTPUT_SOURCE_PRIORITY,
|
||||
P18_SET_BAT_CHARGING_THRESHOLDS_WHEN_UTILITY_AVAIL, /* Battery re-charging and re-discharing voltage when utility is available */
|
||||
P18_SET_CHARGING_SOURCE_PRIORITY,
|
||||
P18_SET_SOLAR_POWER_PRIORITY,
|
||||
P18_SET_AC_INPUT_VOLTAGE_RANGE,
|
||||
P18_SET_BAT_TYPE,
|
||||
P18_SET_OUTPUT_MODEL,
|
||||
P18_SET_BAT_CUTOFF_VOLTAGE,
|
||||
P18_SET_SOLAR_CONFIG,
|
||||
P18_SET_CLEAR_GENERATED,
|
||||
P18_SET_DATE_TIME,
|
||||
P18_SET_AC_CHARGE_TIME_BUCKET,
|
||||
P18_SET_AC_SUPPLY_LOAD_TIME_BUCKET,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
P18_BT_AGM = 0,
|
||||
P18_BT_FLOODED = 1,
|
||||
P18_BT_USER = 2,
|
||||
} p18_battery_type_t;
|
||||
|
||||
typedef enum {
|
||||
P18_IVR_APPLIANCE = 0,
|
||||
P18_IVR_UPS = 1,
|
||||
} p18_input_voltage_range_t;
|
||||
|
||||
typedef enum {
|
||||
P18_OSP_SOLAR_UTILITY_BATTERY = 0,
|
||||
P18_OSP_SOLAR_BATTERY_UTILITY = 1,
|
||||
} p18_output_source_priority_t;
|
||||
|
||||
typedef enum {
|
||||
P18_CSP_SOLAR_FIRST = 0,
|
||||
P18_CSP_SOLAR_AND_UTILITY = 1,
|
||||
P18_CSP_SOLAR_ONLY = 2,
|
||||
} p18_charger_source_priority_t;
|
||||
|
||||
typedef enum {
|
||||
P18_MT_OFF_GRID_TIE = 0,
|
||||
P18_MT_GRID_TIE = 1,
|
||||
} p18_machine_type_t;
|
||||
|
||||
typedef enum {
|
||||
P18_TRANSFORMERLESS = 0,
|
||||
P18_TRANSFORMER = 1,
|
||||
} p18_topology_t;
|
||||
|
||||
typedef enum {
|
||||
P18_OMS_SINGLE_MODULE = 0,
|
||||
P18_OMS_PARALLEL_OUTPUT = 1,
|
||||
P18_OMS_PHASE1_OF_3PHASE_OUTPUT = 2,
|
||||
P18_OMS_PHASE2_OF_3PHASE_OUTPUT = 3,
|
||||
P18_OMS_PHASE3_OF_3PHASE_OUTPUT = 4,
|
||||
} p18_output_model_setting_t;
|
||||
|
||||
typedef enum {
|
||||
P18_SPP_BATTERY_LOAD_UTILITY = 0,
|
||||
P18_SPP_LOAD_BATTERY_UTILITY = 1,
|
||||
} p18_solar_power_priority_t;
|
||||
|
||||
typedef enum {
|
||||
P18_MPPT_CS_ABNORMAL = 0,
|
||||
P18_MPPT_CS_NOT_CHARGED = 1,
|
||||
P18_MPPT_CS_CHARGED = 2,
|
||||
} p18_mppt_charger_status_t;
|
||||
|
||||
typedef enum {
|
||||
P18_BPD_DONOTHING = 0,
|
||||
P18_BPD_CHARGE = 1,
|
||||
P18_BPD_DISCHARGE = 2,
|
||||
} p18_battery_power_direction_t;
|
||||
|
||||
typedef enum {
|
||||
P18_DAPD_DONOTHING = 0,
|
||||
P18_DAPD_AC_DC = 1,
|
||||
P18_DAPD_DC_AC = 2,
|
||||
} p18_dc_ac_power_direction_t;
|
||||
|
||||
typedef enum {
|
||||
P18_LPD_DONOTHING = 0,
|
||||
P18_LPD_INPUT = 1,
|
||||
P18_LPD_OUTPUT = 2,
|
||||
} p18_line_power_direction_t;
|
||||
|
||||
typedef enum {
|
||||
P18_WM_POWER_ON_MODE = 0,
|
||||
P18_WM_STANDBY_MODE = 1,
|
||||
P18_WM_BYPASS_MODE = 2,
|
||||
P18_WM_BATTERY_MODE = 3,
|
||||
P18_WM_FAULT_MODE = 4,
|
||||
P18_WM_HYBRID_MODE = 5,
|
||||
} p18_working_mode_t;
|
||||
|
||||
typedef enum {
|
||||
P18_PCS_NON_EXISTENT = 0,
|
||||
P18_PCS_EXISTENT = 1,
|
||||
} p18_parallel_id_connection_status_t;
|
||||
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* Message structs */
|
||||
|
||||
typedef struct {
|
||||
unsigned int id;
|
||||
} p18_protocol_id_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int year;
|
||||
unsigned short month;
|
||||
unsigned short day;
|
||||
unsigned short hour;
|
||||
unsigned short minute;
|
||||
unsigned short second;
|
||||
} p18_current_time_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned long kwh;
|
||||
} p18_total_generated_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned long kwh;
|
||||
} p18_year_generated_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned long kwh;
|
||||
} p18_month_generated_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned long kwh;
|
||||
} p18_day_generated_msg_t;
|
||||
|
||||
typedef struct {
|
||||
short length;
|
||||
char id[32];
|
||||
} p18_series_number_msg_t;
|
||||
|
||||
typedef struct {
|
||||
char main_cpu_version[6];
|
||||
char slave1_cpu_version[6];
|
||||
char slave2_cpu_version[6];
|
||||
} p18_cpu_version_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int ac_input_rating_voltage; /* unit: 0.1V */
|
||||
unsigned int ac_input_rating_current; /* unit: 0.1A */
|
||||
unsigned int ac_output_rating_voltage; /* unit: 0.1A */
|
||||
unsigned int ac_output_rating_freq; /* unit: 0.1Hz */
|
||||
unsigned int ac_output_rating_current; /* unit: 0.1A */
|
||||
unsigned int ac_output_rating_apparent_power; /* unit: VA */
|
||||
unsigned int ac_output_rating_active_power; /* unit: W */
|
||||
unsigned int battery_rating_voltage; /* unit: 0.1V */
|
||||
unsigned int battery_recharge_voltage; /* unit: 0.1V */
|
||||
unsigned int battery_redischarge_voltage; /* unit: 0.1V */
|
||||
unsigned int battery_under_voltage; /* unit: 0.1V */
|
||||
unsigned int battery_bulk_voltage; /* unit: 0.1V */
|
||||
unsigned int battery_float_voltage; /* unit: 0.1V */
|
||||
p18_battery_type_t battery_type;
|
||||
unsigned int max_ac_charging_current; /* unit: A */
|
||||
unsigned int max_charging_current; /* unit: A */
|
||||
p18_input_voltage_range_t input_voltage_range;
|
||||
p18_output_source_priority_t output_source_priority;
|
||||
p18_charger_source_priority_t charger_source_priority;
|
||||
unsigned int parallel_max_num;
|
||||
p18_machine_type_t machine_type;
|
||||
p18_topology_t topology;
|
||||
p18_output_model_setting_t output_model_setting;
|
||||
p18_solar_power_priority_t solar_power_priority;
|
||||
char mppt[4];
|
||||
} p18_rated_information_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int grid_voltage; /* unit: 0.1V */
|
||||
unsigned int grid_freq; /* unit: 0.1Hz */
|
||||
unsigned int ac_output_voltage; /* unit: 0.1V */
|
||||
unsigned int ac_output_freq; /* unit: 0.1Hz */
|
||||
unsigned int ac_output_apparent_power; /* unit: VA */
|
||||
unsigned int ac_output_active_power; /* unit: W */
|
||||
unsigned int output_load_percent; /* unit: % */
|
||||
unsigned int battery_voltage; /* unit: 0.1V */
|
||||
unsigned int battery_voltage_scc; /* unit: 0.1V */
|
||||
unsigned int battery_voltage_scc2; /* unit: 0.1V */
|
||||
unsigned int battery_discharge_current; /* unit: A */
|
||||
unsigned int battery_charging_current; /* unit: A */
|
||||
unsigned int battery_capacity; /* unit: % */
|
||||
unsigned int inverter_heat_sink_temp; /* unit: C */
|
||||
unsigned int mppt1_charger_temp; /* unit: C */
|
||||
unsigned int mppt2_charger_temp; /* unit: C */
|
||||
unsigned int pv1_input_power; /* unit: W */
|
||||
unsigned int pv2_input_power; /* unit: W */
|
||||
unsigned int pv1_input_voltage; /* unit: 0.1V */
|
||||
unsigned int pv2_input_voltage; /* unit: 0.1V */
|
||||
bool settings_values_changed; /* inverter returns:
|
||||
0: nothing changed
|
||||
1: something changed */
|
||||
p18_mppt_charger_status_t mppt1_charger_status;
|
||||
p18_mppt_charger_status_t mppt2_charger_status;
|
||||
bool load_connected; /* inverter returns:
|
||||
0: disconnected
|
||||
1: connected */
|
||||
p18_battery_power_direction_t battery_power_direction;
|
||||
p18_dc_ac_power_direction_t dc_ac_power_direction;
|
||||
p18_line_power_direction_t line_power_direction;
|
||||
unsigned int local_parallel_id; /* 0 .. (parallel number - 1) */
|
||||
} p18_general_status_msg_t;
|
||||
|
||||
typedef struct {
|
||||
p18_working_mode_t mode;
|
||||
} p18_working_mode_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int fault_code;
|
||||
bool line_fail;
|
||||
bool output_circuit_short;
|
||||
bool inverter_over_temperature;
|
||||
bool fan_lock;
|
||||
bool battery_voltage_high;
|
||||
bool battery_low;
|
||||
bool battery_under;
|
||||
bool over_load;
|
||||
bool eeprom_fail;
|
||||
bool power_limit;
|
||||
bool pv1_voltage_high;
|
||||
bool pv2_voltage_high;
|
||||
bool mppt1_overload_warning;
|
||||
bool mppt2_overload_warning;
|
||||
bool battery_too_low_to_charge_for_scc1;
|
||||
bool battery_too_low_to_charge_for_scc2;
|
||||
} p18_faults_warnings_msg_t;
|
||||
|
||||
typedef struct {
|
||||
bool buzzer;
|
||||
bool overload_bypass;
|
||||
bool lcd_escape_to_default_page_after_1min_timeout;
|
||||
bool overload_restart;
|
||||
bool over_temp_restart;
|
||||
bool backlight_on;
|
||||
bool alarm_on_primary_source_interrupt;
|
||||
bool fault_code_record;
|
||||
char reserved;
|
||||
} p18_flags_statuses_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned int ac_output_voltage; /* unit: 0.1V */
|
||||
unsigned int ac_output_freq;
|
||||
p18_input_voltage_range_t ac_input_voltage_range;
|
||||
unsigned int battery_under_voltage;
|
||||
unsigned int charging_float_voltage;
|
||||
unsigned int charging_bulk_voltage;
|
||||
unsigned int battery_recharge_voltage;
|
||||
unsigned int battery_redischarge_voltage;
|
||||
unsigned int max_charging_current;
|
||||
unsigned int max_ac_charging_current;
|
||||
p18_battery_type_t battery_type;
|
||||
p18_output_source_priority_t output_source_priority;
|
||||
p18_charger_source_priority_t charger_source_priority;
|
||||
p18_solar_power_priority_t solar_power_priority;
|
||||
p18_machine_type_t machine_type;
|
||||
p18_output_model_setting_t output_model_setting;
|
||||
bool flag_buzzer;
|
||||
bool flag_overload_restart;
|
||||
bool flag_over_temp_restart;
|
||||
bool flag_backlight_on;
|
||||
bool flag_alarm_on_primary_source_interrupt;
|
||||
bool flag_fault_code_record;
|
||||
bool flag_overload_bypass;
|
||||
bool flag_lcd_escape_to_default_page_after_1min_timeout;
|
||||
} p18_defaults_msg_t;
|
||||
|
||||
typedef struct {
|
||||
size_t len;
|
||||
int amps[32];
|
||||
} p18_max_charging_current_selectable_values_msg_t;
|
||||
|
||||
typedef struct {
|
||||
size_t len;
|
||||
int amps[32];
|
||||
} p18_max_ac_charging_current_selectable_values_msg_t;
|
||||
|
||||
typedef struct {
|
||||
p18_parallel_id_connection_status_t parallel_id_connection_status;
|
||||
int serial_number_valid_length;
|
||||
char serial_number[32];
|
||||
p18_charger_source_priority_t charger_source_priority;
|
||||
unsigned int max_ac_charging_current; /* unit: A */
|
||||
unsigned int max_charging_current; /* unit: A */
|
||||
p18_output_model_setting_t output_model_setting;
|
||||
} p18_parallel_rated_information_msg_t;
|
||||
|
||||
typedef struct {
|
||||
p18_parallel_id_connection_status_t parallel_id_connection_status;
|
||||
p18_working_mode_t work_mode;
|
||||
unsigned int fault_code;
|
||||
unsigned int grid_voltage; /* unit: 0.1V */
|
||||
unsigned int grid_freq; /* unit: 0.1Hz */
|
||||
unsigned int ac_output_voltage; /* unit: 0.1V */
|
||||
unsigned int ac_output_freq; /* unit: 0.1Hz */
|
||||
unsigned int ac_output_apparent_power; /* unit: VA */
|
||||
unsigned int ac_output_active_power; /* unit: W */
|
||||
unsigned int total_ac_output_apparent_power; /* unit: VA */
|
||||
unsigned int total_ac_output_active_power; /* unit: W */
|
||||
unsigned int output_load_percent; /* unit: % */
|
||||
unsigned int total_output_load_percent; /* unit: % */
|
||||
unsigned int battery_voltage; /* unit: 0.1V */
|
||||
unsigned int battery_discharge_current; /* unit: A */
|
||||
unsigned int battery_charging_current; /* unit: A */
|
||||
unsigned int total_battery_charging_current; /* unit: A */
|
||||
unsigned int battery_capacity; /* unit: % */
|
||||
unsigned int pv1_input_power; /* unit: W */
|
||||
unsigned int pv2_input_power; /* unit: W */
|
||||
unsigned int pv1_input_voltage; /* unit: 0.1V */
|
||||
unsigned int pv2_input_voltage; /* unit: 0.1V */
|
||||
p18_mppt_charger_status_t mppt1_charger_status;
|
||||
p18_mppt_charger_status_t mppt2_charger_status;
|
||||
bool load_connected; /* inverter returns:
|
||||
0: disconnected
|
||||
1: connected */
|
||||
p18_battery_power_direction_t battery_power_direction;
|
||||
p18_dc_ac_power_direction_t dc_ac_power_direction;
|
||||
p18_line_power_direction_t line_power_direction;
|
||||
unsigned int max_temp; /* unit: C */
|
||||
} p18_parallel_general_status_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned short start_h;
|
||||
unsigned short start_m;
|
||||
unsigned short end_h;
|
||||
unsigned short end_m;
|
||||
} p18_ac_charge_time_bucket_msg_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned short start_h;
|
||||
unsigned short start_m;
|
||||
unsigned short end_h;
|
||||
unsigned short end_m;
|
||||
} p18_ac_supply_load_time_bucket_msg_t;
|
||||
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* Some constants and allowed values */
|
||||
|
||||
typedef struct {
|
||||
unsigned int id;
|
||||
char message[64];
|
||||
} p18_fault_code_list_item_t;
|
||||
|
||||
typedef struct {
|
||||
char *key;
|
||||
char *p18_key;
|
||||
char *title;
|
||||
} p18_flag_printable_list_item_t;
|
||||
extern const p18_flag_printable_list_item_t p18_flags_printable_list[9];
|
||||
|
||||
extern const int p18_ac_output_rated_voltages[5];
|
||||
|
||||
extern const char *p18_battery_util_recharging_voltages_12v_unit[8];
|
||||
extern const char *p18_battery_util_recharging_voltages_24v_unit[8];
|
||||
extern const char *p18_battery_util_recharging_voltages_48v_unit[8];
|
||||
|
||||
extern const char *p18_battery_util_redischarging_voltages_12v_unit[12];
|
||||
extern const char *p18_battery_util_redischarging_voltages_24v_unit[12];
|
||||
extern const char *p18_battery_util_redischarging_voltages_48v_unit[12];
|
||||
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* Common methods */
|
||||
|
||||
bool p18_build_command(int command, const char **args, size_t args_size, char *buf);
|
||||
bool p18_validate_query_response(const char *buf, size_t size, size_t *data_size);
|
||||
bool p18_set_result(const char *buf, size_t size);
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* Command-specific methods */
|
||||
|
||||
P18_UNPACK_FN(protocol_id);
|
||||
P18_UNPACK_FN(current_time);
|
||||
P18_UNPACK_FN(total_generated);
|
||||
P18_UNPACK_FN(year_generated);
|
||||
P18_UNPACK_FN(month_generated);
|
||||
P18_UNPACK_FN(day_generated);
|
||||
P18_UNPACK_FN(series_number);
|
||||
P18_UNPACK_FN(cpu_version);
|
||||
P18_UNPACK_FN(rated_information);
|
||||
P18_UNPACK_FN(general_status);
|
||||
P18_UNPACK_FN(working_mode);
|
||||
P18_UNPACK_FN(faults_warnings);
|
||||
P18_UNPACK_FN(flags_statuses);
|
||||
P18_UNPACK_FN(defaults);
|
||||
P18_UNPACK_FN(max_charging_current_selectable_values);
|
||||
P18_UNPACK_FN(max_ac_charging_current_selectable_values);
|
||||
P18_UNPACK_FN(parallel_rated_information);
|
||||
P18_UNPACK_FN(parallel_general_status);
|
||||
P18_UNPACK_FN(ac_charge_time_bucket);
|
||||
P18_UNPACK_FN(ac_supply_load_time_bucket);
|
||||
|
||||
|
||||
/* ------------------------------------------ */
|
||||
/* Label getters */
|
||||
|
||||
const char *p18_battery_type_label(p18_battery_type_t type);
|
||||
const char *p18_input_voltage_range_label(p18_input_voltage_range_t range);
|
||||
const char *p18_output_source_priority_label(p18_output_source_priority_t priority);
|
||||
const char *p18_charge_source_priority_label(p18_charger_source_priority_t priority);
|
||||
const char *p18_machine_type_label(p18_machine_type_t type);
|
||||
const char *p18_topology_label(p18_topology_t topology);
|
||||
const char *p18_output_model_setting_label(p18_output_model_setting_t setting);
|
||||
const char *p18_solar_power_priority_label(p18_solar_power_priority_t priority);
|
||||
const char *p18_mppt_charger_status_label(p18_mppt_charger_status_t status);
|
||||
const char *p18_battery_power_direction_label(p18_battery_power_direction_t direction);
|
||||
const char *p18_dc_ac_power_direction_label(p18_dc_ac_power_direction_t direction);
|
||||
const char *p18_line_power_direction_label(p18_line_power_direction_t direction);
|
||||
const char *p18_working_mode_label(p18_working_mode_t mode);
|
||||
const char *p18_fault_code_label(unsigned int code);
|
||||
const char *p18_parallel_connection_status_label(p18_parallel_id_connection_status_t status);
|
||||
|
||||
#endif //ISV_P18_H
|
82
print.h
Normal file
82
print.h
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ISV_PRINT_H
|
||||
#define ISV_PRINT_H
|
||||
|
||||
#include "p18.h"
|
||||
#include "variant.h"
|
||||
|
||||
#define MAKE_PRINT_FN_NAME(msg_type) print_ ## msg_type ## _msg
|
||||
#define PRINT_FN_NAME(msg_type) MAKE_PRINT_FN_NAME(msg_type)
|
||||
|
||||
#define PRINT_FN(msg_type) \
|
||||
void PRINT_FN_NAME(msg_type)(const P18_MSG_T(msg_type) *m, print_format_t format)
|
||||
|
||||
typedef enum {
|
||||
PRINT_UNIT_V = 1,
|
||||
PRINT_UNIT_A,
|
||||
PRINT_UNIT_WH,
|
||||
PRINT_UNIT_KWH,
|
||||
PRINT_UNIT_VA,
|
||||
PRINT_UNIT_HZ,
|
||||
PRINT_UNIT_PERCENTAGE,
|
||||
PRINT_UNIT_CELSIUS,
|
||||
} print_unit_t;
|
||||
|
||||
typedef enum {
|
||||
PRINT_FORMAT_TABLE,
|
||||
PRINT_FORMAT_PARSABLE_TABLE,
|
||||
PRINT_FORMAT_JSON,
|
||||
PRINT_FORMAT_JSON_W_UNITS,
|
||||
} print_format_t;
|
||||
|
||||
typedef struct {
|
||||
char *key;
|
||||
char *title;
|
||||
variant_t value;
|
||||
short precision;
|
||||
print_unit_t unit;
|
||||
} print_item_t;
|
||||
|
||||
void print_json(print_item_t *items, size_t size, bool with_units);
|
||||
void print_set_result(bool success, print_format_t format);
|
||||
bool print_is_json_format(print_format_t f);
|
||||
|
||||
PRINT_FN(protocol_id);
|
||||
PRINT_FN(current_time);
|
||||
PRINT_FN(total_generated);
|
||||
PRINT_FN(year_generated);
|
||||
PRINT_FN(month_generated);
|
||||
PRINT_FN(day_generated);
|
||||
PRINT_FN(series_number);
|
||||
PRINT_FN(cpu_version);
|
||||
PRINT_FN(rated_information);
|
||||
PRINT_FN(general_status);
|
||||
PRINT_FN(working_mode);
|
||||
PRINT_FN(faults_warnings);
|
||||
PRINT_FN(flags_statuses);
|
||||
PRINT_FN(defaults);
|
||||
PRINT_FN(max_charging_current_selectable_values);
|
||||
PRINT_FN(max_ac_charging_current_selectable_values);
|
||||
PRINT_FN(parallel_rated_information);
|
||||
PRINT_FN(parallel_general_status);
|
||||
PRINT_FN(ac_charge_time_bucket);
|
||||
PRINT_FN(ac_supply_load_time_bucket);
|
||||
|
||||
#endif //ISV_PRINT_H
|
115
util.c
Normal file
115
util.c
Normal file
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* 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 <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
#define HEXDUMP_COLS 8
|
||||
|
||||
/* based on: https://gist.github.com/richinseattle/c527a3acb6f152796a580401057c78b4 */
|
||||
void hexdump(void *mem, unsigned int len)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
for (i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {
|
||||
/* Print offset */
|
||||
if (i % HEXDUMP_COLS == 0)
|
||||
printf("0x%06x: ", i);
|
||||
|
||||
if (i < len)
|
||||
/* Print hex data */
|
||||
printf("%02x ", 0xFF & ((char *) mem)[i]);
|
||||
else
|
||||
/* End of block, just aligning for ASCII dump */
|
||||
printf(" ");
|
||||
|
||||
/* Print ASCII dump */
|
||||
if (i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {
|
||||
for (j = i - (HEXDUMP_COLS - 1); j <= i; j++) {
|
||||
if (j >= len)
|
||||
/* end of block, not really printing */
|
||||
putchar(' ');
|
||||
else if (isprint(((char *) mem)[j]))
|
||||
/* printable char */
|
||||
putchar(0xFF & ((char *) mem)[j]);
|
||||
else
|
||||
/* other char */
|
||||
putchar('.');
|
||||
}
|
||||
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* dst buffer's length must be at least len+1 */
|
||||
void substr_copy(char *dst, const char *src, int len)
|
||||
{
|
||||
strncpy(dst, src, len);
|
||||
dst[len] = '\0';
|
||||
}
|
||||
|
||||
bool isnumeric(const char *s)
|
||||
{
|
||||
size_t len = strlen(s);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (!isdigit(s[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isdatevalid(const int y, const int m, const int d)
|
||||
{
|
||||
/* primitive out of range checks */
|
||||
if (y < 2000 || y > 2099)
|
||||
return false;
|
||||
|
||||
if (d < 1 || d > 31)
|
||||
return false;
|
||||
|
||||
if (m < 1 || m > 12)
|
||||
return false;
|
||||
|
||||
/* some more clever date validity checks */
|
||||
if ((m == 4 || m == 6 || m == 9 || m == 11) && d == 31)
|
||||
return false;
|
||||
|
||||
/* and finally a february check */
|
||||
/* i always wondered, when do people born at feb 29 celebrate their bday? */
|
||||
return m != 2 || ((y % 4 != 0 && d <= 28) || (y % 4 == 0 && d <= 29));
|
||||
}
|
||||
|
||||
bool instrarray(const char *needle, const char **list, size_t list_size, int *index)
|
||||
{
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < list_size; i++) {
|
||||
/* LOG("%s: comparing %s == %s\n", __func__, list[i], needle); */
|
||||
if (!strcmp(list[i], needle)) {
|
||||
found = true;
|
||||
if (index != NULL)
|
||||
*index = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
57
util.h
Normal file
57
util.h
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ISV_UTIL_H
|
||||
#define ISV_UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
extern bool g_verbose;
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
#define UNUSED(x) (void)(x)
|
||||
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
||||
|
||||
#define LOG(f_, ...) \
|
||||
if (g_verbose) fprintf(stderr, (f_), ##__VA_ARGS__)
|
||||
|
||||
#define ERROR(f_, ...) \
|
||||
fprintf(stderr, (f_), ##__VA_ARGS__)
|
||||
|
||||
#define HEXDUMP(mem, len) \
|
||||
if (g_verbose) hexdump((mem), (len))
|
||||
|
||||
#define FOREACH_OFSIZE(item, array, len) \
|
||||
for (int loop_keep = 1, loop_count = 0; \
|
||||
loop_keep && loop_count != (int)(len); \
|
||||
loop_keep = !loop_keep, loop_count++) \
|
||||
for (item = (array) + loop_count; loop_keep; loop_keep = !loop_keep)
|
||||
|
||||
#define FOREACH(item, array) \
|
||||
FOREACH_OFSIZE(item, array, ARRAY_SIZE(array))
|
||||
|
||||
void hexdump(void *mem, unsigned int len);
|
||||
void substr_copy(char *dst, const char *src, int len);
|
||||
bool isnumeric(const char *s);
|
||||
bool isdatevalid(int y, int m, int d);
|
||||
bool instrarray(const char *needle, const char **list, size_t list_size, int *index);
|
||||
|
||||
#endif //ISV_UTIL_H
|
86
variant.c
Normal file
86
variant.c
Normal file
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 <stdio.h>
|
||||
#include "variant.h"
|
||||
|
||||
variant_t variant_double(double d)
|
||||
{
|
||||
variant_t v;
|
||||
v.type = VARIANT_TYPE_DOUBLE;
|
||||
v.d = d;
|
||||
return v;
|
||||
}
|
||||
|
||||
variant_t variant_long(long l)
|
||||
{
|
||||
variant_t v;
|
||||
v.type = VARIANT_TYPE_LONG;
|
||||
v.l = l;
|
||||
return v;
|
||||
}
|
||||
|
||||
variant_t variant_bool(bool b)
|
||||
{
|
||||
variant_t v;
|
||||
v.type = VARIANT_TYPE_BOOL;
|
||||
v.b = b;
|
||||
return v;
|
||||
}
|
||||
|
||||
variant_t variant_flag(bool b)
|
||||
{
|
||||
variant_t v;
|
||||
v.type = VARIANT_TYPE_FLAG;
|
||||
v.b = b;
|
||||
return v;
|
||||
}
|
||||
|
||||
/* this just stores a pointer to a string, it doesn't copy the string itself */
|
||||
variant_t variant_string(const char *s)
|
||||
{
|
||||
variant_t v;
|
||||
v.type = VARIANT_TYPE_STRING;
|
||||
v.s = s;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline bool variant_is_string(variant_t v)
|
||||
{
|
||||
return v.type == VARIANT_TYPE_STRING;
|
||||
}
|
||||
|
||||
inline bool variant_is_long(variant_t v)
|
||||
{
|
||||
return v.type == VARIANT_TYPE_LONG;
|
||||
}
|
||||
|
||||
inline bool variant_is_bool(variant_t v)
|
||||
{
|
||||
return v.type == VARIANT_TYPE_BOOL;
|
||||
}
|
||||
|
||||
inline bool variant_is_flag(variant_t v)
|
||||
{
|
||||
return v.type == VARIANT_TYPE_FLAG;
|
||||
}
|
||||
|
||||
inline bool variant_is_double(variant_t v)
|
||||
{
|
||||
return v.type == VARIANT_TYPE_DOUBLE;
|
||||
}
|
54
variant.h
Normal file
54
variant.h
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef ISV_VARIANT_H
|
||||
#define ISV_VARIANT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
VARIANT_TYPE_STRING,
|
||||
VARIANT_TYPE_LONG,
|
||||
VARIANT_TYPE_DOUBLE,
|
||||
VARIANT_TYPE_BOOL,
|
||||
VARIANT_TYPE_FLAG,
|
||||
} variant_type_t;
|
||||
|
||||
typedef struct {
|
||||
variant_type_t type;
|
||||
double d;
|
||||
long l;
|
||||
bool b;
|
||||
const char *s;
|
||||
} variant_t;
|
||||
|
||||
variant_t variant_double(double d);
|
||||
variant_t variant_long(long l);
|
||||
variant_t variant_bool(bool b);
|
||||
variant_t variant_flag(bool b);
|
||||
variant_t variant_string(const char *s);
|
||||
|
||||
void variant_to_string(variant_t v, char *buf, size_t bufsize);
|
||||
|
||||
bool variant_is_string(variant_t v);
|
||||
bool variant_is_long(variant_t v);
|
||||
bool variant_is_bool(variant_t v);
|
||||
bool variant_is_flag(variant_t v);
|
||||
bool variant_is_double(variant_t v);
|
||||
|
||||
#endif //ISV_VARIANT_H
|
Loading…
x
Reference in New Issue
Block a user