-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from infertux/mqtt
Refactor to add MQTT integration
- Loading branch information
Showing
18 changed files
with
3,727 additions
and
352 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
--- | ||
BasedOnStyle: LLVM | ||
ColumnLimit: 120 | ||
ColumnLimit: 140 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
/deploy.sh | ||
/growatt | ||
/html/ | ||
/tests/mock-server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,29 @@ | ||
CC=clang | ||
RM=rm -f | ||
CFLAGS=$(shell pkg-config --cflags libbsd libmodbus) | ||
LIBS=$(shell pkg-config --libs libbsd libmodbus) -pthread | ||
|
||
SRCS=src/growatt.c src/*.h | ||
CC?=clang | ||
#CC?=gcc | ||
RM=rm -fv | ||
CFLAGS=$(shell pkg-config --cflags libbsd libconfig libmodbus libmosquitto) | ||
LIBS=$(shell pkg-config --libs libbsd libconfig libmodbus libmosquitto) -pthread | ||
SRCS=src/* | ||
TESTS=tests/*.c | ||
|
||
all: growatt | ||
|
||
doc: $(SRCS) | ||
doxygen .doxygen | ||
|
||
growatt: $(SRCS) | ||
$(CC) $(CFLAGS) $(LIBS) -Wall -Werror -pedantic -O3 -o growatt src/growatt.c | ||
$(CC) -v $(CFLAGS) $(LIBS) -Wall -Werror -O3 -o growatt src/*.c | ||
|
||
lint: | ||
clang-format --verbose --Werror -i --style=file src/* | ||
clang-tidy --checks='*,-altera-id-dependent-backward-branch,-altera-unroll-loops,-cert-err33-c,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-llvm-header-guard,-llvmlibc-restrict-system-libc-headers,-readability-function-cognitive-complexity' --format-style=llvm src/* -- $(CFLAGS) | ||
clang-format --verbose --Werror -i --style=file $(SRCS) $(TESTS) | ||
clang-tidy --checks='*,-altera-id-dependent-backward-branch,-altera-unroll-loops,-bugprone-assignment-in-if-condition,-cert-err33-c,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-cppcoreguidelines-avoid-magic-numbers,-llvm-header-guard,-llvmlibc-restrict-system-libc-headers,-readability-function-cognitive-complexity' --format-style=llvm $(SRCS) $(TESTS) -- $(CFLAGS) | ||
.PHONY: lint | ||
|
||
test: growatt $(TESTS) | ||
$(CC) -v $(shell pkg-config --libs --cflags libbsd libmodbus) -Wall -Werror -o tests/mock-server $(TESTS) | ||
timeout 30 mosquitto_sub -h test.mosquitto.org -p 1884 -u rw -P readwrite -t homeassistant/sensor/growatt/state -d & | ||
./tests/mock-server & | ||
./growatt config-example.conf || true | ||
|
||
clean: | ||
$(RM) growatt | ||
$(RM) growatt tests/mock-server |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
- add program option --verbose instead of compile-time LOG_VERBOSE define | ||
- test program with either --mqtt XOR --prometheus but not both | ||
- go through commented code | ||
- fix all lint warnings | ||
- use condition variables to exit all threads cleanly? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Block device path or host:port to connect to (required) | ||
# device_or_uri = "/dev/ttyUSB0" | ||
device_or_uri = "127.0.0.1:1502" | ||
|
||
// Prometheus config (optional block) | ||
prometheus = { | ||
port = 1234 | ||
} | ||
|
||
// MQTT config (optional block) | ||
mqtt = { | ||
host = "test.mosquitto.org" | ||
port = 1884 | ||
username = "wo" | ||
password = "writeonly" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
device_or_uri = "127.0.0.1:1502" | ||
prometheus = { | ||
port = 1234 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,152 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
|
||
#include <assert.h> | ||
#include <libconfig.h> | ||
#include <limits.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
|
||
#include "http.h" | ||
#include "log.h" | ||
|
||
#define MAX_PORT_NUMBER USHRT_MAX | ||
#include "mqtt.h" | ||
#include "prometheus.h" | ||
|
||
enum { | ||
MAX_DEVICES = 8, | ||
RADIX_DECIMAL = 10, | ||
STDC_VERSION_MIN = 201710L, | ||
}; | ||
|
||
int main(int argc, char *argv[]) { | ||
if (argc != 3) { | ||
fprintf(LOG_ERROR, "Usage: %s <device_id[,device2_id]> <port>\n", argv[0]); | ||
fprintf(LOG_ERROR, "Example: %s 1 1234\n", argv[0]); | ||
fprintf(LOG_ERROR, "Example: %s 1,2 1234\n", argv[0]); | ||
typedef struct __attribute__((aligned(64))) { | ||
const char *device_or_uri; | ||
prometheus_config prometheus_config; | ||
mqtt_config mqtt_config; | ||
} config; | ||
|
||
static int usage(char const program[static 1]) { | ||
fprintf(stderr, "Usage: %s <config_file>\n", program); | ||
fprintf(stderr, "Example: %s /etc/growatt-exporter.conf\n", program); | ||
return EXIT_FAILURE; | ||
} | ||
|
||
static int join_thread(thrd_t const *thread, char const label[static 1]) { | ||
int value = 0; | ||
int code = thrd_join(*thread, &value); | ||
|
||
if (code != thrd_success || value != EXIT_SUCCESS) { | ||
LOG(LOG_ERROR, "Thread %s failed with value %d (code = %d)", label, value, code); | ||
|
||
keep_running = 0; | ||
|
||
if (!strcmp(label, "MDBS")) { | ||
stop_prometheus_thread(); | ||
// kill(SIGTERM, 0); | ||
} | ||
} else { | ||
LOG(LOG_INFO, "Thread %s exited successfully", label); | ||
} | ||
|
||
return value; | ||
} | ||
|
||
/*static void sig_handler(int signal) | ||
{ | ||
LOG(LOG_INFO, "Got signal %d", signal); | ||
keep_running = 0; | ||
}*/ | ||
|
||
int parse_config(config *config, config_t *parser, char const *filename) { | ||
if (!config_read_file(parser, filename)) { | ||
LOG(LOG_ERROR, "%s:%d - %s\n", config_error_file(parser), config_error_line(parser), config_error_text(parser)); | ||
return EXIT_FAILURE; | ||
} | ||
|
||
uint8_t device_ids[MAX_DEVICES] = {0}; | ||
uint8_t current_device_id = 0; | ||
char *device_id = NULL; | ||
char *rest = argv[1]; | ||
while ((device_id = strtok_r(rest, ",", &rest))) { | ||
device_ids[current_device_id++] = strtol(device_id, NULL, RADIX_DECIMAL); | ||
if (CONFIG_TRUE != config_lookup_string(parser, "device_or_uri", &config->device_or_uri)) { | ||
LOG(LOG_ERROR, "No 'device_or_uri' setting in configuration file"); | ||
return EXIT_FAILURE; | ||
} | ||
|
||
if (CONFIG_TRUE != config_lookup_int(parser, "prometheus.port", &config->prometheus_config.port)) { | ||
config->prometheus_config.port = 0; | ||
} | ||
|
||
if (CONFIG_TRUE != config_lookup_int(parser, "mqtt.port", &config->mqtt_config.port)) { | ||
config->mqtt_config.port = 0; | ||
} | ||
|
||
const uint16_t port = strtol(argv[2], NULL, RADIX_DECIMAL); | ||
if (port < 1 || port > MAX_PORT_NUMBER) { | ||
fprintf(LOG_ERROR, "Invalid port number: %d\n", port); | ||
config_lookup_string(parser, "mqtt.host", &config->mqtt_config.host); | ||
config_lookup_string(parser, "mqtt.username", &config->mqtt_config.username); | ||
config_lookup_string(parser, "mqtt.password", &config->mqtt_config.password); | ||
|
||
return EXIT_SUCCESS; | ||
} | ||
|
||
int main(int argc, char *argv[argc + 1]) { | ||
static_assert(__STDC_VERSION__ >= STDC_VERSION_MIN, "C17+ required"); | ||
|
||
// disable log buffering | ||
setbuf(stdout, NULL); | ||
setbuf(stderr, NULL); | ||
|
||
// signal(SIGINT, sig_handler); | ||
|
||
if (argc < 2) { | ||
return usage(argv[0]); | ||
} | ||
|
||
config config; | ||
config_t parser_config; | ||
config_init(&parser_config); | ||
if (parse_config(&config, &parser_config, argv[1])) { | ||
config_destroy(&parser_config); | ||
return EXIT_FAILURE; | ||
} | ||
|
||
thrd_t prometheus_thread = 0; | ||
thrd_t mqtt_thread = 0; | ||
thrd_t modbus_thread = 0; | ||
|
||
if (config.prometheus_config.port) { | ||
int status = thrd_create(&prometheus_thread, (thrd_start_t)start_prometheus_thread, &config.prometheus_config); | ||
if (status != thrd_success) { | ||
PERROR("thrd_create() failed"); | ||
config_destroy(&parser_config); | ||
return EXIT_FAILURE; | ||
} | ||
} | ||
|
||
if (config.mqtt_config.port) { | ||
int status = thrd_create(&mqtt_thread, (thrd_start_t)start_mqtt_thread, &config.mqtt_config); | ||
if (status != thrd_success) { | ||
PERROR("thrd_create() failed"); | ||
config_destroy(&parser_config); | ||
return EXIT_FAILURE; | ||
} | ||
} | ||
|
||
if (!prometheus_thread && !mqtt_thread) { | ||
LOG(LOG_ERROR, "You must configure at least Prometheus or MQTT (or both)"); | ||
config_destroy(&parser_config); | ||
return EXIT_FAILURE; | ||
} | ||
|
||
int status = thrd_create(&modbus_thread, (thrd_start_t)start_modbus_thread, (void *)config.device_or_uri); | ||
if (status != thrd_success) { | ||
PERROR("thrd_create() failed"); | ||
config_destroy(&parser_config); | ||
return EXIT_FAILURE; | ||
} | ||
|
||
return http(port, device_ids); | ||
// FIXME: catch MQTT thread termination somehow | ||
int value = join_thread(&modbus_thread, "MDBS"); | ||
|
||
if (prometheus_thread) { | ||
value += join_thread(&prometheus_thread, "PRMT"); | ||
} | ||
if (mqtt_thread) { | ||
value += join_thread(&mqtt_thread, "MQTT"); | ||
} | ||
|
||
config_destroy(&parser_config); | ||
|
||
LOG(LOG_INFO, "Bye"); | ||
exit(value); // will terminate any remaining threads | ||
} |
Oops, something went wrong.