add: version.h

fix: refactor
This commit is contained in:
Tobias Reisinger 2020-06-21 00:10:37 +02:00
parent 532750da74
commit f5f9be803c
36 changed files with 27 additions and 34 deletions

384
src/argparse.c Normal file
View file

@ -0,0 +1,384 @@
/**
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
* All rights reserved.
*
* Use of this source code is governed by a MIT-style license that can be found
* in the LICENSE file.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "argparse.h"
#define OPT_UNSET 1
#define OPT_LONG (1 << 1)
static const char *
prefix_skip(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
static int
prefix_cmp(const char *str, const char *prefix)
{
for (;; str++, prefix++)
if (!*prefix) {
return 0;
} else if (*str != *prefix) {
return (unsigned char)*prefix - (unsigned char)*str;
}
}
static void
argparse_error(struct argparse *self, const struct argparse_option *opt,
const char *reason, int flags)
{
(void)self;
if (flags & OPT_LONG) {
fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
} else {
fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
}
exit(1);
}
static int
argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
int flags)
{
const char *s = NULL;
if (!opt->value)
goto skipped;
switch (opt->type) {
case ARGPARSE_OPT_BOOLEAN:
if (flags & OPT_UNSET) {
*(int *)opt->value = *(int *)opt->value - 1;
} else {
*(int *)opt->value = *(int *)opt->value + 1;
}
if (*(int *)opt->value < 0) {
*(int *)opt->value = 0;
}
break;
case ARGPARSE_OPT_BIT:
if (flags & OPT_UNSET) {
*(int *)opt->value &= ~opt->data;
} else {
*(int *)opt->value |= opt->data;
}
break;
case ARGPARSE_OPT_STRING:
if (self->optvalue) {
*(const char **)opt->value = self->optvalue;
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(const char **)opt->value = *++self->argv;
} else {
argparse_error(self, opt, "requires a value", flags);
}
break;
case ARGPARSE_OPT_INTEGER:
errno = 0;
if (self->optvalue) {
*(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects an integer value", flags);
break;
case ARGPARSE_OPT_FLOAT:
errno = 0;
if (self->optvalue) {
*(float *)opt->value = strtof(self->optvalue, (char **)&s);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(float *)opt->value = strtof(*++self->argv, (char **)&s);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects a numerical value", flags);
break;
default:
assert(0);
}
skipped:
if (opt->callback) {
return opt->callback(self, opt);
}
return 0;
}
static void
argparse_options_check(const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
switch (options->type) {
case ARGPARSE_OPT_END:
case ARGPARSE_OPT_BOOLEAN:
case ARGPARSE_OPT_BIT:
case ARGPARSE_OPT_INTEGER:
case ARGPARSE_OPT_FLOAT:
case ARGPARSE_OPT_STRING:
case ARGPARSE_OPT_GROUP:
continue;
default:
fprintf(stderr, "wrong option type: %d", options->type);
break;
}
}
}
static int
argparse_short_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
if (options->short_name == *self->optvalue) {
self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
return argparse_getvalue(self, options, 0);
}
}
return -2;
}
static int
argparse_long_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
const char *rest;
int opt_flags = 0;
if (!options->long_name)
continue;
rest = prefix_skip(self->argv[0] + 2, options->long_name);
if (!rest) {
// negation disabled?
if (options->flags & OPT_NONEG) {
continue;
}
// only OPT_BOOLEAN/OPT_BIT supports negation
if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
ARGPARSE_OPT_BIT) {
continue;
}
if (prefix_cmp(self->argv[0] + 2, "no-")) {
continue;
}
rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
if (!rest)
continue;
opt_flags |= OPT_UNSET;
}
if (*rest) {
if (*rest != '=')
continue;
self->optvalue = rest + 1;
}
return argparse_getvalue(self, options, opt_flags | OPT_LONG);
}
return -2;
}
int
argparse_init(struct argparse *self, struct argparse_option *options,
const char *const *usages, int flags)
{
memset(self, 0, sizeof(*self));
self->options = options;
self->usages = usages;
self->flags = flags;
self->description = NULL;
self->epilog = NULL;
return 0;
}
void
argparse_describe(struct argparse *self, const char *description,
const char *epilog)
{
self->description = description;
self->epilog = epilog;
}
int
argparse_parse(struct argparse *self, int argc, const char **argv)
{
self->argc = argc - 1;
self->argv = argv + 1;
self->out = argv;
argparse_options_check(self->options);
for (; self->argc; self->argc--, self->argv++) {
const char *arg = self->argv[0];
if (arg[0] != '-' || !arg[1]) {
if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
goto end;
}
// if it's not option or is a single char '-', copy verbatim
self->out[self->cpidx++] = self->argv[0];
continue;
}
// short option
if (arg[1] != '-') {
self->optvalue = arg + 1;
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
while (self->optvalue) {
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
}
continue;
}
// if '--' presents
if (!arg[2]) {
self->argc--;
self->argv++;
break;
}
// long option
switch (argparse_long_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
continue;
unknown:
fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
argparse_usage(self);
exit(1);
}
end:
memmove(self->out + self->cpidx, self->argv,
self->argc * sizeof(*self->out));
self->out[self->cpidx + self->argc] = NULL;
return self->cpidx + self->argc;
}
void
argparse_usage(struct argparse *self)
{
if (self->usages) {
fprintf(stdout, "Usage: %s\n", *self->usages++);
while (*self->usages && **self->usages)
fprintf(stdout, " or: %s\n", *self->usages++);
} else {
fprintf(stdout, "Usage:\n");
}
// print description
if (self->description)
fprintf(stdout, "%s\n", self->description);
fputc('\n', stdout);
const struct argparse_option *options;
// figure out best width
size_t usage_opts_width = 0;
size_t len;
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
len = 0;
if ((options)->short_name) {
len += 2;
}
if ((options)->short_name && (options)->long_name) {
len += 2; // separator ", "
}
if ((options)->long_name) {
len += strlen((options)->long_name) + 2;
}
if (options->type == ARGPARSE_OPT_INTEGER) {
len += strlen("=<int>");
}
if (options->type == ARGPARSE_OPT_FLOAT) {
len += strlen("=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
len += strlen("=<str>");
}
len = (len + 3) - ((len + 3) & 3);
if (usage_opts_width < len) {
usage_opts_width = len;
}
}
usage_opts_width += 4; // 4 spaces prefix
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
size_t pos = 0;
int pad = 0;
if (options->type == ARGPARSE_OPT_GROUP) {
fputc('\n', stdout);
fprintf(stdout, "%s", options->help);
fputc('\n', stdout);
continue;
}
pos = fprintf(stdout, " ");
if (options->short_name) {
pos += fprintf(stdout, "-%c", options->short_name);
}
if (options->long_name && options->short_name) {
pos += fprintf(stdout, ", ");
}
if (options->long_name) {
pos += fprintf(stdout, "--%s", options->long_name);
}
if (options->type == ARGPARSE_OPT_INTEGER) {
pos += fprintf(stdout, "=<int>");
} else if (options->type == ARGPARSE_OPT_FLOAT) {
pos += fprintf(stdout, "=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
pos += fprintf(stdout, "=<str>");
}
if (pos <= usage_opts_width) {
pad = usage_opts_width - pos;
} else {
fputc('\n', stdout);
pad = usage_opts_width;
}
fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
}
// print epilog
if (self->epilog)
fprintf(stdout, "%s\n", self->epilog);
}
int
argparse_help_cb(struct argparse *self, const struct argparse_option *option)
{
(void)option;
argparse_usage(self);
exit(0);
}

5016
src/confini.c Normal file

File diff suppressed because it is too large Load diff

31
src/database.c Normal file
View file

@ -0,0 +1,31 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <lmdb.h>
#include <config.h>
#include <constants.h>
void
database_setup(MDB_env **mdb_env, config_t *config)
{
int err;
if(mdb_env_create(mdb_env) != 0)
{
perror("Can't create mdb handle");
exit(1);
}
if((err = mdb_env_set_maxdbs(*mdb_env, MDB_MAXDBS)) != 0)
{
fprintf(stderr, "mdb_env_set_maxdbs error %s\n", mdb_strerror(err));
exit(1);
}
if(mdb_env_open(*mdb_env, config->database, MDB_NOSUBDIR, 0700) != 0)
{
perror("Can't open mdb file");
exit(1);
}
}

14
src/drivers/gpio.c Normal file
View file

@ -0,0 +1,14 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_gpio_set(int pin, int value)
{
// disable "unused parameter" warning (happens when using wiring_debug)
(void)pin;
(void)value;
digitalWrite(pin, value);
}

14
src/drivers/piface.c Normal file
View file

@ -0,0 +1,14 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_piface_set(int pin, int value)
{
// disable "unused parameter" warning (happens when using wiring_debug)
(void)pin;
(void)value;
digitalWrite(PIFACE_GPIO_BASE + pin, value);
}

142
src/handlers/command.c Normal file
View file

@ -0,0 +1,142 @@
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <uuid/uuid.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <logger.h>
#include <handlers.h>
#include <helpers.h>
#include <enums.h>
#include <mpack.h>
#include <models/schedule.h>
static void
handler_command_set_name(mpack_node_t map, controller_t *controller)
{
char name_buffer[MAX_NAME_LENGTH + 1];
mpack_node_copy_cstr(mpack_node_map_uint(map, COMMAND_MAPPING_NAME), name_buffer, MAX_NAME_LENGTH + 1);
controller_set_name(controller, name_buffer);
}
static void
handler_command_set_schedule(mpack_node_t map, controller_t *controller)
{
uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
relay_t *target_relay = controller->relays[relay_num];
mpack_node_t schedules_array = mpack_node_map_uint(map, COMMAND_MAPPING_SCHEDULES_ARRAY);
for(int i = 0; i < 7; ++i)
{
mpack_node_t schedules_map = mpack_node_array_at(schedules_array, i);
uuid_t schedule_id;
memcpy(schedule_id, mpack_node_data(mpack_node_map_uint(schedules_map, COMMAND_MAPPING_SCHEDULE_ID)), sizeof(uuid_t));
uint16_t periods_count = mpack_node_u16(mpack_node_map_uint(schedules_map, COMMAND_MAPPING_PERIODS_COUNT));
uint16_t *periods = (uint16_t*)mpack_node_bin_data(mpack_node_map_uint(schedules_map, COMMAND_MAPPING_PERIODS_BLOB));
if(target_relay->schedules[i])
{
schedule_free(target_relay->schedules[i]);
}
target_relay->schedules[i] = schedule_create(schedule_id, i, periods_count, periods);
}
relay_debug(target_relay);
}
static void
handler_command_set_relay_name(mpack_node_t map, controller_t *controller)
{
uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
const char *relay_name = mpack_node_str(mpack_node_map_uint(map, COMMAND_MAPPING_NAME));
if(relay_num < controller->relay_count)
{
relay_set_name(controller->relays[relay_num], relay_name);
}
relay_debug(controller->relays[relay_num]);
}
void
handler_command(int fd, controller_t *controller)
{
struct sockaddr_storage their_addr;
socklen_t addr_size;
int client_fd;
addr_size = sizeof(their_addr);
if((client_fd = accept(fd, (struct sockaddr *) &their_addr, &addr_size)) < 0)
{
LOG_ERROR("could not accept client: %s\n", strerror(errno));
return;
}
uint32_t payload_length;
if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0)
{
LOG_ERROR("unable to receive header: %s\n", strerror(errno));
return;
}
void *payload = malloc((payload_length + 1));
ssize_t bytes_transferred;
if((bytes_transferred = recv(client_fd, payload, payload_length, 0)) <= 0)
{
LOG_ERROR("unable to receive payload: %s\n", strerror(errno));
return;
}
mpack_tree_t tree;
mpack_tree_init_data(&tree, payload, payload_length);
mpack_tree_parse(&tree);
mpack_node_t root = mpack_tree_root(&tree);
uint8_t command_code = mpack_node_u8(mpack_node_map_uint(root, COMMAND_MAPPING_CODE));
LOG_INFO("received command %d\n", command_code);
switch(command_code)
{
case COMMAND_CODE_GET_TIME:
break;
case COMMAND_CODE_GET_ID:
break;
case COMMAND_CODE_SET_NAME:
handler_command_set_name(root, controller);
break;
case COMMAND_CODE_GET_NAME:
break;
case COMMAND_CODE_SET_SCHEDULE:
handler_command_set_schedule(root, controller);
break;
case COMMAND_CODE_GET_SCHEDULE:
break;
case COMMAND_CODE_SET_RELAY_NAME:
handler_command_set_relay_name(root, controller);
break;
case COMMAND_CODE_GET_RELAY_NAME:
break;
default:
LOG_ERROR("received invalid command\n");
}
if(mpack_tree_destroy(&tree) != mpack_ok)
{
LOG_WARN("error when destroying mpack tree\n");
}
free(payload);
close(client_fd);
}

89
src/handlers/discovery.c Normal file
View file

@ -0,0 +1,89 @@
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <uuid/uuid.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <logger.h>
#include <handlers.h>
#include <helpers.h>
#include <mpack.h>
#include <enums.h>
void
handler_discovery(int fd, controller_t *controller)
{
ssize_t bytes_transferred;
uint16_t discovery_answer_port;
struct sockaddr_in si_other;
socklen_t slen = sizeof(si_other);
if((bytes_transferred = recvfrom(fd, &discovery_answer_port, sizeof(discovery_answer_port), 0, (struct sockaddr *) &si_other, &slen)) <= 0)
{
LOG_ERROR("received invalid discovery from %s\n", inet_ntoa(si_other.sin_addr));
return;
}
LOG_INFO("received discovery from %s:%d\n", inet_ntoa(si_other.sin_addr), discovery_answer_port);
if(discovery_answer_port == 0)
{
LOG_ERROR("invalid port received\n");
return;
}
char* payload;
size_t payload_size;
mpack_writer_t writer;
mpack_writer_init_growable(&writer, &payload, &payload_size);
mpack_start_map(&writer, 4);
mpack_write_uint(&writer, DISCOVERY_MAPPING_ID);
mpack_write_bin(&writer, (char*)controller->id, sizeof(uuid_t));
mpack_write_uint(&writer, DISCOVERY_MAPPING_COMMAND_PORT);
mpack_write_u16(&writer, controller->command_port);
mpack_write_uint(&writer, DISCOVERY_MAPPING_RELAY_COUNT);
mpack_write_u8(&writer, controller->relay_count);
mpack_write_uint(&writer, DISCOVERY_MAPPING_NAME);
mpack_write_cstr(&writer, controller->name);
mpack_finish_map(&writer);
// finish writing
if(mpack_writer_destroy(&writer) != mpack_ok)
{
LOG_ERROR("error writing discovery answer payload\n");
return;
}
int fd_answer = helper_connect_tcp_server(inet_ntoa(si_other.sin_addr), discovery_answer_port);
if(fd_answer == -1)
{
LOG_ERROR("error during connecting\n");
free(payload);
return;
}
if((bytes_transferred = send(fd_answer, &payload_size, sizeof(payload_size), 0)) <= 0)
{
LOG_ERROR("error during sending\n");
free(payload);
close(fd_answer);
return;
}
if((bytes_transferred = send(fd_answer, payload, payload_size, 0)) <= 0)
{
LOG_ERROR("error during sending\n");
free(payload);
close(fd_answer);
return;
}
free(payload);
close(fd_answer);
}

46
src/handlers/loop.c Normal file
View file

@ -0,0 +1,46 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <logger.h>
#include <models/controller.h>
#include <handlers.h>
#include <drivers.h>
#include <enums.h>
#include <helpers.h>
#include <wiringPi.h>
#include <wiring_debug.h>
void
handler_loop(controller_t *controller)
{
time_t timestamp = time(NULL);
struct tm *time_struct = localtime(&timestamp);
LOG_DEBUG("===== IDLE LOOP START =====\n");
for(uint_fast8_t i = 0; i < controller->relay_count; ++i)
{
relay_t *relay = controller->relays[i];
int is_active = 0;
if(relay_is_active(relay, time_struct))
{
LOG_DEBUG("relay %d is active\n", i);
is_active = 1;
}
if(global_config.relay_configs[i].inverted)
{
is_active = !is_active;
}
switch(global_config.relay_configs[i].driver)
{
case RELAY_DRIVER_GPIO:
driver_gpio_set(global_config.relay_configs[i].pin, is_active);
break;
case RELAY_DRIVER_PIFACE:
driver_piface_set(global_config.relay_configs[i].pin, is_active);
break;
default:
LOG_WARN("relay %d is not using a driver\n", i);
}
}
}

50
src/handlers/poll.c Normal file
View file

@ -0,0 +1,50 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <lmdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <poll.h>
#include <signal.h>
#include <logger.h>
#include <models/controller.h>
#include <database.h>
#include <config.h>
#include <constants.h>
#include <handlers.h>
#include <drivers.h>
#include <enums.h>
#include <helpers.h>
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
void
handler_poll(struct pollfd *fds, controller_t *controller, MDB_env *mdb_env)
{
/* An event on one of the fds has occurred. */
for(int i = 0; i < POLL_FDS_COUNT; i++) {
if(fds[i].revents & POLLIN)
{
/* data may be read on device number i. */
LOG_DEBUG("fd %i may read data\n", fds[i].fd);
switch(i)
{
case POLL_FDS_DISCOVERY:
handler_discovery(fds[i].fd, controller);
break;
case POLL_FDS_COMMAND:
handler_command(fds[i].fd, controller);
controller_save(controller, mdb_env);
break;
}
}
if(fds[i].revents & POLLHUP)
{
/* A hangup has occurred on device number i. */
LOG_DEBUG("fd %i got closed\n", fds[i].fd);
}
}
}

52
src/helpers/bind_server.c Normal file
View file

@ -0,0 +1,52 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <logger.h>
#include <helpers.h>
int
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog)
{
char port_str[6];
sprintf(port_str, "%d", port);
struct addrinfo hints, *res;
int fd;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((status = getaddrinfo(addr, port_str, &hints, &res)) != 0)
{
LOG_ERROR("getaddrinfo: %s\n", gai_strerror(status));
return -1;
}
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1)
{
LOG_ERROR("error binding socket: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
if ((status = listen(fd, max_client_backlog)) == -1)
{
LOG_ERROR("error setting up listener: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return fd;
}

View file

@ -0,0 +1,39 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <logger.h>
#include <helpers.h>
int
helper_connect_tcp_server(char* host, uint16_t port)
{
char port_str[6];
sprintf(port_str, "%d", port);
int s, status;
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; //set IP Protocol flag (IPv4 or IPv6 - we don't care)
hints.ai_socktype = SOCK_STREAM; //set socket flag
if ((status = getaddrinfo(host, port_str, &hints, &res)) != 0) { //getaddrinfo() will evaluate the given address, using the hints-flags and port, and return an IP address and other server infos
LOG_ERROR("getaddrinfo: %s\n", gai_strerror(status));
return -1;
}
//res got filled out by getaddrinfo() for us
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
LOG_ERROR("connect() failed\n");
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return s;
}

24
src/helpers/get_port.c Normal file
View file

@ -0,0 +1,24 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <helpers.h>
#include <logger.h>
uint16_t
helper_get_port(int sock)
{
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
if (getsockname(sock, (struct sockaddr *)&sin, &len) == -1)
{
LOG_ERROR("could not get socket name for port: %s\n", strerror(errno));
return 0;
}
else
{
return ntohs(sin.sin_port);
}
}

11
src/helpers/get_weekday.c Normal file
View file

@ -0,0 +1,11 @@
#include <time.h>
#include <helpers.h>
int
helper_get_weekday(const struct tm *time_struct)
{
int wday_sun_sat = time_struct->tm_wday;
int wday_mon_sun = (wday_sun_sat + 6) % 7;
return wday_mon_sun;
}

115
src/helpers/load_config.c Normal file
View file

@ -0,0 +1,115 @@
#include <stdlib.h>
#include <string.h>
#include <helpers.h>
#include <config.h>
#include <logger.h>
#include <confini.h>
#define CONFINI_IS_KEY(SECTION, KEY) \
(ini_array_match(SECTION, disp->append_to, '.', disp->format) && \
ini_string_match_ii(KEY, disp->data, disp->format))
int
helper_load_config(IniDispatch *disp, void *config_void)
{
config_t *config = (config_t*)config_void;
char relay_section_name[10]; // "relay-255\0" is longest name
if(disp->type == INI_KEY)
{
if(CONFINI_IS_KEY("controller", "name"))
{
strncpy(config->name, disp->value, MAX_NAME_LENGTH);
config->name[MAX_NAME_LENGTH] = '\0';
return 0;
}
if(CONFINI_IS_KEY("controller", "database"))
{
config->database = malloc(sizeof(char) * (strlen(disp->value) + 1));
strcpy(config->database, disp->value);
return 0;
}
if(CONFINI_IS_KEY("controller", "log-level"))
{
if(strcasecmp(disp->value, "trace") == 0)
{
config->log_level = LOG_LEVEL_TRACE;
return 0;
}
if(strcasecmp(disp->value, "debug") == 0)
{
config->log_level = LOG_LEVEL_DEBUG;
return 0;
}
if(strcasecmp(disp->value, "info") == 0)
{
config->log_level = LOG_LEVEL_INFO;
return 0;
}
if(strcasecmp(disp->value, "warn") == 0)
{
config->log_level = LOG_LEVEL_WARN;
return 0;
}
if(strcasecmp(disp->value, "error") == 0)
{
config->log_level = LOG_LEVEL_ERROR;
return 0;
}
if(strcasecmp(disp->value, "fatal") == 0)
{
config->log_level = LOG_LEVEL_FATAL;
return 0;
}
LOG_WARN("invalid log-level '%s'\n", disp->value);
return 0;
}
if(CONFINI_IS_KEY("controller", "discovery-port"))
{
config->discovery_port = atoi(disp->value);
return 0;
}
if(CONFINI_IS_KEY("controller", "relay-count"))
{
config->relay_count = atoi(disp->value);
config->relay_configs = malloc(sizeof(config_relay_t) * config->relay_count);
for(uint8_t i = 0; i < config->relay_count; ++i)
{
config->relay_configs[i].driver= RELAY_DRIVER_NONE;
}
LOG_TRACE("config relay-count set to %u\n", config->relay_count);
return 0;
}
for(uint8_t i = 0; i < config->relay_count; ++i)
{
sprintf(relay_section_name, "relay-%u", i);
if(CONFINI_IS_KEY(relay_section_name, "pin"))
{
config->relay_configs[i].pin = atoi(disp->value);
return 0;
}
if(CONFINI_IS_KEY(relay_section_name, "inverted"))
{
config->relay_configs[i].inverted = atoi(disp->value);
return 0;
}
if(CONFINI_IS_KEY(relay_section_name, "driver"))
{
if(strcasecmp(disp->value, "gpio") == 0)
{
config->relay_configs[i].driver = RELAY_DRIVER_GPIO;
return 0;
}
if(strcasecmp(disp->value, "piface") == 0)
{
config->relay_configs[i].driver = RELAY_DRIVER_PIFACE;
return 0;
}
LOG_WARN("invalid driver '%s' in section '%s'\n", disp->value, relay_section_name);
return 0;
}
}
}
return 0;
}

View file

@ -0,0 +1,57 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <logger.h>
#include <helpers.h>
int
helper_open_discovery_socket(uint16_t discovery_port)
{
struct addrinfo hints, *res;
int fd, status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; // use ipv4
hints.ai_socktype = SOCK_DGRAM; //set socket flag
hints.ai_flags = AI_PASSIVE; // get my IP
char discovery_port_str[6];
sprintf(discovery_port_str, "%u", discovery_port);
//get connection info for our computer
if ((status = getaddrinfo(NULL, discovery_port_str, &hints, &res)) != 0)
{
LOG_FATAL("getaddrinfo: %s\n", gai_strerror(status));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
//creating socket
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
int yes = 1;
// lose the pesky "Address already in use" error message
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
{
LOG_FATAL("setsockopt: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
if (bind(fd, res->ai_addr, res->ai_addrlen) == -1)
{
LOG_FATAL("bind: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
freeaddrinfo(res);
LOG_INFO("opened discovery socket on port %u\n", discovery_port);
return fd;
}

70
src/helpers/parse_cli.c Normal file
View file

@ -0,0 +1,70 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <argparse.h>
#include <config.h>
#include <logger.h>
#include <helpers.h>
#include <version.h>
static const char *const usage[] = {
"controller [options] [[--] args]",
"controller [options]",
NULL,
};
#define PERM_READ (1<<0)
#define PERM_WRITE (1<<1)
#define PERM_EXEC (1<<2)
void
helper_parse_cli(int argc, const char **argv, config_t *config)
{
int version = 0;
struct argparse_option options[] =
{
OPT_HELP(),
OPT_GROUP("Basic options"),
OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG),
OPT_BOOLEAN('v', "version", &version, "print version", NULL, 0, OPT_NONEG),
OPT_END(),
};
struct argparse argparse;
argparse_init(&argparse, options, usage, 0);
argparse_describe(
&argparse,
"\nA brief description of what the program does and how it works.",
"\nAdditional description of the program after the description of the arguments."
);
argc = argparse_parse(&argparse, argc, argv);
if(version)
{
printf("%s\n", EMGAUWA_CONTROLLER_VERSION);
exit(0);
}
if(argc == 1)
{
if(strcmp(argv[0], "start") == 0)
{
config->run_type = RUN_TYPE_START;
return;
}
if(strcmp(argv[0], "test") == 0)
{
config->run_type = RUN_TYPE_TEST;
return;
}
LOG_FATAL("bad action '%s' given ('start', 'test')\n", argv[0]);
exit(1);
}
else
{
LOG_FATAL("no action given ('start', 'test')\n");
exit(1);
}
return;
}

58
src/logger.c Normal file
View file

@ -0,0 +1,58 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <config.h>
#include <logger.h>
#define COLOR_TRACE COLOR_GREEN
#define COLOR_DEBUG COLOR_BLUE
#define COLOR_INFO COLOR_CYAN
#define COLOR_WARN COLOR_YELLOW
#define COLOR_ERROR COLOR_RED
#define COLOR_FATAL COLOR_MAGENTA
void
logger_log(FILE *stream, log_level_t level, const char *filename, int line, const char *func, const char *msg, ...)
{
if(global_config.log_level < level)
{
return;
}
switch(level)
{
case LOG_LEVEL_TRACE:
fprintf(stream, COLOR_TRACE "[TRACE] ");
break;
case LOG_LEVEL_DEBUG:
fprintf(stream, COLOR_DEBUG "[DEBUG] ");
break;
case LOG_LEVEL_INFO:
fprintf(stream, COLOR_INFO "[INFO ] ");
break;
case LOG_LEVEL_WARN:
fprintf(stream, COLOR_WARN "[WARN ] ");
break;
case LOG_LEVEL_ERROR:
fprintf(stream, COLOR_ERROR "[ERROR] ");
break;
case LOG_LEVEL_FATAL:
fprintf(stream, COLOR_FATAL "[FATAL] ");
break;
default:
fprintf(stream, COLOR_NONE "[LOG ] ");
break;
}
char timestamp_str[32];
time_t rawtime = time(NULL);
strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func);
va_list args;
va_start(args, msg);
vfprintf(stream, msg, args);
va_end(args);
}

168
src/main.c Normal file
View file

@ -0,0 +1,168 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <lmdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <poll.h>
#include <signal.h>
#include <logger.h>
#include <models/controller.h>
#include <database.h>
#include <config.h>
#include <constants.h>
#include <handlers.h>
#include <drivers.h>
#include <enums.h>
#include <runners.h>
#include <helpers.h>
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <confini.h>
config_t global_config;
static MDB_env *mdb_env;
static controller_t *this_controller;
static struct pollfd poll_fds[POLL_FDS_COUNT];
static void
terminate(int signum)
{
LOG_INFO("terminating controller (%d)\n", signum);
for(int i = 0; i < POLL_FDS_COUNT; ++i)
{
close(poll_fds[i].fd);
}
mdb_env_close(mdb_env);
controller_free(this_controller);
free(global_config.database);
free(global_config.relay_configs);
exit(signum);
}
/**
* @brief The main function
*
* @param argc UNUSED
* @param argv UNUSED
*
* @return Statuscode to indicate success (0) or failure (!0)
*/
int
main(int argc, const char** argv)
{
(void)argc;
(void)argv;
signal(SIGINT, terminate);
signal(SIGABRT, terminate);
signal(SIGTERM, terminate);
/******************** LOAD CONFIG ********************/
global_config.file = "controller.ini";
global_config.log_level = LOG_LEVEL_INFO;
helper_parse_cli(argc, argv, &global_config);
FILE * const ini_file = fopen(global_config.file, "rb");
if(ini_file == NULL)
{
LOG_FATAL("config file '%s' was not found\n", global_config.file);
exit(1);
}
if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, helper_load_config, &global_config))
{
LOG_FATAL("unable to parse ini file\n");
exit(1);
}
fclose(ini_file);
if(sizeof(time_t) < 8)
{
LOG_WARN("this system is not using 8-bit time\n");
}
/******************** SETUP DATABASE AND THIS CONTROLLER ********************/
database_setup(&mdb_env, &global_config);
this_controller = controller_load(mdb_env);
int fd_discovery = helper_open_discovery_socket(this_controller->discovery_port);
int fd_command = helper_bind_tcp_server("0.0.0.0", this_controller->command_port, 128);
this_controller->command_port = helper_get_port(fd_command);
controller_save(this_controller, mdb_env);
/******************** SETUP WIRINGPI ********************/
wiringPiSetup();
piFaceSetup(PIFACE_GPIO_BASE);
for(uint_fast8_t i = 0; i < this_controller->relay_count; ++i)
{
if(global_config.relay_configs[i].driver == RELAY_DRIVER_GPIO)
{
pinMode(global_config.relay_configs[i].pin, OUTPUT);
}
}
/******************** SETUP SOCKETS ********************/
int timeout_msecs = ACCEPT_TIMEOUT_MSECONDS;
int ret;
/* Open STREAMS device. */
poll_fds[POLL_FDS_DISCOVERY].fd = fd_discovery;
poll_fds[POLL_FDS_DISCOVERY].events = POLLIN;
LOG_DEBUG("setup fd_discovery as %i on index %i\n", fd_discovery, POLL_FDS_DISCOVERY);
poll_fds[POLL_FDS_COMMAND].fd = fd_command;
poll_fds[POLL_FDS_COMMAND].events = POLLIN;
LOG_DEBUG("setup fd_command as %i on index %i\n", fd_command, POLL_FDS_COMMAND);
/******************** CHECK FOR TESTING RUN ********************/
if(global_config.run_type == RUN_TYPE_TEST)
{
runner_test(this_controller);
terminate(0);
}
/******************** START MAIN LOOP ********************/
for(;;)
{
ret = poll(poll_fds, POLL_FDS_COUNT, timeout_msecs);
if(ret == 0)
{
handler_loop(this_controller);
}
if(ret > 0)
{
handler_poll(poll_fds, this_controller, mdb_env);
}
}
close(fd_discovery);
mdb_env_close(mdb_env);
return 0;
}

72
src/models/controller.c Normal file
View file

@ -0,0 +1,72 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <models/controller.h>
#include <macros.h>
#include <config.h>
#include <constants.h>
controller_t*
controller_create(void)
{
controller_t *new_controller = malloc(sizeof(*new_controller));
uuid_generate(new_controller->id);
strncpy(new_controller->name, global_config.name, MAX_NAME_LENGTH);
new_controller->name[MAX_NAME_LENGTH] = '\0';
new_controller->command_port = 0;
new_controller->discovery_port = global_config.discovery_port;
new_controller->relay_count = global_config.relay_count;
new_controller->relays = malloc(sizeof(relay_t) * new_controller->relay_count);
uint8_t i;
for(i = 0; i < new_controller->relay_count; ++i)
{
new_controller->relays[i] = relay_create(i);
}
return new_controller;
}
void
controller_set_name(controller_t *controller, const char *name)
{
strncpy(controller->name, name, MAX_NAME_LENGTH);
controller->name[MAX_NAME_LENGTH] = '\0';
}
void
controller_free(controller_t *controller)
{
for(int i = 0; i < controller->relay_count; ++i)
{
relay_free(controller->relays[i]);
}
free(controller->relays);
free(controller);
}
void
controller_debug(controller_t *controller)
{
if(controller == NULL)
{
LOG_DEBUG("controller is NULL\n");
return;
}
char uuid_str[37];
uuid_unparse(controller->id, uuid_str);
LOG_DEBUG("(1/5) %s @ %p\n", uuid_str, (void*)controller);
LOG_DEBUG("(2/5) name: %s\n", controller->name);
LOG_DEBUG("(3/5) command_port: %5d discovery_port: %5d\n", controller->command_port, controller->discovery_port);
LOG_DEBUG("(4/5) relay count: %3d\n", controller->relay_count);
LOG_DEBUG("(5/5) relays @ %p:\n", (void*)controller->relays);
for(int i = 0; i < controller->relay_count; ++i)
{
relay_debug(controller->relays[i]);
}
}

View file

@ -0,0 +1,85 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <models/controller.h>
#include <macros.h>
static void
controller_load_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_controller_e key_controller, MDB_val *value)
{
int err;
MDB_val key;
key.mv_size = sizeof(db_key_controller_e);
key.mv_data = &key_controller;
if((err = mdb_get(mdb_txn, mdb_dbi, &key, value)) != 0)
{
LOG_ERROR("mdb_get error %s\n", mdb_strerror(err));
exit(1);
}
}
controller_t*
controller_load(MDB_env *mdb_env)
{
int err;
MDB_txn *mdb_txn;
MDB_dbi mdb_dbi;
controller_t *new_controller;
if((err = mdb_txn_begin(mdb_env, NULL, MDB_RDONLY, &mdb_txn)) != 0)
{
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
return NULL;
}
if((err = mdb_dbi_open(mdb_txn, "controller", 0, &mdb_dbi)) != 0)
{
switch(err)
{
case MDB_NOTFOUND:
LOG_INFO("no controller found in db. creating new one\n");
mdb_txn_abort(mdb_txn);
new_controller = controller_create();
controller_save(new_controller, mdb_env);
return new_controller;
default:
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
return NULL;
}
}
new_controller = malloc(sizeof(controller_t));
MDB_val value;
controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_ID, &value);
memmove(new_controller->id, (uuid_t*)value.mv_data, sizeof(uuid_t));
controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_NAME, &value);
strncpy(new_controller->name, (char*)value.mv_data, MAX_NAME_LENGTH);
new_controller->name[MAX_NAME_LENGTH] = '\0';
controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_COMMAND_PORT, &value);
new_controller->command_port = ((uint16_t*)value.mv_data)[0];
controller_load_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_DISCOVERY_PORT, &value);
new_controller->discovery_port = ((uint16_t*)value.mv_data)[0];
new_controller->relay_count = global_config.relay_count;
mdb_txn_abort(mdb_txn); // transaction is read only
new_controller->relays = malloc(sizeof(relay_t*) * new_controller->relay_count);
for(uint8_t i = 0; i < new_controller->relay_count; i++)
{
LOG_TRACE("loading relay %d\n", i);
new_controller->relays[i] = relay_load(mdb_env, i);
}
return new_controller;
}

View file

@ -0,0 +1,90 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <models/controller.h>
#include <macros.h>
int
controller_save_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_controller_e key_controller, MDB_val value)
{
int err;
MDB_val key;
key.mv_size = sizeof(db_key_controller_e);
key.mv_data = &key_controller;
if((err = mdb_put(mdb_txn, mdb_dbi, &key, &value, 0)) != 0)
{
LOG_ERROR("mdb_put error %s\n", mdb_strerror(err));
mdb_txn_abort(mdb_txn);
return 1;
}
return 0;
}
int
controller_save(controller_t *controller, MDB_env *mdb_env)
{
int err;
MDB_txn *mdb_txn;
MDB_dbi mdb_dbi;
MDB_val value;
if((err = mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn)) != 0)
{
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
exit(1);
}
if((err = mdb_dbi_open(mdb_txn, "controller", MDB_CREATE, &mdb_dbi)) != 0)
{
LOG_ERROR("mdb_dbi_open error %s\n", mdb_strerror(err));
exit(1);
}
value.mv_size = sizeof(uuid_t);
value.mv_data = controller->id;
if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_ID, value))
{
LOG_ERROR("failed to save ID\n");
return 1;
}
value.mv_size = sizeof(char) * (strlen(controller->name) + 1);
value.mv_data = controller->name;
if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_NAME, value))
{
LOG_ERROR("failed to save name\n");
return 1;
}
value.mv_size = sizeof(controller->command_port);
value.mv_data = &controller->command_port;
if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_COMMAND_PORT, value))
{
LOG_ERROR("failed to save command port\n");
return 1;
}
value.mv_size = sizeof(controller->discovery_port);
value.mv_data = &controller->discovery_port;
if(controller_save_single(mdb_txn, mdb_dbi, DB_KEY_CONTROLLER_DISCOVERY_PORT, value))
{
LOG_ERROR("failed to save discovery port\n");
return 1;
}
mdb_txn_commit(mdb_txn);
for(uint8_t i = 0; i < controller->relay_count; ++i)
{
LOG_TRACE("saving relays[%d/%d]\n", i, controller->relay_count);
relay_save(controller->relays[i], mdb_env);
}
return 0;
}

47
src/models/period.c Normal file
View file

@ -0,0 +1,47 @@
#include <stdlib.h>
#include <logger.h>
#include <constants.h>
#include <models/period.h>
period_t*
period_create(uint16_t start, uint16_t end)
{
period_t *new_period = malloc(sizeof(period_t));
new_period->start = start;
new_period->end = end;
return new_period;
}
int
period_includes_time(period_t *period, struct tm *time_struct)
{
uint16_t start = period->start;
uint16_t end = period->end;
time_t timestamp = time_struct->tm_hour * 60;
timestamp += time_struct->tm_min;
// "normal" timespan
if(start < end)
{
if(start <= timestamp && end > timestamp)
{
return 1;
}
return 0;
}
// timespan goes through 00:00
if(end < start)
{
if(start >= timestamp && end < timestamp)
{
return 1;
}
return 0;
}
return 0;
}

80
src/models/relay.c Normal file
View file

@ -0,0 +1,80 @@
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <logger.h>
#include <helpers.h>
#include <models/relay.h>
relay_t*
relay_create(uint8_t number)
{
relay_t *new_relay = malloc(sizeof(relay_t));
new_relay->number = number;
new_relay->name[0] = '\0';
uuid_t off_id;
memset(off_id, 0, sizeof(uuid_t));
memcpy(off_id, "off", 3);
for(int i = 0; i < 7; ++i)
{
new_relay->schedules[i] = schedule_create(off_id, i, 0, NULL);
}
return new_relay;
}
void
relay_set_name(relay_t *relay, const char *name)
{
strncpy(relay->name, name, MAX_NAME_LENGTH);
relay->name[MAX_NAME_LENGTH] = '\0';
}
int
relay_is_active(relay_t *relay, struct tm *time_struct)
{
schedule_t *schedule = relay->schedules[helper_get_weekday(time_struct)];
if(schedule->length == 0)
{
return 0;
}
for(uint16_t i = 0; i < schedule->length; ++i)
{
if(period_includes_time(schedule->periods[i], time_struct))
{
return 1;
}
}
return 0;
}
void
relay_debug(relay_t *relay)
{
if(relay == NULL)
{
LOG_DEBUG("relay is NULL\n");
return;
}
LOG_DEBUG("(1/3) %d @ %p\n", relay->number, (void*)relay);
LOG_DEBUG("(2/3) name: %s\n", relay->name);
LOG_DEBUG("(3/3) schedules @ %p:\n", (void*)relay->schedules);
for(int i = 0; i < 7; ++i)
{
schedule_debug(relay->schedules[i]);
}
}
void
relay_free(relay_t *relay)
{
for(int i = 0; i < 7; ++i)
{
schedule_free(relay->schedules[i]);
}
free(relay);
}

88
src/models/relay_load.c Normal file
View file

@ -0,0 +1,88 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <models/relay.h>
#include <macros.h>
static int
relay_load_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_relay_e key_relay, uint8_t relay_num, MDB_val *value)
{
int err;
size_t key_size = sizeof(db_key_relay_e) + sizeof(uint8_t);
uint8_t *key_data = malloc(key_size);
uint8_t *key_data_writer = key_data;
memmove(key_data_writer, &relay_num, sizeof(uint8_t));
key_data_writer += sizeof(uint8_t);
memmove(key_data_writer, &key_relay, sizeof(db_key_relay_e));
MDB_val key;
key.mv_size = key_size;
key.mv_data = key_data;
if((err = mdb_get(mdb_txn, mdb_dbi, &key, value)) != 0)
{
LOG_ERROR("mdb_get error %s\n", mdb_strerror(err));
mdb_txn_abort(mdb_txn);
free(key_data);
return 1;
}
free(key_data);
return 0;
}
relay_t*
relay_load(MDB_env *mdb_env, uint8_t num)
{
int err;
MDB_txn *mdb_txn;
MDB_dbi mdb_dbi;
relay_t *new_relay;
if((err = mdb_txn_begin(mdb_env, NULL, MDB_RDONLY, &mdb_txn)) != 0)
{
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
return relay_create(num);
}
if((err = mdb_dbi_open(mdb_txn, "relays", 0, &mdb_dbi)) != 0)
{
switch(err)
{
case MDB_NOTFOUND:
LOG_INFO("no relay for num %d found in db. returning new one (no relays db)\n", num);
mdb_txn_abort(mdb_txn);
return relay_create(num);
default:
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
return relay_create(num);
}
}
new_relay = malloc(sizeof(relay_t));
new_relay->number = num;
MDB_val value;
if((err = relay_load_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_NAME, num, &value)) != 0)
{
LOG_INFO("no relay for num %d found in db. returning new one\n", num);
mdb_txn_abort(mdb_txn); // transaction is read only
return relay_create(num);
}
strncpy(new_relay->name, (char*)value.mv_data, MAX_NAME_LENGTH);
new_relay->name[MAX_NAME_LENGTH] = '\0';
mdb_txn_abort(mdb_txn); // transaction is read only
for(int i = 0; i < 7; ++i)
{
new_relay->schedules[i] = schedule_load(mdb_env, num, i);
}
return new_relay;
}

75
src/models/relay_save.c Normal file
View file

@ -0,0 +1,75 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <models/relay.h>
#include <macros.h>
static int
relay_save_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_relay_e key_relay, uint8_t relay_num, MDB_val value)
{
int err;
size_t key_size = sizeof(db_key_relay_e) + sizeof(uint8_t);
uint8_t *key_data = malloc(key_size);
uint8_t *key_data_writer = key_data;
memmove(key_data_writer, &relay_num, sizeof(uint8_t));
key_data_writer += sizeof(uint8_t);
memmove(key_data_writer, &key_relay, sizeof(db_key_relay_e));
MDB_val key;
key.mv_size = key_size;
key.mv_data = key_data;
if((err = mdb_put(mdb_txn, mdb_dbi, &key, &value, 0)) != 0)
{
LOG_ERROR("mdb_put error %s\n", mdb_strerror(err));
mdb_txn_abort(mdb_txn);
free(key_data);
return 1;
}
free(key_data);
return 0;
}
int
relay_save(relay_t *relay, MDB_env *mdb_env)
{
LOG_TRACE("saving relay %d @ %p\n", relay->number, relay);
int err;
MDB_txn *mdb_txn;
MDB_dbi mdb_dbi;
MDB_val value;
if((err = mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn)) != 0)
{
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
exit(1);
}
if((err = mdb_dbi_open(mdb_txn, "relays", MDB_CREATE, &mdb_dbi)) != 0)
{
LOG_ERROR("mdb_dbi_open error %s\n", mdb_strerror(err));
exit(1);
}
value.mv_size = sizeof(char) * (strlen(relay->name) + 1);
value.mv_data = relay->name;
if(relay_save_single(mdb_txn, mdb_dbi, DB_KEY_RELAY_NAME, relay->number, value))
{
LOG_ERROR("failed to save name\n");
return 1;
}
mdb_txn_commit(mdb_txn);
for(int i = 0; i < 7; ++i)
{
schedule_save(relay->schedules[i], relay->number, mdb_env);
}
return 0;
}

94
src/models/schedule.c Normal file
View file

@ -0,0 +1,94 @@
#include <stdlib.h>
#include <string.h>
#include <logger.h>
#include <models/schedule.h>
schedule_t*
schedule_create(uuid_t id, uint8_t weekday, uint16_t length, uint16_t *periods_blob)
{
schedule_t *new_schedule = malloc(sizeof(schedule_t));
memmove(new_schedule->id, id, sizeof(uuid_t));
new_schedule->weekday = weekday;
new_schedule->length = length;
new_schedule->periods = NULL;
if(length)
{
new_schedule->periods = malloc(sizeof(period_t*) * length);
for(uint16_t i = 0; i < length; ++i)
{
uint16_t start = periods_blob[0 + (i * 2)];
uint16_t end = periods_blob[1 + (i * 2)];
new_schedule->periods[i] = period_create(start, end);
}
}
return new_schedule;
}
uint16_t*
schedule_periods_to_blob(schedule_t *schedule)
{
uint16_t *periods_blob = malloc(sizeof(uint16_t) * ((2 * schedule->length) + 1));
periods_blob[0] = schedule->length;
for(uint16_t i = 0; i < schedule->length; ++i)
{
periods_blob[1 + (i * 2)] = schedule->periods[i]->start;
periods_blob[2 + (i * 2)] = schedule->periods[i]->end;
}
return periods_blob;
}
void
schedule_free(schedule_t *schedule)
{
for(uint16_t i = 0; i < schedule->length; ++i)
{
free(schedule->periods[i]);
}
free(schedule->periods);
free(schedule);
}
void
schedule_debug(schedule_t *schedule)
{
if(schedule == NULL)
{
LOG_DEBUG("schedule is NULL\n");
return;
}
char uuid_str[UUID_STR_LEN];
uuid_unparse(schedule->id, uuid_str);
LOG_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)schedule);
LOG_DEBUG("(2/4) period count: %3d\n", schedule->length);
LOG_DEBUG("(3/4) weekday: %3d\n", schedule->weekday);
// one block: "HH:MM-HH:MM, " --> size: 13 (14 with '\0')
char *periods_debug_str = malloc(sizeof(char) * ((schedule->length * 13) + 1));
periods_debug_str[0] = '\0';
for(uint16_t i = 0; i < schedule->length; ++i)
{
sprintf(
periods_debug_str + (13 * i),
"%02d:%02d-%02d:%02d, ",
schedule->periods[i]->start / 60,
schedule->periods[i]->start % 60,
schedule->periods[i]->end / 60,
schedule->periods[i]->end % 60
);
}
LOG_DEBUG("(4/4) periods: %s\n", periods_debug_str);
free(periods_debug_str);
}

View file

@ -0,0 +1,96 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <models/relay.h>
#include <macros.h>
static int
schedule_load_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_schedule_e key_schedule, uint8_t relay_num, uint8_t weekday, MDB_val *value)
{
int err;
size_t key_size = sizeof(db_key_schedule_e) + (2 * sizeof(uint8_t));
uint8_t *key_data = malloc(key_size);
uint8_t *key_data_writer = key_data;
memmove(key_data_writer, &relay_num, sizeof(uint8_t));
key_data_writer += sizeof(uint8_t);
memmove(key_data_writer, &weekday, sizeof(uint8_t));
key_data_writer += sizeof(uint8_t);
memmove(key_data_writer, &key_schedule, sizeof(db_key_schedule_e));
MDB_val key;
key.mv_size = key_size;
key.mv_data = key_data;
if((err = mdb_get(mdb_txn, mdb_dbi, &key, value)) != 0)
{
LOG_ERROR("mdb_get error %s\n", mdb_strerror(err));
mdb_txn_abort(mdb_txn);
free(key_data);
return 1;
}
free(key_data);
return 0;
}
schedule_t*
schedule_load(MDB_env *mdb_env, uint8_t relay_num, uint8_t weekday)
{
int err;
MDB_txn *mdb_txn;
MDB_dbi mdb_dbi;
schedule_t *new_schedule;
uuid_t off_id;
memset(off_id, 0, sizeof(uuid_t));
memcpy(off_id, "off", 3);
if((err = mdb_txn_begin(mdb_env, NULL, MDB_RDONLY, &mdb_txn)) != 0)
{
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
return schedule_create(off_id, weekday, 0, NULL);
}
if((err = mdb_dbi_open(mdb_txn, "schedules", 0, &mdb_dbi)) != 0)
{
switch(err)
{
case MDB_NOTFOUND:
LOG_INFO("no schedule db found in db. returning new one (no schedules db)\n");
mdb_txn_abort(mdb_txn);
break;
default:
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
}
return schedule_create(off_id, weekday, 0, NULL);
}
MDB_val value;
if((err = schedule_load_single(mdb_txn, mdb_dbi, DB_KEY_SCHEDULE_ID, relay_num, weekday, &value)) != 0)
{
LOG_INFO("no schedule for relay %d and weekday %d found in db. returning new one\n", relay_num, weekday);
mdb_txn_abort(mdb_txn); // transaction is read only
return schedule_create(off_id, weekday, 0, NULL);
}
uuid_t *schedule_id = (uuid_t*)value.mv_data;
if((err = schedule_load_single(mdb_txn, mdb_dbi, DB_KEY_SCHEDULE_PERIODS, relay_num, weekday, &value)) != 0)
{
LOG_INFO("no schedule for relay %d and weekday %d found in db. returning new one\n", relay_num, weekday);
mdb_txn_abort(mdb_txn); // transaction is read only
return schedule_create(off_id, weekday, 0, NULL);
}
uint16_t schedule_periods_length = ((uint16_t*)value.mv_data)[0];
uint16_t *schedule_periods = ((uint16_t*)value.mv_data) + 1;
new_schedule = schedule_create(*schedule_id, weekday, schedule_periods_length, schedule_periods);
mdb_txn_abort(mdb_txn); // transaction is read only
return new_schedule;
}

View file

@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <models/schedule.h>
#include <models/relay.h>
#include <macros.h>
static int
schedule_save_single(MDB_txn *mdb_txn, MDB_dbi mdb_dbi, db_key_schedule_e key_schedule, uint8_t relay_num, uint8_t weekday, MDB_val value)
{
int err;
size_t key_size = sizeof(db_key_schedule_e) + (2 * sizeof(uint8_t));
uint8_t *key_data = malloc(key_size);
uint8_t *key_data_writer = key_data;
memmove(key_data_writer, &relay_num, sizeof(uint8_t));
key_data_writer += sizeof(uint8_t);
memmove(key_data_writer, &weekday, sizeof(uint8_t));
key_data_writer += sizeof(uint8_t);
memmove(key_data_writer, &key_schedule, sizeof(db_key_schedule_e));
MDB_val key;
key.mv_size = key_size;
key.mv_data = key_data;
if((err = mdb_put(mdb_txn, mdb_dbi, &key, &value, 0)) != 0)
{
LOG_ERROR("mdb_put error %s\n", mdb_strerror(err));
mdb_txn_abort(mdb_txn);
free(key_data);
return 1;
}
free(key_data);
return 0;
}
int
schedule_save(schedule_t *schedule, uint8_t relay_num, MDB_env *mdb_env)
{
char uuid_str[37];
uuid_unparse(schedule->id, uuid_str);
LOG_TRACE("saving schedule %s @ %p\n", uuid_str, schedule);
int err;
MDB_txn *mdb_txn;
MDB_dbi mdb_dbi;
MDB_val value;
if((err = mdb_txn_begin(mdb_env, NULL, 0, &mdb_txn)) != 0)
{
LOG_ERROR("mdb_txn_begin error %s\n", mdb_strerror(err));
exit(1);
}
if((err = mdb_dbi_open(mdb_txn, "schedules", MDB_CREATE, &mdb_dbi)) != 0)
{
LOG_ERROR("mdb_dbi_open error %s\n", mdb_strerror(err));
exit(1);
}
value.mv_size = sizeof(uuid_t);
value.mv_data = schedule->id;
if(schedule_save_single(mdb_txn, mdb_dbi, DB_KEY_SCHEDULE_ID, relay_num, schedule->weekday, value))
{
LOG_ERROR("failed to save ID\n");
return 1;
}
// save periods blob
uint16_t *periods_blob = schedule_periods_to_blob(schedule);
value.mv_size = sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
value.mv_data = periods_blob;
if(schedule_save_single(mdb_txn, mdb_dbi, DB_KEY_SCHEDULE_PERIODS, relay_num, schedule->weekday, value))
{
free(periods_blob);
LOG_ERROR("failed to save periods\n");
return 1;
}
free(periods_blob);
mdb_txn_commit(mdb_txn);
return 0;
}

6440
src/mpack.c Normal file

File diff suppressed because it is too large Load diff

35
src/runners/test.c Normal file
View file

@ -0,0 +1,35 @@
#include <unistd.h>
#include <runners.h>
#include <logger.h>
#include <drivers.h>
#include <drivers.h>
void
runner_test(controller_t *controller)
{
// from x down to 0 to turn all relays off in the end
for(uint_fast8_t i = 0; i < controller->relay_count; ++i)
{
for(int test_run = 2; test_run >= 0; --test_run)
{
int is_active = test_run % 2;
if(global_config.relay_configs[i].inverted)
{
is_active = !is_active;
}
switch(global_config.relay_configs[i].driver)
{
case RELAY_DRIVER_GPIO:
driver_gpio_set(global_config.relay_configs[i].pin, is_active);
break;
case RELAY_DRIVER_PIFACE:
driver_piface_set(global_config.relay_configs[i].pin, is_active);
break;
default:
LOG_WARN("relay %d is not using a driver\n", i);
}
sleep(1);
}
}
}