Prepare go-rewrite

This commit is contained in:
Tobias Reisinger 2021-02-11 12:32:08 +01:00
parent c49ada88c3
commit c515f55b8b
57 changed files with 103 additions and 42639 deletions

View file

@ -1,94 +0,0 @@
cmake_minimum_required (VERSION 3.7)
project(controller
VERSION 0.4.0
LANGUAGES C)
add_executable(controller src/main.c)
target_link_libraries(controller -lwiringPi)
target_link_libraries(controller -lwiringPiDev)
target_link_libraries(controller -lsqlite3)
target_link_libraries(controller -luuid)
option(WIRING_PI_DEBUG "Use WiringPi Debugging Tool (OFF)" OFF)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D'__FILENAME__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR}/src/)/,,$(abspath $<))\"'")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
file(GLOB_RECURSE ALL_SOURCE_FILES src/*.c)
if(WIRING_PI_DEBUG)
message("Showing wiringPi calls as debug")
add_definitions("-DWIRING_PI_DEBUG")
endif(WIRING_PI_DEBUG)
aux_source_directory(src/ SRC_DIR)
aux_source_directory(src/models MODELS_SRC)
aux_source_directory(src/helpers HELPERS_SRC)
aux_source_directory(src/handlers HANDLERS_SRC)
aux_source_directory(src/drivers DRIVERS_SRC)
aux_source_directory(src/runners RUNNERS_SRC)
aux_source_directory(vendor VENDOR_SRC)
add_dependencies(controller sql)
configure_file("version.h.in" "version.h" @ONLY)
target_sources(controller PRIVATE ${SRC_DIR} ${MODELS_SRC} ${HELPERS_SRC} ${HANDLERS_SRC} ${DRIVERS_SRC} ${RUNNERS_SRC} ${VENDOR_SRC})
target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor)
target_include_directories(controller PRIVATE ${CMAKE_BINARY_DIR})
add_custom_target(sql
COMMAND ./compile_sql.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(run
COMMAND ${CMAKE_BINARY_DIR}/controller
DEPENDS controller
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(docs
COMMAND doxygen
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
IF(CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "loading debug targets")
add_custom_target(debug
COMMAND gdb ${CMAKE_BINARY_DIR}/controller
DEPENDS controller
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind
COMMAND valgrind -s ${CMAKE_BINARY_DIR}/controller
DEPENDS controller
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind-leak
COMMAND valgrind --leak-check=full --show-leak-kinds=all ${CMAKE_BINARY_DIR}/controller
DEPENDS controller
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(
clang-tidy
COMMAND /usr/bin/clang-tidy
${ALL_SOURCE_FILES}
--header-filter=${CMAKE_SOURCE_DIR}/include/*
--
-std=gnu99
-I${CMAKE_SOURCE_DIR}/include
-I${CMAKE_SOURCE_DIR}/vendor
-I${CMAKE_BINARY_DIR}
)
ENDIF(CMAKE_BUILD_TYPE MATCHES Debug)

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module emgauwa.app/controller
go 1.15

View file

@ -1,13 +0,0 @@
#ifndef CONTROLLER_CLI_H
#define CONTROLLER_CLI_H
typedef struct cli
{
const char *config_file;
int demo_mode;
} cli_t;
void
cli_parse(int argc, const char **argv, cli_t *cli);
#endif /* CONTROLLER_CLI_H */

View file

@ -1,18 +0,0 @@
#ifndef CONTROLLER_COLORS_H
#define CONTROLLER_COLORS_H
#define COLOR_RED "\033[0;31m"
#define COLORB_RED "\033[1;31m"
#define COLOR_GREEN "\033[0;32m"
#define COLORB_GREEN "\033[1;32m"
#define COLOR_YELLOW "\033[0;33m"
#define COLORB_YELLOW "\033[1;33m"
#define COLOR_BLUE "\033[0;34m"
#define COLORB_BLUE "\033[1;34m"
#define COLOR_MAGENTA "\033[0;35m"
#define COLORB_MAGENTA "\033[1;35m"
#define COLOR_CYAN "\033[0;36m"
#define COLORB_CYAN "\033[1;36m"
#define COLOR_NONE "\033[0m"
#endif //CONTROLLER_COLORS_H

View file

@ -1,66 +0,0 @@
#ifndef CONTROLLER_CONFIG_H
#define CONTROLLER_CONFIG_H
#include <stdint.h>
#include <toml.h>
#include <constants.h>
#include <enums.h>
typedef struct
{
uint8_t pin;
int inverted;
int init;
relay_driver_t driver;
uint8_t pulse_duration;
} config_relay_t;
typedef struct
{
char *name;
char *database;
char *user;
char *group;
char *mqtt_host;
char *include;
int relays_init;
uint8_t relay_count;
struct
{
int level;
FILE *file;
} logging;
struct
{
uint16_t discovery;
uint16_t mqtt;
} ports;
config_relay_t *relay_configs;
} config_t;
extern config_t *global_config;
void
config_init();
void
config_free();
void
config_load_string(char **holder, const char *value);
void
config_load(config_t *config, const char *cli_config_file);
void
config_load_file(config_t *config, const char *file_name);
void
config_load_directory(config_t *config, const char *directory_name);
#endif //CONTROLLER_CONFIG_H

View file

@ -1,17 +0,0 @@
#ifndef CONTROLLER_CONNECTIONS_H
#define CONTROLLER_CONNECTIONS_H
#include <mongoose.h>
struct mg_connection*
connection_discovery_bind(struct mg_mgr *mgr);
struct mg_connection*
connection_command_bind(struct mg_mgr *mgr);
struct mg_connection*
connection_mqtt_connect(struct mg_mgr *mgr);
extern struct mg_connection *global_connection_mqtt;
#endif /* CONTROLLER_CONNECTIONS_H */

View file

@ -1,32 +0,0 @@
#ifndef CONTROLLER_CONTANTS_H
#define CONTROLLER_CONTANTS_H
#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
#define SECONDS_PER_MINUTE 60
#define POLL_FDS_COUNT 2
/**
* @brief Limit the maximum length of a controller/relay/etc name
*
* The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required.
*/
#define MAX_NAME_LENGTH 128
/**
* @brief How many milli seconds to wait until poll timeout in main loop
*/
#define ACCEPT_TIMEOUT_MSECONDS 1000
#define PIFACE_GPIO_BASE 200
#define DEFAULT_CONTROLLER_NAME "emgauwa-controller"
#define DEFAULT_CONFIG_PATH "emgauwa-controller.conf"
#define DEFAULT_GLOBAL_CONFIG_PATH "/etc/emgauwa/controller.conf"
#define DEFAULT_DATABASE_PATH "emgauwa-controller.sqlite"
#define DEFAULT_DISCOVERY_PORT 4421
#define DEFAULT_MQTT_PORT 1885
#define DEFAULT_MQTT_HOST "127.0.0.1"
#endif /* CONTROLLER_CONTANTS_H */

View file

@ -1,40 +0,0 @@
#ifndef CONTROLLER_DATABASE_H
#define CONTROLLER_DATABASE_H
#include <sqlite3.h>
extern sqlite3 *global_database;
void
database_init();
void
database_free();
void
database_migrate();
int
database_transaction_begin();
void
database_transaction_commit();
void
database_transaction_rollback();
int
database_helper_get_id(sqlite3_stmt *stmt);
int*
database_helper_get_ids(sqlite3_stmt *stmt);
char*
database_helper_get_string(sqlite3_stmt *stmt);
char**
database_helper_get_strings(sqlite3_stmt *stmt);
#endif /* CONTROLLER_DATABASE_H */

View file

@ -1,13 +0,0 @@
#ifndef CONTROLLER_DRIVERS_H
#define CONTROLLER_DRIVERS_H
#include <models/relay.h>
#include <enums.h>
void
driver_piface_set(int pin, int value);
void
driver_gpio_set(int pin, int value);
#endif /* CONTROLLER_DRIVERS_H */

View file

@ -1,25 +0,0 @@
#ifndef CONTROLLER_ENUMS_H
#define CONTROLLER_ENUMS_H
typedef enum
{
POLL_FDS_DISCOVERY,
POLL_FDS_COMMAND
} poll_fds_t;
typedef enum
{
DISCOVERY_MAPPING_ID = 0,
DISCOVERY_MAPPING_NAME = 1,
DISCOVERY_MAPPING_COMMAND_PORT = 2,
DISCOVERY_MAPPING_RELAY_COUNT = 3,
} discovery_mapping_t;
typedef enum
{
RELAY_DRIVER_NONE,
RELAY_DRIVER_GPIO,
RELAY_DRIVER_PIFACE,
} relay_driver_t;
#endif /* CONTROLLER_ENUMS_H */

View file

@ -1,32 +0,0 @@
#ifndef CONTROLLER_HANDLERS_H
#define CONTROLLER_HANDLERS_H
#include <mongoose.h>
#include <models/controller.h>
/**
* @brief Handle the command processing
*
* @param fd File descriptor to receive initial data from
* @param controller Controller to use for answering command
*/
void
handler_command(struct mg_connection *c, int ev, void *ev_data);
/**
* @brief Handle the discovery processing
*
* @param fd File descriptor to receive initial data from
* @param controller Controller to use for answering discovery
*/
void
handler_discovery(struct mg_connection *c, int ev, void *ev_data);
void
handler_mqtt(struct mg_connection *c, int ev, void *ev_data);
void
handler_loop(struct mg_connection *c_mqtt);
#endif /* CONTROLLER_HANDLERS_H */

View file

@ -1,34 +0,0 @@
#ifndef CONTROLLER_HELPERS_H
#define CONTROLLER_HELPERS_H
#include <time.h>
#include <config.h>
int
helper_connect_tcp_server(char* host, uint16_t port);
int
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog);
uint16_t
helper_get_port(int sock);
/**
* @brief Open socket for discovery
*
* Will exit program when unable to open socket.
*
* @param discovery_port Port number to listen on for discovery broadcasts
*
* @return Open socket to accept discovery broadcasts on
*/
int
helper_open_discovery_socket(uint16_t discovery_port);
int
helper_get_weekday(const struct tm *time_struct);
int
helper_drop_privileges();
#endif /* CONTROLLER_HELPERS_H */

View file

@ -1,22 +0,0 @@
#ifndef EMGAUWA_LOGGER_H
#define EMGAUWA_LOGGER_H
#include <stdio.h>
#include <time.h>
#include <syslog.h>
#include <colors.h>
#include <config.h>
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...);
#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#endif //EMGAUWA_LOGGER_H

View file

@ -1,6 +0,0 @@
#ifndef CONTROLLER_MACROS_H
#define CONTROLLER_MACROS_H
#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
#endif /* CONTROLLER_MACROS_H */

View file

@ -1,107 +0,0 @@
#ifndef CONTROLLER_CONTROLLER_H
#define CONTROLLER_CONTROLLER_H
#include <uuid/uuid.h>
#include <stdint.h>
#include <lmdb.h>
#include <config.h>
#include <models/relay.h>
/**
* @brief Information about this controller
*/
typedef struct
{
int id;
/**
* @brief A unique UUID for this controller
*/
uuid_t uid;
/**
* @brief The name of this controller
*
* Includes a \0 terminator.
*/
char name[MAX_NAME_LENGTH + 1];
/**
* @brief The command port the controller was bound to
*/
uint16_t command_port;
relay_t **relays;
} controller_t;
/**
* @brief Key to save controller information in database
*/
typedef enum
{
DB_KEY_CONTROLLER_ID = 0,
DB_KEY_CONTROLLER_NAME = 1,
DB_KEY_CONTROLLER_COMMAND_PORT = 2,
DB_KEY_CONTROLLER_DISCOVERY_PORT = 3,
} db_key_controller_e;
/**
* @brief Create a new instance of controller
*
* This should not fail. The instance will be created with malloc and genric default values
*
* @return A new instance of #controller
*/
controller_t*
controller_create(void);
/**
* @brief Load a controller for database or create a new one
*
* Will return NULL when transaction can't start.
*
* @param mdb_env An opened MDB_env to load from
*
* @return A loaded or new instance of controller or NULL on database error
*/
controller_t*
controller_load();
/**
* @brief Save a controller to the database
*
* @param controller Instance of a controller
* @param mdb_env Already created MDB_env
*
* @return Indicator to show success (0) or failure (!0)
*/
int
controller_save();
/**
* @brief Sets a name to a controller.
* This function won't perform any checks (e.g. no NULL checks)
*
* @param controller Set the name to this controller
* @param name Name to be set
*/
void
controller_set_name(controller_t *controller, const char *name);
void
controller_free(controller_t *controller);
/**
* @brief Debug an instance of #controller
*
* Will use #LOG_DEBUG to log. So log will be depending on #LOG_LEVEL
*
* @param cntrlr #controller to debug
*/
void
controller_debug(controller_t *controller);
extern controller_t *global_controller;
#endif //CONTROLLER_CONTROLLER_H

View file

@ -1,16 +0,0 @@
#ifndef CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H
#define CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id);
int
junction_relay_schedule_remove_for_relay(int relay_id);
int
junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids);
int*
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id);
#endif /* CONTROLLER_MODELS_JUNCTION_RELAY_SCHEDULE_H */

View file

@ -1,16 +0,0 @@
#ifndef CONTROLLER_PERIOD_H
#define CONTROLLER_PERIOD_H
#include <stdint.h>
#include <time.h>
typedef struct
{
uint16_t start;
uint16_t end;
} period_t;
int
period_includes_time(period_t period, struct tm *time_struct);
#endif /* CONTROLLER_PERIOD_H */

View file

@ -1,54 +0,0 @@
#ifndef CONTROLLER_RELAY_H
#define CONTROLLER_RELAY_H
#include <stdint.h>
#include <time.h>
#include <lmdb.h>
#include <constants.h>
#include <models/schedule.h>
typedef struct
{
int id;
uint8_t number;
int is_on;
int pulse_timer;
int sent_to_broker;
char name[MAX_NAME_LENGTH + 1];
schedule_t *schedules[7];
} relay_t;
/**
* @brief Key to save relay information in database
*/
typedef enum
{
DB_KEY_RELAY_NAME = 0,
} db_key_relay_e;
relay_t*
relay_create(uint8_t number);
void
relay_set_name(relay_t *relay, const char *name);
relay_t*
relay_load(uint8_t number);
int
relay_save(relay_t *relay);
void
relay_reload_schedules(relay_t *relay);
int
relay_is_on_schedule(relay_t *relay, struct tm *time_struct);
void
relay_free(relay_t *relay);
void
relay_debug(relay_t *relay);
#endif //CONTROLLER_RELAY_H

View file

@ -1,58 +0,0 @@
#ifndef CONTROLLER_SCHEDULE_H
#define CONTROLLER_SCHEDULE_H
#include <stdint.h>
#include <uuid/uuid.h>
#include <lmdb.h>
#include <models/period.h>
typedef struct
{
int id;
uuid_t uid;
uint8_t weekday;
uint16_t periods_count;
period_t *periods;
} schedule_t;
/**
* @brief Key to save schedule information in database
*/
typedef enum
{
DB_KEY_SCHEDULE_ID = 0,
DB_KEY_SCHEDULE_PERIODS = 1,
} db_key_schedule_e;
schedule_t*
schedule_create(uuid_t uid, uint16_t length, uint16_t *periods_blob);
int
schedule_save(schedule_t *schedule);
schedule_t*
schedule_get_by_uid(uuid_t uid);
schedule_t**
schedule_get_relay_weekdays(int relay_id);
uint16_t*
schedule_periods_to_blob(schedule_t *schedule);
void
schedule_free(schedule_t *schedule);
void
schedule_free_list(schedule_t **schedules_list);
void
schedule_debug(schedule_t *schedule);
int
schedule_uid_parse(const char *uid_str, uuid_t result);
void
schedule_uid_unparse(const uuid_t uid, char *result);
#endif /* CONTROLLER_SCHEDULE_H */

View file

@ -1,11 +0,0 @@
#ifndef CONTROLLER_RUNNERS_H
#define CONTROLLER_RUNNERS_H
#include <stdint.h>
#include <models/controller.h>
void
runner_test();
#endif /* CONTROLLER_RUNNERS_H */

View file

View file

@ -1,16 +0,0 @@
#ifndef CONTROLLER_WIRING_DEBUG_H
#define CONTROLLER_WIRING_DEBUG_H
#include <logger.h>
#ifdef WIRING_PI_DEBUG
#define LOG_WIRING_PI LOGGER_DEBUG
#define wiringPiSetup() LOG_WIRING_PI("wiringPi wiringPiSetup()\n")
#define wiringPiSetupSys() LOG_WIRING_PI("wiringPi wiringPiSetupSys()\n")
#define pinMode(x,y) LOG_WIRING_PI("wiringPi pinMode(%d, %d)\n", x, y)
#define digitalWrite(x,y) LOG_WIRING_PI("wiringPi digitalWrite(%d, %d)\n", x, y)
#define piFaceSetup(x) LOG_WIRING_PI("wiringPi piFaceSetup(%d)\n", x)
#endif
#endif /* CONTROLLER_WIRING_DEBUG_H */

100
main.go Normal file
View file

@ -0,0 +1,100 @@
package main
/*
#cgo CFLAGS: -g -Wall
#cgo LDFLAGS: -lwiringPi -lwiringPiDev
#include <wiringPi.h>
*/
import "C"
import (
"net/http"
"strings"
"github.com/go-playground/validator/v10"
"github.com/spf13/viper"
"emgauwa.app/emgauwa-core/models"
"emgauwa.app/emgauwa-core/utils"
)
type (
EmgauwaValidator struct {
validator *validator.Validate
}
)
func (cv *EmgauwaValidator) Validate(i interface{}) error {
return cv.validator.Struct(i)
}
func SkipperSwagger(c echo.Context) bool {
if strings.Contains(c.Request().URL.Path, "swagger") {
return true
}
return false
}
func init() {
utils.ConfigInit()
utils.LoggerInit()
models.DatabaseInit()
}
// @title Emgauwa API
// @version 1.0
// @description This is a sample server server.
// @termsOfService http://swagger.io/terms/
// @contact.name Tobias Reisinger
// @contact.url https://serguzim.me
// @contact.email tobias@msrg.cc
func main() {
C.wiringPiSetup()
e := echo.New()
v := validator.New()
v.RegisterValidation("hhmmformat", utils.ValidatorsHHMMFormat)
e.Validator = &EmgauwaValidator{validator: v}
e.Pre(middleware.RemoveTrailingSlash())
e.Use(middleware.CORS())
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Skipper: SkipperSwagger,
}))
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: "${time_rfc3339} [${method}] ${uri} > ${status} (${latency_human})\n",
}))
e.Use(middleware.Recover())
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
Skipper: SkipperSwagger,
Root: viper.GetString("content-dir"),
HTML5: true,
}))
e.GET("api/swagger/*", echoSwagger.WrapHandler)
e.GET("api/swagger", func(c echo.Context) error {
return c.Redirect(http.StatusMovedPermanently, "/api/swagger/index.html")
})
r_api_v1 := e.Group("api/v1")
{
r_api_v1.GET("/schedules", api_v1.Schedules_GET)
r_api_v1.POST("/schedules", api_v1.Schedules_POST)
r_api_v1.GET("/schedules/:schedule_uid", api_v1.Schedules_UID_GET)
r_api_v1.PUT("/schedules/:schedule_uid", api_v1.Schedules_UID_PUT)
r_api_v1.DELETE("/schedules/:schedule_uid", api_v1.Schedules_UID_DELETE)
r_api_v1.GET("/schedules/tag/:tag", api_v1.Schedules_Tag_TAG_GET)
r_api_v1.POST("/schedules/list", api_v1.Schedules_List_POST)
r_api_v1.GET("/controllers", api_v1.Controllers_GET)
r_api_v1.GET("/tags", api_v1.Tags_GET)
r_api_v1.GET("/ws/relays", api_v1.WS_Relays_GET)
}
e.Start(viper.GetString("bind.http"))
}

View file

@ -1,49 +0,0 @@
create table controllers
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128),
command_port INTEGER
);
create table relays
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
number INTEGER
NOT NULL,
name VARCHAR(128)
);
create table schedules
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128),
periods BLOB
);
create table junction_relay_schedule
(
weekday SMALLINT
NOT NULL,
relay_id INTEGER
REFERENCES relays (id)
ON DELETE CASCADE,
schedule_id INTEGER
DEFAULT 1
REFERENCES schedules (id)
ON DELETE SET DEFAULT
);
INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00');
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05');

View file

@ -1,49 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <argparse.h>
#include <cli.h>
#include <config.h>
#include <logger.h>
#include <helpers.h>
#include <version.h>
static const char *const usage[] = {
"controller [options]",
NULL,
};
void
cli_parse(int argc, const char **argv, cli_t *cli)
{
cli->config_file = NULL;
cli->demo_mode = 0;
int version = 0;
struct argparse_option options[] =
{
OPT_HELP(),
OPT_GROUP("Basic options"),
OPT_STRING('c', "config", &cli->config_file, "path to config file", NULL, 0, OPT_NONEG),
OPT_BOOLEAN('d', "demo", &cli->demo_mode, "demo mode", 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."
);
argparse_parse(&argparse, argc, argv);
if(version)
{
printf("%s\n", EMGAUWA_CONTROLLER_VERSION);
exit(0);
}
}

View file

@ -1,442 +0,0 @@
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <config.h>
#include <constants.h>
#include <logger.h>
config_t *global_config;
static int
config_load_log_level(config_t *config, char *value)
{
if(strcmp(value, "debug") == 0)
{
setlogmask(LOG_UPTO(LOG_DEBUG));
config->logging.level = LOG_DEBUG;
return 0;
}
if(strcmp(value, "info") == 0)
{
setlogmask(LOG_UPTO(LOG_INFO));
config->logging.level = LOG_INFO;
return 0;
}
if(strcmp(value, "notice") == 0)
{
setlogmask(LOG_UPTO(LOG_NOTICE));
config->logging.level = LOG_NOTICE;
return 0;
}
if(strcmp(value, "warning") == 0)
{
setlogmask(LOG_UPTO(LOG_WARNING));
config->logging.level = LOG_WARNING;
return 0;
}
if(strcmp(value, "err") == 0)
{
setlogmask(LOG_UPTO(LOG_ERR));
config->logging.level = LOG_ERR;
return 0;
}
if(strcmp(value, "crit") == 0)
{
setlogmask(LOG_UPTO(LOG_CRIT));
config->logging.level = LOG_CRIT;
return 0;
}
if(strcmp(value, "emerg") == 0)
{
setlogmask(LOG_UPTO(LOG_EMERG));
config->logging.level = LOG_EMERG;
return 0;
}
LOGGER_WARNING("invalid log-level '%s'\n", value);
return 0;
}
static int
config_load_log_file(config_t *config, char *value)
{
if(strcmp(value, "stdout") == 0)
{
config->logging.file = stdout;
return 0;
}
if(strcmp(value, "stderr") == 0)
{
config->logging.file = stderr;
return 0;
}
config->logging.file = fopen(value, "a+");
return 0;
}
static void
config_init_relay_configs(config_t *config)
{
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;
config->relay_configs[i].inverted = 0;
config->relay_configs[i].init = -1;
config->relay_configs[i].pin = 0;
config->relay_configs[i].pulse_duration = 0;
}
}
static void
config_load_section_controller(config_t *config, toml_table_t* controller)
{
toml_datum_t config_entry;
config_entry = toml_string_in(controller, "name");
if(config_entry.ok)
{
config_load_string(&config->name, config_entry.u.s);
free(config_entry.u.s);
}
config_entry = toml_string_in(controller, "database");
if(config_entry.ok)
{
config_load_string(&config->database, config_entry.u.s);
free(config_entry.u.s);
}
config_entry = toml_string_in(controller, "user");
if(config_entry.ok)
{
config_load_string(&config->user, config_entry.u.s);
free(config_entry.u.s);
}
config_entry = toml_string_in(controller, "group");
if(config_entry.ok)
{
config_load_string(&config->group, config_entry.u.s);
free(config_entry.u.s);
}
config_entry = toml_string_in(controller, "mqtt-host");
if(config_entry.ok)
{
config_load_string(&config->mqtt_host, config_entry.u.s);
free(config_entry.u.s);
}
config_entry = toml_int_in(controller, "relays-init");
if(config_entry.ok)
{
config->relays_init = config_entry.u.i;
}
config_entry = toml_string_in(controller, "include");
if(config_entry.ok)
{
config_load_string(&config->include, config_entry.u.s);
free(config_entry.u.s);
}
}
static void
config_load_section_logging(config_t *config, toml_table_t* logging)
{
toml_datum_t config_entry;
config_entry = toml_string_in(logging, "level");
if(config_entry.ok)
{
config_load_log_level(config, config_entry.u.s);
free(config_entry.u.s);
}
config_entry = toml_string_in(logging, "file");
if(config_entry.ok)
{
config_load_log_file(config, config_entry.u.s);
free(config_entry.u.s);
}
}
static void
config_load_section_ports(config_t *config, toml_table_t* ports)
{
toml_datum_t config_entry;
config_entry = toml_int_in(ports, "discovery");
if(config_entry.ok)
{
config->ports.discovery = config_entry.u.i;
}
config_entry = toml_int_in(ports, "mqtt");
if(config_entry.ok)
{
config->ports.mqtt = config_entry.u.i;
}
}
static void
config_load_section_relay(config_t *config, toml_table_t* relay, int relay_num)
{
toml_datum_t config_entry;
config_entry = toml_int_in(relay, "pin");
if(config_entry.ok)
{
config->relay_configs[relay_num].pin = config_entry.u.i;
}
config_entry = toml_int_in(relay, "inverted");
if(config_entry.ok)
{
config->relay_configs[relay_num].inverted = config_entry.u.i;
}
config_entry = toml_int_in(relay, "init");
if(config_entry.ok)
{
config->relay_configs[relay_num].init = config_entry.u.i;
}
config_entry = toml_int_in(relay, "pulse-duration");
if(config_entry.ok)
{
config->relay_configs[relay_num].pulse_duration = config_entry.u.i;
}
config_entry = toml_string_in(relay, "driver");
if(config_entry.ok)
{
for(int i = 0; config_entry.u.s[i] != '\0'; ++i)
{
config_entry.u.s[i] = tolower(config_entry.u.s[i]);
}
if(strcmp(config_entry.u.s, "gpio") == 0)
{
config->relay_configs[relay_num].driver = RELAY_DRIVER_GPIO;
}
else if(strcmp(config_entry.u.s, "piface") == 0)
{
config->relay_configs[relay_num].driver = RELAY_DRIVER_PIFACE;
}
else
{
LOGGER_WARNING("invalid driver '%s' in section for relay %d\n", config_entry.u.s, relay_num);
}
free(config_entry.u.s);
}
}
void
config_init()
{
global_config = calloc(1, sizeof(config_t));
config_load_string(&global_config->name, DEFAULT_CONTROLLER_NAME);
config_load_string(&global_config->database, DEFAULT_DATABASE_PATH);
config_load_string(&global_config->mqtt_host, DEFAULT_MQTT_HOST);
global_config->user = NULL;
global_config->group = NULL;
global_config->include = NULL;
global_config->relays_init = 0;
global_config->relay_count = 0;
config_load_string(&global_config->mqtt_host, "127.0.0.1");
global_config->ports.mqtt = DEFAULT_MQTT_PORT;
global_config->ports.discovery = DEFAULT_DISCOVERY_PORT;
global_config->logging.level = LOG_DEBUG;
global_config->logging.file = stdout;
}
void
config_free()
{
free(global_config->name);
free(global_config->database);
free(global_config->user);
free(global_config->group);
free(global_config->mqtt_host);
free(global_config->include);
free(global_config);
}
void
config_load_string(char **holder, const char *value)
{
if(*holder)
{
free(*holder);
}
size_t value_len = strlen(value);
char *new_holder = malloc(sizeof(char) * (value_len + 1));
strcpy(new_holder, value);
new_holder[value_len] = '\0';
*holder = new_holder;
}
static int
config_try_file(const char *path)
{
if(access(path, F_OK) != 0)
{
return 1;
}
if(access(path, R_OK) != 0)
{
return 1;
}
return 0;
}
void
config_load(config_t *config, const char *cli_config_file)
{
if(cli_config_file)
{
if(config_try_file(cli_config_file) == 0)
{
config_load_file(config, cli_config_file);
return;
}
LOGGER_CRIT("unable to open the passed config file '%s'\n", cli_config_file);
exit(1);
}
if(config_try_file(DEFAULT_CONFIG_PATH) == 0)
{
config_load_file(config, DEFAULT_CONFIG_PATH);
return;
}
if(config_try_file(DEFAULT_GLOBAL_CONFIG_PATH) == 0)
{
config_load_file(config, DEFAULT_GLOBAL_CONFIG_PATH);
return;
}
}
void
config_load_file(config_t *config, const char *file_name)
{
FILE *fp;
toml_table_t* config_toml;
char errbuf[256];
/* Open the file and parse content */
fp = fopen(file_name, "r");
if(fp == NULL) {
LOGGER_CRIT("unable to open config file '%s'\n", file_name);
exit(1);
}
config_toml = toml_parse_file(fp, errbuf, sizeof(errbuf));
fclose(fp);
if(config_toml == NULL) {
LOGGER_CRIT("unable to parse config file '%s': %s\n", file_name, errbuf);
exit(1);
}
toml_table_t* controller = toml_table_in(config_toml, "controller");
if(controller)
{
config_load_section_controller(config, controller);
}
toml_table_t* logging = toml_table_in(config_toml, "logging");
if(logging)
{
config_load_section_logging(config, logging);
}
toml_table_t* ports = toml_table_in(config_toml, "ports");
if(ports)
{
config_load_section_ports(config, ports);
}
toml_array_t* relays = toml_array_in(config_toml, "relays");
if(relays)
{
config->relay_count = toml_array_nelem(relays);
config_init_relay_configs(config);
for(int i = 0; i < config->relay_count; ++i)
{
config_load_section_relay(config, toml_table_at(relays, i), i);
}
}
toml_free(config_toml);
LOGGER_DEBUG("Loaded config from %s\n", file_name);
}
void
config_load_directory(config_t *config, const char *directory_name)
{
struct dirent *directory_entry;
DIR *directory;
(void)config;
directory = opendir(directory_name);
if(directory == NULL)
{
LOGGER_CRIT("cannot open directory '%s': %s\n", directory_name, strerror(errno));
exit(1);
}
while((directory_entry = readdir(directory)) != NULL)
{
struct stat sb;
const char *entry_name = directory_entry->d_name;
size_t copied = 0;
// Add 2 for '/' and '\0'.
size_t path_len = strlen(directory_name) + strlen(entry_name) + 1;
char *path = malloc(sizeof(char) * (path_len + 1));
path[0] = '\0';
strncat(path + copied, directory_name, path_len - copied);
copied = strlen(path);
if(path[copied - 1] != '/')
{
strncat(path + copied, "/", path_len - copied);
copied = strlen(path);
}
strncat(path + copied, entry_name, path_len - copied);
if(stat(path, &sb))
{
LOGGER_WARNING("failed to get info for '%s': %s\n", path, strerror(errno));
}
if(S_ISREG(sb.st_mode))
{
config_load_file(config, path);
}
free(path);
}
closedir(directory);
}

View file

@ -1,35 +0,0 @@
#include <stdio.h>
#include <connections.h>
#include <models/controller.h>
#include <config.h>
#include <handlers.h>
struct mg_connection *global_connection_mqtt;
struct mg_connection*
connection_discovery_bind(struct mg_mgr *mgr)
{
char address[32];
sprintf(address, "udp://0.0.0.0:%u", global_config->ports.discovery);
struct mg_connection *c = mg_bind(mgr, address, handler_discovery);
return c;
}
struct mg_connection*
connection_command_bind(struct mg_mgr *mgr)
{
char address[32];
sprintf(address, "tcp://0.0.0.0:%u", global_controller->command_port);
struct mg_connection *c = mg_bind(mgr, address, handler_command);
return c;
}
struct mg_connection*
connection_mqtt_connect(struct mg_mgr *mgr)
{
char address[512];
sprintf(address, "tcp://%s:%u", global_config->mqtt_host, global_config->ports.mqtt);
struct mg_connection *c = mg_connect(mgr, address, handler_mqtt);
return c;
}

View file

@ -1,264 +0,0 @@
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <logger.h>
#include <database.h>
#include <sql/migration_0.h>
sqlite3 *global_database;
static int in_transaction;
void
database_init()
{
int rc = sqlite3_open(global_config->database, &global_database);
if(rc)
{
LOGGER_CRIT("can't open database: %s\n", sqlite3_errmsg(global_database));
exit(1);
}
database_migrate();
sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0);
in_transaction = 0;
}
void
database_free()
{
sqlite3_close(global_database);
}
void
database_migrate()
{
uint16_t version_num = 0;
int s, rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL);
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
version_num = sqlite3_column_int(stmt, 0);
}
else
{
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
LOGGER_INFO("migrating LEVEL 0\n");
rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg);
if(rc)
{
LOGGER_CRIT("couldn't migrate LEVEL 0 (%s)\n", err_msg);
exit(1);
}
new_version_num = 1;
default:
break;
}
char pragma_query[32];
sprintf(pragma_query, "PRAGMA user_version=%d;", new_version_num);
sqlite3_exec(global_database, pragma_query, 0, 0, 0);
LOGGER_DEBUG("storing new user_version %d\n", new_version_num);
return;
}
int
database_transaction_begin()
{
if(!in_transaction)
{
LOGGER_DEBUG("beginning transaction\n");
sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
in_transaction = 1;
return 1;
}
return 0;
}
void
database_transaction_commit()
{
LOGGER_DEBUG("commiting transaction\n");
sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
in_transaction = 0;
}
void
database_transaction_rollback()
{
LOGGER_DEBUG("rolling back transaction\n");
sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
in_transaction = 0;
}
int
database_helper_get_id(sqlite3_stmt *stmt)
{
int result = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
result = sqlite3_column_int(stmt, 0);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return 0;
}
}
}
sqlite3_finalize(stmt);
return result;
}
int*
database_helper_get_ids(sqlite3_stmt *stmt)
{
int *result = malloc(sizeof(int));
int new_id;
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
new_id = sqlite3_column_int(stmt, 0);
if(new_id != 0) // found row for other target (relay <> schedule)
{
row++;
result = (int*)realloc(result, sizeof(int) * (row + 1));
result[row - 1] = new_id;
}
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
result[row] = 0;
return result;
}
char*
database_helper_get_string(sqlite3_stmt *stmt)
{
char *result = NULL;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
const char *found_string = (const char *)sqlite3_column_text(stmt, 0);
result = (char*)malloc(sizeof(char) * (strlen(found_string) + 1));
strcpy(result, found_string);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
return result;
}
char**
database_helper_get_strings(sqlite3_stmt *stmt)
{
char **result = malloc(sizeof(char*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
const char *new_string = (const char *)sqlite3_column_text(stmt, 0);
int new_string_len = strlen(new_string);
row++;
result = (char**)realloc(result, sizeof(char*) * (row + 1));
result[row - 1] = malloc(sizeof(char) * (new_string_len + 1));
strcpy(result[row - 1], new_string);
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
result[row] = NULL;
return result;
}

View file

@ -1,11 +0,0 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_gpio_set(int pin, int value)
{
digitalWrite(pin, value);
}

View file

@ -1,11 +0,0 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_piface_set(int pin, int value)
{
digitalWrite(PIFACE_GPIO_BASE + pin, value);
}

View file

@ -1,251 +0,0 @@
#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 <database.h>
#include <handlers.h>
#include <helpers.h>
#include <mpack.h>
#include <models/controller.h>
#include <models/relay.h>
#include <models/schedule.h>
#include <models/junction_relay_schedule.h>
typedef enum
{
COMMAND_CODE_CONTROLLER_ID_GET = 0,
COMMAND_CODE_CONTROLLER_TIME_GET = 1,
COMMAND_CODE_CONTROLLER_NAME_SET = 2,
COMMAND_CODE_CONTROLLER_NAME_GET = 3,
COMMAND_CODE_RELAY_SCHEDULES_SET = 100,
COMMAND_CODE_RELAY_SCHEDULES_GET = 101,
COMMAND_CODE_RELAY_NAME_SET = 102,
COMMAND_CODE_RELAY_NAME_GET = 103,
COMMAND_CODE_RELAY_PULSE = 200,
COMMAND_CODE_SCHEDULE_UPDATE = 300
} command_code_t;
typedef enum
{
COMMAND_MAPPING_CODE = 0,
COMMAND_MAPPING_NAME = 1,
COMMAND_MAPPING_RELAY_NUM = 2,
COMMAND_MAPPING_SCHEDULES_ARRAY = 3,
COMMAND_MAPPING_SCHEDULE_ID = 4,
COMMAND_MAPPING_PERIODS_COUNT = 5,
COMMAND_MAPPING_PERIODS_BLOB = 6,
COMMAND_MAPPING_PULSE_DURATION = 7,
} control_mapping_t;
static void
handler_command_relay_pulse(mpack_node_t map)
{
uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
if(relay_num > global_config->relay_count)
{
LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count);
return;
}
relay_t *target_relay = global_controller->relays[relay_num];
(void)target_relay;
int duration = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_PULSE_DURATION));
if(duration == 0)
{
duration = global_config->relay_configs[relay_num].pulse_duration;
}
target_relay->pulse_timer = duration;
LOGGER_DEBUG("pulsing relay %d for %ds\n", relay_num, duration);
}
static void
handler_command_controller_name_set(mpack_node_t map)
{
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(global_controller, name_buffer);
LOGGER_DEBUG("setting new name %s for controller\n", name_buffer);
controller_save();
}
static void
handler_command_relay_name_set(mpack_node_t map)
{
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 > global_config->relay_count)
{
LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count);
return;
}
relay_set_name(global_controller->relays[relay_num], relay_name);
LOGGER_DEBUG("setting new name %s for relay %d\n", relay_name, relay_num);
relay_save(global_controller->relays[relay_num]);
}
static void
handler_command_schedule_update(mpack_node_t map)
{
uuid_t schedule_uid;
memcpy(schedule_uid, mpack_node_data(mpack_node_map_uint(map, COMMAND_MAPPING_SCHEDULE_ID)), sizeof(uuid_t));
uint16_t periods_count = mpack_node_u16(mpack_node_map_uint(map, COMMAND_MAPPING_PERIODS_COUNT));
uint16_t *periods = (uint16_t*)mpack_node_bin_data(mpack_node_map_uint(map, COMMAND_MAPPING_PERIODS_BLOB));
schedule_t *schedule = schedule_get_by_uid(schedule_uid);
schedule_t *new_schedule = schedule_create(schedule_uid, periods_count, periods);
if(schedule)
{
new_schedule->id = schedule->id;
schedule_free(schedule);
}
schedule_save(new_schedule);
int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(new_schedule->id);
for(int i = 0; i < global_config->relay_count; ++i)
{
for(int j = 0; relay_ids[j] != 0; ++j)
{
if(global_controller->relays[i]->id == relay_ids[j])
{
relay_reload_schedules(global_controller->relays[i]);
}
}
}
free(relay_ids);
schedule_free(new_schedule);
}
static void
handler_command_relay_schedules_set(mpack_node_t map)
{
uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM));
if(relay_num > global_config->relay_count)
{
LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count);
return;
}
LOGGER_DEBUG("setting schedules for relay %d\n", relay_num);
relay_t *target_relay = global_controller->relays[relay_num];
database_transaction_begin();
junction_relay_schedule_remove_for_relay(target_relay->id);
mpack_node_t schedules_array = mpack_node_map_uint(map, COMMAND_MAPPING_SCHEDULES_ARRAY);
for(int i = 0; i < 7; ++i)
{
mpack_node_t schedule_map = mpack_node_array_at(schedules_array, i);
uuid_t schedule_uid;
memcpy(schedule_uid, mpack_node_data(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_SCHEDULE_ID)), sizeof(uuid_t));
uint16_t periods_count = mpack_node_u16(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_PERIODS_COUNT));
uint16_t *periods = (uint16_t*)mpack_node_bin_data(mpack_node_map_uint(schedule_map, COMMAND_MAPPING_PERIODS_BLOB));
schedule_t *schedule = schedule_get_by_uid(schedule_uid);
schedule_t *new_schedule = schedule_create(schedule_uid, periods_count, periods);
if(schedule)
{
new_schedule->id = schedule->id;
schedule_free(schedule);
}
schedule_save(new_schedule);
junction_relay_schedule_insert(i, target_relay->id, new_schedule->id);
}
relay_reload_schedules(target_relay);
database_transaction_commit();
}
void
handler_command(struct mg_connection *c, int ev, void *ev_data)
{
(void)ev_data;
if(ev != MG_EV_RECV)
{
return;
}
uint32_t payload_length = *((uint32_t*)c->recv_mbuf.buf);
if(c->recv_mbuf.len < payload_length + sizeof(payload_length))
{
return;
}
char *payload = c->recv_mbuf.buf + sizeof(payload_length);
mpack_tree_t tree;
mpack_tree_init_data(&tree, payload, payload_length);
mpack_tree_parse(&tree);
mpack_node_t root = mpack_tree_root(&tree);
uint16_t command_code = mpack_node_u16(mpack_node_map_uint(root, COMMAND_MAPPING_CODE));
LOGGER_DEBUG("received command %d\n", command_code);
switch(command_code)
{
case COMMAND_CODE_CONTROLLER_ID_GET:
break;
case COMMAND_CODE_CONTROLLER_TIME_GET:
break;
case COMMAND_CODE_CONTROLLER_NAME_SET:
handler_command_controller_name_set(root);
break;
case COMMAND_CODE_CONTROLLER_NAME_GET:
break;
case COMMAND_CODE_RELAY_SCHEDULES_SET:
handler_command_relay_schedules_set(root);
break;
case COMMAND_CODE_RELAY_SCHEDULES_GET:
break;
case COMMAND_CODE_RELAY_NAME_SET:
handler_command_relay_name_set(root);
break;
case COMMAND_CODE_RELAY_NAME_GET:
break;
case COMMAND_CODE_RELAY_PULSE:
handler_command_relay_pulse(root);
break;
case COMMAND_CODE_SCHEDULE_UPDATE:
handler_command_schedule_update(root);
break;
default:
LOGGER_ERR("received invalid command\n");
}
if(mpack_tree_destroy(&tree) != mpack_ok)
{
LOGGER_WARNING("error when destroying mpack tree\n");
}
LOGGER_DEBUG("done with command %d - closing connection\n", command_code);
c->flags |= MG_F_SEND_AND_CLOSE;
}

View file

@ -1,95 +0,0 @@
#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 <mongoose.h>
#include <handlers.h>
#include <helpers.h>
#include <mpack.h>
#include <enums.h>
void
handler_discovery(struct mg_connection *c, int ev, void *ev_data)
{
(void)ev_data;
if(ev == MG_EV_RECV)
{
uint16_t discovery_answer_port;
char ip_buf[32];
mg_conn_addr_to_str(c, ip_buf, sizeof(ip_buf), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_REMOTE);
if(c->recv_mbuf.len != sizeof(discovery_answer_port))
{
LOGGER_ERR("received invalid discovery from %s\n", ip_buf);
return;
}
discovery_answer_port = *((uint16_t*)c->recv_mbuf.buf);
LOGGER_INFO("received discovery from %s:%d\n", ip_buf, discovery_answer_port);
if(discovery_answer_port == 0)
{
LOGGER_ERR("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*)global_controller->uid, sizeof(uuid_t));
mpack_write_uint(&writer, DISCOVERY_MAPPING_COMMAND_PORT);
mpack_write_u16(&writer, global_controller->command_port);
mpack_write_uint(&writer, DISCOVERY_MAPPING_RELAY_COUNT);
mpack_write_u8(&writer, global_config->relay_count);
mpack_write_uint(&writer, DISCOVERY_MAPPING_NAME);
mpack_write_cstr(&writer, global_controller->name);
mpack_finish_map(&writer);
// finish writing
if(mpack_writer_destroy(&writer) != mpack_ok)
{
LOGGER_ERR("error writing discovery answer payload\n");
return;
}
size_t bytes_transferred;
int fd_answer = helper_connect_tcp_server(ip_buf, discovery_answer_port);
if(fd_answer == -1)
{
LOGGER_ERR("error during connecting\n");
free(payload);
return;
}
if((bytes_transferred = send(fd_answer, &payload_size, sizeof(payload_size), 0)) <= 0)
{
LOGGER_ERR("error during sending\n");
free(payload);
close(fd_answer);
return;
}
if((bytes_transferred = send(fd_answer, payload, payload_size, 0)) <= 0)
{
LOGGER_ERR("error during sending\n");
free(payload);
close(fd_answer);
return;
}
free(payload);
}
}

View file

@ -1,92 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <uuid/uuid.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(struct mg_connection *c_mqtt)
{
char topic_buf[100];
char payload_buf[2];
char controller_uid[UUID_STR_LEN];
uuid_unparse(global_controller->uid, controller_uid);
struct tm time_last, time_now;
time_t timestamp;
timestamp = time(NULL) - 1;
localtime_r(&timestamp, &time_last);
timestamp = time(NULL);
localtime_r(&timestamp, &time_now);
for(uint_fast8_t i = 0; i < global_config->relay_count; ++i)
{
relay_t *relay = global_controller->relays[i];
int is_on = 0;
int is_on_schedule = relay_is_on_schedule(relay, &time_now);
int pulse_relay = global_config->relay_configs[i].pulse_duration;
if(is_on_schedule)
{
if(!pulse_relay)
{
is_on = 1;
LOGGER_DEBUG("relay %d is active\n", i);
}
if(pulse_relay && relay->pulse_timer)
{
is_on = 1;
--relay->pulse_timer;
LOGGER_DEBUG("relay %d is pulsing for %d more seconds\n", i, relay->pulse_timer);
}
}
else
{
relay->pulse_timer = 0;
}
if(relay->is_on != is_on)
{
relay->sent_to_broker = 0;
}
if(!relay->sent_to_broker && c_mqtt)
{
sprintf(topic_buf, "controller/%s/relay/%u", controller_uid, i);
sprintf(payload_buf, "%u", is_on);
mg_mqtt_publish(c_mqtt, topic_buf, 0, MG_MQTT_QOS(0), payload_buf, strlen(payload_buf));
relay->sent_to_broker = (rand() % 45) + 15;
LOGGER_DEBUG("sent relay %d status (%d) to mqtt broker\n", i, is_on);
}
if(relay->sent_to_broker)
{
--relay->sent_to_broker;
}
relay->is_on = is_on;
if(global_config->relay_configs[i].inverted)
{
is_on = !is_on;
}
switch(global_config->relay_configs[i].driver)
{
case RELAY_DRIVER_GPIO:
driver_gpio_set(global_config->relay_configs[i].pin, is_on);
break;
case RELAY_DRIVER_PIFACE:
driver_piface_set(global_config->relay_configs[i].pin, is_on);
break;
default:
LOGGER_WARNING("relay %d is not using a driver\n", i);
}
}
}

View file

@ -1,43 +0,0 @@
#include <logger.h>
#include <handlers.h>
#include <connections.h>
#include <models/controller.h>
void
handler_mqtt(struct mg_connection *nc, int ev, void *p) {
struct mg_mqtt_message *msg = (struct mg_mqtt_message *) p;
(void) nc;
switch (ev)
{
case MG_EV_CONNECT:
{
struct mg_send_mqtt_handshake_opts opts;
memset(&opts, 0, sizeof(opts));
// TODO add password
mg_set_protocol_mqtt(nc);
mg_send_mqtt_handshake_opt(nc, global_controller->name, opts);
break;
}
case MG_EV_MQTT_CONNACK:
if(msg->connack_ret_code != MG_EV_MQTT_CONNACK_ACCEPTED)
{
LOGGER_INFO("Got MQTT connection error: %d\n", msg->connack_ret_code);
break;
}
if(!global_connection_mqtt)
{
LOGGER_DEBUG("connected to MQTT server\n");
global_connection_mqtt = nc;
}
break;
case MG_EV_CLOSE:
if(global_connection_mqtt)
{
LOGGER_DEBUG("disconnected from MQTT server\n");
}
global_connection_mqtt = NULL;
break;
}
}

View file

@ -1,52 +0,0 @@
#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)
{
LOGGER_ERR("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)
{
LOGGER_ERR("error binding socket: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
if ((status = listen(fd, max_client_backlog)) == -1)
{
LOGGER_ERR("error setting up listener: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return fd;
}

View file

@ -1,39 +0,0 @@
#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
LOGGER_ERR("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) {
LOGGER_ERR("connect() failed\n");
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return s;
}

View file

@ -1,81 +0,0 @@
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <config.h>
#include <logger.h>
static uid_t
get_uid_for_user(char *user)
{
if(user == NULL || user[0] == '\0')
{
return getuid();
}
struct passwd *pwd = calloc(1, sizeof(struct passwd));
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getpwnam_r(user, pwd, buffer, buffer_len, &pwd);
if(pwd == NULL)
{
LOGGER_CRIT("couldn't find user to drop privileges\n");
exit(1);
}
uid_t result = pwd->pw_uid;
free(buffer);
free(pwd);
return result;
}
static gid_t
get_gid_for_group(char *group)
{
if(group == NULL || group[0] == '\0')
{
return getgid();
}
struct group *grp = calloc(1, sizeof(struct group));
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getgrnam_r(group, grp, buffer, buffer_len, &grp);
if(grp == NULL)
{
LOGGER_CRIT("couldn't find group to drop privileges\n");
exit(1);
}
gid_t result = grp->gr_gid;
free(buffer);
free(grp);
return result;
}
int
helper_drop_privileges()
{
uid_t uid = get_uid_for_user(global_config->user);
gid_t gid = get_gid_for_group(global_config->group);
LOGGER_DEBUG("drop privileges to %lu:%lu\n", uid, gid);
if (setgid(gid) == -1)
{
LOGGER_CRIT("failed to drop group privileges\n");
exit(1);
}
if (setuid(uid) == -1)
{
LOGGER_CRIT("failed to drop user privileges\n");
exit(1);
}
return 0;
}

View file

@ -1,24 +0,0 @@
#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)
{
LOGGER_ERR("could not get socket name for port: %s\n", strerror(errno));
return 0;
}
else
{
return ntohs(sin.sin_port);
}
}

View file

@ -1,11 +0,0 @@
#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;
}

View file

@ -1,57 +0,0 @@
#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)
{
LOGGER_CRIT("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)
{
LOGGER_CRIT("setsockopt: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
if (bind(fd, res->ai_addr, res->ai_addrlen) == -1)
{
LOGGER_CRIT("bind: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
freeaddrinfo(res);
LOGGER_INFO("opened discovery socket on port %u\n", discovery_port);
return fd;
}

View file

@ -1,96 +0,0 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <config.h>
#include <logger.h>
const char *COLOR_DEBUG = COLOR_GREEN;
const char *COLOR_INFO = COLOR_CYAN;
const char *COLOR_NOTICE = COLOR_CYAN;
const char *COLOR_WARNING = COLOR_YELLOW;
const char *COLOR_ERR = COLOR_RED;
const char *COLOR_CRIT = COLOR_MAGENTA;
const char *COLOR_EMERG = COLOR_MAGENTA;
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...)
{
if(global_config->logging.level < level)
{
return;
}
va_list args;
const char *level_str;
const char *color;
switch(level)
{
case LOG_DEBUG:
color = COLOR_DEBUG;
level_str = "DEBUG";
break;
case LOG_INFO:
color = COLOR_INFO;
level_str = "INFO";
break;
case LOG_NOTICE:
color = COLOR_NOTICE;
level_str = "NOTE";
break;
case LOG_WARNING:
color = COLOR_WARNING;
level_str = "WARN";
break;
case LOG_ERR:
color = COLOR_ERR;
level_str = "ERROR";
break;
case LOG_CRIT:
color = COLOR_CRIT;
level_str = "CRIT";
break;
case LOG_EMERG:
color = COLOR_EMERG;
level_str = "EMERG";
break;
default:
return;
}
char timestamp_str[32];
time_t rawtime;
time(&rawtime);
strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
size_t timestamp_len = strlen(timestamp_str);
size_t buffer_size = 128;
buffer_size += timestamp_len;
buffer_size += strlen(filename);
buffer_size += strlen(func);
buffer_size += strlen(msg);
char *buffer = malloc(sizeof(char) * (buffer_size));
sprintf(buffer, "%s %s[%5s] %s:%d:%s " COLOR_NONE "%s", timestamp_str, color, level_str, filename, line, func, msg);
// start arg va_list and find log_len
va_start(args, msg);
size_t log_len = vsnprintf(NULL, 0, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
va_end(args);
char *log_line = malloc(sizeof(char) * (log_len + 1));
// start arg va_list again and write log_line
va_start(args, msg);
vsprintf(log_line, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
va_end(args);
syslog(level, "%s", log_line + timestamp_len + 1);
fprintf(global_config->logging.file, "%s", log_line);
fflush(global_config->logging.file);
free(buffer);
free(log_line);
}

View file

@ -1,189 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <lmdb.h>
#include <signal.h>
#include <syslog.h>
#include <cli.h>
#include <logger.h>
#include <mongoose.h>
#include <models/controller.h>
#include <database.h>
#include <config.h>
#include <connections.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>
static struct mg_mgr mgr;
static void
terminate(int signum)
{
LOGGER_INFO("terminating controller (%d)\n", signum);
// TODO fix mg_mgr_free() causing loop (can't terminate)
//LOGGER_DEBUG("freeing mongoose manager\n");
//mg_mgr_free(&mgr);
LOGGER_DEBUG("closing database\n");
database_free();
LOGGER_DEBUG("freeing global controller\n");
controller_free(global_controller);
LOGGER_DEBUG("freeing relay configs config\n");
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);
openlog("emgauwa-controller", 0, LOG_USER);
setlogmask(LOG_UPTO(LOG_INFO));
/******************** LOAD CONFIG ********************/
config_init();
cli_t cli;
cli_parse(argc, argv, &cli);
config_load(global_config, cli.config_file);
if(global_config->logging.file == NULL)
{
global_config->logging.file = stdout;
}
if(global_config->include)
{
config_load_directory(global_config, global_config->include);
}
if(sizeof(time_t) < 8)
{
LOGGER_WARNING("this system is not using 8-bit time\n");
}
/******************** INIT DATABASE, SOCKETS AND THIS CONTROLLER ********************/
mg_mgr_init(&mgr, NULL);
database_init();
global_controller = controller_load();
if(!global_controller)
{
global_controller = controller_create();
controller_save();
}
connection_discovery_bind(&mgr);
connection_mqtt_connect(&mgr);
struct mg_connection *c_command = connection_command_bind(&mgr);
if(global_controller->command_port == 0)
{
global_controller->command_port = helper_get_port(c_command->sock);
controller_save();
}
controller_debug(global_controller);
helper_drop_privileges();
/******************** SETUP WIRINGPI ********************/
wiringPiSetup();
int piface_setup = 0;
for(uint_fast8_t i = 0; i < global_config->relay_count; ++i)
{
int relay_default = global_config->relay_configs[i].init;
if(relay_default == -1)
{
relay_default = global_config->relays_init;
}
if(relay_default == -1)
{
relay_default = global_config->relay_configs[i].inverted;
}
if(global_config->relay_configs[i].driver == RELAY_DRIVER_GPIO)
{
pinMode(global_config->relay_configs[i].pin, OUTPUT);
driver_gpio_set(global_config->relay_configs[i].pin, relay_default);
}
if(global_config->relay_configs[i].driver == RELAY_DRIVER_PIFACE)
{
if(!piface_setup)
{
piFaceSetup(PIFACE_GPIO_BASE);
piface_setup = 1;
}
driver_piface_set(global_config->relay_configs[i].pin, relay_default);
}
}
/******************** CHECK FOR TESTING RUN ********************/
if(cli.demo_mode)
{
runner_test(global_controller);
terminate(0);
}
/******************** START MAIN LOOP ********************/
time_t timer = 0;
for (;;)
{
mg_mgr_poll(&mgr, 200);
if(time(NULL) - timer >= 1)
{
if(!global_connection_mqtt)
{
connection_mqtt_connect(&mgr);
}
handler_loop(global_connection_mqtt);
timer = time(NULL);
}
}
terminate(0);
return 0;
}

View file

@ -1,256 +0,0 @@
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <sqlite3.h>
#include <models/controller.h>
#include <logger.h>
#include <config.h>
#include <constants.h>
#include <database.h>
controller_t *global_controller;
static int
db_update_insert(controller_t *controller, sqlite3_stmt *stmt)
{
LOGGER_DEBUG("saving controller '%s' into database (id: %d)\n", controller->name, controller->id);
int rc;
sqlite3_bind_int(stmt, 1, controller->id);
sqlite3_bind_blob(stmt, 2, controller->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, controller->name, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, controller->command_port);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static controller_t*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
controller_t *new_controller = malloc(sizeof(controller_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i': // id
new_controller->id = sqlite3_column_int(stmt, i);
break;
case 'c': // command_port
new_controller->command_port = sqlite3_column_int(stmt, i);
break;
case 'n': // name
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // uid
uuid_copy(new_controller->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count);
uint8_t i;
for(i = 0; i < global_config->relay_count; ++i)
{
new_controller->relays[i] = relay_load(i);
if(!new_controller->relays[i])
{
new_controller->relays[i] = relay_create(i);
relay_save(new_controller->relays[i]);
}
}
return new_controller;
}
static controller_t**
controller_db_select(sqlite3_stmt *stmt)
{
controller_t **all_controllers = malloc(sizeof(controller_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
controller_t *new_controller = controller_db_select_mapper(stmt);
row++;
all_controllers = (controller_t**)realloc(all_controllers, sizeof(controller_t*) * (row + 1));
all_controllers[row - 1] = new_controller;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_controllers[row] = NULL;
return all_controllers;
}
int
controller_save()
{
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(global_controller->id)
{
sqlite3_prepare_v2(global_database, "UPDATE controllers set uid = ?2, name = ?3, command_port = ?4 WHERE id = ?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO controllers(uid, name, command_port) values (?2, ?3, ?4);", -1, &stmt, NULL);
}
int result = db_update_insert(global_controller, stmt);
if(result)
{
if(global_controller->id)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
if(!global_controller->id)
{
global_controller->id = sqlite3_last_insert_rowid(global_database);
}
if(opened_transaction)
{
database_transaction_commit();
}
}
return result;
}
controller_t*
controller_load()
{
LOGGER_DEBUG("getting controller from database\n");
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM controllers LIMIT 1;", -1, &stmt, NULL);
controller_t **sql_result = controller_db_select(stmt);
controller_t *result = sql_result[0];
free(sql_result);
return result;
}
int
controller_remove(controller_t *controller)
{
sqlite3_stmt *stmt;
if(!controller->id)
{
return 0;
}
sqlite3_prepare_v2(global_database, "DELETE FROM controllers WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, controller->id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
controller_t*
controller_create(void)
{
controller_t *new_controller = malloc(sizeof(*new_controller));
new_controller->id = 0;
uuid_generate(new_controller->uid);
strncpy(new_controller->name, global_config->name, MAX_NAME_LENGTH);
new_controller->name[MAX_NAME_LENGTH] = '\0';
new_controller->command_port = 0;
new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count);
uint8_t i;
for(i = 0; i < global_config->relay_count; ++i)
{
new_controller->relays[i] = relay_load(i);
if(!new_controller->relays[i])
{
new_controller->relays[i] = relay_create(i);
relay_save(new_controller->relays[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 < global_config->relay_count; ++i)
{
relay_free(controller->relays[i]);
}
free(controller->relays);
free(controller);
}
void
controller_debug(controller_t *controller)
{
if(controller == NULL)
{
LOGGER_DEBUG("controller is NULL\n");
return;
}
char uuid_str[37];
uuid_unparse(controller->uid, uuid_str);
LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)controller);
LOGGER_DEBUG("(2/3) name: %s\n", controller->name);
LOGGER_DEBUG("(3/3) relays @ %p:\n", (void*)controller->relays);
for(int i = 0; i < global_config->relay_count; ++i)
{
relay_debug(controller->relays[i]);
}
}

View file

@ -1,102 +0,0 @@
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <models/junction_relay_schedule.h>
#include <logger.h>
#include <macros.h>
#include <database.h>
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO junction_relay_schedule(weekday, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
return 0;
}
sqlite3_finalize(stmt);
return 1;
}
int
junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids)
{
int rc;
sqlite3_stmt *stmt;
static const char query_base[] = "INSERT INTO junction_relay_schedule (weekday, schedule_id, relay_id) VALUES";
static const char query_extender[] = " (?, ?, ?)";
size_t query_len = STRLEN(query_base) + (7 * (STRLEN(query_extender) + 1)) + 1;
char *query = malloc(sizeof(char) * query_len + 1);
strncpy(query, query_base, query_len);
query_len -= STRLEN(query_base);
for(int i = 0; i < 7; ++i)
{
strncat(query, query_extender, query_len);
query_len -= STRLEN(query_extender);
char *query_divider = (i < 7 - 1) ? "," : ";";
strncat(query, query_divider, query_len);
query_len -= 1;
}
sqlite3_prepare_v2(global_database, query, -1, &stmt, NULL);
for(int i = 0; i < 7; ++i)
{
sqlite3_bind_int(stmt, i * 3 + 1, i);
sqlite3_bind_int(stmt, i * 3 + 2, schedule_ids[i]);
sqlite3_bind_int(stmt, i * 3 + 3, relay_id);
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOGGER_ERR("error inserting data: %s", sqlite3_errmsg(global_database));
return 0;
}
sqlite3_finalize(stmt);
return 1;
}
int
junction_relay_schedule_remove_for_relay(int relay_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int*
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return database_helper_get_ids(stmt);
}

View file

@ -1,37 +0,0 @@
#include <stdlib.h>
#include <logger.h>
#include <constants.h>
#include <models/period.h>
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;
}

View file

@ -1,293 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <uuid/uuid.h>
#include <logger.h>
#include <helpers.h>
#include <database.h>
#include <models/relay.h>
#include <models/junction_relay_schedule.h>
static int
db_update_insert(relay_t *relay, sqlite3_stmt *stmt)
{
LOGGER_DEBUG("saving relay '%s' into database (id: %d)\n", relay->name, relay->id);
int rc;
sqlite3_bind_int(stmt, 1, relay->id);
sqlite3_bind_int(stmt, 2, relay->number);
sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static relay_t*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
relay_t *new_relay = malloc(sizeof(relay_t));
new_relay->is_on = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i':
new_relay->id = sqlite3_column_int(stmt, i);
break;
case 'n':
switch(name[1])
{
case 'a': // name
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // number
new_relay->number = sqlite3_column_int(stmt, i);
break;
default:
break;
}
break;
default: // ignore columns not implemented
break;
}
}
memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7);
relay_reload_schedules(new_relay);
new_relay->is_on = -1;
new_relay->pulse_timer = 0;
new_relay->sent_to_broker = 0;
return new_relay;
}
static relay_t**
relay_db_select(sqlite3_stmt *stmt)
{
relay_t **all_relays = malloc(sizeof(relay_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
relay_t *new_relay = relay_db_select_mapper(stmt);
row++;
all_relays = (relay_t**)realloc(all_relays, sizeof(relay_t*) * (row + 1));
all_relays[row - 1] = new_relay;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_relays[row] = NULL;
return all_relays;
}
int
relay_save(relay_t *relay)
{
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(relay->id)
{
sqlite3_prepare_v2(global_database, "UPDATE relays set number = ?2, name = ?3 WHERE id = ?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO relays(number, name) values (?2, ?3);", -1, &stmt, NULL);
}
int result = db_update_insert(relay, stmt);
if(result)
{
if(relay->id)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
if(relay->id == 0)
{
relay->id = sqlite3_last_insert_rowid(global_database);
LOGGER_DEBUG("new relay - new id: %d\n", relay->id);
}
LOGGER_DEBUG("cleaning relay_schedule junction\n");
junction_relay_schedule_remove_for_relay(relay->id);
LOGGER_DEBUG("rebuilding relay_schedule junction\n");
int schedule_ids[7];
for(int i = 0; i < 7; ++i)
{
schedule_ids[i] = relay->schedules[i]->id;
}
junction_relay_schedule_insert_weekdays(relay->id, schedule_ids);
if(opened_transaction)
{
database_transaction_commit();
}
}
return result;
}
relay_t*
relay_create(uint8_t number)
{
relay_t *new_relay = malloc(sizeof(relay_t));
new_relay->id = 0;
new_relay->number = number;
new_relay->name[0] = '\0';
new_relay->is_on = -1;
new_relay->pulse_timer = 0;
new_relay->sent_to_broker = 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_get_by_uid(off_id);
}
return new_relay;
}
relay_t*
relay_load(uint8_t number)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM relays WHERE number = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, number);
relay_t **sql_result = relay_db_select(stmt);
relay_t *result = sql_result[0];
free(sql_result);
return result;
}
void
relay_reload_schedules(relay_t *relay)
{
schedule_t **schedules = schedule_get_relay_weekdays(relay->id);
uuid_t off_id;
memset(off_id, 0, sizeof(uuid_t));
memcpy(off_id, "off", 3);
int fill_with_off = 0;
for(int i = 0; i < 7; ++i)
{
if(schedules[i] == NULL || fill_with_off)
{
LOGGER_WARNING("got only %d/7 schedules for relay_id %d\n", i, relay->id);
relay->schedules[i] = schedule_get_by_uid(off_id);
fill_with_off = 1;
}
else
{
if(relay->schedules[i])
{
schedule_free(relay->schedules[i]);
}
relay->schedules[i] = schedules[i];
}
}
free(schedules); // don't free list, because contents are kept in relay->schedules
}
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_on_schedule(relay_t *relay, struct tm *time_struct)
{
schedule_t *schedule = relay->schedules[helper_get_weekday(time_struct)];
if(schedule->periods_count == 0)
{
return 0;
}
for(uint16_t i = 0; i < schedule->periods_count; ++i)
{
if(period_includes_time(schedule->periods[i], time_struct))
{
return 1;
}
}
return 0;
}
void
relay_debug(relay_t *relay)
{
if(relay == NULL)
{
LOGGER_DEBUG("relay is NULL\n");
return;
}
LOGGER_DEBUG("(1/3) %3d @ %p\n", relay->number, (void*)relay);
LOGGER_DEBUG("(2/3) id: %3d; name: %s\n", relay->id, relay->name);
LOGGER_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);
}

View file

@ -1,322 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <logger.h>
#include <database.h>
#include <models/schedule.h>
#include <models/junction_relay_schedule.h>
static int
db_update_insert(schedule_t *schedule, sqlite3_stmt *stmt)
{
char uuid_str[UUID_STR_LEN];
uuid_unparse(schedule->uid, uuid_str);
LOGGER_DEBUG("saving schedule '%s' into database (id: %d)\n", uuid_str, schedule->id);
int rc;
uint16_t *periods_blob = schedule_periods_to_blob(schedule);
int blob_size = (int)sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
sqlite3_bind_int(stmt, 1, schedule->id);
sqlite3_bind_blob(stmt, 2, schedule->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, periods_blob, blob_size, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
free(periods_blob);
return rc != SQLITE_DONE;
}
static schedule_t*
schedule_db_select_mapper(sqlite3_stmt *stmt)
{
const uint16_t *periods_blob;
schedule_t *new_schedule = malloc(sizeof(schedule_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i': // id
new_schedule->id = sqlite3_column_int(stmt, i);
break;
case 'p': // periods
periods_blob = sqlite3_column_blob(stmt, i);
new_schedule->periods_count = periods_blob[0];
new_schedule->periods = malloc(sizeof(period_t) * periods_blob[0]);
for(int i = 0; i < periods_blob[0]; ++i)
{
new_schedule->periods[i].start = periods_blob[(i * 2) + 1];
new_schedule->periods[i].end = periods_blob[(i * 2) + 2];
}
break;
case 'u': // uid
uuid_copy(new_schedule->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
return new_schedule;
}
static schedule_t**
schedule_db_select(sqlite3_stmt *stmt)
{
schedule_t **all_schedules = malloc(sizeof(schedule_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
schedule_t *new_schedule = schedule_db_select_mapper(stmt);
row++;
all_schedules = (schedule_t**)realloc(all_schedules, sizeof(schedule_t*) * (row + 1));
all_schedules[row - 1] = new_schedule;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_schedules[row] = NULL;
return all_schedules;
}
int
schedule_save(schedule_t *schedule)
{
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(schedule->id)
{
sqlite3_prepare_v2(global_database, "UPDATE schedules SET uid = ?2, periods = ?3 WHERE id=?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO schedules(uid, periods) values (?2, ?3);", -1, &stmt, NULL);
}
int result = db_update_insert(schedule, stmt);
if(result)
{
if(schedule->id)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
if(!schedule->id)
{
schedule->id = sqlite3_last_insert_rowid(global_database);
}
if(opened_transaction)
{
database_transaction_commit();
}
}
return result;
}
schedule_t**
schedule_get_relay_weekdays(int relay_id)
{
LOGGER_DEBUG("getting schedules [relay_id=%d] from database\n", relay_id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT schedules.* FROM schedules INNER JOIN junction_relay_schedule ON schedules.id == junction_relay_schedule.schedule_id WHERE junction_relay_schedule.relay_id = ?1 ORDER BY junction_relay_schedule.weekday ASC", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return schedule_db_select(stmt);
}
schedule_t*
schedule_get_by_uid(uuid_t uid)
{
char uuid_str[UUID_STR_LEN];
schedule_uid_unparse(uid, uuid_str);
LOGGER_DEBUG("getting schedule [uid=%s] from database\n", uuid_str);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
schedule_t **sql_result = schedule_db_select(stmt);
schedule_t *result = sql_result[0];
free(sql_result);
return result;
}
schedule_t*
schedule_create(uuid_t uid, uint16_t length, uint16_t *periods_blob)
{
schedule_t *new_schedule = malloc(sizeof(schedule_t));
new_schedule->id = 0;
memmove(new_schedule->uid, uid, sizeof(uuid_t));
new_schedule->periods_count = length;
new_schedule->periods = NULL;
if(length)
{
new_schedule->periods = malloc(sizeof(period_t) * length);
for(uint16_t i = 0; i < length; ++i)
{
new_schedule->periods[i].start = periods_blob[0 + (i * 2)];
new_schedule->periods[i].end = periods_blob[1 + (i * 2)];
}
}
return new_schedule;
}
uint16_t*
schedule_periods_to_blob(schedule_t *schedule)
{
uint16_t *periods_blob = malloc(sizeof(uint16_t) * ((2 * schedule->periods_count) + 1));
periods_blob[0] = schedule->periods_count;
for(uint16_t i = 0; i < schedule->periods_count; ++i)
{
periods_blob[1 + (i * 2)] = schedule->periods[i].start;
periods_blob[2 + (i * 2)] = schedule->periods[i].end;
}
return periods_blob;
}
int
schedule_uid_parse(const char *uid_str, uuid_t result)
{
if(strcmp("off", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "off", 3);
return 0;
}
if(strcmp("on", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "on", 2);
return 0;
}
if(uuid_parse(uid_str, result))
{
return 1;
}
return 0;
}
void
schedule_uid_unparse(const uuid_t uid, char *result)
{
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "off");
return;
}
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "on");
return;
}
uuid_unparse(uid, result);
}
void
schedule_free(schedule_t *schedule)
{
free(schedule->periods);
free(schedule);
}
void
schedule_free_list(schedule_t **schedules)
{
for(int i = 0; schedules[i] != NULL; ++i)
{
schedule_free(schedules[i]);
}
free(schedules);
}
void
schedule_debug(schedule_t *schedule)
{
if(schedule == NULL)
{
LOGGER_DEBUG("schedule is NULL\n");
return;
}
char uuid_str[UUID_STR_LEN];
schedule_uid_unparse(schedule->uid, uuid_str);
LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)schedule);
LOGGER_DEBUG("(2/3) id: %3d; period count: %3d\n", schedule->id, schedule->periods_count);
// one block: "HH:MM-HH:MM, " --> size: 13 (14 with '\0')
char *periods_debug_str = malloc(sizeof(char) * ((schedule->periods_count * 13) + 1));
periods_debug_str[0] = '\0';
for(uint16_t i = 0; i < schedule->periods_count; ++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
);
}
LOGGER_DEBUG("(3/3) periods: %s\n", periods_debug_str);
free(periods_debug_str);
}

View file

@ -1,35 +0,0 @@
#include <unistd.h>
#include <runners.h>
#include <logger.h>
#include <drivers.h>
#include <drivers.h>
void
runner_test()
{
// from x down to 0 to turn all relays off in the end
for(uint_fast8_t i = 0; i < global_config->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:
LOGGER_WARNING("relay %d is not using a driver\n", i);
}
sleep(1);
}
}
}

384
vendor/argparse.c vendored
View file

@ -1,384 +0,0 @@
/**
* 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);
}

130
vendor/argparse.h vendored
View file

@ -1,130 +0,0 @@
/**
* 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.
*/
#ifndef ARGPARSE_H
#define ARGPARSE_H
/* For c++ compatibility */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct argparse;
struct argparse_option;
typedef int argparse_callback (struct argparse *self,
const struct argparse_option *option);
enum argparse_flag {
ARGPARSE_STOP_AT_NON_OPTION = 1,
};
enum argparse_option_type {
/* special */
ARGPARSE_OPT_END,
ARGPARSE_OPT_GROUP,
/* options with no arguments */
ARGPARSE_OPT_BOOLEAN,
ARGPARSE_OPT_BIT,
/* options with arguments (optional or required) */
ARGPARSE_OPT_INTEGER,
ARGPARSE_OPT_FLOAT,
ARGPARSE_OPT_STRING,
};
enum argparse_option_flags {
OPT_NONEG = 1, /* disable negation */
};
/**
* argparse option
*
* `type`:
* holds the type of the option, you must have an ARGPARSE_OPT_END last in your
* array.
*
* `short_name`:
* the character to use as a short option name, '\0' if none.
*
* `long_name`:
* the long option name, without the leading dash, NULL if none.
*
* `value`:
* stores pointer to the value to be filled.
*
* `help`:
* the short help message associated to what the option does.
* Must never be NULL (except for ARGPARSE_OPT_END).
*
* `callback`:
* function is called when corresponding argument is parsed.
*
* `data`:
* associated data. Callbacks can use it like they want.
*
* `flags`:
* option flags.
*/
struct argparse_option {
enum argparse_option_type type;
const char short_name;
const char *long_name;
void *value;
const char *help;
argparse_callback *callback;
intptr_t data;
int flags;
};
/**
* argpparse
*/
struct argparse {
// user supplied
const struct argparse_option *options;
const char *const *usages;
int flags;
const char *description; // a description after usage
const char *epilog; // a description at the end
// internal context
int argc;
const char **argv;
const char **out;
int cpidx;
const char *optvalue; // current option value
};
// built-in callbacks
int argparse_help_cb(struct argparse *self,
const struct argparse_option *option);
// built-in option macros
#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 }
#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ }
#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ }
#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ }
#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 }
#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \
"show this help message and exit", \
argparse_help_cb, 0, OPT_NONEG)
int argparse_init(struct argparse *self, struct argparse_option *options,
const char *const *usages, int flags);
void argparse_describe(struct argparse *self, const char *description,
const char *epilog);
int argparse_parse(struct argparse *self, int argc, const char **argv);
void argparse_usage(struct argparse *self);
#ifdef __cplusplus
}
#endif
#endif

16173
vendor/mongoose.c vendored

File diff suppressed because it is too large Load diff

6285
vendor/mongoose.h vendored

File diff suppressed because it is too large Load diff

6440
vendor/mpack.c vendored

File diff suppressed because it is too large Load diff

7151
vendor/mpack.h vendored

File diff suppressed because it is too large Load diff

2274
vendor/toml.c vendored

File diff suppressed because it is too large Load diff

175
vendor/toml.h vendored
View file

@ -1,175 +0,0 @@
/*
MIT License
Copyright (c) 2017 - 2019 CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef TOML_H
#define TOML_H
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
#define TOML_EXTERN extern "C"
#else
#define TOML_EXTERN extern
#endif
typedef struct toml_timestamp_t toml_timestamp_t;
typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_datum_t toml_datum_t;
/* Parse a file. Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
char* errbuf,
int errbufsz);
/* Parse a string containing the full config.
* Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
char* errbuf,
int errbufsz);
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
* directly or indirectly are no longer valid.
*/
TOML_EXTERN void toml_free(toml_table_t* tab);
/* Timestamp types. The year, month, day, hour, minute, second, z
* fields may be NULL if they are not relevant. e.g. In a DATE
* type, the hour, minute, second and z fields will be NULLs.
*/
struct toml_timestamp_t {
struct { /* internal. do not use. */
int year, month, day;
int hour, minute, second, millisec;
char z[10];
} __buffer;
int *year, *month, *day;
int *hour, *minute, *second, *millisec;
char* z;
};
/*-----------------------------------------------------------------
* Enhanced access methods
*/
struct toml_datum_t {
int ok;
union {
toml_timestamp_t* ts; /* ts must be freed after use */
char* s; /* string value. s must be freed after use */
int b; /* bool value */
int64_t i; /* int value */
double d; /* double value */
} u;
};
/* on arrays: */
/* ... retrieve size of array. */
TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
/* ... retrieve values using index. */
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
/* ... retrieve array or table using index. */
TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
/* on tables: */
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
/* ... retrieve values using key. */
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
/* .. retrieve array or table using key. */
TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
const char* key);
TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
const char* key);
/*-----------------------------------------------------------------
* lesser used
*/
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
0 if unknown
*/
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
/* Return the key of an array */
TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
/* Return the number of key-values in a table */
TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
/* Return the number of arrays in a table */
TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
/* Return the number of sub-tables in a table */
TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
/* Return the key of a table*/
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
/*--------------------------------------------------------------
* misc
*/
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
void (*xxfree)(void*));
/*--------------------------------------------------------------
* deprecated
*/
/* A raw value, must be processed by toml_rto* before using. */
typedef const char* toml_raw_t;
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
#endif /* TOML_H */

View file

@ -1 +0,0 @@
#define EMGAUWA_CONTROLLER_VERSION "@CMAKE_PROJECT_VERSION@"